Ticket #2706: UpgradeComponent.patch

File UpgradeComponent.patch, 38.5 KB (added by wraitii, 5 years ago)
  • binaries/data/mods/public/art/textures/ui/session/icons/upgrade.png

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
  • binaries/data/mods/public/gui/session/input.js

    Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/upgrade.png
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +application/octet-stream
    \ No newline at end of property
     
    16141614    });
    16151615}
    16161616
    1617 // Transform a wall to a gate
    1618 function transformWallToGate(template)
     1617// Upgrade an entity to another
     1618function upgradeEntity(Template)
    16191619{
    16201620    var selection = g_Selection.toList();
    16211621    Engine.PostNetworkCommand({
    1622         "type": "wall-to-gate",
    1623         "entities": selection.filter( function(e) { return getWallGateTemplate(e) == template } ),
    1624         "template": template,
     1622        "type": "upgrade",
     1623        "entities": selection,
     1624        "template": Template,
     1625        "queued": false
    16251626    });
    16261627}
    16271628
    1628 // Gets the gate form (if any) of a given long wall piece
    1629 function getWallGateTemplate(entity)
     1629// Cancel upgrading entities
     1630function cancelUpgradeEntity()
    16301631{
    1631     // TODO: find the gate template name in a better way
    1632     var entState = GetEntityState(entity);
    1633     var index;
    1634 
    1635     if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
    1636         return entState.template.substr(0, index) + "gate";
    1637     return undefined;
     1632    var selection = g_Selection.toList();
     1633    Engine.PostNetworkCommand({
     1634        "type": "cancel-upgrade",
     1635        "entities": selection,
     1636        "queued": false
     1637    });
    16381638}
    16391639
    16401640// Set the camera to follow the given unit
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    372372        for (var i in selection)
    373373        {
    374374            var state = GetEntityState(selection[i]);
    375             if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     375            if (state.gate && !gates.length)
    376376            {
    377                 var gateTemplate = getWallGateTemplate(state.id);
    378                 if (gateTemplate)
    379                 {
    380                     var tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
    381                     if (!tooltipString)
    382                     {
    383                         warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
    384                         tooltipString = "";
    385                     }
    386                     walls.push({
    387                         "tooltip": translate(tooltipString),
    388                         "template": gateTemplate,
    389                         "callback": function (item) { transformWallToGate(item.template); }
    390                     });
    391                 }
    392 
    393                 // We only need one entity per type.
    394                 longWallTypes[state.template] = true;
    395             }
    396             else if (state.gate && !gates.length)
    397             {
    398377                gates.push({
    399378                    "gate": state.gate,
    400379                    "tooltip": translate("Lock Gate"),
     
    424403    },
    425404    "setTooltip": function(data)
    426405    {
    427         var tooltip = data.item.tooltip;
    428         if (data.item.template)
    429         {
    430             data.template = GetTemplateData(data.item.template);
    431             data.wallCount = data.selection.reduce(function (count, ent) {
    432                     var state = GetEntityState(ent);
    433                     if (hasClass(state, "LongWall") && !state.gate)
    434                         count++;
    435                     return count;
    436                 }, 0);
    437 
    438             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
    439 
    440 
    441             data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(data.template, data.wallCount));
    442             if (data.neededResources)
    443                 tooltip += getNeededResourcesTooltip(data.neededResources);
    444         }
    445         data.button.tooltip = tooltip;
     406        data.button.tooltip = data.item.tooltip;
    446407    },
    447408    "setGraphics": function(data)
    448409    {
    449         data.affordableMask.hidden == data.neededResources ? true : false;
    450410        var gateIcon;
    451411        if (data.item.gate)
    452412        {
    453             // If already a gate, show locking actions
     413            // Show locking actions
    454414            gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
    455415            if (data.item.gate.locked === undefined)
    456416                data.guiSelection.hidden = false
     
    457417            else
    458418                data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
    459419        }
    460         else
    461         {
    462             // otherwise show gate upgrade icon
    463             var template = GetTemplateData(data.item.template);
    464             if (!template)
    465                 return;
    466             gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
    467             data.guiSelection.hidden = true;
    468         }
    469 
    470420        data.icon.sprite = "stretched:session/" + gateIcon;
    471421    },
    472422    "setPosition": function(data)
     
    541491    },
    542492};
    543493
     494// UPGRADE
     495g_SelectionPanels.Upgrade = {
     496    "getMaxNumberOfItems": function()
     497    {
     498        return 24 - getNumberOfRightPanelButtons();
     499    },
     500    "getItems": function(unitEntState, selection)
     501    {
     502        // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
     503        if (selection.length > 1)
     504            return false;
     505        var items = [];
     506        for (var ent of selection)
     507        {
     508            var state = GetEntityState(ent);
     509            if (!state.upgrade)
     510                continue;
     511           
     512            for each (var upgrade in state.upgrade.upgrades)
     513            {
     514                var item = {};
     515                item.entType = upgrade.entity;
     516                item.template = GetTemplateData(upgrade.entity);
     517                if (!item.template) // abort if no template
     518                    return false;
     519
     520                item.icon = upgrade.icon;
     521                if (!item.icon)
     522                    item.icon = "portraits/" + item.template.icon;
     523
     524                if (upgrade.requiredTechnology !== null)
     525                {
     526                    item.requiredTechnology = upgrade.requiredTechnology;
     527                    if (!item.requiredTechnology && item.template.requiredTechnology)
     528                        item.requiredTechnology = item.template.requiredTechnology
     529                }
     530                item.cost = upgrade.cost;
     531                item.time = upgrade.time;
     532
     533                if (state.upgrade.progress === undefined)
     534                {
     535                    item.upgrading = -2;
     536                    item.tooltip = translate("Upgrade into a " + item.template.name.generic + (upgrade.tooltip? ".\n" + upgrade.tooltip : "."));
     537                    item.callback = function(data) { upgradeEntity(data.entType); };
     538                }
     539                else if (state.upgrade.template !== upgrade.entity)
     540                {
     541                    item.upgrading = -1;
     542                    item.tooltip = translate("Cannot upgrade when the entity is already upgrading.");
     543                    item.callback = function(data) { };
     544                }
     545                else
     546                {
     547                    item.upgrading = state.upgrade.progress;
     548                    item.tooltip = translate("Cancel Upgrading");
     549                    item.callback = function(data) { cancelUpgradeEntity(); };
     550                }
     551                items.push(item);
     552            }
     553        }
     554        return items;
     555    },
     556    "addData" : function(data)
     557    {
     558        data.item.technologyEnabled = true;
     559        if (data.item.requiredTechnology)
     560            data.item.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", data.item.requiredTechnology);
     561        if (data.item.cost)
     562        {
     563            var totalCost = { "food": 0, "wood": 0, "stone": 0, "metal": 0, "time": 0 };    // I've been unable to find an utility function for this, should be one though.
     564            for (var i in data.item.cost)
     565                totalCost[i] += data.item.cost[i];
     566            data.item.neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCost);
     567        }
     568        data.item.limits = getEntityLimitAndCount(data.playerState, data.item.entType);
     569        return true;
     570    },
     571    "setAction": function(data)
     572    {
     573        data.button.onPress = function() { data.item.callback(data.item); };
     574    },
     575    "setTooltip": function(data)
     576    {
     577        var tooltip = data.item.tooltip;
     578
     579        if (data.item.upgrading !== -2)
     580        {
     581            data.button.tooltip = tooltip;
     582            return;
     583        }
     584
     585        if (data.item.cost)
     586            tooltip += "\n" + getEntityCostTooltip(data.item);
     587       
     588        tooltip += formatLimitString(data.item.limits.entLimit, data.item.limits.entCount, data.item.limits.entLimitChangers);
     589        if (!data.item.technologyEnabled)
     590        {
     591            var techName = getEntityNames(GetTechnologyData(data.item.requiredTechnology));
     592            tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { technology: techName });
     593        }
     594        if (data.item.neededResources)
     595            tooltip += getNeededResourcesTooltip(data.item.neededResources);
     596
     597        data.button.tooltip = tooltip;
     598    },
     599    "setGraphics": function(data)
     600    {
     601        var grayscale = "";
     602        if (data.item.upgrading == -1 || !data.item.technologyEnabled || (data.item.limits.canBeAddedCount == 0 && data.item.upgrading == -2))
     603        {
     604            data.button.enabled = false;
     605            grayscale = "grayscale:";
     606            data.affordableMask.hidden = false;
     607            data.affordableMask.sprite = "colour: 0 0 0 127";
     608        }
     609        else if (data.item.upgrading == -2 && data.item.neededResources)
     610        {
     611            data.button.enabled = false;
     612            data.affordableMask.hidden = false;
     613            data.affordableMask.sprite = resourcesToAlphaMask(data.item.neededResources);
     614        }
     615       
     616        data.icon.sprite = "stretched:" + grayscale + "session/" + data.item.icon;
     617
     618
     619        var guiObject = Engine.GetGUIObjectByName("unitUpgradeProgressSlider["+data.i+"]");
     620        var size = guiObject.size;
     621        if (data.item.upgrading < 0)
     622            size.top = size.right;
     623        else
     624            size.top = size.left + Math.round(Math.max(0,data.item.upgrading) * (size.right - size.left));
     625        guiObject.size = size;
     626    },
     627    "setPosition": function(data)
     628    {
     629        var index = data.i + getNumberOfRightPanelButtons();
     630        setPanelObjectPosition(data.button, index, data.rowLength);
     631    },
     632};
     633
    544634// QUEUE
    545635g_SelectionPanels.Queue = {
    546636    "getMaxNumberOfItems": function()
     
    10151105    // RIGHT PANE
    10161106    "Gate", // must always be shown on gates
    10171107    "Pack", // must always be shown on packable entities
     1108    "Upgrade", // must always be shown on upgradable entities
    10181109    "Training",
    10191110    "Construction",
    10201111    "Research", // normal together with training
  • binaries/data/mods/public/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, "Barter": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0};
     2var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0, "Upgrade": 0};
    33
    44/**
    55 * Set the position of a panel object according to the index,
     
    289289        sum += g_unitPanelButtons["Pack"];
    290290    if (g_SelectionPanels["Gate"].used)
    291291        sum += g_unitPanelButtons["Gate"];
     292    if (g_SelectionPanels["Upgrade"].used)
     293        sum += g_unitPanelButtons["Upgrade"];
    292294    return sum;
    293295}
  • binaries/data/mods/public/gui/session/selection_panels_right/gate_panel.xml

     
    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="colour: 255 0 0 127"/>
    1110        </object>
    1211    </repeat>
    1312    </object>
  • 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 name="unitUpgradeUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     13        </object>
     14    </repeat>
     15    </object>
     16</object>
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    Property changes on: binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    180180        "gate": null,
    181181        "guard": null,
    182182        "pack": null,
     183        "upgrade" : null,
    183184        "player": -1,
    184185        "position": null,
    185186        "production": null,
     
    232233        };
    233234    }
    234235
     236    var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     237    if (cmpUpgrade)
     238    {
     239        ret.upgrade = {
     240            "upgrades" : cmpUpgrade.GetUpgrades(),
     241            "progress": cmpUpgrade.GetProgress(),
     242            "template": cmpUpgrade.GetUpgradingTo()
     243        };
     244    }
     245
    235246    var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
    236247    if (cmpProductionQueue)
    237248    {
     
    653664        };
    654665        ret.icon = template.Identity.Icon;
    655666        ret.tooltip =  template.Identity.Tooltip;
    656         ret.gateConversionTooltip =  template.Identity.GateConversionTooltip;
    657667        ret.requiredTechnology = template.Identity.RequiredTechnology;
    658668        ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
    659669    }
  • binaries/data/mods/public/simulation/components/Identity.js

     
    3030        "</element>" +
    3131    "</optional>" +
    3232    "<optional>" +
    33         "<element name='GateConversionTooltip'>" +
    34             "<text/>" +
    35         "</element>" +
    36     "</optional>" +
    37     "<optional>" +
    3833        "<element name='Rollover'>" +
    3934            "<text/>" +
    4035        "</element>" +
  • binaries/data/mods/public/simulation/components/Upgrade.js

     
     1function Upgrade() {}
     2
     3const UPGRADING_PROGRESS_INTERVAL = 250;
     4
     5Upgrade.prototype.Schema =
     6    "<oneOrMore>" +
     7        "<element>" +
     8            "<anyName />" +
     9            "<interleave>" +
     10                "<element name='Entity' a:help='Entity to upgrade to'>" +
     11                    "<text/>" +
     12                "</element>" +
     13                "<optional>" +
     14                    "<element name='Icon' a:help='Icon to show in the GUI'>" +
     15                        "<text/>" +
     16                    "</element>" +
     17                "</optional>" +
     18                "<optional>" +
     19                    "<element name='Tooltip' a:help='This will be added to the tooltip to help the player choose why to upgrade.'>" +
     20                        "<text/>" +
     21                    "</element>" +
     22                "</optional>" +
     23                "<optional>" +
     24                    "<element name='Time' a:help='Time required to upgrade this entity, in milliseconds'>" +
     25                        "<data type='nonNegativeInteger'/>" +
     26                    "</element>" +
     27                "</optional>" +
     28                "<optional>" +
     29                    "<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
     30                        "<oneOrMore>" +
     31                            "<choice>" +
     32                                "<element name='food'><data type='nonNegativeInteger'/></element>" +
     33                                "<element name='wood'><data type='nonNegativeInteger'/></element>" +
     34                                "<element name='stone'><data type='nonNegativeInteger'/></element>" +
     35                                "<element name='metal'><data type='nonNegativeInteger'/></element>" +
     36                            "</choice>" +
     37                        "</oneOrMore>" +
     38                    "</element>" +
     39                "</optional>" +
     40                "<optional>" +
     41                    "<element name='RequiredTechnology' a:help='Define what technology is required for this upgrade'>" +
     42                        "<choice>" +
     43                            "<text/>" +
     44                            "<empty/>" +
     45                        "</choice>" +
     46                    "</element>" +
     47                "</optional>" +
     48                "<optional>" +
     49                    "<element name='CheckPlacementRestrictions' a:help='Upgrading will check for placement restrictions'><empty/></element>" +
     50                "</optional>" +
     51            "</interleave>" +
     52        "</element>" +
     53    "</oneOrMore>";
     54
     55Upgrade.prototype.Init = function()
     56{
     57    this.upgrading = false;
     58    this.elapsedTime = 0;
     59    this.timer = undefined;
     60
     61    this.upgradeTemplates = {};
     62
     63    for (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        this.upgradeTemplates[name] = choice;
     70    }
     71};
     72
     73// We need to know our owner for when we're destroyed.
     74Upgrade.prototype.OnOwnershipChanged = function(msg)
     75{
     76    if (msg.to !== -1)
     77        this.owner = msg.to;
     78};
     79
     80Upgrade.prototype.ChangeUpgradedEntityCount = function(amount)
     81{
     82    if (!this.IsUpgrading())
     83        return;
     84    var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     85    var template = cmpTempMan.GetTemplate(this.upgrading);
     86    var category = null;
     87    if (template.TrainingRestrictions)
     88        category = template.TrainingRestrictions.Category;
     89    else if (template.BuildRestrictions)
     90        category = template.BuildRestrictions.Category;
     91
     92    var cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
     93    cmpEntityLimits.ChangeCount(category,amount);
     94}
     95
     96Upgrade.prototype.OnDestroy = function()
     97{
     98    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     99    var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(this.owner), IID_Player);
     100    if (cmpPlayer)
     101    {
     102        var costs = this.GetResourceCosts(this.upgrading);
     103        cmpPlayer.AddResources(costs);
     104    }
     105    if (this.GetProgress() < 1)
     106        this.ChangeUpgradedEntityCount(-1);
     107    this.CancelTimer();
     108};
     109
     110Upgrade.prototype.CanUpgradeTo = function(Template)
     111{
     112    return this.upgradeTemplates[Template] !== undefined;
     113};
     114
     115Upgrade.prototype.GetUpgrades = function()
     116{
     117    var ret = [];
     118
     119    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     120
     121    for each (var choice in this.template)
     122    {
     123        var entType = choice.Entity;
     124        if (cmpIdentity)
     125            entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     126
     127        var hasCosts = false;
     128        var cost = {};
     129        if (choice.Cost)
     130        {
     131            hasCosts = true;
     132            for (var type in choice.Cost)
     133                cost[type] = ApplyValueModificationsToTemplate("Upgrade/Cost/"+type, +choice.Cost[type], this.owner, entType);
     134        }
     135        if (choice.Time)
     136        {
     137            hasCosts = true;
     138            cost["time"] = ApplyValueModificationsToTemplate("Upgrade/Time", +choice.Time/1000.0, this.owner, entType);
     139        }
     140        ret.push(
     141        {
     142            "entity": entType,
     143            "icon": choice.Icon || undefined,
     144            "cost": hasCosts ? cost : undefined,
     145            "tooltip": choice.Tooltip || undefined,
     146            "requiredTechnology": "RequiredTechnology" in choice ? choice.RequiredTechnology : null,
     147        });
     148    }
     149
     150    return ret;
     151};
     152
     153Upgrade.prototype.CancelTimer = function()
     154{
     155    if (this.timer)
     156    {
     157        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     158        cmpTimer.CancelTimer(this.timer);
     159        this.timer = undefined;
     160    }
     161};
     162
     163Upgrade.prototype.IsUpgrading = function()
     164{
     165    return this.upgrading !== false;
     166};
     167
     168Upgrade.prototype.GetUpgradingTo = function()
     169{
     170    return this.upgrading;
     171};
     172
     173Upgrade.prototype.CheckPlacementRestrictions = function(Template)
     174{
     175    if (!this.upgradeTemplates[Template])
     176        return undefined;
     177
     178    return ("CheckPlacementRestrictions" in this.template[this.upgradeTemplates[Template]]);
     179};
     180
     181Upgrade.prototype.GetRequiredTechnology = function(Template)
     182{
     183    if (!this.upgradeTemplates[Template])
     184        return undefined;
     185
     186    var choice = this.upgradeTemplates[Template];
     187
     188    if ("RequiredTechnology" in this.template[choice] && this.template[choice].RequiredTechnology === undefined)
     189    {
     190        var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     191        var template = cmpTemplateManager.GetTemplate(this.template[choice].Entity);
     192        if (template.Identity.RequiredTechnology)
     193            return template.Identity.RequiredTechnology;
     194    }
     195    else if ("RequiredTechnology" in this.template[choice])
     196        return this.template[choice].RequiredTechnology;
     197    return null;
     198};
     199
     200Upgrade.prototype.GetResourceCosts = function(Template)
     201{
     202    if (!this.upgradeTemplates[Template])
     203        return undefined;
     204
     205    var choice = this.upgradeTemplates[Template];
     206    if (!this.template[choice].Cost)
     207        return {};
     208
     209    var costs = {};
     210    for (var r in this.template[choice].Cost)
     211    {
     212        costs[r] = +this.template[choice].Cost[r];
     213        costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, costs[r], this.entity);
     214    }
     215    return costs;
     216};
     217
     218Upgrade.prototype.Upgrade = function(Template, PlayerComponent)
     219{
     220    if (this.IsUpgrading())
     221        return false;
     222
     223    if (!this.upgradeTemplates[Template])
     224        return false;
     225
     226    var cmpPlayer = PlayerComponent;
     227    if (!cmpPlayer)
     228    {
     229        var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     230        var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(this.owner), IID_Player);
     231    }
     232    if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(Template)))
     233        return false;
     234
     235    this.upgrading = Template;
     236
     237    // prevent cheating
     238    this.ChangeUpgradedEntityCount(1);
     239
     240    if (this.GetUpgradeTime(Template) !== 0)
     241    {
     242        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     243        this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, {"upgrading": Template});
     244    }
     245    else
     246    {
     247        this.UpgradeProgress();
     248    }
     249    return true;
     250};
     251
     252Upgrade.prototype.CancelUpgrade = function(PlayerComponent)
     253{
     254    if (this.IsUpgrading() === false)
     255        return;
     256
     257    var cmpPlayer = PlayerComponent;
     258    if (!cmpPlayer)
     259    {
     260        var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     261        var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(this.owner), IID_Player);
     262    }
     263    if (cmpPlayer)
     264    {
     265        var costs = this.GetResourceCosts(this.upgrading);
     266        cmpPlayer.AddResources(costs);
     267    }
     268
     269    this.ChangeUpgradedEntityCount(-1);
     270
     271    this.CancelTimer();
     272    this.upgrading = false;
     273    this.SetElapsedTime(0);
     274};
     275
     276Upgrade.prototype.GetUpgradeTime = function(Template)
     277{
     278    var template = this.upgrading || Template;
     279    var choice = this.upgradeTemplates[template];
     280    if (!choice)
     281        return undefined;
     282    return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0;
     283};
     284
     285Upgrade.prototype.GetElapsedTime = function()
     286{
     287    return this.elapsedTime;
     288};
     289
     290Upgrade.prototype.GetProgress = function()
     291{
     292    // Hopefully undefined and 0 are the only thing that would trigger that.
     293    if (!this.IsUpgrading())
     294        return undefined;
     295    return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime();
     296};
     297
     298Upgrade.prototype.SetElapsedTime = function(time)
     299{
     300    this.elapsedTime = time;
     301};
     302
     303Upgrade.prototype.UpgradeProgress = function(data, lateness)
     304{
     305    if (this.elapsedTime >= this.GetUpgradeTime())
     306    {
     307        this.CancelTimer();
     308
     309        var choice = this.upgradeTemplates[this.upgrading];
     310
     311        // Done un/packing, copy our parameters to the final entity
     312        var newEntity = Engine.AddEntity(this.upgrading);
     313        if (newEntity == INVALID_ENTITY)
     314        {
     315            // Error (e.g. invalid template names)
     316            error("UpgradeProgress: Error creating entity for '" + this.upgrading + "'");
     317            return;
     318        }
     319
     320        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     321        var cmpNewPosition = Engine.QueryInterface(newEntity, IID_Position);
     322        if (cmpPosition.IsInWorld())
     323        {
     324            var pos = cmpPosition.GetPosition2D();
     325            cmpNewPosition.JumpTo(pos.x, pos.y);
     326        }
     327        var rot = cmpPosition.GetRotation();
     328        cmpNewPosition.SetYRotation(rot.y);
     329        cmpNewPosition.SetXZRotation(rot.x, rot.z);
     330        cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
     331
     332        var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     333        var cmpNewOwnership = Engine.QueryInterface(newEntity, IID_Ownership);
     334        cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     335
     336        // Copy control groups
     337        var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     338        var cmpNewObstruction = Engine.QueryInterface(newEntity, IID_Obstruction);
     339        if (cmpObstruction && cmpNewObstruction)
     340        {
     341            cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     342            cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     343        }
     344
     345        // Maintain current health level
     346        var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     347        var cmpNewHealth = Engine.QueryInterface(newEntity, IID_Health);
     348        var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
     349        cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
     350
     351        var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
     352        var cmpNewUnitAI = Engine.QueryInterface(newEntity, IID_UnitAI);
     353        if (cmpUnitAI && cmpNewUnitAI)
     354        {
     355            var pos = cmpUnitAI.GetHeldPosition();
     356            if (pos)
     357                cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
     358            if (cmpUnitAI.GetStanceName())
     359                cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
     360            cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
     361            cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
     362        }
     363
     364        // Maintain the list of guards
     365        var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard);
     366        var cmpNewGuard = Engine.QueryInterface(newEntity, IID_Guard);
     367        if (cmpGuard && cmpNewGuard)
     368            cmpNewGuard.SetEntities(cmpGuard.GetEntities());
     369
     370        Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: newEntity });
     371
     372        this.ChangeUpgradedEntityCount(-1);
     373
     374        // Play notification sound
     375        PlaySound("upgraded", newEntity);
     376
     377        // Destroy current entity
     378        Engine.DestroyEntity(this.entity);
     379    }
     380    else
     381    {
     382        this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
     383    }
     384};
     385
     386/**
     387 * @return True if the entity to transform into is allowed on the current terrain/territory.
     388           False otherwise
     389*/
     390Upgrade.prototype.CanTransform = function (Template)
     391{
     392    var canTransform = true;
     393
     394    var choice = this.upgradeTemplates[Template];
     395    if (!choice)
     396        return false;
     397
     398    // Create a preview entity to check.
     399    var previewEntityTemplate = Template;
     400    var previewEntity = Engine.AddEntity("preview|"+previewEntityTemplate);
     401
     402    if (previewEntity == INVALID_ENTITY)
     403        canTransform = false;
     404    else
     405    {
     406        var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
     407        if (cmpBuildRestrictions)
     408        {
     409            // Position and ownership needs to be set for placement checks
     410            var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     411            if (!cmpPosition.IsInWorld())
     412                canTransform = false;
     413
     414            var pos = cmpPosition.GetPosition2D();
     415            var angle = cmpPosition.GetRotation();
     416            // move us away to avoid our obstruction blocking the upgrade.
     417            cmpPosition.MoveOutOfWorld();
     418
     419            var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
     420            cmpNewPosition.JumpTo(pos.x, pos.y);
     421            cmpNewPosition.SetYRotation(angle.y);
     422
     423            var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     424            var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
     425            cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
     426
     427            var checkPlacement = cmpBuildRestrictions.CheckPlacement();
     428            if (checkPlacement && !checkPlacement.success)
     429                canTransform = false;
     430
     431            // TODO maybe: this is only really needed if the new obstruction is bigger.
     432            var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
     433            if (canTransform && cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
     434            {
     435                // Check for units
     436                var collisions = cmpNewObstruction.GetEntityCollisions(false, true);
     437                if (collisions.length !== 0)
     438                    canTransform = false;
     439            }
     440
     441            // Move preview entity out of world so it won't interfere with additional calls
     442            //    to this function for this entity on the same sim update.
     443            // Specifically, CheckPlacement() would incorrectly spot and fail on earlier preview entities
     444            //    if those are left in the world.
     445            cmpNewPosition.MoveOutOfWorld();
     446            cmpPosition.JumpTo(pos.x, pos.y);
     447            cmpPosition.SetYRotation(angle.y);
     448        }
     449    }
     450    Engine.DestroyEntity(previewEntity);
     451    return canTransform;
     452};
     453
     454Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
  • binaries/data/mods/public/simulation/components/interfaces/Upgrade.js

    Property changes on: binaries/data/mods/public/simulation/components/Upgrade.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1Engine.RegisterInterface("Upgrade");
  • binaries/data/mods/public/simulation/helpers/Commands.js

    Property changes on: binaries/data/mods/public/simulation/components/interfaces/Upgrade.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    551551        }
    552552    },
    553553
    554     "wall-to-gate": function(player, cmd, data)
    555     {
    556         for each (var ent in data.entities)
    557         {
    558             TryTransformWallToGate(ent, data.cmpPlayer, cmd.template);
    559         }
    560     },
    561 
    562554    "lock-gate": function(player, cmd, data)
    563555    {
    564556        for each (var ent in data.entities)
     
    642634            }
    643635        }
    644636    },
     637
     638    "upgrade": function(player, cmd, data)
     639    {
     640        for each (var ent in data.entities)
     641        {
     642            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     643
     644            var templateName = cmd.template;
     645
     646            if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(templateName))
     647                continue;
     648
     649            if (cmpUpgrade.CheckPlacementRestrictions(templateName) && !cmpUpgrade.CanTransform(templateName))
     650            {
     651                var notification = {"players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade as distance requirements are not verified or terrain is obstructed." };
     652                var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     653                cmpGUIInterface.PushNotification(notification);
     654                continue;
     655            }
     656
     657            // Check entity limits
     658            var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     659            var template = cmpTempMan.GetTemplate(templateName);
     660            var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     661            if ((template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category,1))
     662                || (template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category)))
     663            {
     664                if (g_DebugCommands)
     665                    warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd));
     666                continue;
     667            }
     668
     669            var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
     670            if (cmpUpgrade.GetRequiredTechnology() && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology()))
     671            {
     672                if (g_DebugCommands)
     673                    warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
     674                continue;
     675            }
     676
     677            cmpUpgrade.Upgrade(templateName, data.cmpPlayer);
     678        }
     679    },
     680
     681    "cancel-upgrade": function(player, cmd, data)
     682    {
     683        for each (var ent in data.entities)
     684        {
     685            var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     686
     687            if (!cmpUpgrade)
     688                continue
     689
     690            cmpUpgrade.CancelUpgrade(data.cmpPlayer);
     691        }
     692    },
     693
    645694    "dialog-answer": function(player, cmd, data)
    646695    {
    647696        // Currently nothing. Triggers can read it anyway, and send this
     
    15121561    return entities.filter(function(ent) { return CanControlUnitOrIsAlly(ent, player, controlAll);} );
    15131562}
    15141563
    1515 /**
    1516  * Try to transform a wall to a gate
    1517  */
    1518 function TryTransformWallToGate(ent, cmpPlayer, template)
    1519 {
    1520     var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    1521     if (!cmpIdentity)
    1522         return;
    1523 
    1524     // Check if this is a valid long wall segment
    1525     if (!cmpIdentity.HasClass("LongWall"))
    1526     {
    1527         if (g_DebugCommands)
    1528             warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));
    1529         return;
    1530     }
    1531 
    1532     var civ = cmpIdentity.GetCiv();
    1533     var gate = Engine.AddEntity(template);
    1534 
    1535     var cmpCost = Engine.QueryInterface(gate, IID_Cost);
    1536     if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    1537     {
    1538         if (g_DebugCommands)
    1539             warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));
    1540 
    1541         Engine.DestroyEntity(gate);
    1542         return;
    1543     }
    1544 
    1545     ReplaceBuildingWith(ent, gate);
    1546 }
    1547 
    1548 /**
    1549  * Unconditionally replace a building with another one
    1550  */
    1551 function ReplaceBuildingWith(ent, building)
    1552 {
    1553     // Move the building to the right place
    1554     var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1555     var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    1556     var pos = cmpPosition.GetPosition2D();
    1557     cmpBuildingPosition.JumpTo(pos.x, pos.y);
    1558     var rot = cmpPosition.GetRotation();
    1559     cmpBuildingPosition.SetYRotation(rot.y);
    1560     cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
    1561 
    1562     // Copy ownership
    1563     var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1564     var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
    1565     cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
    1566 
    1567     // Copy control groups
    1568     var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1569     var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    1570     cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    1571     cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
    1572 
    1573     // Copy health level from the old entity to the new
    1574     var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    1575     var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    1576     var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
    1577     var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
    1578     cmpBuildingHealth.SetHitpoints(buildingHitpoints);
    1579 
    1580     PlaySound("constructed", building);
    1581 
    1582     Engine.PostMessage(ent, MT_ConstructionFinished,
    1583         { "entity": ent, "newentity": building });
    1584     Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
    1585 
    1586     Engine.DestroyEntity(ent);
    1587 }
    1588 
    15891564Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
    15901565Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
    15911566Engine.RegisterGlobal("GetDockAngle", GetDockAngle);
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml

     
    5151  <Identity>
    5252    <Classes datatype="tokens">LongWall</Classes>
    5353    <Tooltip>Long wall segments can be converted to gates.</Tooltip>
    54     <GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>
    5554  </Identity>
     55  <Upgrade>
     56    <Gate>
     57      <Entity>structures/{civ}_wall_gate</Entity>
     58      <Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip>
     59      <Cost>
     60        <stone>60</stone>
     61      </Cost>
     62      <Time>10000</Time>
     63    </Gate>
     64  </Upgrade>
    5665</Entity>
  • binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml

     
    2121    <SelectionGroupName>other/wallset_palisade</SelectionGroupName>
    2222    <SpecificName>Palisade</SpecificName>
    2323    <GenericName>Wooden Wall</GenericName>
    24     <GateConversionTooltip>Convert Wooden Wall into Wooden Gate</GateConversionTooltip>
    2524    <Classes datatype="tokens">-StoneWall Palisade</Classes>
    2625    <History>A cheap, quick defensive structure constructed with sharpened tree trunks</History>
    2726    <Icon>gaia/special_palisade.png</Icon>
     
    4140  <WallPiece>
    4241    <Length>11.0</Length>
    4342  </WallPiece>
     43  <Upgrade>
     44    <Gate>
     45      <Entity>other/palisades_rocks_gate</Entity>
     46      <Cost>
     47        <stone>0</stone>
     48        <wood>20</wood>
     49      </Cost>
     50      <Time>5000</Time>
     51    </Gate>
     52  </Upgrade>
    4453</Entity>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml

     
    5555    </Classes>
    5656    <Icon>structures/palisade.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>