Ticket #2706: UpgradeComponent-final.patch

File UpgradeComponent-final.patch, 47.5 KB (added by wraitii, 2 years ago)
  • binaries/data/mods/public/globalscripts/Templates.js

     
    270270        };
    271271        ret.icon = template.Identity.Icon;
    272272        ret.tooltip =  template.Identity.Tooltip;
    273         ret.gateConversionTooltip =  template.Identity.GateConversionTooltip;
    274273        ret.requiredTechnology = template.Identity.RequiredTechnology;
    275274        ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
    276275    }
  • binaries/data/mods/public/gui/common/tooltips.js

     
    291291        trainNum = 1;
    292292
    293293    let totalCosts = multiplyEntityCosts(template, trainNum);
    294     totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
     294    if (template.cost.time)
     295        totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
    295296
    296297    let costs = [];
    297298
  • binaries/data/mods/public/gui/session/input.js

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

     
    472472    },
    473473    "getItems": function(unitEntState, selection)
    474474    {
    475         // Allow long wall pieces to be converted to gates
    476         let longWallTypes = {};
    477         let walls = [];
    478475        let gates = [];
    479476        for (let ent of selection)
    480477        {
    481478            let state = GetEntityState(ent);
    482             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     479            if (state.gate && !gates.length)
    483480            {
    484                 let gateTemplate = getWallGateTemplate(state.id);
    485                 if (gateTemplate)
    486                 {
    487                     let tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
    488                     if (!tooltipString)
    489                     {
    490                         warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
    491                         tooltipString = "";
    492                     }
    493                     walls.push({
    494                         "tooltip": translate(tooltipString),
    495                         "template": gateTemplate,
    496                         "callback": function (item) { transformWallToGate(item.template); }
    497                     });
    498                 }
    499 
    500                 // We only need one entity per type.
    501                 longWallTypes[state.template] = true;
    502             }
    503             else if (state.gate && !gates.length)
    504             {
    505481                gates.push({
    506482                    "gate": state.gate,
    507483                    "tooltip": translate("Lock Gate"),
     
    521497                    delete gates[j].gate.locked;
    522498        }
    523499
    524         // Place wall conversion options after gate lock/unlock icons.
    525         return gates.concat(walls);
     500        return gates;
    526501    },
    527502    "setupButton": function(data)
    528503    {
    529504        data.button.onPress = function() {data.item.callback(data.item); };
    530505
    531         let tooltips = [data.item.tooltip];
    532         if (data.item.template)
    533         {
    534             data.template = GetTemplateData(data.item.template);
    535             data.wallCount = data.selection.reduce(count, ent => {
    536                     let state = GetEntityState(ent);
    537                     if (hasClass(state, "LongWall") && !state.gate)
    538                         ++count;
    539                     return count;
    540                 }, 0);
     506        data.button.tooltip = data.item.tooltip;
    541507
    542             tooltips.push(getEntityCostTooltip(data.template, data.wallCount));
    543 
    544             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    545                 "cost": multiplyEntityCosts(data.template, data.wallCount)
    546             });
    547 
    548             tooltips.push(getNeededResourcesTooltip(data.neededResources));
    549         }
    550         data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    551 
    552508        data.button.enabled = controlsPlayer(data.unitEntState.player);
    553509        let gateIcon;
    554510        if (data.item.gate)
    555511        {
    556             // If already a gate, show locking actions
     512            // show locking actions
    557513            gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    558514            if (data.item.gate.locked === undefined)
    559515                data.guiSelection.hidden = false;
     
    560516            else
    561517                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    562518        }
    563         else
    564         {
    565             // Otherwise show gate upgrade icon
    566             let template = GetTemplateData(data.item.template);
    567             if (!template)
    568                 return false;
    569             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    570             data.guiSelection.hidden = true;
    571         }
    572519
    573520        data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
    574521
     
    609556        }
    610557        let items = [];
    611558        if (checks.packButton)
    612             items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
     559            items.push({
     560                "packing": false,
     561                "packed": false,
     562                "tooltip": translate("Pack"),
     563                "callback": function() { packUnit(true); }
     564            });
    613565        if (checks.unpackButton)
    614             items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
     566            items.push({
     567                "packing": false,
     568                "packed": true,
     569                "tooltip": translate("Unpack"),
     570                "callback": function() { packUnit(false); }
     571            });
    615572        if (checks.packCancelButton)
    616             items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
     573            items.push({
     574                "packing": true,
     575                "packed": false,
     576                "tooltip": translate("Cancel Packing"),
     577                "callback": function() { cancelPackUnit(true); }
     578            });
    617579        if (checks.unpackCancelButton)
    618             items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
     580            items.push({
     581                "packing": true,
     582                "packed": true,
     583                "tooltip": translate("Cancel Unpacking"),
     584                "callback": function() { cancelPackUnit(false); }
     585            });
    619586        return items;
    620587    },
    621588    "setupButton": function(data)
     
    10511018    }
    10521019};
    10531020
     1021g_SelectionPanels.Upgrade = {
     1022    "getMaxNumberOfItems": function()
     1023    {
     1024        return 24 - getNumberOfRightPanelButtons();
     1025    },
     1026    "getItems": function(unitEntState, selection)
     1027    {
     1028        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     1029        // TODO: if the units are all the same, this should probably still be possible.
     1030        if (selection.length > 1)
     1031            return false;
     1032 
     1033        if (!unitEntState.upgrade)
     1034            return false;
     1035 
     1036        var items = [];
     1037
     1038        for (let upgrade of unitEntState.upgrade.upgrades)
     1039        {
     1040            items.push({
     1041                "entity": upgrade.entity,
     1042                "cost": upgrade.cost,
     1043                "time": upgrade.time,
     1044                "icon": upgrade.icon,
     1045                "tooltip": upgrade.tooltip,
     1046                "requiredTechnology": upgrade.requiredTechnology,
     1047            });
     1048        }
     1049        return items;
     1050    },
     1051    "setupButton" : function(data)
     1052    {
     1053        let template = GetTemplateData(data.item.entity);
     1054        if (!template)
     1055            return false;
     1056
     1057        let technologyEnabled = true;
     1058
     1059        if (data.item.requiredTechnology)
     1060            technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
     1061                "tech": requiredTechnology,
     1062                "player": data.unitEntState.player
     1063            });
     1064
     1065        let neededResources;
     1066        if (data.item.cost)
     1067            neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
     1068                "cost": data.item.cost,
     1069                "player": data.unitEntState.player
     1070            });
     1071
     1072        let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
     1073
     1074        let progress = data.unitEntState.upgrade.progress || 0;
     1075        let isUpgrading = data.unitEntState.upgrade.template == data.item.entity;
     1076
     1077        let tooltip;
     1078        if (!progress)
     1079        {
     1080            if (data.item.tooltip)
     1081                tooltip = sprintf(translate("\nUpgrade into a %(name)s.%(tooltip)s"), {
     1082                    "name": template.name.generic,
     1083                    "tooltip": data.item.tooltip
     1084                });
     1085            else
     1086                tooltip = sprintf(translate("\nUpgrade into a %(name)s."), {"name": template.name.generic});
     1087
     1088            if (data.item.cost)
     1089                tooltip += "\n" + getEntityCostTooltip(data.item);
     1090
     1091            tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
     1092            if (!technologyEnabled)
     1093                tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
     1094                    "technology": getEntityNames(GetTechnologyData(data.item.requiredTechnology))
     1095                });
     1096            if (neededResources)
     1097                tooltip += getNeededResourcesTooltip(neededResources);
     1098
     1099            data.button.onPress = function() { upgradeEntity(data.item.entity); };
     1100        }
     1101        else if (isUpgrading)
     1102        {
     1103            tooltip = translate("Cancel Upgrading");
     1104            data.button.onPress = function() { cancelUpgradeEntity(); };
     1105        }
     1106        else
     1107        {
     1108            tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     1109            data.button.onPress = function() {};
     1110        }
     1111        data.button.tooltip = tooltip;
     1112
     1113        let modifier = "";
     1114        if (!isUpgrading)
     1115        {
     1116            if (progress || !technologyEnabled || limits.canBeAddedCount == 0)
     1117            {
     1118                data.button.enabled = false;
     1119                modifier = "color:0 0 0 127:grayscale:";
     1120            }
     1121            else if (neededResources)
     1122            {
     1123                data.button.enabled = false;
     1124                modifier = resourcesToAlphaMask(neededResources) + ":";
     1125            }
     1126        }
     1127
     1128        data.icon.sprite = modifier + "stretched:session/" +
     1129            (data.item.icon || "portraits/" + template.icon);
     1130
     1131        let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
     1132        if (isUpgrading)
     1133            progressOverlay.size.top = progressOverlay.size.left + Math.round(progress * (progressOverlay.size.right - progressOverlay.size.left));
     1134
     1135        progressOverlay.hidden = !isUpgrading;
     1136
     1137        let index = data.i + getNumberOfRightPanelButtons();
     1138        setPanelObjectPosition(data.button, index, data.rowLength);
     1139        return true;
     1140    }
     1141};
     1142
    10541143/**
    10551144 * If two panels need the same space, so they collide,
    10561145 * the one appearing first in the order is rendered.
     
    10681157    // RIGHT PANE
    10691158    "Gate", // Must always be shown on gates
    10701159    "Pack", // Must always be shown on packable entities
     1160    "Upgrade", // Must always be shown on upgradable entities
    10711161    "Training",
    10721162    "Construction",
    10731163    "Research", // Normal together with training
  • binaries/data/mods/public/gui/session/selection_panels_helpers.js

     
    44const BARTER_ACTIONS = ["Sell", "Buy"];
    55const GATE_ACTIONS = ["lock", "unlock"];
    66
     7// upgrade constants
     8const UPGRADING_NOT_STARTED = -2;
     9const UPGRADING_CHOSEN_OTHER = -1;
     10
     11// ==============================================
     12// BARTER HELPERS
     13// Resources to sell on barter panel
    714var g_BarterSell = "food";
    815
    916function canMoveSelectionIntoFormation(formationTemplate)
  • binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml

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

    Property changes on: binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +text/xml
    \ No newline at end of property
     
    11// The number of currently visible buttons (used to optimise showing/hiding)
    2 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Alert": 0, "Barter": 0, "Construction": 0, "Command": 0, "AllyCommand": 0, "Stance": 0, "Gate": 0, "Pack": 0};
     2var g_unitPanelButtons = {
     3    "Selection": 0,
     4    "Queue": 0,
     5    "Formation": 0,
     6    "Garrison": 0,
     7    "Training": 0,
     8    "Research": 0,
     9    "Alert": 0,
     10    "Barter": 0,
     11    "Construction": 0,
     12    "Command": 0,
     13    "AllyCommand": 0,
     14    "Stance": 0,
     15    "Gate":0,
     16    "Pack": 0,
     17    "Upgrade": 0
     18};
    319
    420/**
    521 * Set the position of a panel object according to the index,
     
    225241{
    226242    var sum = 0;
    227243
    228     for (let prop of ["Construction", "Training", "Pack", "Gate"])
     244    for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
    229245        if (g_SelectionPanels[prop].used)
    230246            sum += g_unitPanelButtons[prop];
    231247
  • binaries/data/mods/public/simulation/components/GuiInterface.js

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

     
    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

     
    3333{
    3434    if (this.timer)
    3535    {
    36         var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     36        let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    3737        cmpTimer.CancelTimer(this.timer);
    3838        this.timer = undefined;
    3939    }
     
    5656        return;
    5757
    5858    this.packing = true;
    59     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     59    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    6060    this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, {"packing": true});
    61     var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     61    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    6262    if (cmpVisual)
    6363        cmpVisual.SelectAnimation("packing", true, 1.0, "packing");
    6464};
     
    7070        return;
    7171
    7272    this.packing = true;
    73     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     73    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    7474    this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, {"packing": false});
    75     var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     75    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    7676    if (cmpVisual)
    7777        cmpVisual.SelectAnimation("unpacking", true, 1.0, "unpacking");
    7878};
     
    8888    this.SetElapsedTime(0);
    8989
    9090    // Clear animation
    91     var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     91    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    9292    if (cmpVisual)
    9393        cmpVisual.SelectAnimation("idle", false, 1.0, "");
    9494};
     
    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    let 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());
     132    if (newEntity)
     133        PlaySound(this.packed ? "packed" : "unpacked", newEntity);
    147134
    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
    190         var sound = this.packed ? "packed" : "unpacked";
    191         PlaySound(sound, newEntity);
    192 
    193         // Destroy current entity
    194         Engine.DestroyEntity(this.entity);
    195     }
    196     else
    197     {
    198         this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
    199     }
    200135};
    201136
    202137Engine.RegisterComponentType(IID_Pack, "Pack", Pack);
  • binaries/data/mods/public/simulation/components/Upgrade.js

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

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

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

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

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

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