Ticket #2706: UpgradeComponentRC-fixed5.patch

File UpgradeComponentRC-fixed5.patch, 46.0 KB (added by wraitii, 6 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/common/tooltips.js

     
    288288        trainNum = 1;
    289289
    290290    let totalCosts = multiplyEntityCosts(template, trainNum);
    291     totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
     291    if (template.cost.time)
     292        totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
    292293
    293294    let costs = [];
    294295
  • binaries/data/mods/public/gui/session/input.js

     
    16321632    });
    16331633}
    16341634
    1635 // Transform a wall to a gate
    1636 function transformWallToGate(template)
     1635// Upgrade an entity to another
     1636function upgradeEntity(Template)
    16371637{
    1638     var selection = g_Selection.toList();
    16391638    Engine.PostNetworkCommand({
    1640         "type": "wall-to-gate",
    1641         "entities": selection.filter(e => getWallGateTemplate(e) == template),
    1642         "template": template,
     1639        "type": "upgrade",
     1640        "entities": g_Selection.toList(),
     1641        "template": Template,
     1642        "queued": false
    16431643    });
    16441644}
    16451645
    1646 // Gets the gate form (if any) of a given long wall piece
    1647 function getWallGateTemplate(entity)
     1646// Cancel upgrading entities
     1647function cancelUpgradeEntity()
    16481648{
    1649     // TODO: find the gate template name in a better way
    1650     var entState = GetEntityState(entity);
    1651     var index;
    1652 
    1653     if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
    1654         return entState.template.substr(0, index) + "gate";
    1655     return undefined;
     1649    Engine.PostNetworkCommand({
     1650        "type": "cancel-upgrade",
     1651        "entities": g_Selection.toList(),
     1652        "queued": false
     1653    });
    16561654}
    16571655
    16581656// Set the camera to follow the given unit
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    473473    },
    474474    "getItems": function(unitEntState, selection)
    475475    {
    476         // Allow long wall pieces to be converted to gates
    477         let longWallTypes = {};
    478         let walls = [];
    479476        let gates = [];
    480477        for (let ent of selection)
    481478        {
    482479            let state = GetEntityState(ent);
    483             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     480            if (state.gate && !gates.length)
    484481            {
    485                 let gateTemplate = getWallGateTemplate(state.id);
    486                 if (gateTemplate)
    487                 {
    488                     let tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
    489                     if (!tooltipString)
    490                     {
    491                         warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
    492                         tooltipString = "";
    493                     }
    494                     walls.push({
    495                         "tooltip": translate(tooltipString),
    496                         "template": gateTemplate,
    497                         "callback": function (item) { transformWallToGate(item.template); }
    498                     });
    499                 }
    500 
    501                 // We only need one entity per type.
    502                 longWallTypes[state.template] = true;
    503             }
    504             else if (state.gate && !gates.length)
    505             {
    506482                gates.push({
    507483                    "gate": state.gate,
    508484                    "tooltip": translate("Lock Gate"),
     
    522498                    delete gates[j].gate.locked;
    523499        }
    524500
    525         // Place wall conversion options after gate lock/unlock icons.
    526         return gates.concat(walls);
     501        return gates;
    527502    },
    528503    "setupButton": function(data)
    529504    {
     
    530505        data.button.onPress = function() {data.item.callback(data.item); };
    531506
    532507        let tooltip = data.item.tooltip;
    533         if (data.item.template)
    534         {
    535             data.template = GetTemplateData(data.item.template);
    536             data.wallCount = data.selection.reduce(function (count, ent) {
    537                     let state = GetEntityState(ent);
    538                     if (hasClass(state, "LongWall") && !state.gate)
    539                         ++count;
    540                     return count;
    541                 }, 0);
     508        data.button.tooltip = data.item.tooltip;
    542509
    543             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
    544 
    545             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    546                 "cost": multiplyEntityCosts(data.template, data.wallCount)
    547             });
    548 
    549             if (data.neededResources)
    550                 tooltip += getNeededResourcesTooltip(data.neededResources);
    551         }
    552         data.button.tooltip = tooltip;
    553 
    554510        data.button.enabled = controlsPlayer(data.unitEntState.player);
    555511        let gateIcon;
    556512        if (data.item.gate)
    557513        {
    558             // If already a gate, show locking actions
     514            // Show locking actions
    559515            gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    560516            if (data.item.gate.locked === undefined)
    561517                data.guiSelection.hidden = false;
     
    562518            else
    563519                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    564520        }
    565         else
    566         {
    567             // Otherwise show gate upgrade icon
    568             let template = GetTemplateData(data.item.template);
    569             if (!template)
    570                 return false;
    571             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    572             data.guiSelection.hidden = true;
    573         }
    574521
    575522        data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
    576523
     
    611558        }
    612559        let items = [];
    613560        if (checks.packButton)
    614             items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
     561            items.push({
     562                "packing": false,
     563                "packed": false,
     564                "tooltip": translate("Pack"),
     565                "callback": function() { packUnit(true); }
     566            });
    615567        if (checks.unpackButton)
    616             items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
     568            items.push({
     569                "packing": false,
     570                "packed": true,
     571                "tooltip": translate("Unpack"),
     572                "callback": function() { packUnit(false); }
     573            });
    617574        if (checks.packCancelButton)
    618             items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
     575            items.push({
     576                "packing": true,
     577                "packed": false,
     578                "tooltip": translate("Cancel Packing"),
     579                "callback": function() { cancelPackUnit(true); }
     580            });
    619581        if (checks.unpackCancelButton)
    620             items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
     582            items.push({
     583                "packing": true,
     584                "packed": true,
     585                "tooltip": translate("Cancel Unpacking"),
     586                "callback": function() { cancelPackUnit(false); }
     587            });
    621588        return items;
    622589    },
    623590    "setupButton": function(data)
     
    10541021    }
    10551022};
    10561023
     1024g_SelectionPanels.Upgrade = {
     1025    "getMaxNumberOfItems": function()
     1026    {
     1027        return 24 - getNumberOfRightPanelButtons();
     1028    },
     1029    "getItems": function(unitEntState, selection)
     1030    {
     1031        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     1032        // TODO: if the units are all the same, this should probably still be possible.
     1033        if (selection.length > 1)
     1034            return false;
     1035 
     1036        if (!unitEntState.upgrade)
     1037            return false;
     1038 
     1039        var items = [];
     1040
     1041        for (let upgrade of unitEntState.upgrade.upgrades)
     1042        {
     1043            var item = {
     1044                "entity": upgrade.entity,
     1045                "cost": upgrade.cost,
     1046                "time": upgrade.time,
     1047                "icon": upgrade.icon,
     1048                "tooltip": upgrade.tooltip,
     1049                "requiredTechnology": upgrade.requiredTechnology,
     1050            };
     1051            items.push(item);
     1052        }
     1053        return items;
     1054    },
     1055    "setupButton" : function(data)
     1056    {
     1057        let template = GetTemplateData(data.item.entity);
     1058        if (!template)
     1059            return false;
     1060
     1061        let technologyEnabled = true;
     1062
     1063        if (data.item.requiredTechnology)
     1064            technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
     1065                "tech": requiredTechnology,
     1066                "player": data.unitEntState.player
     1067            });
     1068
     1069        let neededResources;
     1070        if (data.item.cost)
     1071            neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
     1072                "cost": data.item.cost,
     1073                "player": data.unitEntState.player
     1074            });
     1075
     1076        let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
     1077
     1078        let progress = data.unitEntState.upgrade.progress || 0;
     1079        let isUpgrading = data.unitEntState.upgrade.template == data.item.entity;
     1080
     1081        let tooltip;
     1082        if (!progress)
     1083        {
     1084            tooltip = sprintf(translate("Upgrade into a %(name)s.%(tooltip)s"), {
     1085                "name": template.name.generic,
     1086                "tooltip": data.item.tooltip ? "\n" + data.item.tooltip : ""
     1087            });
     1088            if (data.item.cost)
     1089                tooltip += "\n" + getEntityCostTooltip(data.item);
     1090
     1091            tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
     1092            if (!technologyEnabled)
     1093                tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
     1094                    "technology": getEntityNames(GetTechnologyData(data.item.requiredTechnology))
     1095                });
     1096            if (neededResources)
     1097                tooltip += getNeededResourcesTooltip(neededResources);
     1098
     1099            data.button.onPress = function() { upgradeEntity(data.item.entity); };
     1100        }
     1101        else if (isUpgrading)
     1102        {
     1103            tooltip = translate("Cancel Upgrading");
     1104            data.button.onPress = function() { cancelUpgradeEntity(); };
     1105        }
     1106        else
     1107        {
     1108            tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     1109            data.button.onPress = function() {};
     1110        }
     1111        data.button.tooltip = tooltip;
     1112
     1113        let modifier = "";
     1114        if (!isUpgrading)
     1115        {
     1116            if (progress || !technologyEnabled || limits.canBeAddedCount == 0)
     1117            {
     1118                data.button.enabled = false;
     1119                modifier = "color:0 0 0 127:grayscale:";
     1120            }
     1121            else if (neededResources)
     1122            {
     1123                data.button.enabled = false;
     1124                modifier = resourcesToAlphaMask(neededResources) + ":";
     1125            }
     1126        }
     1127
     1128        data.icon.sprite = modifier + "stretched:session/" +
     1129            (data.item.icon || "portraits/" + template.icon);
     1130
     1131        let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
     1132        if (isUpgrading)
     1133        {
     1134            let size = progressOverlay.size;
     1135            size.top = size.left + Math.round(progress * (size.right - size.left));
     1136            progressOverlay.size = size;
     1137        }
     1138        progressOverlay.hidden = !isUpgrading;
     1139
     1140        let index = data.i + getNumberOfRightPanelButtons();
     1141        setPanelObjectPosition(data.button, index, data.rowLength);
     1142        return true;
     1143    },
     1144};
     1145
    10571146/**
    10581147 * If two panels need the same space, so they collide,
    10591148 * the one appearing first in the order is rendered.
     
    10711160    // RIGHT PANE
    10721161    "Gate", // Must always be shown on gates
    10731162    "Pack", // Must always be shown on packable entities
     1163    "Upgrade", // Must always be shown on upgradable entities
    10741164    "Training",
    10751165    "Construction",
    10761166    "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" sprite="stretched:session/icons/upgrade.png"/>
     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>
     13    </repeat>
     14    </object>
     15</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 = {
     3    "Selection": 0,
     4    "Queue": 0,
     5    "Formation": 0,
     6    "Garrison": 0,
     7    "Training": 0,
     8    "Research": 0,
     9    "Alert": 0,
     10    "Barter": 0,
     11    "Construction": 0,
     12    "Command": 0,
     13    "AllyCommand": 0,
     14    "Stance": 0,
     15    "Gate":0,
     16    "Pack": 0,
     17    "Upgrade": 0
     18};
    319
    420/**
    521 * Set the position of a panel object according to the index,
     
    225241{
    226242    var sum = 0;
    227243
    228     for (let prop of ["Construction", "Training", "Pack", "Gate"])
     244    for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
    229245        if (g_SelectionPanels[prop].used)
    230246            sum += g_unitPanelButtons[prop];
    231247
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    237237        "market": null,
    238238        "mirage": null,
    239239        "pack": null,
     240        "upgrade" : null,
    240241        "player": -1,
    241242        "position": null,
    242243        "production": null,
     
    303304            "progress": cmpPack.GetProgress(),
    304305        };
    305306
     307    var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     308    if (cmpUpgrade)
     309        ret.upgrade = {
     310            "upgrades" : cmpUpgrade.GetUpgrades(),
     311            "progress": cmpUpgrade.GetProgress(),
     312            "template": cmpUpgrade.GetUpgradingTo()
     313        };
     314
    306315    let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
    307316    if (cmpProductionQueue)
    308317        ret.production = {
  • binaries/data/mods/public/simulation/components/Identity.js

     
    3737    "<optional>" +
    3838        "<element name='Tooltip'>" +
    3939            "<text/>" +
    40         "</element>" +
     40        "</element>" +
     41    "</optional>" +
     42    "<optional>" +
     43        "<element name='Rollover'>" +
     44            "<text/>" +
     45        "</element>" +
    4146    "</optional>" +
    4247    "<optional>" +
    43         "<element name='GateConversionTooltip'>" +
    44             "<text/>" +
    45         "</element>" +
    46     "</optional>" +
    47     "<optional>" +
    48         "<element name='Rollover'>" +
    49             "<text/>" +
    50         "</element>" +
    51     "</optional>" +
    52     "<optional>" +
    5348        "<element name='History'>" +
    5449            "<text/>" +
    5550        "</element>" +
  • binaries/data/mods/public/simulation/components/interfaces/Upgrade.js

     
     1Engine.RegisterInterface("Upgrade");
  • 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 (nb:GUI only)'><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)
     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   
     90    var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     91    var template = cmpTempMan.GetTemplate(this.upgrading);
     92
     93    var category;
     94    if (template.TrainingRestrictions)
     95        category = template.TrainingRestrictions.Category;
     96    else if (template.BuildRestrictions)
     97        category = template.BuildRestrictions.Category;
     98
     99    if (!category)
     100        return;
     101
     102    var cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
     103    cmpEntityLimits.ChangeCount(category, amount);
     104};
     105
     106Upgrade.prototype.CanUpgradeTo = function(template)
     107{
     108    return this.upgradeTemplates[template] !== undefined;
     109};
     110
     111Upgrade.prototype.GetUpgrades = function()
     112{
     113    var ret = [];
     114
     115    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     116
     117    for (let option in this.template)
     118    {
     119        let choice = this.template[option];
     120        let entType = choice.Entity;
     121        if (cmpIdentity)
     122            entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     123
     124        let hasCosts;
     125        let cost = {};
     126        if (choice.Cost)
     127        {
     128            hasCosts = true;
     129            for (let type in choice.Cost)
     130                cost[type] = ApplyValueModificationsToTemplate("Upgrade/Cost/"+type, +choice.Cost[type], this.owner, entType);
     131        }
     132        if (choice.Time)
     133        {
     134            hasCosts = true;
     135            cost.time = ApplyValueModificationsToTemplate("Upgrade/Time", +choice.Time/1000.0, this.owner, entType);
     136        }
     137        ret.push({
     138            "entity": entType,
     139            "icon": choice.Icon || undefined,
     140            "cost": hasCosts,
     141            "tooltip": choice.Tooltip || undefined,
     142            "requiredTechnology": this.GetRequiredTechnology(option),
     143        });
     144    }
     145
     146    return ret;
     147};
     148
     149Upgrade.prototype.CancelTimer = function()
     150{
     151    if (!this.timer)
     152        return;
     153
     154    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     155    cmpTimer.CancelTimer(this.timer);
     156    this.timer = undefined;
     157};
     158
     159Upgrade.prototype.IsUpgrading = function()
     160{
     161    return !!this.upgrading;
     162};
     163
     164Upgrade.prototype.GetUpgradingTo = function()
     165{
     166    return this.upgrading;
     167};
     168
     169Upgrade.prototype.WillCheckPlacementRestrictions = function(template)
     170{
     171    if (!this.upgradeTemplates[template])
     172        return undefined;
     173
     174    // is undefined by default so use X in Y
     175    return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
     176};
     177
     178Upgrade.prototype.GetRequiredTechnology = function(templateArg)
     179{
     180    let choice = this.upgradeTemplates[templateArg] || templateArg
     181
     182    if (this.template[choice].RequiredTechnology)
     183        return this.template[choice].RequiredTechnology;
     184
     185    if (!("RequiredTechnology" in this.template[choice]))
     186        return undefined;
     187
     188    let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     189    let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     190
     191    let entType = this.template[choice].Entity;
     192    if (cmpIdentity)
     193        entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     194
     195    let template = cmpTemplateManager.GetTemplate(entType);
     196    if (template.Identity.RequiredTechnology)
     197        return template.Identity.RequiredTechnology;
     198
     199    return undefined;
     200};
     201
     202Upgrade.prototype.GetResourceCosts = function(template)
     203{
     204    if (!this.upgradeTemplates[template])
     205        return undefined;
     206
     207    var choice = this.upgradeTemplates[template];
     208    if (!this.template[choice].Cost)
     209        return {};
     210
     211    var costs = {};
     212    for (let r in this.template[choice].Cost)
     213    {
     214        costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, +this.template[choice].Cost[r], this.entity);
     215    }
     216    return costs;
     217};
     218
     219Upgrade.prototype.Upgrade = function(template)
     220{
     221    if (this.IsUpgrading() || !this.upgradeTemplates[template])
     222        return false;
     223
     224    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     225
     226    if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(template)))
     227        return false;
     228
     229    this.upgrading = template;
     230
     231    // Prevent cheating
     232    this.ChangeUpgradedEntityCount(1);
     233
     234    if (this.GetUpgradeTime(template) !== 0)
     235    {
     236        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     237        this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
     238    }
     239    else
     240        this.UpgradeProgress();
     241
     242    return true;
     243};
     244
     245Upgrade.prototype.CancelUpgrade = function()
     246{
     247    if (!this.IsUpgrading())
     248        return;
     249
     250    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     251    if (cmpPlayer)
     252    {
     253        var costs = this.GetResourceCosts(this.upgrading);
     254        cmpPlayer.AddResources(costs);
     255    }
     256
     257    this.ChangeUpgradedEntityCount(-1);
     258
     259    this.upgrading = false;
     260    this.CancelTimer();
     261    this.SetElapsedTime(0);
     262};
     263
     264Upgrade.prototype.GetUpgradeTime = function(templateArg)
     265{
     266    var template = this.upgrading || templateArg;
     267    var choice = this.upgradeTemplates[template];
     268    if (!choice)
     269        return undefined;
     270    return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0;
     271};
     272
     273Upgrade.prototype.GetElapsedTime = function()
     274{
     275    return this.elapsedTime;
     276};
     277
     278Upgrade.prototype.GetProgress = function()
     279{
     280    if (!this.IsUpgrading())
     281        return undefined;
     282    return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime();
     283};
     284
     285Upgrade.prototype.SetElapsedTime = function(time)
     286{
     287    this.elapsedTime = time;
     288};
     289
     290Upgrade.prototype.UpgradeProgress = function(data, lateness)
     291{
     292    if (this.elapsedTime < this.GetUpgradeTime())
     293    {
     294        this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
     295        return;
     296    }
     297
     298    this.CancelTimer();
     299
     300    var newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
     301
     302    if (newEntity)
     303        PlaySound("upgraded", newEntity);
     304};
     305
     306Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    580580        }
    581581    },
    582582
    583     "wall-to-gate": function(player, cmd, data)
    584     {
    585         for (let ent of data.entities)
    586             TryTransformWallToGate(ent, data.cmpPlayer, cmd);
    587     },
    588 
    589583    "lock-gate": function(player, cmd, data)
    590584    {
    591585        for (let ent of data.entities)
     
    660654        }
    661655    },
    662656
     657    "upgrade": function(player, cmd, data)
     658    {
     659        for (let ent of data.entities)
     660        {
     661            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     662
     663            if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template))
     664                continue;
     665
     666            if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template))
     667            {
     668                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     669                cmpGUIInterface.PushNotification({
     670                    "players": [data.cmpPlayer.GetPlayerID()],
     671                    "message": markForTranslation("Cannot upgrade as distance requirements are not verified or terrain is obstructed.")
     672                });
     673                continue;
     674            }
     675
     676            if (!CanGarrisonedChangeTemplate(ent, cmd.template))
     677            {
     678                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     679                cmpGUIInterface.PushNotification({
     680                    "players": [data.cmpPlayer.GetPlayerID()],
     681                    "message": markForTranslation("Cannot upgrade a garrisoned entity.")
     682                });
     683                continue;
     684            }
     685
     686            // Check entity limits
     687            var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     688            var template = cmpTemplateManager.GetTemplate(cmd.template);
     689            var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     690            if (template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1) ||
     691                template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category))
     692            {
     693                if (g_DebugCommands)
     694                    warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
     695                continue;
     696            }
     697
     698            var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
     699            if (cmpUpgrade.GetRequiredTechnology(cmd.template) && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology(cmd.template)))
     700            {
     701                if (g_DebugCommands)
     702                    warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
     703                continue;
     704            }
     705
     706            cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer);
     707        }
     708    },
     709
     710    "cancel-upgrade": function(player, cmd, data)
     711    {
     712        for (let ent of data.entities)
     713        {
     714            let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     715            if (cmpUpgrade)
     716                cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     717        }
     718    },
     719   
    663720    "attack-request": function(player, cmd, data)
    664721    {
    665722        // Send a chat message to human players
     
    15571614    return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
    15581615}
    15591616
    1560 /**
    1561  * Try to transform a wall to a gate
    1562  */
    1563 function TryTransformWallToGate(ent, cmpPlayer, cmd)
    1564 {
    1565     var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    1566     if (!cmpIdentity)
    1567         return;
    1568 
    1569     if (!cmpIdentity.HasClass("LongWall"))
    1570     {
    1571         if (g_DebugCommands)
    1572             warn("Invalid command: invalid wall conversion to gate for player: " + uneval(cmd));
    1573         return;
    1574     }
    1575 
    1576     var gate = Engine.AddEntity(cmd.template);
    1577 
    1578     var cmpCost = Engine.QueryInterface(gate, IID_Cost);
    1579     if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    1580     {
    1581         if (g_DebugCommands)
    1582             warn("Invalid command: convert gate cost check failed: " + uneval(cmd));
    1583 
    1584         Engine.DestroyEntity(gate);
    1585         return;
    1586     }
    1587 
    1588     ReplaceBuildingWith(ent, gate);
    1589 }
    1590 
    1591 /**
    1592  * Unconditionally replace a building with another one
    1593  */
    1594 function ReplaceBuildingWith(ent, building)
    1595 {
    1596     // Move the building to the right place
    1597     var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1598     var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    1599     var pos = cmpPosition.GetPosition2D();
    1600     cmpBuildingPosition.JumpTo(pos.x, pos.y);
    1601     var rot = cmpPosition.GetRotation();
    1602     cmpBuildingPosition.SetYRotation(rot.y);
    1603     cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
    1604 
    1605     // Copy ownership
    1606     var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1607     var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
    1608     cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
    1609 
    1610     // Copy control groups
    1611     var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1612     var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    1613     cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    1614     cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
    1615 
    1616     // Copy health level from the old entity to the new
    1617     var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    1618     var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    1619     var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    1620     var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
    1621     cmpBuildingHealth.SetHitpoints(buildingHitpoints);
    1622 
    1623     PlaySound("constructed", building);
    1624 
    1625     Engine.PostMessage(ent, MT_ConstructionFinished,
    1626         { "entity": ent, "newentity": building });
    1627     Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
    1628 
    1629     Engine.DestroyEntity(ent);
    1630 }
    1631 
    16321617Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    16331618Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    16341619Engine.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.
     4function ChangeEntityTemplate(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("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'");
     11        return INVALID_ENTITY;
     12    }
     13
     14    var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
     15    var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
     16    if (cmpPosition && cmpNewPosition)
     17    {
     18        if (cmpPosition.IsInWorld())
     19        {
     20            var pos = cmpPosition.GetPosition2D();
     21            cmpNewPosition.JumpTo(pos.x, pos.y);
     22        }
     23        var rot = cmpPosition.GetRotation();
     24        cmpNewPosition.SetYRotation(rot.y);
     25        cmpNewPosition.SetXZRotation(rot.x, rot.z);
     26        cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
     27    }
     28
     29    var cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
     30    var cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
     31    if (cmpOwnership && cmpNewOwnership)
     32        cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     33
     34    // Copy control groups
     35    var cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction);
     36    var cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction);
     37    if (cmpObstruction && cmpNewObstruction)
     38    {
     39        cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     40        cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     41    }
     42
     43    // Rescale capture points
     44    var cmpCapturable = Engine.QueryInterface(oldEnt, IID_Capturable);
     45    var cmpNewCapturable = Engine.QueryInterface(newEnt, IID_Capturable);
     46    if (cmpCapturable && cmpNewCapturable)
     47    {
     48        let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
     49        let newCp = cmpCapturable.GetCapturePoints().map(v => v / scale);
     50        cmpNewCapturable.SetCapturePoints(newCp);
     51    }
     52
     53    // Maintain current health level
     54    var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health);
     55    var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health);
     56    if (cmpHealth && cmpNewHealth)
     57    {
     58        var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
     59        cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
     60    }
     61
     62    var cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
     63    var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
     64    if (cmpUnitAI && cmpNewUnitAI)
     65    {
     66        var pos = cmpUnitAI.GetHeldPosition();
     67        if (pos)
     68            cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
     69        if (cmpUnitAI.GetStanceName())
     70            cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
     71        cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
     72        cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
     73    }
     74
     75    // Maintain the list of guards
     76    var cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard);
     77    var cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard);
     78    if (cmpGuard && cmpNewGuard)
     79        cmpNewGuard.SetEntities(cmpGuard.GetEntities());
     80
     81    TransferGarrisonedUnits(oldEnt, newEnt);
     82
     83    Engine.BroadcastMessage(MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
     84
     85    Engine.DestroyEntity(oldEnt);
     86
     87    return newEnt;
     88};
     89
     90function CanGarrisonedChangeTemplate(ent, template)
     91{
     92    var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     93    var unitAI = Engine.QueryInterface(ent, IID_UnitAI);
     94    if (cmpPosition && !cmpPosition.IsInWorld() && unitAI && unitAI.IsGarrisoned())
     95    {
     96        // We're a garrisoned unit, assume impossibility as I've been unable to find a way to get the holder ID.
     97        // TODO: change this if that ever becomes possibles
     98        return false;
     99    }
     100    return true;
     101}
     102
     103function ObstructionsBlockingTemplateChange(ent, templateArg)
     104{
     105    var previewEntity = Engine.AddEntity("preview|"+templateArg);
     106
     107    if (previewEntity == INVALID_ENTITY)
     108        return true;
     109
     110    var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
     111    var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     112    var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     113
     114    var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
     115
     116    // Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better
     117    // Plus there are no real entities without owners currently.
     118    if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership)
     119        return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
     120
     121    var pos = cmpPosition.GetPosition2D();
     122    var angle = cmpPosition.GetRotation();
     123    // move us away to prevent our own obstruction from blocking the upgrade.
     124    cmpPosition.MoveOutOfWorld();
     125
     126    cmpNewPosition.JumpTo(pos.x, pos.y);
     127    cmpNewPosition.SetYRotation(angle.y);
     128
     129    var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
     130    cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     131
     132    var checkPlacement = cmpBuildRestrictions.CheckPlacement();
     133
     134    if (checkPlacement && !checkPlacement.success)
     135        return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
     136
     137    var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     138    var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName(ent));
     139    var newTemplate = cmpTemplateManager.GetTemplate(templateArg);
     140
     141    // Check if units are blocking our template change
     142    if (template.Obstruction && newTemplate.Obstruction)
     143    {
     144        // This only needs to be done if the new template is strictly bigger than the old one
     145        // "Obstructions" are annoying to test so just check.
     146        if (newTemplate.Obstruction.Obstructions ||
     147
     148            newTemplate.Obstruction.Static && template.Obstruction.Static &&
     149                (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Static["@width"] ||
     150                 newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Static["@depth"]) ||
     151            newTemplate.Obstruction.Static && template.Obstruction.Unit &&
     152                (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Unit["@radius"] ||
     153                 newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Unit["@radius"]) ||
     154
     155            newTemplate.Obstruction.Unit && template.Obstruction.Unit &&
     156                newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Unit["@radius"] ||
     157            newTemplate.Obstruction.Unit && template.Obstruction.Static &&
     158                (newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@width"] ||
     159                 newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@depth"]))
     160        {
     161            var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
     162            if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
     163            {
     164                // Check for units
     165                var collisions = cmpNewObstruction.GetEntityCollisions(false, true);
     166                if (collisions.length)
     167                    return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
     168            }
     169        }
     170    }
     171
     172    return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
     173};
     174
     175function DeleteEntityAndReturn(ent, cmpPosition, position, angle, cmpNewPosition, ret)
     176{
     177    // prevent preview from interfering in the world
     178    cmpNewPosition.MoveOutOfWorld();
     179    cmpPosition.JumpTo(position.x, position.y);
     180    cmpPosition.SetYRotation(angle.y);
     181
     182    Engine.DestroyEntity(ent);
     183    return ret;
     184};
     185
     186function TransferGarrisonedUnits(oldEnt, newEnt)
     187{
     188    // Transfer garrisoned units if possible, or unload them
     189    var cmpGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
     190    var cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
     191    if (!cmpNewGarrison || !cmpGarrison || !cmpGarrison.GetEntities().length)
     192        return; // nothing to do as the code will by default unload all.
     193
     194    var garrisonedEntities = cmpGarrison.GetEntities().slice();
     195    for (let j in garrisonedEntities)
     196    {
     197        var cmpUnitAI = Engine.QueryInterface(garrisonedEntities[j], IID_UnitAI);
     198        cmpGarrison.Eject(garrisonedEntities[j]);
     199        cmpUnitAI.Autogarrison(newEnt);
     200        cmpNewGarrison.Garrison(garrisonedEntities[j]);
     201    }
     202};
     203
     204Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
     205Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate);
     206Engine.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

     
    3838  <Identity>
    3939    <Classes datatype="tokens">LongWall</Classes>
    4040    <Tooltip>Long wall segments can be converted to gates.</Tooltip>
    41     <GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>
    4241  </Identity>
     42  <Upgrade>
     43    <Gate>
     44      <Entity>structures/{civ}_wall_gate</Entity>
     45      <Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip>
     46      <Cost>
     47        <stone>60</stone>
     48      </Cost>
     49      <Time>10000</Time>
     50    </Gate>
     51  </Upgrade>
    4352</Entity>