Ticket #1716: formation-order-queues.patch

File formation-order-queues.patch, 10.7 KB (added by leper, 12 years ago)

Proper formation order queueing

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

     
    174174};
    175175
    176176/**
     177 * Call obj.functname(args) on UnitAI components of all members,
     178 * and return true if all calls return true.
     179 */
     180Formation.prototype.TestAllMemberFunction = function(funcname, args)
     181{
     182    for each (var ent in this.members)
     183    {
     184        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     185        if (!cmpUnitAI[funcname].apply(cmpUnitAI, args))
     186            return false;
     187    }
     188    return true;
     189};
     190
     191/**
    177192 * Set all members to form up into the formation shape.
    178193 * If moveCenter is true, the formation center will be reinitialised
    179194 * to the center of the units.
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    512512        },
    513513
    514514        "Order.Attack": function(msg) {
    515             // TODO: we should move in formation towards the target,
    516             // then break up into individuals when close enough to it
     515            // TODO on what should we base this range?
     516            // Check if we are already in range, otherwise walk there
     517            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 20))
     518            {
     519                if (!this.TargetIsAlive(msg.data.target))
     520                    // The target was destroyed
     521                    this.FinishOrder();
     522                else
     523                    // Out of range; move there in formation
     524                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 20 });
     525                return;
     526            }
    517527
    518528            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     529            // We don't want to rearrange the formation if the individual units are carrying
     530            // out a task and one of the members dies/leaves the formation.
     531            cmpFormation.SetRearrange(false);
    519532            cmpFormation.CallMemberFunction("Attack", [msg.data.target, false]);
    520533
    521             // TODO: we should wait until the target is killed, then
    522             // move on to the next queued order.
    523             // Don't bother now, just disband the formation immediately.
    524             cmpFormation.Disband();
     534            this.SetNextStateAlwaysEntering("MEMBER");
    525535        },
    526536
    527537        "Order.Heal": function(msg) {
    528             // TODO: see notes in Order.Attack
     538            // TODO on what should we base this range?
     539            // Check if we are already in range, otherwise walk there
     540            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
     541            {
     542                if (!this.TargetIsAlive(msg.data.target))
     543                    // The target was destroyed
     544                    this.FinishOrder();
     545                else
     546                    // Out of range; move there in formation
     547                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
     548                return;
     549            }
     550
    529551            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     552            // We don't want to rearrange the formation if the individual units are carrying
     553            // out a task and one of the members dies/leaves the formation.
     554            cmpFormation.SetRearrange(false);
    530555            cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]);
    531             cmpFormation.Disband();
     556
     557            this.SetNextStateAlwaysEntering("MEMBER");
    532558        },
    533559
    534560        "Order.Repair": function(msg) {
     
    551577            cmpFormation.SetRearrange(false);
    552578            cmpFormation.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]);
    553579
    554             this.SetNextState("REPAIR");
     580            this.SetNextStateAlwaysEntering("REPAIR");
    555581        },
    556582
    557583        "Order.Gather": function(msg) {
    558             // TODO: see notes in Order.Attack
     584            // TODO on what should we base this range?
     585            // Check if we are already in range, otherwise walk there
     586            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
     587            {
     588                if (!this.CanGather(msg.data.target))
     589                    // The target isn't gatherable
     590                    this.FinishOrder();
     591                    // TODO should be a GatherNearPosition order.
     592                // else if (!this.TargetIsAlive(msg.data.target))
     593                    // gather near position
     594                    //cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.lastPos.x, msg.data.lastPos.z, msg.data.type, msg.data.template, false]);
     595                else
     596                    // Out of range; move there in formation
     597                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
     598                return;
     599            }
    559600
    560             // If the resource no longer exists, send a GatherNearPosition order
    561601            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
    562             if (this.CanGather(msg.data.target))
    563                 cmpFormation.CallMemberFunction("Gather", [msg.data.target, false]);
    564             else
    565                 cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.lastPos.x, msg.data.lastPos.z, msg.data.type, msg.data.template, false]);
     602            // We don't want to rearrange the formation if the individual units are carrying
     603            // out a task and one of the members dies/leaves the formation.
     604            cmpFormation.SetRearrange(false);
     605            cmpFormation.CallMemberFunction("Gather", [msg.data.target, false]);
    566606
    567             cmpFormation.Disband();
     607            this.SetNextStateAlwaysEntering("MEMBER");
    568608        },
    569609
    570610        "Order.GatherNearPosition": function(msg) {
    571             // TODO: see notes in Order.Attack
     611            // TODO on what should we base this range?
     612            // Check if we are already in range, otherwise walk there
     613            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 20))
     614            {
     615                if (!this.TargetIsAlive(msg.data.target))
     616                    // The target was destroyed
     617                    this.FinishOrder();
     618                else
     619                    // Out of range; move there in formation
     620                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 20 });
     621                return;
     622            }
     623
    572624            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     625            // We don't want to rearrange the formation if the individual units are carrying
     626            // out a task and one of the members dies/leaves the formation.
     627            cmpFormation.SetRearrange(false);
    573628            cmpFormation.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]);
    574             cmpFormation.Disband();
     629
     630            this.SetNextStateAlwaysEntering("MEMBER");
    575631        },
    576632
    577633        "Order.ReturnResource": function(msg) {
    578             // TODO: see notes in Order.Attack
     634            // TODO on what should we base this range?
     635            // Check if we are already in range, otherwise walk there
     636            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
     637            {
     638                if (!this.TargetIsAlive(msg.data.target))
     639                    // The target was destroyed
     640                    this.FinishOrder();
     641                else
     642                    // Out of range; move there in formation
     643                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
     644                return;
     645            }
     646
    579647            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     648            // We don't want to rearrange the formation if the individual units are carrying
     649            // out a task and one of the members dies/leaves the formation.
     650            cmpFormation.SetRearrange(false);
    580651            cmpFormation.CallMemberFunction("ReturnResource", [msg.data.target, false]);
    581             cmpFormation.Disband();
     652
     653            this.SetNextStateAlwaysEntering("MEMBER");
    582654        },
    583655
    584656        "Order.Garrison": function(msg) {
    585             // TODO: see notes in Order.Attack
     657            // TODO on what should we base this range?
     658            // Check if we are already in range, otherwise walk there
     659            if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
     660            {
     661                if (!this.TargetIsAlive(msg.data.target))
     662                    // The target was destroyed
     663                    this.FinishOrder();
     664                else
     665                    // Out of range; move there in formation
     666                    this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
     667                return;
     668            }
     669
    586670            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     671            // We don't want to rearrange the formation if the individual units are carrying
     672            // out a task and one of the members dies/leaves the formation.
     673            cmpFormation.SetRearrange(false);
    587674            cmpFormation.CallMemberFunction("Garrison", [msg.data.target, false]);
    588             cmpFormation.Disband();
     675
     676            this.SetNextStateAlwaysEntering("MEMBER");
    589677        },
    590678
    591679        "IDLE": {
     
    601689            "MoveCompleted": function(msg) {
    602690                if (this.FinishOrder())
    603691                    return;
    604                    
     692
    605693                // If this was the last order, attempt to disband the formation.
    606694                var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
    607695                cmpFormation.FindInPosition();
     
    620708                cmpFormation.Disband();
    621709            },
    622710        },
     711
     712        "MEMBER": {
     713            // Wait for individual members to finish
     714            "enter": function(msg) {
     715                this.StartTimer(1000, 1000);
     716            },
     717
     718            "Timer": function(msg) {
     719                var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     720
     721                // Have all members finished the task?
     722                if (!cmpFormation.TestAllMemberFunction("HasFinishedOrder", []))
     723                    return;
     724
     725                // Execute the next order
     726                if (this.FinishOrder())
     727                    return;
     728
     729                // No more order left.
     730                cmpFormation.Disband();
     731            },
     732
     733            "leave": function(msg) {
     734                this.StopTimer();
     735                var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     736                cmpFormation.CallMemberFunction("ResetFinishOrder", []);
     737            },
     738        },
    623739    },
    624740
    625741
    626742    // States for entities moving as part of a formation:
    627743    "FORMATIONMEMBER": {
    628744        "FormationLeave": function(msg) {
     745            // We're not in a formation anymore, so no need to track this.
     746            this.finishedOrder = false;
     747
    629748            // Stop moving as soon as the formation disbands
    630749            this.StopMoving();
    631750
     
    18491968    this.isGarrisoned = false;
    18501969    this.isIdle = false;
    18511970    this.lastFormationName = "";
     1971    this.finishedOrder = false; // used to find if all formation members finished the order
    18521972
    18531973    // For preventing increased action rate due to Stop orders or target death.
    18541974    this.lastAttacked = undefined;
     
    18621982    return (this.template.FormationController == "true");
    18631983};
    18641984
     1985UnitAI.prototype.IsFormationMember = function()
     1986{
     1987    return (this.formationController != INVALID_ENTITY);
     1988};
     1989
     1990UnitAI.prototype.HasFinishedOrder = function()
     1991{
     1992    return this.finishedOrder;
     1993};
     1994
     1995UnitAI.prototype.ResetFinishOrder = function()
     1996{
     1997    this.finishedOrder = false;
     1998};
     1999
    18652000UnitAI.prototype.IsAnimal = function()
    18662001{
    18672002    return (this.template.NaturalBehaviour ? true : false);
     
    21492284    else
    21502285    {
    21512286        this.SetNextState("IDLE");
     2287
     2288        // Check if there are queued formation orders
     2289        if (this.IsFormationMember())
     2290        {
     2291            var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI);
     2292            if (cmpUnitAI)
     2293            {
     2294                // Inform the formation controller that we finished this task
     2295                this.finishedOrder = true;
     2296                // We don't want to carry out the default order
     2297                // if there are still queued formation orders left
     2298                if (cmpUnitAI.GetOrders().length > 1)
     2299                    return true;
     2300            }
     2301        }
     2302
    21522303        return false;
    21532304    }
    21542305};