Ticket #2706: UpgradeComponent.5.patch

File UpgradeComponent.5.patch, 45.9 KB (added by wraitii, 3 years ago)

Updated for A20

  • globalscripts/Templates.js

     
    226226        };
    227227        ret.icon = template.Identity.Icon;
    228228        ret.tooltip =  template.Identity.Tooltip;
    229         ret.gateConversionTooltip =  template.Identity.GateConversionTooltip;
    230229        ret.requiredTechnology = template.Identity.RequiredTechnology;
    231230        ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
    232231    }
  • gui/session/unit_commands.js

     
    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,
     
    279279        sum += g_unitPanelButtons["Pack"];
    280280    if (g_SelectionPanels["Gate"].used)
    281281        sum += g_unitPanelButtons["Gate"];
     282    if (g_SelectionPanels["Upgrade"].used)
     283        sum += g_unitPanelButtons["Upgrade"];
    282284    return sum;
    283285}
  • 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
  • gui/session/selection_panels.js

     
    472472    },
    473473    "getItems": function(unitEntState, selection)
    474474    {
    475         // Allow long wall pieces to be converted to gates
    476         var longWallTypes = {};
    477         var walls = [];
    478475        var gates = [];
    479476        for (var ent of selection)
    480477        {
    481478            var state = GetEntityState(ent);
    482             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     479            if (state.gate && !gates.length)
    483480            {
    484                 var gateTemplate = getWallGateTemplate(state.id);
    485                 if (gateTemplate)
    486                 {
    487                     var 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"),
     
    520496                for (var j = 0; j < gates.length; ++j)
    521497                    delete gates[j].gate.locked;
    522498        }
    523 
    524         // Place wall conversion options after gate lock/unlock icons.
    525         var items = gates.concat(walls);
    526         return items;
     499        return gates;
    527500    },
    528501    "setAction": function(data)
    529502    {
     
    531504    },
    532505    "setTooltip": function(data)
    533506    {
    534         var tooltip = data.item.tooltip;
    535         if (data.item.template)
    536         {
    537             data.template = GetTemplateData(data.item.template);
    538             data.wallCount = data.selection.reduce(function (count, ent) {
    539                     var state = GetEntityState(ent);
    540                     if (hasClass(state, "LongWall") && !state.gate)
    541                         count++;
    542                     return count;
    543                 }, 0);
    544 
    545             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
    546 
    547 
    548             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(data.template, data.wallCount));
    549             if (data.neededResources)
    550                 tooltip += getNeededResourcesTooltip(data.neededResources);
    551         }
    552         data.button.tooltip = tooltip;
     507        data.button.tooltip = data.item.tooltip;
    553508    },
    554509    "setGraphics": function(data)
    555510    {
    556         data.affordableMask.hidden == data.neededResources ? true : false;
    557511        var gateIcon;
    558512        if (data.item.gate)
    559513        {
    560             // If already a gate, show locking actions
     514            // Show locking actions
    561515            gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    562516            if (data.item.gate.locked === undefined)
    563517                data.guiSelection.hidden = false;
     
    564518            else
    565519                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    566520        }
    567         else
    568         {
    569             // otherwise show gate upgrade icon
    570             var template = GetTemplateData(data.item.template);
    571             if (!template)
    572                 return;
    573             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    574             data.guiSelection.hidden = true;
    575         }
    576 
    577521        data.icon.sprite = "stretched:session/" + gateIcon;
    578522    },
    579523    "setPosition": function(data)
     
    648592    },
    649593};
    650594
     595// UPGRADE
     596g_SelectionPanels.Upgrade = {
     597    "getMaxNumberOfItems": function()
     598    {
     599        return 24 - getNumberOfRightPanelButtons();
     600    },
     601    "getItems": function(unitEntState, selection)
     602    {
     603        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     604        // TODO: if the units are all the same, this should probably still be possible.
     605        if (selection.length > 1)
     606            return false;
     607
     608        if (!unitEntState.upgrade)
     609            return false;
     610
     611        var items = [];
     612
     613        for each (var upgrade in unitEntState.upgrade.upgrades)
     614        {
     615            var item = {};
     616            item.entType = upgrade.entity;
     617            item.template = GetTemplateData(upgrade.entity);
     618            if (!item.template) // abort if no template
     619                return false;
     620
     621            item.icon = upgrade.icon;
     622            if (!item.icon)
     623                item.icon = "portraits/" + item.template.icon;
     624
     625            if (upgrade.requiredTechnology !== null)
     626            {
     627                item.requiredTechnology = upgrade.requiredTechnology;
     628                if (!item.requiredTechnology && item.template.requiredTechnology)
     629                    item.requiredTechnology = item.template.requiredTechnology
     630            }
     631            item.cost = upgrade.cost;
     632            item.time = upgrade.time;
     633
     634            if (unitEntState.upgrade.progress === undefined)
     635            {
     636                item.upgrading = UPGRADING_NOT_STARTED;
     637                item.tooltip = sprintf(translate("Upgrade into a %(name)s.%(tooltip)s"), { name: item.template.name.generic, tooltip: (upgrade.tooltip? "\n" + upgrade.tooltip : "") });
     638                item.callback = function(data) { upgradeEntity(data.entType); };
     639            }
     640            else if (unitEntState.upgrade.template !== upgrade.entity)
     641            {
     642                item.upgrading = UPGRADING_CHOSEN_OTHER;
     643                item.tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     644                item.callback = function(data) { };
     645            }
     646            else
     647            {
     648                item.upgrading = unitEntState.upgrade.progress;
     649                item.tooltip = translate("Cancel Upgrading");
     650                item.callback = function(data) { cancelUpgradeEntity(); };
     651            }
     652            items.push(item);
     653        }
     654        return items;
     655    },
     656    "addData" : function(data)
     657    {
     658        data.item.technologyEnabled = true;
     659        if (data.item.requiredTechnology)
     660            data.item.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", data.item.requiredTechnology);
     661        if (data.item.cost)
     662        {
     663            var totalCost = {};
     664            for (var i in data.item.cost)
     665                totalCost[i] = data.item.cost[i];
     666            data.item.neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCost);
     667        }
     668        data.item.limits = getEntityLimitAndCount(data.playerState, data.item.entType);
     669        return true;
     670    },
     671    "setAction": function(data)
     672    {
     673        data.button.onPress = function() { data.item.callback(data.item); };
     674    },
     675    "setTooltip": function(data)
     676    {
     677        var tooltip = data.item.tooltip;
     678
     679        if (data.item.upgrading !== UPGRADING_NOT_STARTED)
     680        {
     681            data.button.tooltip = tooltip;
     682            return;
     683        }
     684
     685        if (data.item.cost)
     686            tooltip += "\n" + getEntityCostTooltip(data.item);
     687       
     688        tooltip += formatLimitString(data.item.limits.entLimit, data.item.limits.entCount, data.item.limits.entLimitChangers);
     689        if (!data.item.technologyEnabled)
     690        {
     691            var techName = getEntityNames(GetTechnologyData(data.item.requiredTechnology));
     692            tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { technology: techName });
     693        }
     694        if (data.item.neededResources)
     695            tooltip += getNeededResourcesTooltip(data.item.neededResources);
     696
     697        data.button.tooltip = tooltip;
     698    },
     699    "setGraphics": function(data)
     700    {
     701        var grayscale = "";
     702        if (data.item.upgrading == UPGRADING_CHOSEN_OTHER || !data.item.technologyEnabled || (data.item.limits.canBeAddedCount == 0 && data.item.upgrading == UPGRADING_NOT_STARTED))
     703        {
     704            data.button.enabled = false;
     705            grayscale = "grayscale:";
     706            data.affordableMask.hidden = false;
     707            data.affordableMask.sprite = "colour: 0 0 0 127";
     708        }
     709        else if (data.item.upgrading == UPGRADING_NOT_STARTED && data.item.neededResources)
     710        {
     711            data.button.enabled = false;
     712            data.affordableMask.hidden = false;
     713            data.affordableMask.sprite = resourcesToAlphaMask(data.item.neededResources);
     714        }
     715       
     716        data.icon.sprite = "stretched:" + grayscale + "session/" + data.item.icon;
     717
     718        var guiObject = Engine.GetGUIObjectByName("unitUpgradeProgressSlider["+data.i+"]");
     719        var size = guiObject.size;
     720        if (data.item.upgrading < 0)
     721            size.top = size.right;
     722        else
     723            size.top = size.left + Math.round(Math.max(0,data.item.upgrading) * (size.right - size.left));
     724        guiObject.size = size;
     725    },
     726    "setPosition": function(data)
     727    {
     728        var index = data.i + getNumberOfRightPanelButtons();
     729        setPanelObjectPosition(data.button, index, data.rowLength);
     730    },
     731};
     732
    651733// QUEUE
    652734g_SelectionPanels.Queue = {
    653735    "getMaxNumberOfItems": function()
     
    11391221    // RIGHT PANE
    11401222    "Gate", // must always be shown on gates
    11411223    "Pack", // must always be shown on packable entities
     1224    "Upgrade", // must always be shown on upgradable entities
    11421225    "Training",
    11431226    "Construction",
    11441227    "Research", // normal together with training
  • gui/session/input.js

     
    16551655    });
    16561656}
    16571657
    1658 // Transform a wall to a gate
    1659 function transformWallToGate(template)
     1658// Upgrade an entity to another
     1659function upgradeEntity(Template)
    16601660{
    16611661    var selection = g_Selection.toList();
    16621662    Engine.PostNetworkCommand({
    1663         "type": "wall-to-gate",
    1664         "entities": selection.filter( function(e) { return getWallGateTemplate(e) == template; } ),
    1665         "template": template,
     1663        "type": "upgrade",
     1664        "entities": selection,
     1665        "template": Template,
     1666        "queued": false
    16661667    });
    16671668}
    16681669
    1669 // Gets the gate form (if any) of a given long wall piece
    1670 function getWallGateTemplate(entity)
     1670// Cancel upgrading entities
     1671function cancelUpgradeEntity()
    16711672{
    1672     // TODO: find the gate template name in a better way
    1673     var entState = GetEntityState(entity);
    1674     var index;
    1675 
    1676     if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
    1677         return entState.template.substr(0, index) + "gate";
    1678     return undefined;
     1673    var selection = g_Selection.toList();
     1674    Engine.PostNetworkCommand({
     1675        "type": "cancel-upgrade",
     1676        "entities": selection,
     1677        "queued": false
     1678    });
    16791679}
    16801680
    16811681// Set the camera to follow the given unit
  • 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 name="unitUpgradeUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     13        </object>
     14    </repeat>
     15    </object>
     16</object>
  • gui/session/selection_panels_right/gate_panel.xml

    Property changes on: gui/session/selection_panels_right/upgrade_panel.xml
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    77        <object name="unitGateButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
    88        <object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
    99        <object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
    10         <object name="unitGateUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 127"/>
    1110        </object>
    1211    </repeat>
    1312    </object>
  • simulation/components/Upgrade.js

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

    Property changes on: simulation/components/Upgrade.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    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())
     132    if (newEntity)
    139133        {
    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
    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);
  • 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>" +
  • simulation/components/GuiInterface.js

     
    232232        "guard": null,
    233233        "mirage": null,
    234234        "pack": null,
     235        "upgrade" : null,
    235236        "player": -1,
    236237        "position": null,
    237238        "production": null,
     
    294295        };
    295296    }
    296297
     298    var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     299    if (cmpUpgrade)
     300    {
     301        ret.upgrade = {
     302            "upgrades" : cmpUpgrade.GetUpgrades(),
     303            "progress": cmpUpgrade.GetProgress(),
     304            "template": cmpUpgrade.GetUpgradingTo()
     305        };
     306    }
     307
    297308    var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
    298309    if (cmpProductionQueue)
    299310    {
  • simulation/components/interfaces/Upgrade.js

     
     1Engine.RegisterInterface("Upgrade");
  • simulation/helpers/Transform.js

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

    Property changes on: simulation/helpers/Transform.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    576576        }
    577577    },
    578578
    579     "wall-to-gate": function(player, cmd, data)
    580     {
    581         for each (var ent in data.entities)
    582         {
    583             TryTransformWallToGate(ent, data.cmpPlayer, cmd.template);
    584         }
    585     },
    586 
    587579    "lock-gate": function(player, cmd, data)
    588580    {
    589581        for each (var ent in data.entities)
     
    668660        }
    669661    },
    670662
     663    "upgrade": function(player, cmd, data)
     664    {
     665        for each (var ent in data.entities)
     666        {
     667            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     668
     669            if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template))
     670                continue;
     671
     672            if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template))
     673            {
     674                var notification = {"players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade as distance requirements are not verified or terrain is obstructed." };
     675                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     676                cmpGUIInterface.PushNotification(notification);
     677                continue;
     678            }
     679
     680            if (!CanGarrisonedChangeTemplate(ent, cmd.template))
     681            {
     682                var notification = {"players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade a garrisoned entity." };
     683                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     684                cmpGUIInterface.PushNotification(notification);
     685                continue;
     686            }
     687
     688            // Check entity limits
     689            var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     690            var template = cmpTemplateManager.GetTemplate(cmd.template);
     691            var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     692            if ((template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1))
     693                || (template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category)))
     694            {
     695                if (g_DebugCommands)
     696                    warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
     697                continue;
     698            }
     699
     700            var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
     701            if (cmpUpgrade.GetRequiredTechnology() && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology()))
     702            {
     703                if (g_DebugCommands)
     704                    warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
     705                continue;
     706            }
     707
     708            cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer);
     709        }
     710    },
     711
     712    "cancel-upgrade": function(player, cmd, data)
     713    {
     714        for each (var ent in data.entities)
     715        {
     716            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     717            if (cmpUpgrade)
     718                cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     719        }
     720    },
     721
    671722    "attack-request": function(player, cmd, data)
    672723    {
    673724        // Send a chat message to human players
     
    15611612    return entities.filter(function(ent) { return CanControlUnitOrIsAlly(ent, player, controlAll);} );
    15621613}
    15631614
    1564 /**
    1565  * Try to transform a wall to a gate
    1566  */
    1567 function TryTransformWallToGate(ent, cmpPlayer, template)
    1568 {
    1569     var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    1570     if (!cmpIdentity)
    1571         return;
    1572 
    1573     // Check if this is a valid long wall segment
    1574     if (!cmpIdentity.HasClass("LongWall"))
    1575     {
    1576         if (g_DebugCommands)
    1577             warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));
    1578         return;
    1579     }
    1580 
    1581     var civ = cmpIdentity.GetCiv();
    1582     var gate = Engine.AddEntity(template);
    1583 
    1584     var cmpCost = Engine.QueryInterface(gate, IID_Cost);
    1585     if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    1586     {
    1587         if (g_DebugCommands)
    1588             warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));
    1589 
    1590         Engine.DestroyEntity(gate);
    1591         return;
    1592     }
    1593 
    1594     ReplaceBuildingWith(ent, gate);
    1595 }
    1596 
    1597 /**
    1598  * Unconditionally replace a building with another one
    1599  */
    1600 function ReplaceBuildingWith(ent, building)
    1601 {
    1602     // Move the building to the right place
    1603     var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1604     var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    1605     var pos = cmpPosition.GetPosition2D();
    1606     cmpBuildingPosition.JumpTo(pos.x, pos.y);
    1607     var rot = cmpPosition.GetRotation();
    1608     cmpBuildingPosition.SetYRotation(rot.y);
    1609     cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
    1610 
    1611     // Copy ownership
    1612     var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1613     var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
    1614     cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
    1615 
    1616     // Copy control groups
    1617     var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1618     var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    1619     cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    1620     cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
    1621 
    1622     // Copy health level from the old entity to the new
    1623     var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    1624     var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    1625     var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    1626     var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
    1627     cmpBuildingHealth.SetHitpoints(buildingHitpoints);
    1628 
    1629     PlaySound("constructed", building);
    1630 
    1631     Engine.PostMessage(ent, MT_ConstructionFinished,
    1632         { "entity": ent, "newentity": building });
    1633     Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
    1634 
    1635     Engine.DestroyEntity(ent);
    1636 }
    1637 
    16381615Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    16391616Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    16401617Engine.RegisterGlobal("GetDockAngle", GetDockAngle);
  • simulation/templates/template_unit_support_female_citizen.xml

     
    108108      <RangeMin>0.0</RangeMin>
    109109    </Run>
    110110  </UnitMotion>
     111    <Upgrade>
     112    <ToTrader>
     113      <Entity>units/{civ}_support_trader</Entity>
     114      <Tooltip>Upgrade to a traderBiatch.</Tooltip>
     115      <Cost>
     116        <stone>60</stone>
     117      </Cost>
     118      <Time>0</Time>
     119    </ToTrader>
     120    <ToSlave>
     121      <Entity>units/{civ}_support_slave</Entity>
     122      <Tooltip>Haha!</Tooltip>
     123      <Cost>
     124        <food>10</food>
     125      </Cost>
     126      <Time>0</Time>
     127    </ToSlave>
     128  </Upgrade>
    111129  <Vision>
    112130    <Range>32</Range>
    113131  </Vision>
  • simulation/templates/template_structure_defense_wall_long.xml

     
    5656  <Identity>
    5757    <Classes datatype="tokens">LongWall</Classes>
    5858    <Tooltip>Long wall segments can be converted to gates.</Tooltip>
    59     <GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>
    6059  </Identity>
     60  <Upgrade>
     61    <Gate>
     62      <Entity>structures/{civ}_wall_gate</Entity>
     63      <Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip>
     64      <Cost>
     65        <stone>60</stone>
     66      </Cost>
     67      <Time>10000</Time>
     68    </Gate>
     69  </Upgrade>
    6170</Entity>
  • 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    <History>A cheap, quick defensive structure constructed with sharpened tree trunks</History>
    3231    <Icon>gaia/special_palisade.png</Icon>
     
    4645  <WallPiece>
    4746    <Length>11.0</Length>
    4847  </WallPiece>
     48  <Upgrade>
     49    <Gate>
     50      <Entity>other/palisades_rocks_gate</Entity>
     51      <Cost>
     52        <stone>0</stone>
     53        <wood>20</wood>
     54      </Cost>
     55      <Time>5000</Time>
     56    </Gate>
     57  </Upgrade>
    4958</Entity>
  • 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>