Ticket #13: diffFormation-16-04-2011.patch

File diffFormation-16-04-2011.patch, 16.2 KB (added by Badmadblacksad, 13 years ago)
  • binaries/data/mods/public/gui/session/utility_functions.js

     
    223223{
    224224    switch (formationName)
    225225    {
    226     case "Formation0":
     226    case "Loose":
    227227        return 0;
    228     case "Formation1":
     228    case "Box":
    229229        return 1;
    230     case "Formation2":
     230    case "Column Closed":
    231231        return 2;
    232     case "Formation3":
     232    case "Line Closed":
    233233        return 3;
    234     case "Formation4":
     234    case "Column Open":
    235235        return 4;
    236     case "Formation5":
     236    case "Line Open":
    237237        return 5;
    238     case "Formation6":
     238    case "Flank":
    239239        return 6;
    240     case "Formation7":
     240    case "Skirmish":
    241241        return 7;
    242     case "Formation8":
     242    case "Wedge":
    243243        return 8;
    244     case "Formation9":
     244    case "Testudo":
    245245        return 9;
    246     case "Formation10":
    247         return 10; 
    248     case "Formation11":
     246    case "Phalanx":
     247        return 10;
     248    case "Syntagma":
    249249        return 11;
    250250    case "Formation12":
    251251        return 12;
     
    275275{
    276276    var formations = [];
    277277
    278     formations.push("Formation0");
    279     formations.push("Formation1");
    280     formations.push("Formation2");
    281     formations.push("Formation3");
    282     formations.push("Formation4");
    283     formations.push("Formation5");
    284     formations.push("Formation6");
    285     formations.push("Formation7");
    286     formations.push("Formation8");
    287     formations.push("Formation9");
    288     formations.push("Formation10");
    289     formations.push("Formation11");
     278    formations.push("Loose");
     279    formations.push("Box");
     280    formations.push("Column Closed");
     281    formations.push("Line Closed");
     282    formations.push("Column Open");
     283    formations.push("Line Open");
     284    formations.push("Flank");
     285    formations.push("Skirmish");
     286    formations.push("Wedge");
     287    formations.push("Testudo");
     288    formations.push("Phalanx");
     289    formations.push("Syntagma");
    290290    formations.push("Formation12");
    291291    return formations;
    292292}
  • binaries/data/mods/public/gui/session/input.js

     
    10621062// Performs the specified formation
    10631063function performFormation(entity, formationName)
    10641064{
    1065     submitChatDirectly("FORMATIONS are not implemented yet.");
    1066 
    10671065    if (entity)
    10681066    {
    10691067        console.write(formationName);
     1068
     1069        var selection = g_Selection.toList();
     1070        Engine.PostNetworkCommand({
     1071            "type": "formation",
     1072            "entities": selection,
     1073            "name": formationName
     1074        });
     1075
    10701076    }
    10711077}
    10721078
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    236236       
    237237        var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
    238238        cmpGarrisonHolder.UnloadAll();
     239        break;     
     240
     241    case "formation":
     242        var cmpUnitAI = GetFormationUnitAI(cmd.entities);
     243        if(!cmpUnitAI)
     244            break;
     245        var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation);
     246        if (!cmpFormation)
     247            break;
     248        cmpFormation.LoadFormation(cmd.name);
     249        cmpFormation.MoveMembersIntoFormation(true);
    239250        break;
    240251       
    241252    default:
     
    339350        formationEnt = Engine.AddEntity("special/formation");
    340351        var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation);
    341352        cmpFormation.SetMembers(formation.entities);
     353
     354        // We retrieve the last common formation name
     355        var lastFormationName = undefined;
     356        for each (var ent in formation.entities)
     357        {
     358            var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     359            if (cmpUnitAI)
     360            {
     361                var name = cmpUnitAI.GetLastFormationName();
     362                if (lastFormationName == undefined)
     363                {
     364                    lastFormationName = name;
     365                }
     366                else if (lastFormationName != name)
     367                {
     368                    lastFormationName = undefined;
     369                    break;
     370                }
     371            }
     372        }
     373        if (lastFormationName)
     374            cmpFormation.LoadFormation(lastFormationName);
     375        else
     376            cmpFormation.LoadFormation("default");
    342377    }
    343378
    344379    return Engine.QueryInterface(formationEnt, IID_UnitAI);
  • binaries/data/mods/public/simulation/components/Identity.js

     
    6868                        "<value>Infantry</value>" +
    6969                        "<value>Cavalry</value>" +
    7070                        "<value>Ranged</value>" +
     71                        "<value>Melee</value>" +
    7172                        "<value>Mechanical</value>" +
    7273                        "<value>Ship</value>" +
    7374                        "<value>Siege</value>" +
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    11611161    this.order = undefined; // always == this.orderQueue[0]
    11621162    this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to
    11631163    this.isIdle = false;
     1164    this.lastFormationName = "default";
    11641165
    11651166    this.SetStance(this.template.DefaultStance);
    11661167};
     
    17401741    return this.formationController;
    17411742};
    17421743
     1744UnitAI.prototype.SetLastFormationName = function(name)
     1745{
     1746    this.lastFormationName = name;
     1747};
     1748
     1749UnitAI.prototype.GetLastFormationName = function()
     1750{
     1751  return this.lastFormationName;
     1752};
     1753
    17431754/**
    17441755 * Returns the estimated distance that this unit will travel before either
    17451756 * finishing all of its orders, or reaching a non-walk target (attack, gather, etc).
  • binaries/data/mods/public/simulation/components/Formation.js

     
    99{
    1010    this.members = []; // entity IDs currently belonging to this formation
    1111    this.columnar = false; // whether we're travelling in column (vs box) formation
     12    this.formationName = "default";
    1213};
    1314
    1415Formation.prototype.GetMemberCount = function()
     
    114115    var active = [];
    115116    var positions = [];
    116117
    117     var types = { "Unknown": 0 }; // TODO: make this work so we put ranged behind infantry etc
    118 
    119118    for each (var ent in this.members)
    120119    {
    121120        var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     
    124123
    125124        active.push(ent);
    126125        positions.push(cmpPosition.GetPosition());
    127 
    128         types["Unknown"] += 1; // TODO
    129126    }
    130127
    131128    // Work out whether this should be a column or box formation
     
    133130    var walkingDistance = cmpUnitAI.ComputeWalkingDistance();
    134131    this.columnar = (walkingDistance > g_ColumnDistanceThreshold);
    135132
    136     var offsets = this.ComputeFormationOffsets(types, this.columnar);
     133    var offsets = this.ComputeFormationOffsets(active, this.columnar);
    137134
    138135    var avgpos = this.ComputeAveragePosition(positions);
    139136    var avgoffset = this.ComputeAveragePosition(offsets);
     
    143140    if (moveCenter || !cmpPosition.IsInWorld())
    144141        cmpPosition.JumpTo(avgpos.x, avgpos.z);
    145142
    146     // TODO: assign to minimise worst-case distances or whatever
    147 
    148     for (var i = 0; i < active.length; ++i)
     143    for (var i = 0; i < offsets.length; ++i)
    149144    {
    150145        var offset = offsets[i];
    151146
    152         var cmpUnitAI = Engine.QueryInterface(active[i], IID_UnitAI);
     147        var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI);
    153148        cmpUnitAI.ReplaceOrder("FormationWalk", {
    154149            "target": this.entity,
    155150            "x": offset.x - avgoffset.x,
     
    177172    cmpPosition.JumpTo(avgpos.x, avgpos.z);
    178173};
    179174
    180 Formation.prototype.ComputeFormationOffsets = function(types, columnar)
     175Formation.prototype.ComputeFormationOffsets = function(active, columnar)
    181176{
    182177    var separation = 4; // TODO: don't hardcode this
    183178
    184     var count = types["Unknown"];
     179    var types = {   "Hero" : [],
     180                                    "Melee" : [],
     181                                    "Ranged" : [],
     182                                    "Support" : [],
     183                                    "Unknown": []
     184                                    }; // TODO: make this work so we put ranged behind infantry etc
    185185
     186    for each (var ent in active)
     187    {
     188        var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
     189        var classes = cmpIdentity.GetClassesList();
     190        var done = false;
     191        for each (var cla in classes)
     192        {
     193            if(cla in types)
     194            {
     195                types[cla].push(ent);
     196                done = true;
     197                break;
     198            }
     199        }
     200        if (!done)
     201        {
     202            types["Unknown"].push(ent);
     203        }
     204    }
     205
     206    var count = active.length;
     207
     208    var shape = undefined;
     209    var ordering = "default";
     210
     211    var offsets = [];
     212
    186213    // Choose a sensible width for the basic default formation
    187214    var cols;
    188     if (columnar)
     215    if (columnar || this.formationName == "Column Closed")
    189216    {
    190217        // Have at most 3 files
    191218        if (count <= 3)
    192219            cols = count;
    193220        else
    194221            cols = 3;
     222        shape = "square";
    195223    }
    196     else
     224    else if (this.formationName == "default" || this.formationName == "Phalanx")
    197225    {
    198226        // Try to have at least 5 files (so batch training gives a single line),
    199227        // and at most 8
    200228        if (count <= 5)
    201229            cols = count;
    202         if (count <= 10)
     230        else if (count <= 10)
    203231            cols = 5;
    204232        else if (count <= 16)
    205233            cols = Math.ceil(count / 2);
    206234        else
    207235            cols = 8;
     236        shape = "square";
    208237    }
     238    else if (this.formationName == "Line Closed")
     239    {
     240        if (count <= 3)
     241            cols = count;
     242        else if(count < 30)
     243            cols = Math.ceil(count/2);
     244        else
     245            cols = Math.ceil(count/3);
     246        shape = "square";
     247    }
     248    else if (this.formationName == "Testudo")
     249    {
     250        if (count < 9 || count > 25)
     251        {
     252            this.LoadFormation("default");
     253            return offsets;
     254        }
     255        cols = Math.ceil(Math.sqrt(count));
    209256
    210     var ranks = Math.ceil(count / cols);
     257        shape = "square";
     258    }
     259    else if (this.formationName == "Column Open")
     260    {
     261        cols = 2
     262        shape = "opensquare";
     263    }
     264    else if (this.formationName == "Line Open")
     265    {
     266        if (count <= 5)
     267            cols = 3;
     268        else if (count <= 11)
     269            cols = 4;
     270        else if (count <= 18)
     271            cols = 5;
     272        else
     273            cols = 6;
     274        shape = "opensquare";
     275    }
     276    else if (this.formationName == "Loose") // Circle
     277    {
     278        var depth;
     279        if (count < 5 || count > 36)
     280        {
     281            this.LoadFormation("default");
     282            return offsets;
     283        }
     284        depth = Math.ceil(count / 12);
    211285
    212     var offsets = [];
     286        var left = count;
     287        var radius = Math.min(left,12) * separation / (2 * Math.PI);
     288        for (var c = 0; c < depth; ++c)
     289        {
     290            var ctodo = Math.min(left,12);
     291            var cradius = radius - c * separation / 2;
     292            var delta = 2 * Math.PI / ctodo;
     293            for (var alpha = 0; ctodo; alpha+=delta)
     294            {
     295                var x = Math.cos(alpha) * cradius;
     296                var z = Math.sin(alpha) * cradius;
     297                offsets.push({"x": x, "z": z});
     298                ctodo--;
     299                left--;
     300            }
     301        }
     302    }
     303    else if (this.formationName == "Box") // Box
     304    {
     305        var root;
     306        if (count > 49)
     307        {
     308            this.LoadFormation("default");
     309            return offsets;
     310        }
     311        if (count <= 9)
     312            root = 3;
     313        else if (count > 9 && count <= 25)
     314            root = 5;
     315        else if (count > 25 && count <= 49)
     316            root = 7;
    213317
    214     var left = count;
    215     for (var r = 0; r < ranks; ++r)
     318        var left = count;
     319        var meleeleft = types["Melee"].length;
     320        for (var sq = Math.floor(root/2); sq >= 0; --sq)
     321        {
     322            var width = sq * 2 + 1;
     323            var stodo;
     324            if (sq == 0)
     325            {
     326                stodo = left;
     327            }
     328            else
     329            {
     330                if (meleeleft >= width*width - (width-2)*(width-2)) // form a complete box
     331                {
     332                    stodo = width*width - (width-2)*(width-2);
     333                    meleeleft -= stodo;
     334                }
     335                else    // compact
     336                    stodo = Math.max(0,left - (width-2)*(width-2));
     337            }
     338
     339            for (var r = -sq; r <= sq && stodo; ++r)
     340            {
     341                for (var c = -sq; c <= sq && stodo; ++c)
     342                {
     343                    if (Math.abs(r) == sq || Math.abs(c) == sq)
     344                    {
     345                        var x = c * separation;
     346                        var z = -r * separation;
     347                        offsets.push({"x": x, "z": z});
     348                        stodo--;
     349                        left--;
     350                    }
     351                }
     352            }
     353        }
     354    }
     355    else if (this.formationName == "Skirmish")
    216356    {
    217         var n = Math.min(left, cols);
    218         for (var c = 0; c < n; ++c)
     357        if (count < 9 || count > 25)
    219358        {
    220             var x = ((n-1)/2 - c) * separation;
    221             var z = -r * separation;
    222             offsets.push({"x": x, "z": z});
     359            this.LoadFormation("default");
     360            return offsets;
    223361        }
    224         left -= n;
     362        cols = Math.ceil(count/2);
     363        shape = "opensquare";
    225364    }
     365    else if (this.formationName == "Wedge")
     366    {
     367        var depth;
     368        if (count > 49)
     369        {
     370            this.LoadFormation("default");
     371            return offsets;
     372        }
     373        if (count <= 9)
     374            depth = 3;
     375        else if (count > 9 && count <= 16)
     376            depth = 4;
     377        else if (count > 16 && count <= 25)
     378            depth = 5;
     379        else if (count > 25 && count <= 49)
     380            depth = 7;
    226381
     382        var left = count;
     383        var width = 2 * depth - 1;
     384        for (var p = 0; p < depth && left; ++p)
     385        {
     386            for (var r = p; r < depth && left; ++r)
     387            {
     388                var c1 = depth - r + p;
     389                var c2 = depth + r - p;
     390
     391                if (left)
     392                {
     393                        var x = c1 * separation;
     394                        var z = -r * separation;
     395                        offsets.push({"x": x, "z": z});
     396                        left--;
     397                }
     398                if (left && c1 != c2)
     399                {
     400                        var x = c2 * separation;
     401                        var z = -r * separation;
     402                        offsets.push({"x": x, "z": z});
     403                        left--;
     404                }
     405            }
     406        }
     407    }
     408    else if (this.formationName == "Flank")
     409    {
     410        if (count < 8 || count > 24)
     411        {
     412            this.LoadFormation("default");
     413            return offsets;
     414        }
     415        cols = 3;
     416        var leftside = [];
     417        leftside[0] = Math.ceil(count/2);
     418        leftside[1] = Math.floor(count/2);
     419        ranks = Math.ceil(leftside[0] / cols);
     420        var off = - separation * 4;
     421        for (var side = 0; side < 2; ++side)
     422        {
     423            var left = leftside[side];
     424            off += side * separation * 8;
     425            for (var r = 0; r < ranks; ++r)
     426            {
     427                var n = Math.min(left, cols);
     428                for (var c = 0; c < n; ++c)
     429                {
     430                    var x = off + ((n-1)/2 - c) * separation;
     431                    var z = -r * separation;
     432                    offsets.push({"x": x, "z": z});
     433                }
     434                left -= n;
     435            }
     436        }
     437    }
     438    else if (this.formationName == "Formation12" || this.formationName == "Syntagma")
     439    {
     440        return offsets; //@todo
     441    }
     442
     443    if (shape == "square")
     444    {
     445        var ranks = Math.ceil(count / cols);
     446
     447        var left = count;
     448        for (var r = 0; r < ranks; ++r)
     449        {
     450            var n = Math.min(left, cols);
     451            for (var c = 0; c < n; ++c)
     452            {
     453                var x = ((n-1)/2 - c) * separation;
     454                var z = -r * separation;
     455                offsets.push({"x": x, "z": z});
     456            }
     457            left -= n;
     458        }
     459    }
     460    else if (shape == "opensquare")
     461    {
     462        var left = count;
     463        for (var r = 0; left; ++r)
     464        {
     465            var n = Math.min(left, cols - (r&1?1:0));
     466            for (var c = 0; c < 2*n; c+=2)
     467            {
     468                var x = (- c - (r&1)) * separation;
     469                var z = -r * separation;
     470                offsets.push({"x": x, "z": z});
     471            }
     472            left -= n;
     473        }
     474    }
     475
     476    // TODO: assign to minimise worst-case distances or whatever
     477    if (ordering == "default")
     478    {
     479        for each (var offset in offsets)
     480        {
     481            var ent = undefined;
     482            for (var t in types)
     483            {
     484                if (types[t].length)
     485                {
     486                    ent = types[t].pop();
     487                    offset.ent = ent
     488                    break;
     489                }
     490            }
     491        }
     492    }
     493
    227494    return offsets;
    228495};
    229496
     
    286553        this.RemoveMembers([msg.entity]);
    287554};
    288555
     556Formation.prototype.LoadFormation = function(formationName)
     557{
     558    this.formationName = formationName;
     559    for each(var ent in this.members)
     560    {
     561        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     562        cmpUnitAI.SetLastFormationName(this.formationName);
     563    }
     564}
     565
    289566Engine.RegisterComponentType(IID_Formation, "Formation", Formation);
  • binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml

     
    22<Entity parent="template_unit_infantry">
    33  <Identity>
    44    <GenericName>Ranged</GenericName>
     5    <Classes datatype="tokens">Ranged Infantry</Classes>
    56  </Identity>
    67  <Health>
    78    <Max>90</Max>
  • binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml

     
    22<Entity parent="template_unit_infantry">
    33  <Identity>
    44    <GenericName>Melee Infantry</GenericName>
     5    <Classes datatype="tokens">Melee Infantry</Classes>
    56  </Identity>
    67  <Health>
    78    <Max>100</Max>