Ticket #2706: UpgradeComponent.8.patch

File UpgradeComponent.8.patch, 49.5 KB (added by sanderd17, 8 years ago)
  • binaries/data/mods/public/globalscripts/Templates.js

     
    233233        };
    234234        ret.icon = template.Identity.Icon;
    235235        ret.tooltip =  template.Identity.Tooltip;
    236         ret.gateConversionTooltip =  template.Identity.GateConversionTooltip;
    237236        ret.requiredTechnology = template.Identity.RequiredTechnology;
    238237        ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
    239238    }
  • binaries/data/mods/public/gui/session/input.js

     
    16411641    });
    16421642}
    16431643
    1644 // Transform a wall to a gate
    1645 function transformWallToGate(template)
     1644// Upgrade an entity to another
     1645function upgradeEntity(Template)
    16461646{
    16471647    var selection = g_Selection.toList();
    16481648    Engine.PostNetworkCommand({
    1649         "type": "wall-to-gate",
    1650         "entities": selection.filter( function(e) { return getWallGateTemplate(e) == template; } ),
    1651         "template": template,
     1649        "type": "upgrade",
     1650        "entities": selection,
     1651        "template": Template,
     1652        "queued": false
    16521653    });
    16531654}
    16541655
    1655 // Gets the gate form (if any) of a given long wall piece
    1656 function getWallGateTemplate(entity)
     1656// Cancel upgrading entities
     1657function cancelUpgradeEntity()
    16571658{
    1658     // TODO: find the gate template name in a better way
    1659     var entState = GetEntityState(entity);
    1660     var index;
    1661 
    1662     if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
    1663         return entState.template.substr(0, index) + "gate";
    1664     return undefined;
     1659    var selection = g_Selection.toList();
     1660    Engine.PostNetworkCommand({
     1661        "type": "cancel-upgrade",
     1662        "entities": selection,
     1663        "queued": false
     1664    });
    16651665}
    16661666
    16671667// Set the camera to follow the given unit
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    503503
    504504// GATE
    505505g_SelectionPanels.Gate = {
    506     "getMaxNumberOfItems": function()
    507     {
    508         return 24 - getNumberOfRightPanelButtons();
    509     },
    510     "getItems": function(unitEntState, selection)
    511     {
    512         // Allow long wall pieces to be converted to gates
    513         var longWallTypes = {};
    514         var walls = [];
    515         var gates = [];
    516         for (var ent of selection)
    517         {
    518             var state = GetEntityState(ent);
    519             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
    520             {
    521                 var gateTemplate = getWallGateTemplate(state.id);
    522                 if (gateTemplate)
    523                 {
    524                     var tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
    525                     if (!tooltipString)
    526                     {
    527                         warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
    528                         tooltipString = "";
    529                     }
    530                     walls.push({
    531                         "tooltip": translate(tooltipString),
    532                         "template": gateTemplate,
    533                         "callback": function (item) { transformWallToGate(item.template); }
    534                     });
    535                 }
     506    "getMaxNumberOfItems": function()
     507    {
     508        return 24 - getNumberOfRightPanelButtons();
     509    },
     510   "getItems": function(unitEntState, selection)
     511    {
     512        // Allow long wall pieces to be converted to gates
     513        var longWallTypes = {};
     514        var walls = [];
     515        var gates = [];
     516        for (var ent of selection)
     517        {
     518            var state = GetEntityState(ent);
     519            if (state.gate && !gates.length)
     520            {
     521                gates.push({
     522                    "gate": state.gate,
     523                   "tooltip": translate("Lock Gate"),
     524                    "locked": true,
     525                    "callback": function (item) { lockGate(item.locked); }
     526                });
     527                gates.push({
     528                    "gate": state.gate,
     529                   "tooltip": translate("Unlock Gate"),
     530                    "locked": false,
     531                    "callback": function (item) { lockGate(item.locked); }
     532                });
     533           }
     534            // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
     535            else if (state.gate && state.gate.locked != gates[0].gate.locked)
     536                for (var j = 0; j < gates.length; ++j)
     537                    delete gates[j].gate.locked;
     538       }
    536539
    537                 // We only need one entity per type.
    538                 longWallTypes[state.template] = true;
    539             }
    540             else if (state.gate && !gates.length)
    541             {
    542                 gates.push({
    543                     "gate": state.gate,
    544                     "tooltip": translate("Lock Gate"),
    545                     "locked": true,
    546                     "callback": function (item) { lockGate(item.locked); }
    547                 });
    548                 gates.push({
    549                     "gate": state.gate,
    550                     "tooltip": translate("Unlock Gate"),
    551                     "locked": false,
    552                     "callback": function (item) { lockGate(item.locked); }
    553                 });
    554             }
    555             // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
    556             else if (state.gate && state.gate.locked != gates[0].gate.locked)
    557                 for (var j = 0; j < gates.length; ++j)
    558                     delete gates[j].gate.locked;
    559         }
     540        // Place wall conversion options after gate lock/unlock icons.
     541        var items = gates.concat(walls);
     542        return items;
     543   },
     544    "setAction": function(data)
     545    {
     546        data.button.onPress = function() {data.item.callback(data.item); };
     547    },
     548    "setTooltip": function(data)
     549    {
     550        data.button.tooltip = data.item.tooltip;
     551    },
     552    "setGraphics": function(data)
     553    {
     554        data.button.enabled = controlsPlayer(data.unitEntState.player);
     555        var gateIcon;
     556        if (data.item.gate)
     557        {
     558            // If already a gate, show locking actions
     559           gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
     560            if (data.item.gate.locked === undefined)
     561                data.guiSelection.hidden = false;
     562            else
     563                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
     564        }
    560565
    561         // Place wall conversion options after gate lock/unlock icons.
    562         var items = gates.concat(walls);
    563         return items;
    564     },
    565     "setAction": function(data)
    566     {
    567         data.button.onPress = function() {data.item.callback(data.item); };
    568     },
    569     "setTooltip": function(data)
    570     {
    571         var tooltip = data.item.tooltip;
    572         if (data.item.template)
    573         {
    574             data.template = GetTemplateData(data.item.template);
    575             data.wallCount = data.selection.reduce(function (count, ent) {
    576                     var state = GetEntityState(ent);
    577                     if (hasClass(state, "LongWall") && !state.gate)
    578                         ++count;
    579                     return count;
    580                 }, 0);
     566        data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
     567    },
     568    "setPosition": function(data)
     569    {
     570        var index = data.i + getNumberOfRightPanelButtons();
     571        setPanelObjectPosition(data.button, index, data.rowLength);
     572    }
     573};
    581574
    582             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
    583575
    584             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    585                 "cost": multiplyEntityCosts(data.template, data.wallCount)
    586             });
    587 
    588             if (data.neededResources)
    589                 tooltip += getNeededResourcesTooltip(data.neededResources);
    590         }
    591         data.button.tooltip = tooltip;
    592     },
    593     "setGraphics": function(data)
    594     {
    595         data.button.enabled = controlsPlayer(data.unitEntState.player);
    596         var gateIcon;
    597         if (data.item.gate)
    598         {
    599             // If already a gate, show locking actions
    600             gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    601             if (data.item.gate.locked === undefined)
    602                 data.guiSelection.hidden = false;
    603             else
    604                 data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    605         }
    606         else
    607         {
    608             // otherwise show gate upgrade icon
    609             var template = GetTemplateData(data.item.template);
    610             if (!template)
    611                 return;
    612             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    613             data.guiSelection.hidden = true;
    614         }
    615 
    616         data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
    617     },
    618     "setPosition": function(data)
    619     {
    620         var index = data.i + getNumberOfRightPanelButtons();
    621         setPanelObjectPosition(data.button, index, data.rowLength);
    622     }
    623 };
    624 
    625576// PACK
    626577g_SelectionPanels.Pack = {
    627578    "getMaxNumberOfItems": function()
     
    654605        }
    655606        var items = [];
    656607        if (checks.packButton)
    657             items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
     608            items.push({
     609                "packing": false,
     610                "packed": false,
     611                "tooltip": translate("Pack"),
     612                "callback": function() { packUnit(true); }
     613            });
    658614        if (checks.unpackButton)
    659             items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
     615            items.push({
     616                "packing": false,
     617                "packed": true,
     618                "tooltip": translate("Unpack"),
     619                "callback": function() { packUnit(false); }
     620            });
    660621        if (checks.packCancelButton)
    661             items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
     622            items.push({
     623                "packing": true,
     624                "packed": false,
     625                "tooltip": translate("Cancel Packing"),
     626                "callback": function() { cancelPackUnit(true); }
     627            });
    662628        if (checks.unpackCancelButton)
    663             items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
     629            items.push({
     630                "packing": true,
     631                "packed": true,
     632                "tooltip": translate("Cancel Unpacking"),
     633                "callback": function() { cancelPackUnit(false); }
     634            });
    664635        return items;
    665636    },
    666637    "setAction": function(data)
     
    689660    }
    690661};
    691662
     663// UPGRADE
     664g_SelectionPanels.Upgrade = {
     665    "getMaxNumberOfItems": function()
     666    {
     667        return 24 - getNumberOfRightPanelButtons();
     668    },
     669    "getItems": function(unitEntState, selection)
     670    {
     671        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     672        // TODO: if the units are all the same, this should probably still be possible.
     673        if (selection.length > 1)
     674            return false;
     675
     676        if (!unitEntState.upgrade)
     677            return false;
     678
     679        var items = [];
     680
     681        for (let upgrade of unitEntState.upgrade.upgrades)
     682        {
     683            var item = {};
     684            item.entType = upgrade.entity;
     685            item.template = GetTemplateData(upgrade.entity);
     686            if (!item.template)
     687                return false;
     688
     689            item.icon = upgrade.icon;
     690            if (!item.icon)
     691                item.icon = "portraits/" + item.template.icon;
     692
     693            if (upgrade.requiredTechnology !== null)
     694            {
     695                item.requiredTechnology = upgrade.requiredTechnology;
     696                if (!item.requiredTechnology && item.template.requiredTechnology)
     697                    item.requiredTechnology = item.template.requiredTechnology
     698            }
     699            item.cost = upgrade.cost;
     700            item.time = upgrade.time;
     701
     702            if (unitEntState.upgrade.progress === undefined)
     703            {
     704                item.upgrading = UPGRADING_NOT_STARTED;
     705                item.tooltip = sprintf(translate("Upgrade into a %(name)s.%(tooltip)s"), { name: item.template.name.generic, tooltip: (upgrade.tooltip? "\n" + upgrade.tooltip : "") });
     706                item.callback = function(data) { upgradeEntity(data.entType); };
     707            }
     708            else if (unitEntState.upgrade.template !== upgrade.entity)
     709            {
     710                item.upgrading = UPGRADING_CHOSEN_OTHER;
     711                item.tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     712                item.callback = function(data) { };
     713            }
     714            else
     715            {
     716                item.upgrading = unitEntState.upgrade.progress;
     717                item.tooltip = translate("Cancel Upgrading");
     718                item.callback = function(data) { cancelUpgradeEntity(); };
     719            }
     720            items.push(item);
     721        }
     722        return items;
     723    },
     724    "addData" : function(data)
     725    {
     726        data.item.technologyEnabled = true;
     727        if (data.item.requiredTechnology)
     728            data.item.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", data.item.requiredTechnology);
     729        if (data.item.cost)
     730        {
     731            var totalCost = {};
     732            for (let i in data.item.cost)
     733                totalCost[i] = data.item.cost[i];
     734            data.item.neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": totalCost });
     735        }
     736        data.item.limits = getEntityLimitAndCount(data.playerState, data.item.entType);
     737        return true;
     738    },
     739    "setAction": function(data)
     740    {
     741        data.button.onPress = function() { data.item.callback(data.item); };
     742    },
     743    "setTooltip": function(data)
     744    {
     745        var tooltip = data.item.tooltip;
     746
     747        if (data.item.upgrading !== UPGRADING_NOT_STARTED)
     748        {
     749            data.button.tooltip = tooltip;
     750            return;
     751        }
     752
     753        if (data.item.cost)
     754            tooltip += "\n" + getEntityCostTooltip(data.item);
     755
     756        tooltip += formatLimitString(data.item.limits.entLimit, data.item.limits.entCount, data.item.limits.entLimitChangers);
     757        if (!data.item.technologyEnabled)
     758        {
     759            var techName = getEntityNames(GetTechnologyData(data.item.requiredTechnology));
     760            tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { "technology": techName });
     761        }
     762        if (data.item.neededResources)
     763            tooltip += getNeededResourcesTooltip(data.item.neededResources);
     764
     765        data.button.tooltip = tooltip;
     766    },
     767    "setGraphics": function(data)
     768    {
     769        var grayscale = "";
     770        if (data.item.upgrading == UPGRADING_CHOSEN_OTHER ||
     771            !data.item.technologyEnabled ||
     772            data.item.limits.canBeAddedCount == 0 && data.item.upgrading == UPGRADING_NOT_STARTED)
     773        {
     774            data.button.enabled = false;
     775            grayscale = "grayscale:";
     776            data.affordableMask.hidden = false;
     777            data.affordableMask.sprite = "colour: 0 0 0 127";
     778        }
     779        else if (data.item.upgrading == UPGRADING_NOT_STARTED && data.item.neededResources)
     780        {
     781            data.button.enabled = false;
     782            data.affordableMask.hidden = false;
     783            data.affordableMask.sprite = resourcesToAlphaMask(data.item.neededResources);
     784        }
     785
     786        data.icon.sprite = "stretched:" + grayscale + "session/" + data.item.icon;
     787
     788        var guiObject = Engine.GetGUIObjectByName("unitUpgradeProgressSlider["+data.i+"]");
     789        var size = guiObject.size;
     790        if (data.item.upgrading < 0)
     791            size.top = size.right;
     792        else
     793            size.top = size.left + Math.round(Math.max(0,data.item.upgrading) * (size.right - size.left));
     794        guiObject.size = size;
     795    },
     796    "setPosition": function(data)
     797    {
     798        var index = data.i + getNumberOfRightPanelButtons();
     799        setPanelObjectPosition(data.button, index, data.rowLength);
     800    },
     801};
     802
    692803// QUEUE
    693804g_SelectionPanels.Queue = {
    694805    "getMaxNumberOfItems": function()
     
    11631274    "Stance", // normal together with formation
    11641275
    11651276    // RIGHT PANE
    1166     "Gate", // must always be shown on gates
     1277    "Gate", // must always be shown on gate
    11671278    "Pack", // must always be shown on packable entities
     1279    "Upgrade", // must always be shown on upgradable entities
    11681280    "Training",
    11691281    "Construction",
    11701282    "Research", // normal together with training
  • binaries/data/mods/public/gui/session/selection_panels_helpers.js

     
    88// Gate constants
    99const GATE_ACTIONS = ["lock", "unlock"];
    1010
     11// upgrade constants
     12const UPGRADING_NOT_STARTED = -2;
     13const UPGRADING_CHOSEN_OTHER = -1;
     14
    1115// ==============================================
    1216// BARTER HELPERS
    1317// Resources to sell on barter panel
  • binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<object name="unitUpgradePanel"
     3    size="10 12 100% 100%"
     4>
     5    <object size="0 0 100% 100%">
     6    <repeat count="8">
     7        <object name="unitUpgradeButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     8        <object name="unitUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     9        <object name="unitUpgradeUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     10        <object name="unitUpgradeProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 43 43" z="20"/>
     11        <object name="unitUpgradeSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
     12        <object name="unitUpgradeUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     13        </object>
     14    </repeat>
     15    </object>
     16</object>
  • binaries/data/mods/public/gui/session/unit_commands.js

    Property changes on: binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +text/xml
    \ No newline at end of property
     
    11// The number of currently visible buttons (used to optimise showing/hiding)
    2 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Alert": 0, "Barter": 0, "Construction": 0, "Command": 0, "AllyCommand": 0, "Stance": 0, "Gate": 0, "Pack": 0};
     2var g_unitPanelButtons = { "Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Alert": 0, "Barter": 0, "Construction": 0, "Command": 0, "AllyCommand": 0, "Stance": 0, "Gate":0, "Pack": 0, "Upgrade": 0 };
    33
    44/**
    55 * Set the position of a panel object according to the index,
     
    252252{
    253253    var sum = 0;
    254254
    255     for (let prop of ["Construction", "Training", "Pack", "Gate"])
     255    for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
    256256        if (g_SelectionPanels[prop].used)
    257257            sum += g_unitPanelButtons[prop];
    258258
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    236236        "guard": null,
    237237        "mirage": null,
    238238        "pack": null,
     239        "upgrade" : null,
    239240        "player": -1,
    240241        "position": null,
    241242        "production": null,
     
    295296            "progress": cmpPack.GetProgress(),
    296297        };
    297298
     299    var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     300    if (cmpUpgrade)
     301        ret.upgrade = {
     302            "upgrades" : cmpUpgrade.GetUpgrades(),
     303            "progress": cmpUpgrade.GetProgress(),
     304            "template": cmpUpgrade.GetUpgradingTo()
     305        };
     306
    298307    let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
    299308    if (cmpProductionQueue)
    300309        ret.production = {
  • binaries/data/mods/public/simulation/components/Identity.js

     
    4040        "</element>" +
    4141    "</optional>" +
    4242    "<optional>" +
    43         "<element name='GateConversionTooltip'>" +
    44             "<text/>" +
    45         "</element>" +
    46     "</optional>" +
    47     "<optional>" +
    4843        "<element name='Rollover'>" +
    4944            "<text/>" +
    5045        "</element>" +
  • binaries/data/mods/public/simulation/components/Pack.js

     
    116116
    117117Pack.prototype.PackProgress = function(data, lateness)
    118118{
    119     if (this.elapsedTime >= this.GetPackTime())
     119    if (this.elapsedTime < this.GetPackTime())
    120120    {
     121        this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
     122        return;
     123    }
     124
    121125        this.CancelTimer();
    122126
    123127        this.packed = !this.packed;
    124         this.packing = false;
    125128        Engine.PostMessage(this.entity, MT_PackFinished, { packed: this.packed });
    126129
    127         // Done un/packing, copy our parameters to the final entity
    128         var newEntity = Engine.AddEntity(this.template.Entity);
    129         if (newEntity == INVALID_ENTITY)
    130         {
    131             // Error (e.g. invalid template names)
    132             error("PackProgress: Error creating entity for '" + this.template.Entity + "'");
    133             return;
    134         }
     130    var newEntity = ChangeEntityTemplate(this.entity, this.template.Entity);
    135131
    136         var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    137         var cmpNewPosition = Engine.QueryInterface(newEntity, IID_Position);
    138         if (cmpPosition.IsInWorld())
    139         {
    140             var pos = cmpPosition.GetPosition2D();
    141             cmpNewPosition.JumpTo(pos.x, pos.y);
    142         }
    143         var rot = cmpPosition.GetRotation();
    144         cmpNewPosition.SetYRotation(rot.y);
    145         cmpNewPosition.SetXZRotation(rot.x, rot.z);
    146         cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
    147 
    148         var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    149         var cmpNewOwnership = Engine.QueryInterface(newEntity, IID_Ownership);
    150         cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
    151 
    152         // rescale capture points
    153         var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
    154         var cmpNewCapturable = Engine.QueryInterface(newEntity, IID_Capturable);
    155         if (cmpCapturable && cmpNewCapturable)
    156         {
    157             let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
    158             let newCp = cmpCapturable.GetCapturePoints().map(function (v) { return v / scale; });
    159             cmpNewCapturable.SetCapturePoints(newCp);
    160         }
    161 
    162         // Maintain current health level
    163         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    164         var cmpNewHealth = Engine.QueryInterface(newEntity, IID_Health);
    165         var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    166         cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
    167 
    168         var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
    169         var cmpNewUnitAI = Engine.QueryInterface(newEntity, IID_UnitAI);
    170         if (cmpUnitAI && cmpNewUnitAI)
    171         {
    172             var pos = cmpUnitAI.GetHeldPosition();
    173             if (pos)
    174                 cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
    175             if (cmpUnitAI.GetStanceName())
    176                 cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
    177             cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
    178             cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
    179         }
    180 
    181         // Maintain the list of guards
    182         var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard);
    183         var cmpNewGuard = Engine.QueryInterface(newEntity, IID_Guard);
    184         if (cmpGuard && cmpNewGuard)
    185             cmpNewGuard.SetEntities(cmpGuard.GetEntities());
    186 
    187         Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: newEntity });
    188 
    189         // Play notification sound
     132    if (newEntity)
     133    {
    190134        var sound = this.packed ? "packed" : "unpacked";
    191135        PlaySound(sound, newEntity);
    192 
    193         // Destroy current entity
    194         Engine.DestroyEntity(this.entity);
    195136    }
    196     else
    197     {
    198         this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
    199     }
    200137};
    201138
    202139Engine.RegisterComponentType(IID_Pack, "Pack", Pack);
  • binaries/data/mods/public/simulation/components/Upgrade.js

     
     1function Upgrade() {}
     2
     3const UPGRADING_PROGRESS_INTERVAL = 250;
     4
     5Upgrade.prototype.Schema =
     6    "<oneOrMore>" +
     7        "<element>" +
     8            "<anyName />" +
     9            "<interleave>" +
     10                "<element name='Entity' a:help='Entity to upgrade to'>" +
     11                    "<text/>" +
     12                "</element>" +
     13                "<optional>" +
     14                    "<element name='Icon' a:help='Icon to show in the GUI'>" +
     15                        "<text/>" +
     16                    "</element>" +
     17                "</optional>" +
     18                "<optional>" +
     19                    "<element name='Tooltip' a:help='This will be added to the tooltip to help the player choose why to upgrade.'>" +
     20                        "<text/>" +
     21                    "</element>" +
     22                "</optional>" +
     23                "<optional>" +
     24                    "<element name='Time' a:help='Time required to upgrade this entity, in milliseconds'>" +
     25                        "<data type='nonNegativeInteger'/>" +
     26                    "</element>" +
     27                "</optional>" +
     28                "<optional>" +
     29                    "<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
     30                        "<oneOrMore>" +
     31                            "<choice>" +
     32                                "<element name='food'><data type='nonNegativeInteger'/></element>" +
     33                                "<element name='wood'><data type='nonNegativeInteger'/></element>" +
     34                                "<element name='stone'><data type='nonNegativeInteger'/></element>" +
     35                                "<element name='metal'><data type='nonNegativeInteger'/></element>" +
     36                            "</choice>" +
     37                        "</oneOrMore>" +
     38                    "</element>" +
     39                "</optional>" +
     40                "<optional>" +
     41                    "<element name='RequiredTechnology' a:help='Define what technology is required for this upgrade'>" +
     42                        "<choice>" +
     43                            "<text/>" +
     44                            "<empty/>" +
     45                        "</choice>" +
     46                    "</element>" +
     47                "</optional>" +
     48                "<optional>" +
     49                    "<element name='CheckPlacementRestrictions' a:help='Upgrading will check for placement restrictions'><empty/></element>" +
     50                "</optional>" +
     51            "</interleave>" +
     52        "</element>" +
     53    "</oneOrMore>";
     54
     55Upgrade.prototype.Init = function()
     56{
     57    this.upgrading = false;
     58    this.elapsedTime = 0;
     59    this.timer = undefined;
     60
     61    this.upgradeTemplates = {};
     62
     63    for (let choice in this.template)
     64    {
     65        let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     66        let name = this.template[choice].Entity;
     67        if (cmpIdentity)
     68            name = name.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     69        if (this.upgradeTemplates.name != undefined)
     70            warn("Upgrade Component: entity " + this.entity + " has two upgrades to the same entity, only the last will be used.");
     71        this.upgradeTemplates[name] = choice;
     72    }
     73};
     74
     75// On owner change, abort the upgrade
     76// This will also deal with the "OnDestroy" case.
     77Upgrade.prototype.OnOwnershipChanged = function(msg)
     78{
     79    this.CancelUpgrade();
     80
     81    if (msg.to !== -1)
     82        this.owner = msg.to;
     83};
     84
     85Upgrade.prototype.ChangeUpgradedEntityCount = function(amount)
     86{
     87    if (!this.IsUpgrading())
     88        return;
     89    var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     90    var template = cmpTempMan.GetTemplate(this.upgrading);
     91    if (template.TrainingRestrictions)
     92        var category = template.TrainingRestrictions.Category;
     93    else if (template.BuildRestrictions)
     94        var category = template.BuildRestrictions.Category;
     95
     96    var cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
     97    cmpEntityLimits.ChangeCount(category,amount);
     98};
     99
     100Upgrade.prototype.CanUpgradeTo = function(template)
     101{
     102    return this.upgradeTemplates[template] !== undefined;
     103};
     104
     105Upgrade.prototype.GetUpgrades = function()
     106{
     107    var ret = [];
     108
     109    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     110
     111    for (let option in this.template)
     112    {
     113        let choice = this.template[option];
     114        let entType = choice.Entity;
     115        if (cmpIdentity)
     116            entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     117
     118        let hasCosts = false;
     119        let cost = {};
     120        if (choice.Cost)
     121        {
     122            hasCosts = true;
     123            for (var type in choice.Cost)
     124                cost[type] = ApplyValueModificationsToTemplate("Upgrade/Cost/"+type, +choice.Cost[type], this.owner, entType);
     125        }
     126        if (choice.Time)
     127        {
     128            hasCosts = true;
     129            cost["time"] = ApplyValueModificationsToTemplate("Upgrade/Time", +choice.Time/1000.0, this.owner, entType);
     130        }
     131        ret.push(
     132        {
     133            "entity": entType,
     134            "icon": choice.Icon || undefined,
     135            "cost": hasCosts ? cost : undefined,
     136            "tooltip": choice.Tooltip || undefined,
     137            "requiredTechnology": "RequiredTechnology" in choice ? choice.RequiredTechnology : null,
     138        });
     139    }
     140
     141    return ret;
     142};
     143
     144Upgrade.prototype.CancelTimer = function()
     145{
     146    if (!this.timer)
     147        return;
     148
     149    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     150    cmpTimer.CancelTimer(this.timer);
     151    this.timer = undefined;
     152};
     153
     154Upgrade.prototype.IsUpgrading = function()
     155{
     156    return this.upgrading !== false;
     157};
     158
     159Upgrade.prototype.GetUpgradingTo = function()
     160{
     161    return this.upgrading;
     162};
     163
     164Upgrade.prototype.WillCheckPlacementRestrictions = function(template)
     165{
     166    if (!this.upgradeTemplates[template])
     167        return undefined;
     168
     169    return this.template[this.upgradeTemplates[template]].CheckPlacementRestrictions != undefined;
     170};
     171
     172Upgrade.prototype.GetRequiredTechnology = function(templateArg)
     173{
     174    if (!this.upgradeTemplates[templateArg])
     175        return undefined;
     176
     177    var choice = this.upgradeTemplates[templateArg];
     178
     179    if ("RequiredTechnology" in this.template[choice] && this.template[choice].RequiredTechnology === undefined)
     180    {
     181        var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     182        var template = cmpTemplateManager.GetTemplate(this.template[choice].Entity);
     183        if (template.Identity.RequiredTechnology)
     184            return template.Identity.RequiredTechnology;
     185    }
     186    else if ("RequiredTechnology" in this.template[choice])
     187        return this.template[choice].RequiredTechnology;
     188
     189    return null;
     190};
     191
     192Upgrade.prototype.GetResourceCosts = function(template)
     193{
     194    if (!this.upgradeTemplates[template])
     195        return undefined;
     196
     197    var choice = this.upgradeTemplates[template];
     198    if (!this.template[choice].Cost)
     199        return {};
     200
     201    var costs = {};
     202    for (var r in this.template[choice].Cost)
     203    {
     204        costs[r] = +this.template[choice].Cost[r];
     205        costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, costs[r], this.entity);
     206    }
     207    return costs;
     208};
     209
     210Upgrade.prototype.Upgrade = function(template)
     211{
     212    if (this.IsUpgrading())
     213        return false;
     214
     215    if (!this.upgradeTemplates[template])
     216        return false;
     217
     218    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     219
     220    if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(template)))
     221        return false;
     222
     223    this.upgrading = template;
     224
     225    // Prevent cheating
     226    this.ChangeUpgradedEntityCount(1);
     227
     228    if (this.GetUpgradeTime(template) !== 0)
     229    {
     230        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     231        this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
     232    }
     233    else
     234        this.UpgradeProgress();
     235
     236    return true;
     237};
     238
     239Upgrade.prototype.CancelUpgrade = function()
     240{
     241    if (this.IsUpgrading() === false)
     242        return;
     243
     244    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     245    if (cmpPlayer)
     246    {
     247        var costs = this.GetResourceCosts(this.upgrading);
     248        cmpPlayer.AddResources(costs);
     249    }
     250
     251    this.ChangeUpgradedEntityCount(-1);
     252
     253    this.upgrading = false;
     254    this.CancelTimer();
     255    this.SetElapsedTime(0);
     256};
     257
     258Upgrade.prototype.GetUpgradeTime = function(templateArg)
     259{
     260    var template = this.upgrading || templateArg;
     261    var choice = this.upgradeTemplates[template];
     262    if (!choice)
     263        return undefined;
     264    return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0;
     265};
     266
     267Upgrade.prototype.GetElapsedTime = function()
     268{
     269    return this.elapsedTime;
     270};
     271
     272Upgrade.prototype.GetProgress = function()
     273{
     274    if (!this.IsUpgrading())
     275        return undefined;
     276    return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime();
     277};
     278
     279Upgrade.prototype.SetElapsedTime = function(time)
     280{
     281    this.elapsedTime = time;
     282};
     283
     284Upgrade.prototype.UpgradeProgress = function(data, lateness)
     285{
     286    if (this.elapsedTime < this.GetUpgradeTime())
     287    {
     288        this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
     289        return;
     290    }
     291
     292    this.CancelTimer();
     293
     294    var newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
     295
     296    if (newEntity)
     297        PlaySound("upgraded", newEntity);
     298};
     299
     300Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
  • binaries/data/mods/public/simulation/components/interfaces/Upgrade.js

     
     1Engine.RegisterInterface("Upgrade");
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    578578        }
    579579    },
    580580
    581     "wall-to-gate": function(player, cmd, data)
    582     {
    583         for (let ent of data.entities)
    584             TryTransformWallToGate(ent, data.cmpPlayer, cmd);
    585     },
    586 
    587581    "lock-gate": function(player, cmd, data)
    588582    {
    589583        for (let ent of data.entities)
     
    668662        }
    669663    },
    670664
     665    "upgrade": function(player, cmd, data)
     666    {
     667        for (let ent of data.entities)
     668        {
     669            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     670
     671            if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template))
     672                continue;
     673
     674            if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template))
     675            {
     676                var notification = {
     677                    "players": [data.cmpPlayer.GetPlayerID()],
     678                    "message": "Cannot upgrade as distance requirements are not verified or terrain is obstructed."
     679                };
     680                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     681                cmpGUIInterface.PushNotification(notification);
     682                continue;
     683            }
     684
     685            if (!CanGarrisonedChangeTemplate(ent, cmd.template))
     686            {
     687                var notification = { "players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade a garrisoned entity." };
     688                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     689                cmpGUIInterface.PushNotification(notification);
     690                continue;
     691            }
     692
     693            // Check entity limits
     694            var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     695            var template = cmpTemplateManager.GetTemplate(cmd.template);
     696            var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     697            if ((template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1)) ||
     698                (template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category)))
     699            {
     700                if (g_DebugCommands)
     701                    warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
     702                continue;
     703            }
     704
     705            var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
     706            if (cmpUpgrade.GetRequiredTechnology() && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology()))
     707            {
     708                if (g_DebugCommands)
     709                    warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
     710                continue;
     711            }
     712
     713            cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer);
     714        }
     715    },
     716
     717    "cancel-upgrade": function(player, cmd, data)
     718    {
     719        for (let ent of data.entities)
     720        {
     721            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     722            if (cmpUpgrade)
     723                cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     724        }
     725    },
     726
    671727    "attack-request": function(player, cmd, data)
    672728    {
    673729        // Send a chat message to human players
     
    15671623    return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
    15681624}
    15691625
    1570 /**
    1571  * Try to transform a wall to a gate
    1572  */
    1573 function TryTransformWallToGate(ent, cmpPlayer, cmd)
    1574 {
    1575     var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    1576     if (!cmpIdentity)
    1577         return;
    1578 
    1579     if (!cmpIdentity.HasClass("LongWall"))
    1580     {
    1581         if (g_DebugCommands)
    1582             warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));
    1583         return;
    1584     }
    1585 
    1586     var civ = cmpIdentity.GetCiv();
    1587     var gate = Engine.AddEntity(cmd.template);
    1588 
    1589     var cmpCost = Engine.QueryInterface(gate, IID_Cost);
    1590     if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    1591     {
    1592         if (g_DebugCommands)
    1593             warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));
    1594 
    1595         Engine.DestroyEntity(gate);
    1596         return;
    1597     }
    1598 
    1599     ReplaceBuildingWith(ent, gate);
    1600 }
    1601 
    1602 /**
    1603  * Unconditionally replace a building with another one
    1604  */
    1605 function ReplaceBuildingWith(ent, building)
    1606 {
    1607     // Move the building to the right place
    1608     var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1609     var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    1610     var pos = cmpPosition.GetPosition2D();
    1611     cmpBuildingPosition.JumpTo(pos.x, pos.y);
    1612     var rot = cmpPosition.GetRotation();
    1613     cmpBuildingPosition.SetYRotation(rot.y);
    1614     cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
    1615 
    1616     // Copy ownership
    1617     var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1618     var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
    1619     cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
    1620 
    1621     // Copy control groups
    1622     var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1623     var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    1624     cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    1625     cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
    1626 
    1627     // Copy health level from the old entity to the new
    1628     var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    1629     var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    1630     var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    1631     var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
    1632     cmpBuildingHealth.SetHitpoints(buildingHitpoints);
    1633 
    1634     PlaySound("constructed", building);
    1635 
    1636     Engine.PostMessage(ent, MT_ConstructionFinished,
    1637         { "entity": ent, "newentity": building });
    1638     Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
    1639 
    1640     Engine.DestroyEntity(ent);
    1641 }
    1642 
    16431626Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    16441627Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    16451628Engine.RegisterGlobal("GetDockAngle", GetDockAngle);
  • binaries/data/mods/public/simulation/helpers/Transform.js

     
     1// Helper functions to change an entity's template and check if the transformation is possible
     2
     3// returns the ID of the new entity or INVALID_ENTITY.
     4var ChangeEntityTemplate = function(oldEnt, newTemplate)
     5{
     6    // Done un/packing, copy our parameters to the final entity
     7    var newEnt = Engine.AddEntity(newTemplate);
     8    if (newEnt == INVALID_ENTITY)
     9    {
     10        // Error (e.g. invalid template names)
     11        error("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'");
     12        return INVALID_ENTITY;
     13    }
     14
     15    var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
     16    var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
     17    if (cmpPosition && cmpNewPosition)
     18    {
     19        if (cmpPosition.IsInWorld())
     20        {
     21            var pos = cmpPosition.GetPosition2D();
     22            cmpNewPosition.JumpTo(pos.x, pos.y);
     23        }
     24        var rot = cmpPosition.GetRotation();
     25        cmpNewPosition.SetYRotation(rot.y);
     26        cmpNewPosition.SetXZRotation(rot.x, rot.z);
     27        cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
     28    }
     29
     30    var cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
     31    var cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
     32    if (cmpOwnership && cmpNewOwnership)
     33        cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     34
     35    // Copy control groups
     36    var cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction);
     37    var cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction);
     38    if (cmpObstruction && cmpNewObstruction)
     39    {
     40        cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     41        cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     42    }
     43
     44    // rescale capture points
     45    var cmpCapturable = Engine.QueryInterface(oldEnt, IID_Capturable);
     46    var cmpNewCapturable = Engine.QueryInterface(newEnt, IID_Capturable);
     47    if (cmpCapturable && cmpNewCapturable)
     48    {
     49        let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
     50        let newCp = cmpCapturable.GetCapturePoints().map(function (v) { return v / scale; });
     51        cmpNewCapturable.SetCapturePoints(newCp);
     52    }
     53
     54    // Maintain current health level
     55    var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health);
     56    var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health);
     57    if (cmpHealth && cmpNewHealth)
     58    {
     59        var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
     60        cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
     61    }
     62
     63    var cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
     64    var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
     65    if (cmpUnitAI && cmpNewUnitAI)
     66    {
     67        var pos = cmpUnitAI.GetHeldPosition();
     68        if (pos)
     69            cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
     70        if (cmpUnitAI.GetStanceName())
     71            cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
     72        cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
     73        cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
     74    }
     75
     76    // Maintain the list of guards
     77    var cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard);
     78    var cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard);
     79    if (cmpGuard && cmpNewGuard)
     80        cmpNewGuard.SetEntities(cmpGuard.GetEntities());
     81
     82    TransferGarrisonedUnits(oldEnt, newEnt);
     83
     84    Engine.BroadcastMessage(MT_EntityRenamed, { entity: oldEnt, newentity: newEnt });
     85
     86    // Destroy current entity
     87    Engine.DestroyEntity(oldEnt);
     88
     89    return newEnt;
     90};
     91
     92var CanGarrisonedChangeTemplate = function(ent, template)
     93{
     94    var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     95    var unitAI = Engine.QueryInterface(ent, IID_UnitAI);
     96    if (cmpPosition && !cmpPosition.IsInWorld() && unitAI && unitAI.IsGarrisoned())
     97    {
     98        // We're a garrisoned unit, assume impossibility as I've been unable to find a way to get the holder ID.
     99        // TODO: change this if that ever becomes possibles
     100        return false;
     101    }
     102    return true;
     103}
     104
     105var ObstructionsBlockingTemplateChange = function(ent, templateArg)
     106{
     107    var previewEntity = Engine.AddEntity("preview|"+templateArg);
     108
     109    if (previewEntity == INVALID_ENTITY)
     110        return true;
     111
     112    var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
     113    var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     114    var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     115
     116    var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
     117
     118    // Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better
     119    // Plus there are no real entities without owners currently.
     120    if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership)
     121        return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
     122
     123    var pos = cmpPosition.GetPosition2D();
     124    var angle = cmpPosition.GetRotation();
     125    // move us away to prevent our own obstruction from blocking the upgrade.
     126    cmpPosition.MoveOutOfWorld();
     127
     128    cmpNewPosition.JumpTo(pos.x, pos.y);
     129    cmpNewPosition.SetYRotation(angle.y);
     130
     131    var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
     132    cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     133
     134    var checkPlacement = cmpBuildRestrictions.CheckPlacement();
     135
     136    if (checkPlacement && !checkPlacement.success)
     137        return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
     138
     139    var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     140    var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName(ent));
     141    var newTemplate = cmpTemplateManager.GetTemplate(templateArg);
     142
     143    // Check if units are blocking our template change
     144    if (template.Obstruction && newTemplate.Obstruction)
     145    {
     146        // This only needs to be done if the new template is strictly bigger than the old one
     147        // "Obstructions" are annoying to test so just check.
     148        // This is kind of ugly, sorry about that.
     149        if (newTemplate.Obstruction.Obstructions
     150
     151            || (newTemplate.Obstruction.Static && template.Obstruction.Static
     152                && (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Static["@width"]
     153                 || newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Static["@depth"]))
     154            || (newTemplate.Obstruction.Static && template.Obstruction.Unit
     155                && (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Unit["@radius"]
     156                 || newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Unit["@radius"]))
     157
     158            || (newTemplate.Obstruction.Unit && template.Obstruction.Unit
     159                && newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Unit["@radius"])
     160            || (newTemplate.Obstruction.Unit && template.Obstruction.Static
     161                && (newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@width"]
     162                 || newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@depth"])))
     163        {
     164            var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
     165            if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
     166            {
     167                // Check for units
     168                var collisions = cmpNewObstruction.GetEntityCollisions(false, true);
     169                if (collisions.length !== 0)
     170                    return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
     171            }
     172        }
     173    }
     174
     175    return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
     176};
     177
     178var DeleteEntityAndReturn = function(ent, cmpPosition, position, angle, cmpNewPosition, ret)
     179{
     180    cmpNewPosition.MoveOutOfWorld();    // prevent preview from interfering in the world
     181    cmpPosition.JumpTo(position.x, position.y);
     182    cmpPosition.SetYRotation(angle.y);
     183
     184    Engine.DestroyEntity(ent);
     185    return ret;
     186};
     187
     188var TransferGarrisonedUnits = function(oldEnt, newEnt)
     189{
     190    // Transfer garrisoned units if possible, or unload them
     191    var cmpGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
     192    var cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
     193    if (!cmpNewGarrison || !cmpGarrison || cmpGarrison.GetEntities().length === 0 )
     194        return; // nothing to do as the code will by default unload all.
     195
     196    var garrisonedEntities = cmpGarrison.GetEntities().slice();
     197    for (var j = 0; j < garrisonedEntities.length; ++j)
     198    {
     199        var cmpUnitAI = Engine.QueryInterface(garrisonedEntities[j], IID_UnitAI);
     200        cmpGarrison.Eject(garrisonedEntities[j]);
     201        cmpUnitAI.Autogarrison(newEnt);
     202        cmpNewGarrison.Garrison(garrisonedEntities[j]);
     203    }
     204};
     205
     206Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
     207Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate);
     208Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);
  • binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml

     
    2626    <SelectionGroupName>other/wallset_palisade</SelectionGroupName>
    2727    <SpecificName>Palisade</SpecificName>
    2828    <GenericName>Wooden Wall</GenericName>
    29     <GateConversionTooltip>Convert Wooden Wall into Wooden Gate</GateConversionTooltip>
    3029    <Classes datatype="tokens">-StoneWall Palisade</Classes>
    3130    <Icon>gaia/special_palisade.png</Icon>
    3231    <RequiredTechnology>phase_village</RequiredTechnology>
     
    4544  <WallPiece>
    4645    <Length>11.0</Length>
    4746  </WallPiece>
     47  <Upgrade>
     48    <Gate>
     49      <Entity>other/palisades_rocks_gate</Entity>
     50      <Cost>
     51        <stone>0</stone>
     52        <wood>20</wood>
     53      </Cost>
     54      <Time>5000</Time>
     55    </Gate>
     56  </Upgrade>
    4857</Entity>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml

     
    5555    </Classes>
    5656    <Icon>structures/palisade_wall.png</Icon>
    5757    <Tooltip>A wooden and turf palisade buildable in enemy and neutral territories.</Tooltip>
    58     <GateConversionTooltip>Convert Siege Wall into Siege Wall Gate</GateConversionTooltip>
    5958    <History>Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.</History>
    6059  </Identity>
    6160  <Obstruction>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml

     
    4343  <Identity>
    4444    <Classes datatype="tokens">LongWall</Classes>
    4545    <Tooltip>Long wall segments can be converted to gates.</Tooltip>
    46     <GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>
    4746  </Identity>
     47  <Upgrade>
     48    <Gate>
     49      <Entity>structures/{civ}_wall_gate</Entity>
     50      <Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip>
     51      <Cost>
     52        <stone>60</stone>
     53      </Cost>
     54      <Time>10000</Time>
     55    </Gate>
     56  </Upgrade>
    4857</Entity>