Ticket #865: diffStances-05-06-2011.patch

File diffStances-05-06-2011.patch, 26.0 KB (added by Badmadblacksad, 13 years ago)

a little more advanced stance implementation

  • source/simulation2/components/ICmpRangeManager.cpp

     
    3636BEGIN_INTERFACE_WRAPPER(RangeManager)
    3737DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
    3838DEFINE_INTERFACE_METHOD_5("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
     39DEFINE_INTERFACE_METHOD_3("ModifyRangeActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ModifyRangeActiveQuery, ICmpRangeManager::tag_t, entity_pos_t, entity_pos_t)
    3940DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
    4041DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
    4142DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
  • source/simulation2/components/ICmpRangeManager.h

     
    102102        entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
    103103
    104104    /**
     105     * Modify an active query's range parameters and execute a ResetActiveQuery.
     106     * @param tag identifier of query.
     107     * @param minRange non-negative minimum distance in metres (inclusive).
     108     * @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance.
     109     * @return list of entities matching the query, ordered by increasing distance from the source entity.
     110     */
     111    virtual std::vector<entity_id_t> ModifyRangeActiveQuery(tag_t tag,
     112        entity_pos_t minRange, entity_pos_t maxRange) = 0;
     113
     114    /**
    105115     * Destroy a query and clean up resources. This must be called when an entity no longer needs its
    106116     * query (e.g. when the entity is destroyed).
    107117     * @param tag identifier of query.
  • source/simulation2/components/CCmpRangeManager.cpp

     
    459459        return (tag_t)id;
    460460    }
    461461
     462    virtual std::vector<entity_id_t> ModifyRangeActiveQuery(tag_t tag,
     463        entity_pos_t minRange, entity_pos_t maxRange)
     464    {
     465        std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
     466        if (it == m_Queries.end())
     467        {
     468            LOGERROR(L"CCmpRangeManager: ModifyRangeActiveQuery called with invalid tag %d", tag);
     469            std::vector<entity_id_t> r;
     470            return r;
     471        }
     472
     473        Query& q = it->second;
     474        q.minRange = minRange;
     475        q.maxRange = maxRange;
     476        return ResetActiveQuery(tag);
     477    }
     478
    462479    virtual void DestroyActiveQuery(tag_t tag)
    463480    {
    464481        if (m_Queries.find(tag) == m_Queries.end())
  • binaries/data/mods/public/maps/scenarios/Combat_demo.xml

     
    4040      "Civ": "hele"
    4141    }
    4242  ],
    43   "RevealMap": true
     43  "RevealMap": true,
     44  "DefaultStance": "stand"
    4445}
    4546]]></ScriptSettings>
    4647    <Entities>
     
    12851286        </Entity>
    12861287    </Entities>
    12871288    <Paths/>
    1288 </Scenario>
    1289  No newline at end of file
     1289</Scenario>
  • binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml

     
    4141    }
    4242  ],
    4343  "RevealMap": true,
    44   "DefaultStance": "holdfire",
     44  "DefaultStance": "defensive",
    4545  "GameType": "endless"
    4646}
    4747]]></ScriptSettings>
     
    684684        </Entity>
    685685    </Entities>
    686686    <Paths/>
    687 </Scenario>
    688  No newline at end of file
     687</Scenario>
  • binaries/data/mods/public/gui/session/session.xml

     
    408408    </object>
    409409
    410410    <!-- ================================  ================================ -->
     411    <!-- Stance Selection (Temporary location) -->
     412    <!-- ================================  ================================ -->
     413    <object name="unitStancePanel"
     414        sprite="bottomMiddle"
     415        size="50 50 300 106"
     416        type="text"
     417    >
     418        <object size="-5 -2 59 62" type="image" sprite="snIconSheetTab" tooltip_style="sessionToolTipBottom"
     419             cell_id="4" tooltip="Stances"/>
     420
     421        <object size="56 12 100% 100%">
     422            <repeat count="5">
     423                <object name="unitStanceButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
     424                    <object name="unitStanceIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     425                </object>
     426            </repeat>
     427        </object>
     428    </object>
     429
     430    <!-- ================================  ================================ -->
    411431    <!-- Idle Worker Button -->
    412432    <!-- ================================  ================================ -->
    413433    <object type="image"
     
    638658                </object>
    639659            </object>
    640660
    641             <object name="unitStancePanel"
    642                 style="goldPanelFrilly"
    643                 size="0 100%-56 100% 100%"
    644                 type="text"
    645             >
    646                 <object size="-5 -2 59 62" type="image" sprite="snIconSheetTab" tooltip_style="sessionToolTipBottom"
    647                      cell_id="4" tooltip="Stances"/>
    648 
    649                 [stance commands]
    650             </object>
    651 
    652661            <object name="unitResearchPanel"
    653662                style="goldPanelFrilly"
    654663                size="0 100%-56 100% 100%"
  • binaries/data/mods/public/gui/session/input.js

     
    11451145    }
    11461146}
    11471147
     1148// Performs the specified stance
     1149function performStance(entity, stanceName)
     1150{
     1151    if (entity)
     1152    {
     1153        var selection = g_Selection.toList();
     1154        Engine.PostNetworkCommand({
     1155            "type": "stance",
     1156            "entities": selection,
     1157            "name": stanceName
     1158        });
     1159    }
     1160}
     1161
    11481162// Set the camera to follow the given unit
    11491163function setCameraFollow(entity)
    11501164{
  • binaries/data/mods/public/gui/session/unit_commands.js

     
    66const TRAINING = "Training";
    77const CONSTRUCTION = "Construction";
    88const COMMAND = "Command";
     9const STANCE = "Stance";
    910
    1011// Constants
    1112const COMMANDS_PANEL_WIDTH = 228;
     
    1314const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
    1415
    1516// The number of currently visible buttons (used to optimise showing/hiding)
    16 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Construction": 0, "Command": 0};
     17var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
    1718
    1819// Unit panels are panels with row(s) of buttons
    1920var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Construction", "Research", "Stance", "Command"];
     
    120121        if (numberOfItems > 16)
    121122                numberOfItems =  16;
    122123    }
    123     if (guiName == "Formation")
     124    else if (guiName == "Formation")
    124125    {
    125126        if (numberOfItems > 16)
    126127                numberOfItems =  16;
     128    }   
     129    else if (guiName == "Stance")
     130    {
     131        if (numberOfItems > 5)
     132                numberOfItems =  5;
    127133    }
    128134    else if (guiName == "Queue")
    129135    {
     
    153159        var item = items[i];
    154160        var entType = ((guiName == "Queue")? item.template : item);
    155161        var template;
    156         if (guiName != "Formation" && guiName != "Command")
     162        if (guiName != "Formation" && guiName != "Command" && guiName != "Stance")
    157163        {
    158164            template = GetTemplateData(entType);
    159165            if (!template)
     
    190196            getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
    191197            break;
    192198
     199        case STANCE:
    193200        case FORMATION:
    194201            var tooltip = toTitleCase(item);
    195202            break;
     
    265272                icon.sprite = "formation";
    266273            }
    267274        }
     275        else if (guiName == "Stance")
     276        {
     277            var stanceSelected = Engine.GuiInterfaceCall("StanceSelected", {
     278                "ents": g_Selection.toList(),
     279                "stance": item
     280            });
     281
     282            icon.cell_id = i;
     283            if (stanceSelected)
     284                icon.sprite = "snIconSheetStanceButton";
     285            else
     286                icon.sprite = "snIconSheetStanceButtonDisabled";
     287        }
    268288        else if (guiName == "Command")
    269289        {
    270290            //icon.cell_id = i;
     
    368388            setupUnitPanel("Formation", usedPanels, entState, formations,
    369389                function (item) { performFormation(entState.id, item); } );
    370390
     391        var stances = ["violent","aggressive","passive","defensive","stand"];
     392        if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && stances.length)
     393            setupUnitPanel("Stance", usedPanels, entState, stances,
     394                function (item) { performStance(entState.id, item); } );
     395
    371396        if (entState.buildEntities && entState.buildEntities.length)
    372397        {
    373398            setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);
  • binaries/data/mods/public/gui/session/session.js

     
    235235function updateGroups()
    236236{
    237237    var guiName = "Group";
     238    g_Groups.update();
    238239    for (var i = 0; i < 10; i++)
    239240    {
    240241        var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]");
    241242        var label = getGUIObjectByName("unit"+guiName+"Label["+i+"]").caption = i;
    242         g_Groups.update();
    243243        if (g_Groups.groups[i].getTotalCount() == 0)
    244244            button.hidden = true;
    245245        else
  • binaries/data/mods/public/simulation/helpers/Setup.js

     
    1414        for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))
    1515        {
    1616            var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    17             cmpUnitAI.SetStance(settings.DefaultStance);
     17            cmpUnitAI.SwitchToStance(settings.DefaultStance);
    1818        }
    1919    }
    2020
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    261261        }
    262262        break;
    263263
     264    case "stance":
     265        for each (var ent in cmd.entities)
     266        {
     267            var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     268            if (cmpUnitAI)
     269                cmpUnitAI.SwitchToStance(cmd.name);
     270        }
     271        break;
     272
    264273    default:
    265274        error("Ignoring unrecognised command type '" + cmd.type + "'");
    266275    }
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    288288    return CanMoveEntsIntoFormation(data.ents, data.formationName);
    289289};
    290290
     291GuiInterface.prototype.StanceSelected = function(player, data)
     292{
     293    for each (var ent in data.ents)
     294    {
     295        var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     296        if (cmpUnitAI)
     297        {
     298            if (cmpUnitAI.GetStanceName() == data.stance)
     299                return true;
     300        }
     301    }
     302    return false;
     303};
     304
    291305GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
    292306{
    293307    var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     
    543557    "GetNextNotification": 1,
    544558
    545559    "CanMoveEntsIntoFormation": 1,
     560    "StanceSelected": 1,
    546561
    547562    "SetSelectionHighlight": 1,
    548563    "SetStatusBars": 1,
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    66    "<element name='DefaultStance'>" +
    77        "<choice>" +
    88            "<value>aggressive</value>" +
    9             "<value>holdfire</value>" +
    10             "<value>noncombat</value>" +
     9            "<value>defensive</value>" +
     10            "<value>passive</value>" +
    1111        "</choice>" +
    1212    "</element>" +
    1313    "<element name='FormationController'>" +
     
    5959// TODO: even this limited version isn't implemented properly yet (e.g. it can't handle
    6060// dynamic stance changes).
    6161var g_Stances = {
    62     "aggressive": {
     62    "violent": {
    6363        targetVisibleEnemies: true,
    6464        targetAttackers: true,
    6565        respondFlee: false,
    6666        respondChase: true,
     67        respondStandGround : false,
     68        respondHoldGround : false,
    6769    },
    68     "holdfire": {
    69         targetVisibleEnemies: false,
     70    "aggressive": {
     71        targetVisibleEnemies: true,
    7072        targetAttackers: true,
    7173        respondFlee: false,
    7274        respondChase: true,
     75        respondStandGround : false,
     76        respondHoldGround : false,
    7377    },
    74     "noncombat": {
     78    "defensive": {
     79        targetVisibleEnemies: true,
     80        targetAttackers: true,
     81        respondFlee: false,
     82        respondChase: false,
     83        respondStandGround : false,
     84        respondHoldGround : true,
     85    },
     86    "passive": {
    7587        targetVisibleEnemies: false,
    7688        targetAttackers: true,
    7789        respondFlee: true,
    7890        respondChase: false,
     91        respondStandGround : false,
     92        respondHoldGround : false,
    7993    },
     94    "stand": {
     95        targetVisibleEnemies: true,
     96        targetAttackers: true,
     97        respondFlee: false,
     98        respondChase: false,
     99        respondStandGround : true,
     100        respondHoldGround : false,
     101    },
    80102};
    81103
    82104// See ../helpers/FSM.js for some documentation of this FSM specification syntax
     
    114136        // ignore
    115137    },
    116138
     139    "StanceChanged": function(msg) {
     140        if (this.StanceSpecificQuery(this.stance,true))
     141            return;
     142    },
     143
    117144    // Formation handlers:
    118145
    119146    "FormationLeave": function(msg) {
     
    154181            return;
    155182        }
    156183
     184        this.SetHeldPosition({"x": this.order.data.x, "z": this.order.data.z});
    157185        this.MoveToPoint(this.order.data.x, this.order.data.z);
    158186        this.SetNextState("INDIVIDUAL.WALKING");
    159187    },
     
    217245        }
    218246        this.attackType = type;
    219247
     248        if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     249        {
     250            // We are already at the target
     251            // so try attacking it from here.
     252            this.StopMoving();
     253            if (this.IsAnimal())
     254                this.SetNextState("ANIMAL.COMBAT.ATTACKING");
     255            else
     256                this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
     257        }
    220258        // Try to move within attack range
    221         if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     259        else if (!this.order.data.stand && this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
    222260        {
    223261            // We've started walking to the given point
    224262            if (this.IsAnimal())
     
    227265                this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
    228266        }
    229267        else
    230         {
    231             // We are already at the target, or can't move at all,
    232             // so try attacking it from here.
    233             // TODO: need better handling of the can't-reach-target case
    234             this.StopMoving();
    235             if (this.IsAnimal())
    236                 this.SetNextState("ANIMAL.COMBAT.ATTACKING");
    237             else
    238                 this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
    239         }
     268            this.FinisOrder();
    240269    },
    241270
    242271    "Order.Gather": function(msg) {
     
    484513                // If we entered the idle state we must have nothing better to do,
    485514                // so immediately check whether there's anybody nearby to attack.
    486515                // (If anyone approaches later, it'll be handled via LosRangeUpdate.)
    487                 if (this.losRangeQuery)
    488                 {
    489                     var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    490                     var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
    491                     if (this.GetStance().targetVisibleEnemies)
    492                     {
    493                         if (this.RespondToTargetedEntities(ents))
    494                             return true; // (abort the FSM transition since we may have already switched state)
    495                     }
    496                 }
     516                if (this.StanceSpecificQuery(this.stance))
     517                    return true;
    497518
    498519                // Nobody to attack - stay in idle
    499520                return false;
     
    551572                }
    552573            },
    553574
     575            "StanceChanged": function(msg) {
     576                if (this.StanceSpecificQuery(this.stance))
     577                    return;
     578            },
    554579        },
    555580
    556581        "WALKING": {
     
    605630                "MoveCompleted": function() {
    606631                    this.SetNextState("ATTACKING");
    607632                },
     633
     634                "Attacked": function(msg) {
     635                    // If we're attacked by a close enemy, we should try to defend ourself
     636                    if (this.GetStance().targetAttackers && msg.data.type == "Melee")
     637                    {
     638                        this.RespondToTargetedEntities([msg.data.attacker]);
     639                    }
     640                },
    608641            },
    609642
    610643            "ATTACKING": {
     
    638671                        }
    639672
    640673                        // Can't reach it - try to chase after it
    641                         if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     674                        if (this.GetStance().respondChase && this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
    642675                        {
    643676                            this.SetNextState("COMBAT.CHASING");
    644677                            return;
    645678                        }
     679
     680                        if (this.GetStance().respondHoldGround && this.CheckTargetDistanceFromHeldPosition(this.order.data.target, this.attackType, 50))
     681                        {
     682                            if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     683                            {
     684                                this.SetNextState("COMBAT.CHASING");
     685                                return;
     686                            }
     687                        }
    646688                    }
    647689
    648690                    // Can't reach it, or it doesn't exist any more - give up
    649                     this.FinishOrder();
     691                    if (this.FinishOrder())
     692                        return;
    650693
    651                     // TODO: see if we can switch to a new nearby enemy
     694                    // see if we can switch to a new nearby enemy
     695                    if (this.StanceSpecificQuery(this.stance, true))
     696                        return;
     697
     698                    // we return to our position
     699                    if (this.GetStance().respondHoldGround && this.heldPosition)
     700                    {
     701                        this.Walk(this.heldPosition.x, this.heldPosition.z, false);
     702                        return;
     703                    }
    652704                },
    653705
    654706                // TODO: respond to target deaths immediately, rather than waiting
     
    659711                "enter": function () {
    660712                    this.SelectAnimation("move");
    661713                },
    662            
     714
    663715                "MoveCompleted": function() {
    664716                    this.SetNextState("ATTACKING");
    665717                },
     
    13031355// This should be called whenever our ownership changes.
    13041356UnitAI.prototype.SetupRangeQuery = function(owner)
    13051357{
    1306     var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
    1307     if (!cmpVision)
    1308         return;
    1309    
    13101358    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    13111359    var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
    1312    
     1360
    13131361    if (this.losRangeQuery)
    13141362        rangeMan.DestroyActiveQuery(this.losRangeQuery);
    13151363
    1316     var range = cmpVision.GetRange();
    1317    
    13181364    var players = [];
    1319    
     1365
    13201366    if (owner != -1)
    13211367    {
    13221368        // If unit not just killed, get enemy players via diplomacy
     
    13341380                players.push(i);
    13351381        }
    13361382    }
    1337    
    1338     this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, 0, range, players, IID_DamageReceiver);
     1383
     1384    var range = this.StanceSpecificRange();
     1385
     1386    this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, 0, range.max, players, IID_DamageReceiver);
    13391387    rangeMan.EnableActiveQuery(this.losRangeQuery);
    1340 };
     1388}
    13411389
    13421390//// FSM linkage functions ////
    13431391
     
    18121860    return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
    18131861};
    18141862
     1863UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, type, distance)
     1864{
     1865    var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
     1866    var range = cmpRanged.GetRange(type);
     1867
     1868    var cmpPosition = Engine.QueryInterface(target, IID_Position);
     1869    if (!cmpPosition || !cmpPosition.IsInWorld())
     1870        return false;
     1871
     1872    var pos = cmpPosition.GetPosition();
     1873    var dx = this.heldPosition.x - pos.x;
     1874    var dz = this.heldPosition.z - pos.z;
     1875    var dist = Math.sqrt(dx*dx + dz*dz);
     1876
     1877    return dist < distance + range.max;
     1878};
     1879
    18151880UnitAI.prototype.GetBestAttack = function()
    18161881{
    18171882    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     
    18391904};
    18401905
    18411906/**
     1907 * Try to find one of the given entities which can be attacked,
     1908 * without moving.
     1909 * Returns true if it found something to attack.
     1910 */
     1911UnitAI.prototype.AttackEntityInRange = function(ents)
     1912{
     1913    var type = this.GetBestAttack();
     1914    for each (var target in ents)
     1915    {
     1916        if (this.CanAttack(target) && this.CheckTargetRange(target, IID_Attack, type))
     1917        {
     1918            this.PushOrderFront("Attack", { "target": target, "stand": true });
     1919            return true;
     1920        }
     1921    }
     1922    return false;
     1923};
     1924
     1925/**
     1926 * Try to find one of the given entities which can be attacked,
     1927 * within zone.
     1928 * Returns true if it found something to attack.
     1929 */
     1930UnitAI.prototype.AttackEntityInZone = function(ents)
     1931{
     1932    var type = this.GetBestAttack();
     1933    for each (var target in ents)
     1934    {
     1935        if (this.CanAttack(target) && this.CheckTargetDistanceFromHeldPosition(target, type, 50))
     1936        {
     1937            this.PushOrderFront("Attack", { "target": target });
     1938            return true;
     1939        }
     1940    }
     1941    return false;
     1942};
     1943
     1944/**
    18421945 * Try to respond appropriately given our current stance,
    18431946 * given a list of entities that match our stance's target criteria.
    18441947 * Returns true if it responded.
     
    18511954    if (this.GetStance().respondChase)
    18521955        return this.AttackVisibleEntity(ents);
    18531956
     1957    if (this.GetStance().respondStandGround)
     1958        return this.AttackEntityInRange(ents);
     1959
     1960    if (this.GetStance().respondHoldGround)
     1961        return this.AttackEntityInZone(ents);
     1962
    18541963    if (this.GetStance().respondFlee)
    18551964    {
    18561965        this.PushOrderFront("Flee", { "target": ents[0] });
     
    20882197        this.stance = stance;
    20892198    else
    20902199        error("UnitAI: Setting to invalid stance '"+stance+"'");
     2200}
     2201
     2202UnitAI.prototype.SwitchToStance = function(stance)
     2203{
     2204    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     2205    if (!cmpPosition || !cmpPosition.IsInWorld())
     2206        return;
     2207    var pos = cmpPosition.GetPosition();
     2208    this.SetHeldPosition({"x": pos.x, "z": pos.z});
     2209
     2210    this.SetStance(stance);
     2211    if (stance == "stand" || stance == "defensive" || stance == "passive")
     2212        this.StopMoving();
     2213
     2214    UnitFsm.ProcessMessage(this, {"type": "StanceChanged", "stance": stance});
     2215}
     2216
     2217UnitAI.prototype.StanceSpecificQuery = function(stance, disable)
     2218{
     2219    if (!g_Stances[stance])
     2220    {
     2221        error("UnitAI: Setting to invalid stance '"+stance+"'");
     2222        return false;
     2223    }
     2224
     2225    if (!this.losRangeQuery)
     2226        return false;
     2227
     2228    var range = this.StanceSpecificRange();
     2229
     2230    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     2231    var ents = rangeMan.ModifyRangeActiveQuery(this.losRangeQuery, range.min, range.max);
     2232
     2233    if (disable && !this.IsAnimal())
     2234        rangeMan.DisableActiveQuery(this.losRangeQuery);
     2235
     2236    return this.RespondToTargetedEntities(ents);
    20912237};
    20922238
     2239UnitAI.prototype.StanceSpecificRange = function()
     2240{
     2241    var ret = { "min": 0, "max": 0 };
     2242    if (this.GetStance().respondStandGround)
     2243    {
     2244        var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
     2245        if (!cmpRanged)
     2246            return ret;
     2247        var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
     2248        ret.min = range.min;
     2249        ret.max = range.max;
     2250    }
     2251    else if (this.GetStance().respondChase)
     2252    {
     2253        var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
     2254        if (!cmpVision)
     2255            return ret;
     2256        var range = cmpVision.GetRange();
     2257        ret.max = range;
     2258    }
     2259    else if (this.GetStance().respondHoldGround)
     2260    {
     2261        var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
     2262        if (!cmpRanged)
     2263            return ret;
     2264        var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
     2265        ret.max = range.max + 50;
     2266    }
     2267    return ret;
     2268};
     2269
    20932270UnitAI.prototype.GetStance = function()
    20942271{
    20952272    return g_Stances[this.stance];
    20962273};
    20972274
     2275UnitAI.prototype.GetStanceName = function()
     2276{
     2277    return this.stance;
     2278};
     2279
    20982280//// Helper functions ////
    20992281
    21002282UnitAI.prototype.CanAttack = function(target)
     
    22342416    cmpMotion.SetSpeed(speed);
    22352417};
    22362418
     2419UnitAI.prototype.SetHeldPosition = function(pos)
     2420{
     2421    this.heldPosition = {"x": pos.x, "z": pos.z};
     2422};
     2423
     2424UnitAI.prototype.GetHeldPosition = function(pos)
     2425{
     2426    return this.heldPosition;
     2427};
     2428
     2429
    22372430Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
  • binaries/data/mods/public/simulation/components/Attack.js

     
    245245    }
    246246
    247247    Engine.PostMessage(data.target, MT_Attacked,
    248         { "attacker": this.entity, "target": data.target });
     248        { "attacker": this.entity, "target": data.target, "type": data.type });
    249249
    250250    PlaySound("attack_impact", this.entity);
    251251};
  • binaries/data/mods/public/simulation/components/Promotion.js

     
    6565   
    6666    var cmpCurrentUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
    6767    var cmpPromotedUnitAI = Engine.QueryInterface(promotedUnitEntity, IID_UnitAI);
     68    cmpPromotedUnitAI.SetHeldPosition(cmpCurrentUnitAI.GetHeldPosition());
     69    if (cmpCurrentUnitAI.GetStanceName())
     70        cmpPromotedUnitAI.SetStance(cmpCurrentUnitAI.GetStanceName());
    6871    cmpPromotedUnitAI.Cheer();
    6972    var orders = cmpCurrentUnitAI.GetOrders();
    7073    cmpPromotedUnitAI.AddOrders(orders);
  • binaries/data/mods/public/simulation/templates/template_unit_support.xml

     
    88    <Type>support</Type>
    99  </Minimap>
    1010  <UnitAI>
    11     <DefaultStance>noncombat</DefaultStance>
     11    <DefaultStance>passive</DefaultStance>
    1212  </UnitAI>
    1313  <Cost>
    1414    <BuildTime>15</BuildTime>