Ticket #1001: attack-move.diff

File attack-move.diff, 13.9 KB (added by Jonathan Waller, 13 years ago)
  • binaries/data/mods/public/gui/session/input.js

     
    298298            return {"type": "garrison", "cursor": "action-garrison", "target": target};
    299299        else
    300300            return  {"type": "none", "cursor": "action-garrison-disabled", "target": undefined};
     301    }
     302    else if (Engine.HotkeyIsPressed("attackmove"))
     303    {
     304        return {"type": "attack-move"};
    301305    }
    302306    else
    303307    {
     
    909913        Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued});
    910914        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
    911915        return true;
     916       
     917    case "attack-move":
     918        var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
     919        Engine.PostNetworkCommand({"type": "attack-move", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
     920        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack_move", "entity": selection[0] });
     921        return true;
    912922
    913923    case "build": // (same command as repair)
    914924    case "repair":
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    5252            });
    5353        }
    5454        break;
     55       
     56    case "attack-move":
     57        var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
     58        GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
     59            cmpUnitAI.AttackMove(cmd.x, cmd.z, cmd.queued);
     60        });
     61        break;
    5562
    5663    case "repair":
    5764        // This covers both repairing damaged buildings, and constructing unfinished foundations
     
    413420
    414421    if (formedEnts.length == 0)
    415422    {
    416         // No units support the foundation - return all the others
     423        // No units support the formation - return all the others
    417424        return nonformedUnitAIs;
    418425    }
    419426
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    164164
    165165        this.SetNextState("FORMATIONMEMBER.WALKING");
    166166    },
     167   
     168    // Called when being told to attack-move as part of a formation
     169    "Order.FormationAttackMove": function(msg) {
     170        var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     171        cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z);
    167172
     173        this.SetNextState("FORMATIONMEMBER.ATTACKMOVING");
     174    },
     175
    168176    // Special orders:
    169177    // (these will be overridden by various states)
    170178
     
    394402            // Don't bother now, just disband the formation immediately.
    395403            cmpFormation.Disband();
    396404        },
     405       
     406        "Order.AttackMove": function(msg){
     407            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     408            // The AttackMoveAttack order can mess up the destination position in this.order, so save it.
     409            this.attackMovePosition = {x: this.order.data.x, z: this.order.data.z};
     410            cmpFormation.CallMemberFunction("SetHeldPosition", [this.attackMovePosition.x, this.attackMovePosition.z]);
    397411
     412            this.MoveToPoint(this.attackMovePosition.x, this.attackMovePosition.z);
     413            this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.WALK");
     414        },
     415       
     416        "Order.AttackMoveAttack": function(msg){
     417            // The enemy that was spotted by a formation member.
     418            this.target = msg.data.target;
     419            this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.ATTACK");
     420        },
     421
    398422        "Order.Repair": function(msg) {
    399423            // TODO: see notes in Order.Attack
    400424            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     
    439463                cmpFormation.Disband();
    440464            },
    441465        },
     466       
     467        "ATTACKMOVE": {
     468            "WALK": {
     469                // Whe we enter/re-enter the walk state then set the formation moving again towards the final destination
     470                "enter": function(msg){
     471                    var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     472                    if (this.order.data.x) // The AttackMoveAttack order can mess up the destination position in this.order, so save it.
     473                        this.attackMovePosition = {x: this.order.data.x, z: this.order.data.z};
     474                    cmpFormation.CallMemberFunction("SetHeldPosition", [this.attackMovePosition.x, this.attackMovePosition.z]);
     475
     476                    this.MoveToPoint(this.attackMovePosition.x, this.attackMovePosition.z);
     477                },
     478               
     479                "MoveStarted": function(msg) {
     480                    var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     481                    cmpFormation.MoveMembersIntoFormation(true, true);
     482                },
     483
     484                // For when we have arrived at the commanded destination, break up the formation.
     485                "MoveCompleted": function(msg) {
     486                    if (this.FinishOrder())
     487                        return;
     488
     489                    var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     490                   
     491                    cmpFormation.Disband();
     492                },
     493            },
     494           
     495            "ATTACK":{
     496                // Send all of the formation to attack the enemy that was spotted
     497                "enter": function(msg){
     498                    // When we enter the attack state stop so the formation controller just stays in one spot until
     499                    // the members have finished attacking stuff.
     500                    this.StopMoving();
     501                   
     502                    var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     503                    cmpFormation.CallMemberFunction("Attack", [this.order.data.target]);
     504                    cmpFormation.suspendFormation();
     505                   
     506                    // A timer to check when we should resume the march
     507                    this.StartTimer(1000, 1000);
     508                },
     509               
     510                "Timer": function(msg){
     511                    var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     512                    // If more than 80% of the formation is idle then resume the walk towards the destination.
     513                    if (cmpFormation.CountMemberFunction("IsIdle", []) >= 0.8 * cmpFormation.GetMemberCount()){
     514                        cmpFormation.resumeFormation();
     515                        this.SetNextState("FORMATIONCONTROLLER.ATTACKMOVE.WALK");
     516                    }
     517                },
     518               
     519                "leave": function(msg){
     520                    this.StopTimer();
     521                },
     522            },
     523        },
    442524    },
    443525
    444526
     
    496578            // and no longer moving, because the formation controller might
    497579            // move and we'll automatically start chasing after it again)
    498580        },
     581       
     582        "ATTACKMOVING": {
     583            "enter": function(){
     584                this.SelectAnimation("move");
     585                // Watch for enemies
     586                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     587                var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
     588            },
     589           
     590            "LosRangeUpdate": function(msg) {
     591                var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI);
     592                // TODO: better target selection, e.g. ignore scouts and houses
     593                if (msg.data.added[0]){
     594                    // Tell the formation controller to stop walking and attack this enemy
     595                    // Using an order here isn't ideal because this is basically a sub-order,
     596                    // there doesn't seem to be an alternative though.
     597                    cmpUnitAI.ReplaceOrder("AttackMoveAttack", {target: msg.data.added[0]});
     598                }
     599            },
     600           
     601            "leave": function(msg){
     602                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     603                rangeMan.DisableActiveQuery(this.losRangeQuery);
     604            }
     605        },
    499606    },
    500607
    501608
     
    21732280        var order = this.orderQueue[i];
    21742281        switch (order.type)
    21752282        {
     2283        case "AttackMove":
    21762284        case "Walk":
    21772285            // Add the distance to the target point
    21782286            var dx = order.data.x - pos.x;
     
    21852293
    21862294            break; // and continue the loop
    21872295
     2296        case "AttackMoveAttack":
    21882297        case "WalkToTarget":
    21892298        case "Flee":
    21902299        case "LeaveFoundation":
     
    22582367    this.AddOrder("Attack", { "target": target, "force": true }, queued);
    22592368};
    22602369
     2370UnitAI.prototype.AttackMove = function(x, z, queued)
     2371{
     2372    this.AddOrder("AttackMove", { "x": x, "z": z }, queued);
     2373};
     2374
    22612375UnitAI.prototype.Garrison = function(target, queued)
    22622376{
    22632377    if (!this.CanGarrison(target))
  • binaries/data/mods/public/simulation/components/Formation.js

     
    88Formation.prototype.Init = function()
    99{
    1010    this.members = []; // entity IDs currently belonging to this formation
     11    this.suspendedMembers = []; // entity ID's which currently belong to the formation but are being left to their own devices.
    1112    this.columnar = false; // whether we're travelling in column (vs box) formation
    1213    this.formationName = "Line Closed";
    1314};
    1415
    1516Formation.prototype.GetMemberCount = function()
    1617{
    17     return this.members.length;
     18    var allMembers = this.members.concat(this.suspendedMembers);
     19    return allMembers.length;
    1820};
    1921
    2022/**
     
    2628 */
    2729Formation.prototype.GetPrimaryMember = function()
    2830{
    29     return this.members[0];
     31    return this.members[0] || this.suspendedMembers[0];
    3032};
    3133
    3234/**
     
    3638 */
    3739Formation.prototype.SetMembers = function(ents)
    3840{
     41    this.suspendedMembers = [];
    3942    this.members = ents;
    4043
    4144    for each (var ent in this.members)
     
    5760Formation.prototype.RemoveMembers = function(ents)
    5861{
    5962    this.members = this.members.filter(function(e) { return ents.indexOf(e) == -1; });
     63    this.suspendedMembers = this.suspendedMembers.filter(function(e) { return ents.indexOf(e) == -1; });
    6064
    6165    for each (var ent in ents)
    6266    {
     
    6569    }
    6670
    6771    // If there's nobody left, destroy the formation
    68     if (this.members.length == 0)
     72    if (this.members.length == 0 && this.suspendedMembers.length == 0)
    6973    {
    7074        Engine.DestroyEntity(this.entity);
    7175        return;
     
    8286 */
    8387Formation.prototype.Disband = function()
    8488{
    85     for each (var ent in this.members)
     89    var allMembers = this.members.concat(this.suspendedMembers);
     90    for each (var ent in allMembers)
    8691    {
    8792        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    8893        cmpUnitAI.SetFormationController(INVALID_ENTITY);
    8994    }
    9095
    9196    this.members = [];
     97    this.suspendedMembers = [];
    9298
    9399    Engine.DestroyEntity(this.entity);
    94100};
    95101
     102// Allow the units in the formation to move freely but keep them registered to this formation
     103Formation.prototype.suspendFormation = function(){
     104    this.suspendedMembers = this.members;
     105    this.members = [];
     106};
     107
     108// Make the units resume their positions in the formation
     109Formation.prototype.resumeFormation = function(){
     110    this.SetMembers(this.suspendedMembers);
     111    this.suspendedMembers = [];
     112};
     113
    96114/**
    97115 * Call obj.funcname(args) on UnitAI components of all members.
    98116 */
    99117Formation.prototype.CallMemberFunction = function(funcname, args)
    100118{
    101     for each (var ent in this.members)
     119    var allMembers = this.members.concat(this.suspendedMembers);
     120    for each (var ent in allMembers)
    102121    {
    103122        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    104123        cmpUnitAI[funcname].apply(cmpUnitAI, args);
     
    106125};
    107126
    108127/**
     128 * Call obj.funcname(args) on UnitAI components of all members and count true responses
     129 */
     130Formation.prototype.CountMemberFunction = function(funcname, args)
     131{
     132    var count = 0;
     133    var allMembers = this.members.concat(this.suspendedMembers);
     134    for each (var ent in allMembers)
     135    {
     136        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     137        if (cmpUnitAI[funcname].apply(cmpUnitAI, args))
     138            count ++;
     139    }
     140    return count;
     141};
     142
     143/**
    109144 * Set all members to form up into the formation shape.
    110145 * If moveCenter is true, the formation center will be reinitialised
    111146 * to the center of the units.
     147 * If attackMove is true then the units will be given an AttackMove order
    112148 */
    113 Formation.prototype.MoveMembersIntoFormation = function(moveCenter)
     149Formation.prototype.MoveMembersIntoFormation = function(moveCenter, attackMove)
    114150{
    115151    var active = [];
    116152    var positions = [];
     
    145181        var offset = offsets[i];
    146182
    147183        var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI);
    148         cmpUnitAI.ReplaceOrder("FormationWalk", {
    149             "target": this.entity,
    150             "x": offset.x - avgoffset.x,
    151             "z": offset.z - avgoffset.z
    152         });
     184        if (attackMove){
     185            cmpUnitAI.ReplaceOrder("FormationAttackMove", {
     186                "target": this.entity,
     187                "x": offset.x - avgoffset.x,
     188                "z": offset.z - avgoffset.z
     189            });
     190        }else{
     191            cmpUnitAI.ReplaceOrder("FormationWalk", {
     192                "target": this.entity,
     193                "x": offset.x - avgoffset.x,
     194                "z": offset.z - avgoffset.z
     195            });
     196        }
    153197    }
    154198};
    155199
     
    594638{
    595639    // When an entity is captured or destroyed, it should no longer be
    596640    // controlled by this formation
     641   
     642    var allMembers = this.members.concat(this.suspendedMembers);
    597643
    598     if (this.members.indexOf(msg.entity) != -1)
     644    if (allMembers.indexOf(msg.entity) != -1)
    599645        this.RemoveMembers([msg.entity]);
    600646};
    601647
     
    611657        var cmpNewUnitAI = Engine.QueryInterface(msg.newentity, IID_UnitAI);
    612658        cmpNewUnitAI.SetFormationController(this.entity);
    613659    }
     660    if (this.suspendedMembers.indexOf(msg.entity) != -1)
     661    {
     662        this.suspendedMembers[this.suspendedMembers.indexOf(msg.entity)] = msg.newentity;
     663
     664        var cmpOldUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI);
     665        cmpOldUnitAI.SetFormationController(INVALID_ENTITY);
     666
     667        var cmpNewUnitAI = Engine.QueryInterface(msg.newentity, IID_UnitAI);
     668        cmpNewUnitAI.SetFormationController(this.entity);
     669    }
    614670}
    615671
    616672Formation.prototype.LoadFormation = function(formationName)
    617673{
     674    var allMembers = this.members.concat(this.suspendedMembers);
    618675    this.formationName = formationName;
    619     for each (var ent in this.members)
     676    for each (var ent in allMembers)
    620677    {
    621678        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    622679        cmpUnitAI.SetLastFormationName(this.formationName);
  • binaries/data/config/default.cfg

     
    202202hotkey.profile.toggle = "F11"               ; Enable/disable real-time profiler
    203203hotkey.profile.save = "Shift+F11"           ; Save current profiler data to logs/profile.txt
    204204
     205hotkey.attackmove = Super
     206
    205207; EXPERIMENTAL: joystick/gamepad settings
    206208joystick.enable = false
    207209joystick.deadzone = 8192