Ticket #610: Garrisoning.patch

File Garrisoning.patch, 19.2 KB (added by Evans, 14 years ago)
  • binaries/data/mods/public/gui/session_new/utility_functions.js

     
    160160    {
    161161    case "delete":
    162162        return 1;
     163    case "unload-all":
     164        return 2;
    163165    default:
    164166        return -1;
    165167    }
     
    188190function getEntityCommandsList(entState)
    189191{
    190192    var commands = [];
     193    commands.push("Stop");
     194    commands.push("Stop that!");
     195    if (entState.garrisonHolder)
     196        commands.push("unload-all");
    191197    commands.push("delete");
    192198    return commands;
    193199}
  • binaries/data/mods/public/gui/session_new/input.js

     
    77const SDLK_LSHIFT = 304;
    88const SDLK_RCTRL = 305;
    99const SDLK_LCTRL = 306;
    10 
     10const SDLK_RALT = 307;
     11const SDLK_LALT = 308;
    1112// TODO: these constants should be defined somewhere else instead, in
    1213// case any other code wants to use them too
    1314
     
    3435specialKeyStates[SDLK_LSHIFT] = 0;
    3536specialKeyStates[SDLK_RCTRL] = 0;
    3637specialKeyStates[SDLK_LCTRL] = 0;
     38specialKeyStates[SDLK_RALT] = 0;
     39specialKeyStates[SDLK_LALT] = 0;
     40
    3741// (TODO: maybe we should fix the hotkey system to be usable in this situation,
    3842// rather than hardcoding Shift into this code?)
    3943
     
    7276function determineAction(x, y, fromMinimap)
    7377{
    7478    var selection = g_Selection.toList();
    75 
     79    var ctrlPressed = specialKeyStates[SDLK_LCTRL] || specialKeyStates[SDLK_RCTRL];
    7680    // No action if there's no selection
    7781    if (!selection.length)
    7882        return undefined;
     
    122126            var enemyOwned = ((targetState.player != entState.player)? true : false);
    123127            var gaiaOwned = ((targetState.player == 0)? true : false);
    124128
    125             // If the target is a resource and we have the right kind of resource gatherers selected, then gather
    126             // If the target is a foundation and we have builders selected, then build (or repair)
    127             // If the target is an enemy, then attack
    128             if (targetState.resourceSupply && (playerOwned || gaiaOwned))
     129           
     130            if (targetState.garrisonHolder && playerOwned && ctrlPressed)
    129131            {
     132                return {"type": "garrison", "cursor": "action-garrison", "target": targets[0]};
     133            }
     134            else if (targetState.resourceSupply && (playerOwned || gaiaOwned))
     135            {
     136                // If the target is a resource and we have the right kind of resource gatherers selected, then gather
     137                // If the target is a foundation and we have builders selected, then build (or repair)
     138                // If the target is an enemy, then attack
    130139                var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply);
    131140                if (resource)
    132141                    return {"type": "gather", "cursor": "action-gather-"+resource, "target": targets[0]};
     
    522531                    Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
    523532                    return true;
    524533
     534                case "garrison":
     535                    Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
     536                    //Need to play some sound here??
     537                    return true;
     538                   
    525539                case "set-rallypoint":
    526540                    var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
    527541                    Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z});
     
    801815                    messageBox(320, 180, message, "Confirmation", 0, btCaptions, btCode);
    802816                }
    803817                break;
     818            case "unload-all":
     819                unloadAll(entity);
     820                break;
    804821            default:
    805822                break;
    806823            }
     
    835852    Engine.CameraFollow(0);
    836853}
    837854
     855function unload(garrisonHolder, entity)
     856{
     857    Engine.PostNetworkCommand({"type": "unload", "entity": entity, "garrisonHolder": garrisonHolder});
     858}
     859
     860function unloadAll(garrisonHolder)
     861{
     862    Engine.PostNetworkCommand({"type": "unload-all", "garrisonHolder": garrisonHolder});
     863}
     864 No newline at end of file
  • binaries/data/mods/public/gui/session_new/unit_commands.js

     
    114114    usedPanels[guiName] = 1;
    115115    var numberOfItems = items.length;
    116116    var selection = g_Selection.toList();
    117 
     117    var garrisonGroups = new EntityGroups();
    118118    if (guiName == "Selection")
    119119    {
    120120        if (numberOfItems > 16)
    121121                numberOfItems =  16;
    122122    }
    123     if (guiName == "Formation" || guiName == "Garrison")
     123    if (guiName == "Formation")
    124124    {
    125125        if (numberOfItems > 16)
    126126                numberOfItems =  16;
     
    130130        if (numberOfItems > 16)
    131131            numberOfItems = 16;
    132132    }
     133    else if (guiName == "Garrison")
     134    {
     135        if (numberOfItems > 16)
     136            numberOfItems = 16;
     137        //Group garrisoned units based on class
     138        garrisonGroups.add(unitEntState.garrisonHolder.entities);
     139    }
    133140    else if (guiName == "Command")
    134141    {
    135142        if (numberOfItems > 4)
     
    177184            break;
    178185
    179186        case GARRISON:
    180        
    181        
    182        
    183        
    184        
    185         /*
    186 
    187 !!!!!
    188 GARRISON GOES HERE (need to customize this)
    189 !!!!!
    190        
    191        
    192             var tooltip = getEntityName(template);
    193             var count = g_Selection.groups.getCount(item);
     187            var name = getEntityName(template);
     188            var tooltip = "Unload " + getEntityName(template);
     189            var count = garrisonGroups.getCount(name);
    194190            getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
    195            
    196            
    197         */ 
    198        
    199        
    200        
    201            
    202            
    203191            break;
    204192
    205193        case FORMATION:
     
    337325        if (commands.length)
    338326            setupUnitPanel("Command", usedPanels, entState, commands,
    339327                function (item) { performCommand(entState.id, item); } );
    340 
    341                
    342                
    343                
    344                
    345                
    346 /*
    347 !!!!!
    348                                     GARRISON GOES HERE (need to customize this)
    349 !!!!!
    350 */
    351 
    352 
    353 /*
    354         if (selection.length > 1)
    355             setupUnitPanel("Garrison", usedPanels, entState, g_Selection.groups.getTemplateNames(),
    356                 function (entType) { changePrimarySelectionGroup(entType); } );
    357 */
    358 
    359 
    360 
    361 
    362 
    363 
     328        if (entState.garrisonHolder)
     329        {
     330            var groups = new EntityGroups();
     331            groups.add(entState.garrisonHolder.entities);
     332            setupUnitPanel("Garrison", usedPanels, entState, groups.getTemplateNames(),
     333                function (item)
     334                {
     335                    var template = GetTemplateData(item);
     336                    var unitName = template.name.specific || template.name.generic || "???";
     337                    unload(entState.id, groups.getEntsByName(unitName)[0]);
     338                } );
     339        }
    364340        var formations = getEntityFormationsList(entState);
    365341        if (formations.length)
    366342            setupUnitPanel("Formation", usedPanels, entState, formations,
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    160160        Engine.PostMessage(playerEnt, MT_PlayerDefeated, null);
    161161        break;
    162162
     163    case "garrison":
     164        var targetCmpOwnership = Engine.QueryInterface(cmd.target, IID_Ownership);
     165        if (!targetCmpOwnership || targetCmpOwnership.GetOwner() != player)
     166            break;
     167        for each (var ent in cmd.entities)
     168        {
     169            var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     170            if (!cmpOwnership || cmpOwnership.GetOwner() != player)
     171                break;
     172            var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     173            if (cmpUnitAI)
     174                cmpUnitAI.Garrison(cmd.target);
     175        }
     176        break;
     177       
     178    case "unload":
     179        var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership);
     180        if (!cmpOwnership || cmpOwnership.GetOwner() != player)
     181            break;
     182        var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
     183        if (cmpGarrisonHolder)
     184            cmpGarrisonHolder.Unload(cmd.entity);
     185        break;
     186       
     187    case "unload-all":
     188        var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership);
     189        if (!cmpOwnership || cmpOwnership.GetOwner() != player)
     190            break;
     191       
     192        var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
     193        cmpGarrisonHolder.UnloadAll();
     194        break;
     195       
    163196    default:
    164197        error("Ignoring unrecognised command type '" + cmd.type + "'");
    165198    }
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    6969    }
    7070   
    7171    var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    72     if (cmpPosition)
     72    if (cmpPosition && cmpPosition.IsInWorld())
    7373    {
    7474        ret.position = cmpPosition.GetPosition();
    7575    }
     
    146146        ret.rallyPoint = { };
    147147    }
    148148
     149    var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
     150    if (cmpGarrisonHolder)
     151    {
     152        ret.garrisonHolder = {
     153                "entities": cmpGarrisonHolder.GetEntities()
     154        };
     155    }
     156   
    149157    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    150158    ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
    151159
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

     
    33GarrisonHolder.prototype.Schema =
    44    "<element name='Max'>" +
    55        "<data type='positiveInteger'/>" +
     6    "</element>" +
     7    "<element name='EjectHealth'>" +
     8        "<ref name='positiveDecimal'/>" +
     9    "</element>" +
     10    "<element name='List'>" +
     11        "<attribute name='datatype'>" +
     12            "<value>tokens</value>" +
     13        "</attribute>" +
     14        "<text/>" +
     15    "</element>" +
     16    "<element name='BuffHeal'>" +
     17        "<data type='positiveInteger'/>" +
    618    "</element>";
    719
    8 /*
    9  * TODO: this all needs to be designed and implemented
     20/**
     21 * Initialize GarrisonHolder Component
    1022 */
     23GarrisonHolder.prototype.Init = function()
     24{
     25    //Garrisoned Units
     26    this.entities = [];
     27    this.spaceOccupied = 0;
     28    this.timer = undefined;
     29    this.healRate = this.template.BuffHeal;
    1130
     31};
     32
     33/**
     34 * Return the list of entities garrisoned inside
     35 */
     36GarrisonHolder.prototype.GetEntities = function()
     37{
     38    return this.entities;
     39}
     40
     41/**
     42 * Returns an array of unit classes which can be garrisoned inside this
     43 * particualar entity. Obtained from the entity's template
     44 */
     45GarrisonHolder.prototype.GetAllowedClassesList = function()
     46{
     47    var string = this.template.List._string;
     48    return string.split(/\s+/);
     49};
     50
     51/**
     52 * Get Maximum pop which can be garrisoned
     53 */
     54GarrisonHolder.prototype.GetCapacity = function()
     55{
     56    return this.template.Max;
     57};
     58
     59/**
     60 * Garrison a unit inside.
     61 * Returns true if successful, false if not
     62 * The timer for AutoHeal is started here
     63 */
     64GarrisonHolder.prototype.Garrison = function(entity)
     65{
     66    var entityPopCost = (Engine.QueryInterface(entity, IID_Cost)).GetPopCost();
     67    var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
     68    var allowedClasses = this.GetAllowedClassesList();
     69    var classNotAllowed = true;
     70   
     71    if (!this.HasEnoughHealth())
     72        return false;
     73    //Check if the unit is allowed to be garrisoned inside the building
     74    for each (var allowedClass in allowedClasses)
     75    {
     76        if (entityClasses.indexOf(allowedClass) != -1)
     77        {
     78            classNotAllowed = false;
     79            break;
     80        }
     81    }
     82    if (classNotAllowed)
     83        return false;
     84    if (this.GetCapacity() < (this.spaceOccupied + entityPopCost))
     85        return false;
     86    var cmpPosition = Engine.QueryInterface(entity, IID_Position);
     87    if (!this.timer)
     88    {
     89        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     90        this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
     91    }
     92    if (cmpPosition)
     93    {
     94        //Actual garrisoning happens here
     95        this.entities.push(entity);
     96        this.spaceOccupied += entityPopCost;
     97        cmpPosition.MoveOutOfWorld();
     98        return true;
     99    }
     100    else
     101    {
     102        return false;
     103    }
     104};
     105
     106/**
     107 * Unload units from the garrisoning entity
     108 */
     109GarrisonHolder.prototype.Unload = function(entity)
     110{
     111    var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
     112    var entityIndex = this.entities.indexOf(entity);
     113    this.spaceOccupied -= (Engine.QueryInterface(entity, IID_Cost)).GetPopCost();
     114    this.entities.splice(entityIndex, 1);
     115    var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
     116    var pos = cmpFootprint.PickSpawnPoint(entity);
     117    if (pos.y < 0)
     118    {
     119        // Whoops, something went wrong (maybe there wasn't any space to place the unit).
     120        // What should we do here?
     121        // For now, just move the unit into the middle of the building where it'll probably get stuck
     122        pos = cmpPosition.GetPosition();
     123        warn("Can't find free space to spawn trained unit");
     124    }
     125
     126    var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
     127    cmpNewPosition.JumpTo(pos.x, pos.z);
     128    // TODO: what direction should they face in?
     129
     130    // If a rally point is set, walk towards it
     131    var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
     132    if (cmpUnitAI && cmpRallyPoint)
     133    {
     134        var rallyPos = cmpRallyPoint.GetPosition();
     135        if (rallyPos)
     136        {
     137            cmpUnitAI.Walk(rallyPos.x, rallyPos.z, false);
     138        }
     139        else
     140        {
     141            //Reset state. This needs to be done since they were walking before being moved
     142            //out of the world
     143        }
     144    }
     145};
     146
     147/**
     148 * Used to check if the garrisoning entity's health has fallen below
     149 * a certain limit after which all garrisoned units are unloaded
     150 */
     151GarrisonHolder.prototype.OnHealthChanged = function(msg)
     152{
     153    if (!this.HasEnoughHealth())
     154    {
     155        this.UnloadAll();
     156    }
     157   
     158};
     159
     160/**
     161 * Check if this entity has enough health to garrison units inside it
     162 */
     163GarrisonHolder.prototype.HasEnoughHealth = function()
     164{
     165    var cmpHealth = Engine.QueryInterface(this.entity, IID_Health)
     166    var hitpoints = cmpHealth.GetHitpoints();
     167    var maxHitpoints = cmpHealth.GetMaxHitpoints();
     168    var ejectHitpoints = parseInt(parseFloat(this.template.EjectHealth) * maxHitpoints);
     169    return hitpoints > ejectHitpoints;
     170   
     171};
     172
     173/**
     174 * Unload all units from the entity
     175 */
     176GarrisonHolder.prototype.UnloadAll = function()
     177{
     178    //The entities list is saved to a temporary variable
     179    //because during each loop an element is removed
     180    //from the list
     181    var entities = this.entities.splice(0);
     182    for each (var entity in entities)
     183    {
     184        this.Unload(entity);
     185    }
     186};
     187
     188/**
     189 * Called every second. Heals garrisoned units
     190 */
     191GarrisonHolder.prototype.HealTimeout = function(data)
     192{
     193    if (this.entities.length == 0)
     194    {
     195        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     196        cmpTimer.CancelTimer(this.timer);
     197        this.timer = undefined;
     198    }
     199    else
     200    {
     201        for each (var entity in this.entities)
     202        {
     203            var cmpHealth = Engine.QueryInterface(entity, IID_Health);
     204            if (cmpHealth)
     205            {
     206                if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
     207                    cmpHealth.Increase(this.healRate);
     208            }
     209        }
     210        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     211        this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
     212    }
     213};
     214
     215GarrisonHolder.prototype.OnOwnershipChanged = function(msg)
     216{
     217
     218};
     219
     220/**
     221 * Cancel timer when destroyed
     222 */
     223GarrisonHolder.prototype.OnDestroy = function()
     224{
     225    if (this.timer)
     226    {
     227        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     228        cmpTimer.CancelTimer(this.timer);
     229    }
     230};
     231
    12232Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);
  • binaries/data/mods/public/simulation/components/Identity.js

     
    6060            "<list>" +
    6161                "<zeroOrMore>" +
    6262                    "<choice>" +
     63                        "<value>Unit</value>" +
    6364                        "<value>Organic</value>" +
    6465                        "<value>Foot</value>" +
    6566                        "<value>Mounted</value>" +
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    162162            this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING");
    163163        }
    164164    },
     165   
     166    "Order.Garrison": function(msg) {
     167        if (this.MoveToTarget(this.order.data.target))
     168        {
     169            this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
     170        }
     171        else
     172        {
     173            this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED");
     174        }
     175    },
    165176
    166 
    167177    // States for the special entity representing a group of units moving in formation:
    168178    "FORMATIONCONTROLLER": {
    169179
     
    508518                }
    509519            },
    510520        },
     521
     522        "GARRISON": {
     523            "APPROACHING": {
     524                "enter": function() {
     525                    this.SelectAnimation("walk", false, this.GetWalkSpeed());
     526                    this.PlaySound("walk");
     527                },
     528
     529                "MoveCompleted": function() {
     530                    this.SetNextState("GARRISONED");
     531                },
     532               
     533                "leave": function() {
     534                    this.StopTimer();
     535                }
     536            },
     537
     538            "GARRISONED": {
     539                "enter": function() {
     540                    var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
     541                    if (cmpGarrisonHolder)
     542                    {
     543                        cmpGarrisonHolder.Garrison(this.entity);
     544                       
     545                    }
     546                    if (this.FinishOrder())
     547                        return;
     548                },
     549
     550                "leave": function() {
     551
     552                }
     553            },
     554        },
     555
    511556    },
    512557};
    513558
     
    10021047    this.AddOrder("Attack", { "target": target }, queued);
    10031048};
    10041049
     1050UnitAI.prototype.Garrison = function(target, queued)
     1051{
     1052    if (!this.CanGarrison(target))
     1053    {
     1054        this.WalkToTarget(target, queued);
     1055        return;
     1056    }
     1057    this.AddOrder("Garrison", { "target": target }, queued);
     1058};
     1059
    10051060UnitAI.prototype.Gather = function(target, queued)
    10061061{
    10071062    if (!this.CanGather(target))
     
    10621117    return true;
    10631118};
    10641119
     1120UnitAI.prototype.CanGarrison = function(target)
     1121{
     1122    var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
     1123    if (!cmpGarrisonHolder)
     1124        return false;
     1125   
     1126    return true;
     1127};
     1128
    10651129UnitAI.prototype.CanGather = function(target)
    10661130{
    10671131    // Formation controllers should always respond to commands
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    5454    <RetainInFog>true</RetainInFog>
    5555    <AlwaysVisible>false</AlwaysVisible>
    5656  </Vision>
     57  <GarrisonHolder>
     58    <Max>15</Max>
     59    <EjectHealth>0.1</EjectHealth>
     60    <List datatype="tokens">Unit</List>
     61    <BuffHeal>1</BuffHeal>
     62  </GarrisonHolder>
    5763  <RallyPoint/>
    5864  <Sound>
    5965    <SoundGroups>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    33  <Identity>
    44    <GenericName>Unit</GenericName>
    55    <Classes datatype="tokens">
    6       ConquestCritical
     6      ConquestCritical Unit
    77    </Classes>
    88  </Identity>
    99  <Minimap>