Ticket #2706: UpgradeComponent.10.patch

File UpgradeComponent.10.patch, 45.3 KB (added by sanderd17, 3 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

     
    446446    },
    447447    "getItems": function(unitEntState, selection)
    448448    {
    449         // Allow long wall pieces to be converted to gates
    450         let longWallTypes = {};
    451         let walls = [];
    452449        let gates = [];
    453450        for (let ent of selection)
    454451        {
    455452            let state = GetEntityState(ent);
    456             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     453            if (state.gate && !gates.length)
    457454            {
    458                 let gateTemplate = getWallGateTemplate(state.id);
    459                 if (gateTemplate)
    460                 {
    461                     let tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
    462                     if (!tooltipString)
    463                     {
    464                         warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
    465                         tooltipString = "";
    466                     }
    467                     walls.push({
    468                         "tooltip": translate(tooltipString),
    469                         "template": gateTemplate,
    470                         "callback": function (item) { transformWallToGate(item.template); }
    471                     });
    472                 }
    473 
    474                 // We only need one entity per type.
    475                 longWallTypes[state.template] = true;
    476             }
    477             else if (state.gate && !gates.length)
    478             {
    479455                gates.push({
    480456                    "gate": state.gate,
    481457                    "tooltip": translate("Lock Gate"),
     
    495471                    delete gates[j].gate.locked;
    496472        }
    497473
    498         // Place wall conversion options after gate lock/unlock icons.
    499         return gates.concat(walls);
     474        return gates;
    500475    },
    501476    "setupButton": function(data)
    502477    {
     
    503478        data.button.onPress = function() {data.item.callback(data.item); };
    504479
    505480        let tooltip = data.item.tooltip;
    506         if (data.item.template)
    507         {
    508             data.template = GetTemplateData(data.item.template);
    509             data.wallCount = data.selection.reduce(function (count, ent) {
    510                     let state = GetEntityState(ent);
    511                     if (hasClass(state, "LongWall") && !state.gate)
    512                         ++count;
    513                     return count;
    514                 }, 0);
     481        data.button.tooltip = data.item.tooltip;
    515482
    516             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
    517 
    518             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    519                 "cost": multiplyEntityCosts(data.template, data.wallCount)
    520             });
    521 
    522             if (data.neededResources)
    523                 tooltip += getNeededResourcesTooltip(data.neededResources);
    524         }
    525         data.button.tooltip = tooltip;
    526 
    527483        data.button.enabled = controlsPlayer(data.unitEntState.player);
    528484        let gateIcon;
    529485        if (data.item.gate)
    530486        {
    531             // If already a gate, show locking actions
     487            // Show locking actions
    532488            gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    533489            if (data.item.gate.locked === undefined)
    534490                data.guiSelection.hidden = false;
     
    535491            else
    536492                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    537493        }
    538         else
    539         {
    540             // Otherwise show gate upgrade icon
    541             let template = GetTemplateData(data.item.template);
    542             if (!template)
    543                 return false;
    544             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    545             data.guiSelection.hidden = true;
    546         }
    547494
    548495        data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
    549496
     
    584531        }
    585532        let items = [];
    586533        if (checks.packButton)
    587             items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
     534            items.push({
     535                "packing": false,
     536                "packed": false,
     537                "tooltip": translate("Pack"),
     538                "callback": function() { packUnit(true); }
     539            });
    588540        if (checks.unpackButton)
    589             items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
     541            items.push({
     542                "packing": false,
     543                "packed": true,
     544                "tooltip": translate("Unpack"),
     545                "callback": function() { packUnit(false); }
     546            });
    590547        if (checks.packCancelButton)
    591             items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
     548            items.push({
     549                "packing": true,
     550                "packed": false,
     551                "tooltip": translate("Cancel Packing"),
     552                "callback": function() { cancelPackUnit(true); }
     553            });
    592554        if (checks.unpackCancelButton)
    593             items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
     555            items.push({
     556                "packing": true,
     557                "packed": true,
     558                "tooltip": translate("Cancel Unpacking"),
     559                "callback": function() { cancelPackUnit(false); }
     560            });
    594561        return items;
    595562    },
    596563    "setupButton": function(data)
     
    1027994    }
    1028995};
    1029996
     997g_SelectionPanels.Upgrade = {
     998    "getMaxNumberOfItems": function()
     999    {
     1000        return 24 - getNumberOfRightPanelButtons();
     1001    },
     1002    "getItems": function(unitEntState, selection)
     1003    {
     1004        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     1005        // TODO: if the units are all the same, this should probably still be possible.
     1006        if (selection.length > 1)
     1007            return false;
    10301008
     1009        if (!unitEntState.upgrade)
     1010            return false;
    10311011
     1012        var items = [];
     1013
     1014        for (let upgrade of unitEntState.upgrade.upgrades)
     1015        {
     1016            var item = {
     1017                "entity": upgrade.entity,
     1018                "cost": upgrade.cost,
     1019                "time": upgrade.time,
     1020                "icon": upgrade.icon,
     1021                "tooltip": upgrade.tooltip,
     1022                "requiredTechnology": upgrade.requiredTechnology,
     1023            };
     1024            items.push(item);
     1025        }
     1026        return items;
     1027    },
     1028    "setupButton" : function(data)
     1029    {
     1030        let template = GetTemplateData(data.item.entity);
     1031        if (!template)
     1032            return false;
     1033
     1034        let isUpgrading = data.unitEntState.upgrade.template == data.item.entity;
     1035        let progress = data.unitEntState.upgrade.progress || 0;
     1036
     1037        let technologyEnabled = true;
     1038        let requiredTechnology = data.item.requiredTechnology || template.requiredTechnology;
     1039        if (requiredTechnology)
     1040            technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", requiredTechnology);
     1041
     1042        let neededResources;
     1043        if (data.item.cost)
     1044            neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": data.item.cost });
     1045
     1046        let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
     1047
     1048        let tooltip;
     1049        if (!progress)
     1050        {
     1051            tooltip = sprintf(translate("Upgrade into a %(name)s.%(tooltip)s"), {
     1052                "name": template.name.generic,
     1053                "tooltip": (data.item.tooltip? "\n" + data.item.tooltip : "")
     1054            });
     1055            if (data.item.cost)
     1056                tooltip += "\n" + getEntityCostTooltip(data.item);
     1057
     1058            tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
     1059            if (!technologyEnabled)
     1060                tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
     1061                    "technology": getEntityNames(GetTechnologyData(requiredTechnology))
     1062                });
     1063
     1064            if (data.item.neededResources)
     1065                tooltip += getNeededResourcesTooltip(neededResources);
     1066
     1067            data.button.onPress = function() { upgradeEntity(data.item.entity); };
     1068        }
     1069        else if (isUpgrading)
     1070        {
     1071            tooltip = translate("Cancel Upgrading");
     1072            data.button.onPress = function() { cancelUpgradeEntity(); };
     1073        }
     1074        else
     1075        {
     1076            tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     1077            data.button.onPress = function() {};
     1078        }
     1079        data.button.tooltip = tooltip;
     1080
     1081        let modifier = "";
     1082        if (!isUpgrading)
     1083        {
     1084            if (progress || !technologyEnabled || limits.canBeAddedCount == 0)
     1085            {
     1086                data.button.enabled = false;
     1087                modifier = "color:0 0 0 127:grayscale:";
     1088            }
     1089            else if (neededResources)
     1090            {
     1091                data.button.enabled = false;
     1092                modifier = resourcesToAlphaMask(neededResources);
     1093            }
     1094        }
     1095
     1096        data.icon.sprite = modifier + "stretched:session/" +
     1097            (data.item.icon || "portraits/" + template.icon);
     1098
     1099        let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
     1100        if (isUpgrading)
     1101        {
     1102            let size = progressOverlay.size;
     1103            size.top = size.left + Math.round(progress * (size.right - size.left));
     1104            progressOverlay.size = size;
     1105            progressOverlay.hidden = false;
     1106        }
     1107        else
     1108            progressOverlay.hidden = true;
     1109
     1110        let index = data.i + getNumberOfRightPanelButtons();
     1111        setPanelObjectPosition(data.button, index, data.rowLength);
     1112        return true;
     1113    },
     1114};
     1115
     1116
     1117
    10321118/**
    10331119 * If two panels need the same space, so they collide,
    10341120 * the one appearing first in the order is rendered.
     
    10461132    // RIGHT PANE
    10471133    "Gate", // Must always be shown on gates
    10481134    "Pack", // Must always be shown on packable entities
     1135    "Upgrade", // Must always be shown on upgradable entities
    10491136    "Training",
    10501137    "Construction",
    10511138    "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,
     
    225225{
    226226    var sum = 0;
    227227
    228     for (let prop of ["Construction", "Training", "Pack", "Gate"])
     228    for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
    229229        if (g_SelectionPanels[prop].used)
    230230            sum += g_unitPanelButtons[prop];
    231231
  • 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

     
    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                let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     722                if (cmpUpgrade)
     723                    cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     724            }
     725        },
    671726    "attack-request": function(player, cmd, data)
    672727    {
    673728        // Send a chat message to human players
     
    15671622    return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
    15681623}
    15691624
    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 gate = Engine.AddEntity(cmd.template);
    1587 
    1588     var cmpCost = Engine.QueryInterface(gate, IID_Cost);
    1589     if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    1590     {
    1591         if (g_DebugCommands)
    1592             warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));
    1593 
    1594         Engine.DestroyEntity(gate);
    1595         return;
    1596     }
    1597 
    1598     ReplaceBuildingWith(ent, gate);
    1599 }
    1600 
    1601 /**
    1602  * Unconditionally replace a building with another one
    1603  */
    1604 function ReplaceBuildingWith(ent, building)
    1605 {
    1606     // Move the building to the right place
    1607     var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1608     var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    1609     var pos = cmpPosition.GetPosition2D();
    1610     cmpBuildingPosition.JumpTo(pos.x, pos.y);
    1611     var rot = cmpPosition.GetRotation();
    1612     cmpBuildingPosition.SetYRotation(rot.y);
    1613     cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
    1614 
    1615     // Copy ownership
    1616     var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1617     var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
    1618     cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
    1619 
    1620     // Copy control groups
    1621     var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1622     var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    1623     cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    1624     cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
    1625 
    1626     // Copy health level from the old entity to the new
    1627     var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    1628     var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    1629     var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    1630     var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
    1631     cmpBuildingHealth.SetHitpoints(buildingHitpoints);
    1632 
    1633     PlaySound("constructed", building);
    1634 
    1635     Engine.PostMessage(ent, MT_ConstructionFinished,
    1636         { "entity": ent, "newentity": building });
    1637     Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
    1638 
    1639     Engine.DestroyEntity(ent);
    1640 }
    1641 
    16421625Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    16431626Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    16441627Engine.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

     
    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>