Ticket #2706: UpgradeComponentRC-fixed4.patch

File UpgradeComponentRC-fixed4.patch, 46.0 KB (added by wraitii, 8 years ago)

Fixed everything.

  • 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

     
    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;
     1039
     1040        if (requiredTechnology)
     1041            technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
     1042                    "tech": requiredTechnology,
     1043                    "player": data.unitEntState.player
     1044                });
     1045
     1046        let neededResources;
     1047        if (data.item.cost)
     1048            neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
     1049                    "cost": data.item.cost,
     1050                    "player": data.unitEntState.player
     1051                });
     1052
     1053        let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
     1054
     1055        let tooltip;
     1056        if (!progress)
     1057        {
     1058            tooltip = sprintf(translate("Upgrade into a %(name)s.%(tooltip)s"), {
     1059                "name": template.name.generic,
     1060                "tooltip": (data.item.tooltip? "\n" + data.item.tooltip : "")
     1061            });
     1062            if (data.item.cost)
     1063                tooltip += "\n" + getEntityCostTooltip(data.item);
     1064
     1065            tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
     1066            if (!technologyEnabled)
     1067                tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
     1068                    "technology": getEntityNames(GetTechnologyData(requiredTechnology))
     1069                });
     1070            if (neededResources)
     1071                tooltip += getNeededResourcesTooltip(neededResources);
     1072
     1073            data.button.onPress = function() { upgradeEntity(data.item.entity); };
     1074        }
     1075        else if (isUpgrading)
     1076        {
     1077            tooltip = translate("Cancel Upgrading");
     1078            data.button.onPress = function() { cancelUpgradeEntity(); };
     1079        }
     1080        else
     1081        {
     1082            tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     1083            data.button.onPress = function() {};
     1084        }
     1085        data.button.tooltip = tooltip;
     1086
     1087        let modifier = "";
     1088        if (!isUpgrading)
     1089        {
     1090            if (progress || !technologyEnabled || limits.canBeAddedCount == 0)
     1091            {
     1092                data.button.enabled = false;
     1093                modifier = "color:0 0 0 127:grayscale:";
     1094            }
     1095            else if (neededResources)
     1096            {
     1097                data.button.enabled = false;
     1098                modifier = resourcesToAlphaMask(neededResources) + ":";
     1099            }
     1100        }
     1101
     1102        data.icon.sprite = modifier + "stretched:session/" +
     1103            (data.item.icon || "portraits/" + template.icon);
     1104
     1105        let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
     1106        if (isUpgrading)
     1107        {
     1108            let size = progressOverlay.size;
     1109            size.top = size.left + Math.round(progress * (size.right - size.left));
     1110            progressOverlay.size = size;
     1111            progressOverlay.hidden = false;
     1112        }
     1113        else
     1114            progressOverlay.hidden = true;
     1115
     1116        let index = data.i + getNumberOfRightPanelButtons();
     1117        setPanelObjectPosition(data.button, index, data.rowLength);
     1118        return true;
     1119    },
     1120};
     1121
     1122
     1123
    10321124/**
    10331125 * If two panels need the same space, so they collide,
    10341126 * the one appearing first in the order is rendered.
     
    10461138    // RIGHT PANE
    10471139    "Gate", // Must always be shown on gates
    10481140    "Pack", // Must always be shown on packable entities
     1141    "Upgrade", // Must always be shown on upgradable entities
    10491142    "Training",
    10501143    "Construction",
    10511144    "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 = { "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/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    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 (let 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": this.GetRequiredTechnology(option),
     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;
     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    // is undefined by default so use X in Y
     170    return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
     171};
     172
     173Upgrade.prototype.GetRequiredTechnology = function(templateArg)
     174{
     175    let choice = this.upgradeTemplates[templateArg] || templateArg
     176
     177    if ("RequiredTechnology" in this.template[choice] && !this.template[choice].RequiredTechnology)
     178    {
     179        let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     180        let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     181        let entType = this.template[choice].Entity;
     182        if (cmpIdentity)
     183            entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     184        let template = cmpTemplateManager.GetTemplate(entType);
     185        if (template.Identity.RequiredTechnology)
     186            return template.Identity.RequiredTechnology;
     187    }
     188    else if (this.template[choice].RequiredTechnology)
     189        return this.template[choice].RequiredTechnology;
     190
     191    return undefined;
     192};
     193
     194Upgrade.prototype.GetResourceCosts = function(template)
     195{
     196    if (!this.upgradeTemplates[template])
     197        return undefined;
     198
     199    var choice = this.upgradeTemplates[template];
     200    if (!this.template[choice].Cost)
     201        return {};
     202
     203    var costs = {};
     204    for (let r in this.template[choice].Cost)
     205    {
     206        costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, +this.template[choice].Cost[r], this.entity);
     207    }
     208    return costs;
     209};
     210
     211Upgrade.prototype.Upgrade = function(template)
     212{
     213    if (this.IsUpgrading())
     214        return false;
     215
     216    if (!this.upgradeTemplates[template])
     217        return false;
     218
     219    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     220
     221    if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(template)))
     222        return false;
     223
     224    this.upgrading = template;
     225
     226    // Prevent cheating
     227    this.ChangeUpgradedEntityCount(1);
     228
     229    if (this.GetUpgradeTime(template) !== 0)
     230    {
     231        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     232        this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
     233    }
     234    else
     235        this.UpgradeProgress();
     236
     237    return true;
     238};
     239
     240Upgrade.prototype.CancelUpgrade = function()
     241{
     242    if (this.IsUpgrading() === false)
     243        return;
     244
     245    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     246    if (cmpPlayer)
     247    {
     248        var costs = this.GetResourceCosts(this.upgrading);
     249        cmpPlayer.AddResources(costs);
     250    }
     251
     252    this.ChangeUpgradedEntityCount(-1);
     253
     254    this.upgrading = false;
     255    this.CancelTimer();
     256    this.SetElapsedTime(0);
     257};
     258
     259Upgrade.prototype.GetUpgradeTime = function(templateArg)
     260{
     261    var template = this.upgrading || templateArg;
     262    var choice = this.upgradeTemplates[template];
     263    if (!choice)
     264        return undefined;
     265    return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0;
     266};
     267
     268Upgrade.prototype.GetElapsedTime = function()
     269{
     270    return this.elapsedTime;
     271};
     272
     273Upgrade.prototype.GetProgress = function()
     274{
     275    if (!this.IsUpgrading())
     276        return undefined;
     277    return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime();
     278};
     279
     280Upgrade.prototype.SetElapsedTime = function(time)
     281{
     282    this.elapsedTime = time;
     283};
     284
     285Upgrade.prototype.UpgradeProgress = function(data, lateness)
     286{
     287    if (this.elapsedTime < this.GetUpgradeTime())
     288    {
     289        this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
     290        return;
     291    }
     292
     293    this.CancelTimer();
     294
     295    var newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
     296
     297    if (newEntity)
     298        PlaySound("upgraded", newEntity);
     299};
     300
     301Engine.RegisterComponentType(IID_Upgrade, "Upgrade", 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 cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     677                    cmpGUIInterface.PushNotification({
     678                        "players": [data.cmpPlayer.GetPlayerID()],
     679                        "message": markForTranslation("Cannot upgrade as distance requirements are not verified or terrain is obstructed.")
     680                    });
     681                    continue;
     682                }
     683
     684                if (!CanGarrisonedChangeTemplate(ent, cmd.template))
     685                {
     686                    var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     687                    cmpGUIInterface.PushNotification({
     688                        "players": [data.cmpPlayer.GetPlayerID()],
     689                        "message": markForTranslation("Cannot upgrade a garrisoned entity.")
     690                    });
     691                    continue;
     692                }
     693
     694                // Check entity limits
     695                var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     696                var template = cmpTemplateManager.GetTemplate(cmd.template);
     697                var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     698                if (template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1) ||
     699                    template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category))
     700                {
     701                    if (g_DebugCommands)
     702                        warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
     703                    continue;
     704                }
     705
     706                var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
     707                if (cmpUpgrade.GetRequiredTechnology(cmd.template) && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology(cmd.template)))
     708                {
     709                    if (g_DebugCommands)
     710                        warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
     711                    continue;
     712                }
     713
     714                cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer);
     715            }
     716        },
     717
     718        "cancel-upgrade": function(player, cmd, data)
     719        {
     720            for (let ent of data.entities)
     721            {
     722                let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     723                if (cmpUpgrade)
     724                    cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     725            }
     726        },
    671727    "attack-request": function(player, cmd, data)
    672728    {
    673729        // Send a chat message to human players
     
    15671623    return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
    15681624}
    15691625
    1570 /**
    1571  * Try to transform a wall to a gate
    1572  */
    1573 function TryTransformWallToGate(ent, cmpPlayer, cmd)
    1574 {
    1575     var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    1576     if (!cmpIdentity)
    1577         return;
    1578 
    1579     if (!cmpIdentity.HasClass("LongWall"))
    1580     {
    1581         if (g_DebugCommands)
    1582             warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));
    1583         return;
    1584     }
    1585 
    1586     var 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 
    16421626Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    16431627Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    16441628Engine.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(v => 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)
     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    // prevent preview from interfering in the world
     181    cmpNewPosition.MoveOutOfWorld();
     182    cmpPosition.JumpTo(position.x, position.y);
     183    cmpPosition.SetYRotation(angle.y);
     184
     185    Engine.DestroyEntity(ent);
     186    return ret;
     187};
     188
     189var TransferGarrisonedUnits = function(oldEnt, newEnt)
     190{
     191    // Transfer garrisoned units if possible, or unload them
     192    var cmpGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
     193    var cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
     194    if (!cmpNewGarrison || !cmpGarrison || !cmpGarrison.GetEntities().length)
     195        return; // nothing to do as the code will by default unload all.
     196
     197    var garrisonedEntities = cmpGarrison.GetEntities().slice();
     198    for (let j in garrisonedEntities)
     199    {
     200        var cmpUnitAI = Engine.QueryInterface(garrisonedEntities[j], IID_UnitAI);
     201        cmpGarrison.Eject(garrisonedEntities[j]);
     202        cmpUnitAI.Autogarrison(newEnt);
     203        cmpNewGarrison.Garrison(garrisonedEntities[j]);
     204    }
     205};
     206
     207Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
     208Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate);
     209Engine.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>