Ticket #996: capture.diff

File capture.diff, 35.8 KB (added by sanderd17, 9 years ago)
  • binaries/data/mods/public/gui/common/tooltips.js

     
    140140    if (type === "Charge") return translate("Charge Attack:");
    141141    if (type === "Melee") return translate("Melee Attack:");
    142142    if (type === "Ranged") return translate("Ranged Attack:");
     143    if (type === "Capture") return translate("Capture Attack:");
    143144
    144145    warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { attackType: type }));
    145146    return translate("Attack:");
     
    169170        });
    170171
    171172        var attackLabel = txtFormats.header[0] + getAttackTypeLabel(type) + txtFormats.header[1];
     173        if (type == "Capture")
     174        {
     175            attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
     176                attackLabel: attackLabel,
     177                details: template.attack[type].value,
     178                rate: rate
     179            }));
     180            continue;
     181        }
    172182        if (type != "Ranged")
    173183        {
    174184            attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
  • binaries/data/mods/public/gui/session/selection_details.js

     
    8686        Engine.GetGUIObjectByName("healthSection").hidden = true;
    8787    }
    8888
    89     // TODO: Stamina
    90     var player = Engine.GetPlayerID();
    91     if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
    92         Engine.GetGUIObjectByName("staminaSection").hidden = false;
     89    // CapturePoints
     90    if (entState.capturePoints)
     91    {
     92        var size = 0;
     93        for (let i in entState.capturePoints)
     94        {
     95            var unitCaptureBar = Engine.GetGUIObjectByName("captureBar"+i);
     96            var sizeObj = unitCaptureBar.size;
     97            sizeObj.rleft = size;
     98
     99            size += 100*Math.max(0, Math.min(1, entState.capturePoints[i] / entState.maxCapturePoints));
     100            sizeObj.rright = size;
     101            unitCaptureBar.size = sizeObj;
     102            let c = g_Players[i].color;
     103
     104            unitCaptureBar.sprite = "color: " + c.r + " " + c.g + " " + c.b + " 128";
     105        }
     106
     107        // TODO determinte stats format
     108        Engine.GetGUIObjectByName("captureStats").caption = translate("Capture points");
     109        Engine.GetGUIObjectByName("captureSection").hidden = false;
     110    }
    93111    else
    94         Engine.GetGUIObjectByName("staminaSection").hidden = true;
     112    {
     113        Engine.GetGUIObjectByName("captureSection").hidden = true;
     114    }
    95115
     116    // TODO: Stamina
     117
    96118    // Experience
    97119    if (entState.promotion)
    98120    {
     
    136158        Engine.GetGUIObjectByName("resourceStats").caption = resources;
    137159
    138160        if (entState.hitpoints)
    139             Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("staminaSection").size;
     161            Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("captureSection").size;
    140162        else
    141163            Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size;
    142164
  • binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml

     
    2020        </object>
    2121        </object>
    2222
    23         <!-- Stamina bar -->
    24         <object size="88 28 100% 52" name="staminaSection">
    25         <object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">
    26             <translatableAttribute id="tooltip">Stamina:</translatableAttribute>
     23        <!-- Capture bar -->
     24        <object size="88 28 100% 52" name="captureSection">
     25        <object size="0 0 100% 16" name="captureLabel" type="text" style="StatsTextLeft" ghost="true">
     26            <translatableAttribute id="tooltip">Capture points:</translatableAttribute>
    2727        </object>
    28         <object size="0 0 100% 16" name="staminaStats" type="text" style="StatsTextRight" ghost="true"/>
    29         <object size="1 16 100% 23" name="stamina" type="image">
     28        <object size="0 0 100% 16" name="captureStats" type="text" style="StatsTextRight" ghost="true"/>
     29        <object size="1 16 100% 23" name="capture" type="image">
    3030            <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
    31             <object type="image" sprite="staminaBackground" ghost="true"/>
    32             <object type="image" sprite="staminaForeground" ghost="true" name="staminaBar"/>
     31            <!-- TODO use repeat -->
     32            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar0"/>
     33            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar1"/>
     34            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar2"/>
     35            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar3"/>
     36            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar4"/>
     37            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar5"/>
     38            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar6"/>
     39            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar7"/>
     40            <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar8"/>
    3341            <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
    3442        </object>
    3543        </object>
  • binaries/data/mods/public/gui/session/unit_actions.js

     
    9999        {
    100100            if (!entState.attack || !targetState.hitpoints)
    101101                return false;
    102             if (playerCheck(entState, targetState, ["Neutral", "Enemy"]))
    103                 return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
    104             return false;
     102            return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
    105103        },
    106104        "hotkeyActionCheck": function(target)
    107105        {
     
    672670                    "icon": "kill_small.png"
    673671                };
    674672
     673            if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2)
     674                return {
     675                    "tooltip": translate("You cannot destroy this entity you own less than half the capture points"),
     676                    "icon": "kill_small.png"
     677                };
     678                   
     679
    675680            return {
    676681                "tooltip": translate("Delete"),
    677682                "icon": "kill_small.png"
  • binaries/data/mods/public/simulation/components/Attack.js

     
    159159        "</element>" +
    160160    "</optional>" +
    161161    "<optional>" +
     162        "<element name='Capture'>" +
     163            "<interleave>" +
     164                "<element name='Value' a:help='Capture points value'><ref name='nonNegativeDecimal'/></element>" +
     165                "<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
     166                "<element name='RepeatTime' a:help='Time between attacks (in milliseconds). The attack animation will be stretched to match this time'>" + // TODO: it shouldn't be stretched
     167                    "<data type='positiveInteger'/>" +
     168                "</element>" +
     169                Attack.prototype.bonusesSchema +
     170                Attack.prototype.preferredClassesSchema +
     171                Attack.prototype.restrictedClassesSchema +
     172            "</interleave>" +
     173        "</element>" +
     174    "</optional>" +
     175    "<optional>" +
    162176        "<element name='Charge'>" +
    163177            "<interleave>" +
    164178                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
     
    198212    if (this.template.Charge) ret.push("Charge");
    199213    if (this.template.Melee) ret.push("Melee");
    200214    if (this.template.Ranged) ret.push("Ranged");
     215    if (this.template.Capture) ret.push("Capture");
    201216    return ret;
    202217};
    203218
     
    309324    if (cmpFormation)
    310325        return this.GetBestAttack();
    311326
    312     const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     327    var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
    313328    if (!cmpIdentity)
    314329        return undefined;
    315330
    316     const targetClasses = cmpIdentity.GetClassesList();
    317     const isTargetClass = function (value, i, a) { return targetClasses.indexOf(value) != -1; };
    318     const types = this.GetAttackTypes();
    319     const attack = this;
    320     const isAllowed = function (value, i, a) { return !attack.GetRestrictedClasses(value).some(isTargetClass); }
    321     const isPreferred = function (value, i, a) { return attack.GetPreferredClasses(value).some(isTargetClass); }
    322     const byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
    323331
     332    var targetClasses = cmpIdentity.GetClassesList();
     333    var isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; };
     334
    324335    // Always slaughter domestic animals instead of using a normal attack
    325336    if (isTargetClass("Domestic") && this.template.Slaughter)
    326337        return "Slaughter";
    327338
    328     return types.filter(isAllowed).sort(byPreference).pop();
     339    var attack = this;
     340    var isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass); }
     341
     342    var types = this.GetAttackTypes().filter(isAllowed);
     343
     344    // check if the target is capturable
     345    var captureIndex = types.indexOf("Capture")
     346    if (captureIndex != -1)
     347    {
     348        var cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
     349        var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     350        if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
     351            return "Capture";
     352        // not captureable, so remove this attack
     353        types.splice(captureIndex, 1);
     354    }
     355
     356    var isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); }
     357    var byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
     358
     359
     360    return types.sort(byPreference).pop();
    329361};
    330362
    331363Attack.prototype.CompareEntitiesByPreference = function(a, b)
     
    367399    {
    368400        return ApplyValueModificationsToEntity("Attack/" + type + splash + "/" + damageType, +(template[damageType] || 0), self.entity);
    369401    };
    370    
     402
     403    if (type == "Capture")
     404        return {value: applyMods("Value")};
     405
    371406    return {
    372407        hack: applyMods("Hack"),
    373408        pierce: applyMods("Pierce"),
     
    517552        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    518553        cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id, "playerId":playerId});
    519554    }
     555    else if (type == "Capture")
     556    {
     557        var multiplier = this.GetAttackBonus(type, target);
     558        var cmpHealth = Engine.QueryInterface(target, IID_Health);
     559        if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
     560            return;
     561        multiplier *= cmpHealth.GetMaxHitpoints() / cmpHealth.GetHitpoints();
     562
     563        var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     564        if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
     565            return;
     566        var owner = cmpOwnership.GetOwner();
     567        var cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
     568        if (!cmpCapturable || !cmpCapturable.CanCapture(owner))
     569            return;
     570       
     571        var strength = this.GetAttackStrengths("Capture").value;
     572        cmpCapturable.Reduce(strength * multiplier, owner);
     573    }
    520574    else
    521575    {
    522576        // Melee attack - hurt the target immediately
  • binaries/data/mods/public/simulation/components/Builder.js

     
    2626
    2727Builder.prototype.GetEntitiesList = function()
    2828{
    29     var entities = [];
    3029    var string = this.template.Entities._string;
    31     if (string)
    32     {
    33         // Replace the "{civ}" codes with this entity's civ ID
    34         var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    35         if (cmpIdentity)
    36             string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());
    37         entities = string.split(/\s+/);
    38        
    39         // Remove disabled entities
    40         var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player)
    41         var disabledEntities = cmpPlayer.GetDisabledTemplates();
    42        
    43         for (var i = entities.length - 1; i >= 0; --i)
    44             if (disabledEntities[entities[i]])
    45                 entities.splice(i, 1);
    46     }
    47     return entities;
     30    if (!string)
     31        return [];
     32
     33    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     34    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player)
     35    if (!cmpIdentity || !cmpPlayer)
     36        return [];
     37
     38    // disable building of structures from other civs
     39    if (cmpIdentity.GetCiv() != cmpPlayer.GetCiv())
     40        return [];
     41   
     42    // Replace the "{civ}" codes with this entity's civ ID
     43    var entities = string.replace(/\{civ\}/g, cmpIdentity.GetCiv()).split(/\s+/);
     44    // Remove disabled entities
     45    var disabledEntities = cmpPlayer.GetDisabledTemplates();
     46    return entities.filter(function(e) { return !disabledEntities[e]; });
    4847};
    4948
    5049Builder.prototype.GetRange = function()
  • binaries/data/mods/public/simulation/components/Capturable.js

     
     1function Capturable() {}
     2
     3Capturable.prototype.Schema =
     4    "<element name='CapturePoints' a:help='Maximum capture points'>" +
     5        "<ref name='nonNegativeDecimal'/>" +
     6    "</element>";
     7
     8Capturable.prototype.Init = function()
     9{
     10    // Cache this value
     11    this.maxCp = +this.template.CapturePoints;
     12};
     13
     14//// Interface functions ////
     15
     16/**
     17 * Returns the current capture points array
     18 */
     19Capturable.prototype.GetCapturePoints = function()
     20{
     21    return this.cp;
     22};
     23
     24Capturable.prototype.GetMaxCapturePoints = function()
     25{
     26    return this.maxCp;
     27};
     28
     29/**
     30 * Reset all capture points by assigning them to the current owner
     31 */
     32Capturable.prototype.ResetCapturePoints = function()
     33{
     34    this.cp = [];
     35    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     36    var playerId = QueryOwnerInterface(this.entity, IID_Player).GetPlayerID();
     37
     38    // FIXME: check if this works with observers, defeated players, stuff like that
     39    for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); i++)
     40        if (i == playerId)
     41            this.cp[i] = this.maxCp;
     42        else
     43            this.cp[i] = 0;
     44};
     45
     46/**
     47 * Reduces the amount of capture points of an entity,
     48 * in the favour of the player of the source
     49 * Returns true when captured
     50 */
     51Capturable.prototype.Reduce = function(amount, player)
     52{
     53    // Before changing the value, activate Fogging if necessary to hide changes
     54    let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
     55    if (cmpFogging)
     56        cmpFogging.Activate();
     57
     58    var cmpPlayerSource = QueryPlayerIDInterface(player, IID_Player);
     59
     60    if (!cmpPlayerSource)
     61        warn(source + " has no player component defined on its owner ");
     62
     63    var sourceEnemyCp = 0;
     64    var sourceAllyCp = 0;
     65    for (let i in this.cp)
     66    {
     67        let cp = this.cp[i];
     68        if (cmpPlayerSource.IsAlly(i))
     69            sourceAllyCp += cp;
     70        else
     71            sourceEnemyCp += cp;
     72    }
     73
     74    var newCp = [];
     75    for (let i in this.cp)
     76    {
     77        if (i == cmpPlayerSource.GetPlayerID())
     78            newCp[i] = this.cp[i] + amount;
     79        else if (cmpPlayerSource.IsAlly(i))
     80            newCp[i] = this.cp[i];
     81        else // subtract proportional amount
     82            newCp[i] = this.cp[i] - amount * this.cp[i] / sourceEnemyCp;
     83    }
     84    this.cp = newCp;
     85
     86    if (sourceEnemyCp > amount)
     87        return false;
     88
     89    // We proceed to the capture part
     90
     91    var bestPlayer = 0;
     92    for (let i in this.cp)
     93    {
     94        if (this.cp[i] >= this.cp[bestPlayer])
     95            bestPlayer = i;
     96    }
     97
     98    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     99    cmpOwnership.SetOwner(bestPlayer);
     100
     101    return true;
     102};
     103
     104/**
     105 * Check if the source can (re)capture points from this building
     106 */
     107Capturable.prototype.CanCapture = function(playerID)
     108{
     109    var cmpPlayerSource = QueryPlayerIDInterface(playerID, IID_Player);
     110
     111    if (!cmpPlayerSource)
     112        warn(source + " has no player component defined on its owner ");
     113    var cp = this.GetCapturePoints()
     114    var sourceEnemyCp = 0;
     115    for (let i in this.GetCapturePoints())
     116        if (!cmpPlayerSource.IsAlly(i))
     117            sourceEnemyCp += cp[i];
     118    return sourceEnemyCp > 0;
     119};
     120
     121//// Private functions ////
     122
     123Capturable.prototype.OnValueModification = function(msg)
     124{
     125    if (msg.component != "Capturable")
     126        return;
     127
     128    var oldMaxCp = this.GetMaxCapturePoints();
     129    this.maxCp = Math.round(ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity));
     130    if (oldMaxCp != this.maxCp)
     131    {
     132        var scale = this.maxCp / oldMaxCp;
     133        for (let i in this.cp)
     134            this.cp[i] *= scale;
     135    }
     136};
     137
     138Capturable.prototype.OnOwnershipChanged = function(msg)
     139{
     140    this.ResetCapturePoints();
     141};
     142
     143Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

     
    7575            this.visibleGarrisonPoints.push({"offset":o, "entity": null});
    7676        }
    7777    }
     78    this.captureTimers = {};
    7879};
    7980
    8081/**
     
    269270    if (this.GetGarrisonedEntitiesCount() + extraCount >= this.GetCapacity())
    270271        return false;
    271272
     273    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    272274    if (!this.timer && this.GetHealRate() > 0)
    273     {
    274         var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    275275        this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
    276     }
    277276
    278277    // Actual garrisoning happens here
    279278    this.entities.push(entity);
     
    291290    var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
    292291    if (cmpUnitAI && cmpUnitAI.IsUnderAlert())
    293292        Engine.PostMessage(cmpUnitAI.GetAlertRaiser(), MT_UnitGarrisonedAfterAlert, {"holder": this.entity, "unit": entity});
     293
     294    var cmpAttack = Engine.QueryInterface(entity, IID_Attack);
     295    if (cmpAttack && cmpAttack.GetAttackTypes().indexOf("Capture") != -1)
     296    {
     297        var repeatTime = cmpAttack.GetTimers("Capture").repeat;
     298        var timer = cmpTimer.SetInterval(this.entity, IID_GarrisonHolder, "Recapture", repeatTime, repeatTime, {"entity": entity});
     299        this.captureTimers[entity] = timer;
     300    }
    294301   
    295302    return true;
    296303};
     
    420427    this.OrderWalkToRallyPoint(ejectedEntities);
    421428    this.UpdateGarrisonFlag();
    422429
     430    if (forced || success)
     431    {
     432        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     433        for (let ent of entities)
     434        {
     435            var timerId = this.captureTimers[ent];
     436            if (timerId)
     437            {
     438                cmpTimer.CancelTimer(timerId);
     439                this.captureTimers[ent] = 0;
     440            }
     441        }
     442    }
     443
    423444    return success;
    424445};
    425446
     
    723744    return false;
    724745};
    725746
     747GarrisonHolder.prototype.Recapture = function(data)
     748{
     749    var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
     750    if (!cmpAttack)
     751        return; // error
     752    if (cmpAttack.GetAttackTypes().indexOf("Capture") == -1)
     753        return; // error
     754    cmpAttack.PerformAttack("Capture", this.entity);
     755};
     756
    726757/**
    727758 * Initialise the garrisoned units
    728759 */
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    267267        ret.needsRepair = cmpMirage.NeedsRepair();
    268268    }
    269269
     270    var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable);
     271    if (cmpCapturable)
     272    {
     273        ret.capturePoints = cmpCapturable.GetCapturePoints();
     274        ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
     275    }
     276    // TODO mirage support
     277
    270278    var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
    271279    if (cmpBuilder)
    272280        ret.builder = true;
     
    17011709    var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
    17021710    if (!cmpAttack)
    17031711        return false;
     1712    var cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
     1713    var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
     1714    if (!cmpEntityPlayer || !cmpTargetPlayer)
     1715        return false;
    17041716
    1705     return cmpAttack.CanAttack(data.target);
     1717
     1718    // if the owner is an enemy, it's up to the attack component to decide
     1719    if (!cmpEntityPlayer.IsAlly(cmpTargetPlayer.GetPlayerID()))
     1720        return cmpAttack.CanAttack(data.target);
     1721
     1722    // if the owner is an ally, we could still want to capture some capture points back
     1723    var cmpCapturable = Engine.QueryInterface(data.target, IID_Capturable);
     1724    if (cmpCapturable && cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID()))
     1725        return cmpAttack.CanAttack(data.target);
     1726
     1727    return false;
    17061728};
    17071729
    17081730/*
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

     
    154154    if (!cmpTechnologyManager)
    155155        return [];
    156156   
     157    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     158    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     159    if (!cmpPlayer || !cmpIdentity)
     160        return [];
     161
     162    // don't allow researching techs of other civs
     163    if (cmpIdentity.GetCiv() != cmpPlayer.GetCiv())
     164        return [];
     165
    157166    var techs = string.split(/\s+/);
    158167    var techList = [];
    159168    var superseded = {}; // Stores the tech which supersedes the key
    160169
    161     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    162     if (cmpPlayer)
    163         var disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
     170    var disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
    164171   
    165172    // Add any top level technologies to an array which corresponds to the displayed icons
    166173    // Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...}
  • binaries/data/mods/public/simulation/components/TerritoryDecay.js

     
    11function TerritoryDecay() {}
    22
    33TerritoryDecay.prototype.Schema =
    4     "<element name='HealthDecayRate' a:help='Decay rate in hitpoints per second'>" +
     4    "<element name='DecayRate' a:help='Decay rate in hitpoints per second'>" +
    55        "<data type='positiveInteger'/>" +
    66    "</element>";
    77
     
    99{
    1010    this.timer = undefined;
    1111    this.decaying = false;
     12    this.convertTo = 0; // by default, convert to gaia
    1213};
    1314
    1415TerritoryDecay.prototype.IsConnected = function()
    1516{
     17    this.convertTo = 0;
    1618    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    1719    if (!cmpPosition || !cmpPosition.IsInWorld())
    1820        return false;
     
    2224        return false;
    2325       
    2426    // Prevent special gaia buildings from decaying (e.g. fences, ruins)
    25     if (cmpOwnership.GetOwner() == 0)
    26         return true;
     27    // if (cmpOwnership.GetOwner() == 0)
     28    //  return true;
    2729
    2830    var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
    2931    if (!cmpTerritoryManager)
     
    3234    var pos = cmpPosition.GetPosition2D();
    3335    var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
    3436    if (tileOwner != cmpOwnership.GetOwner())
     37    {
     38        this.convertTo = tileOwner;
    3539        return false;
    36     // TODO: this should probably use the same territory restriction
    37     // logic as BuildRestrictions, to handle allies etc
     40    }
    3841
    3942    return cmpTerritoryManager.IsConnected(pos.x, pos.y);
    4043};
     
    6467    if (connected)
    6568        var decaying = false;
    6669    else
    67         var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity)) > 0);
     70        var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/DecayRate", +this.template.DecayRate, this.entity)) > 0);
    6871    if (decaying === this.decaying)
    6972        return;
    7073    this.decaying = decaying;
     
    8891
    8992TerritoryDecay.prototype.Decay = function()
    9093{
    91     var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    92     if (!cmpHealth)
     94    var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
     95    if (!cmpCapturable)
    9396        return; // error
    9497
    95     var decayRate = ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity);
     98    var decayRate = ApplyValueModificationsToEntity("TerritoryDecay/DecayRate", +this.template.DecayRate, this.entity);
    9699
    97     cmpHealth.Reduce(Math.round(decayRate));
     100    cmpCapturable.Reduce(decayRate, this.convertTo);
    98101};
    99102
    100103Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay);
  • binaries/data/mods/public/simulation/components/interfaces/Capturable.js

     
     1Engine.RegisterInterface("Capturable");
     2
     3
  • binaries/data/mods/public/simulation/data/technologies/decay_outpost.json

     
    77    "icon": "blocks_three.png",
    88    "researchTime": 40,
    99    "tooltip": "Territory decay -50% for Outposts.",
    10     "modifications": [{"value": "TerritoryDecay/HealthDecayRate", "multiply": 0.5}],
     10    "modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}],
    1111    "affects": ["Outpost"],
    1212    "soundComplete": "interface/alarm/alarm_upgradearmory.xml"
    1313}
  • binaries/data/mods/public/simulation/data/technologies/romans/decay_logistics.json

     
    77    "icon": "handcart_empty.png",
    88    "researchTime": 40,
    99    "tooltip": "Entrenched Camps and Siege Walls decay 50% slower.",
    10     "modifications": [{"value": "TerritoryDecay/HealthDecayRate", "multiply": 0.5}],
     10    "modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}],
    1111    "affects": ["ArmyCamp", "SiegeWall"],
    1212    "soundComplete": "interface/alarm/alarm_upgradearmory.xml"
    1313}
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    349349
    350350    "delete-entities": function(player, cmd, data)
    351351    {
    352         for each (var ent in data.entities)
     352        for (let ent of data.entities)
    353353        {
     354            // don't allow to delete entities who are half-captured
     355            var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable);
     356            if (cmpCapturable)
     357            {
     358                var capturePoints = cmpCapturable.GetCapturePoints();
     359                var maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
     360                if (capturePoints[player] < maxCapturePoints / 2)
     361                    return;
     362            }
     363            // either kill or delete the entity
    354364            var cmpHealth = Engine.QueryInterface(ent, IID_Health);
    355365            if (cmpHealth)
    356366            {
  • binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml

     
    6060    </SoundGroups>
    6161  </Sound>
    6262  <TerritoryDecay>
    63     <HealthDecayRate>1</HealthDecayRate>
     63    <DecayRate>1</DecayRate>
    6464  </TerritoryDecay>
    6565  <TerritoryInfluence disable=""/>
    6666  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/ptol_mercenary_camp.xml

     
    6262    </SoundGroups>
    6363  </Sound>
    6464  <TerritoryDecay>
    65     <HealthDecayRate>1</HealthDecayRate>
     65    <DecayRate>1</DecayRate>
    6666  </TerritoryDecay>
    6767  <TerritoryInfluence disable=""/>
    6868  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/ptol_military_colony.xml

     
    7676    </SoundGroups>
    7777  </Sound>
    7878  <TerritoryDecay>
    79     <HealthDecayRate>1</HealthDecayRate>
     79    <DecayRate>1</DecayRate>
    8080  </TerritoryDecay>
    8181  <TerritoryInfluence>
    8282    <Radius>80</Radius>
  • binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml

     
    7676    </SoundGroups>
    7777  </Sound>
    7878  <TerritoryDecay>
    79     <HealthDecayRate>10</HealthDecayRate>
     79    <DecayRate>10</DecayRate>
    8080  </TerritoryDecay>
    8181  <TerritoryInfluence disable=""/>
    8282  <ProductionQueue>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_gate.xml

     
    4444    </Obstructions>
    4545  </Obstruction>
    4646  <TerritoryDecay>
    47     <HealthDecayRate>1</HealthDecayRate>
     47    <DecayRate>1</DecayRate>
    4848  </TerritoryDecay>
    4949  <TerritoryInfluence disable=""/>
    5050  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml

     
    6262    <Static width="37.0" depth="5.0"/>
    6363  </Obstruction>
    6464  <TerritoryDecay>
    65     <HealthDecayRate>1</HealthDecayRate>
     65    <DecayRate>1</DecayRate>
    6666  </TerritoryDecay>
    6767  <TerritoryInfluence disable=""/>
    6868  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml

     
    5555    <Static width="25.0" depth="5.0"/>
    5656  </Obstruction>
    5757  <TerritoryDecay>
    58     <HealthDecayRate>1</HealthDecayRate>
     58    <DecayRate>1</DecayRate>
    5959  </TerritoryDecay>
    6060  <TerritoryInfluence disable=""/>
    6161  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml

     
    4242    <Static width="13.0" depth="5.0"/>
    4343  </Obstruction>
    4444  <TerritoryDecay>
    45     <HealthDecayRate>1</HealthDecayRate>
     45    <DecayRate>1</DecayRate>
    4646  </TerritoryDecay>
    4747  <TerritoryInfluence disable=""/>
    4848  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_tower.xml

     
    3636    <Static width="7.0" depth="7.0"/>
    3737  </Obstruction>
    3838  <TerritoryDecay>
    39     <HealthDecayRate>1</HealthDecayRate>
     39    <DecayRate>1</DecayRate>
    4040  </TerritoryDecay>
    4141  <TerritoryInfluence disable=""/>
    4242  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_tent.xml

     
    5050    </SoundGroups>
    5151  </Sound>
    5252  <TerritoryDecay>
    53     <HealthDecayRate>1</HealthDecayRate>
     53    <DecayRate>1</DecayRate>
    5454  </TerritoryDecay>
    5555  <TerritoryInfluence disable=""/>
    5656  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/sele_military_colony.xml

     
    7575    </SoundGroups>
    7676  </Sound>
    7777  <TerritoryDecay>
    78     <HealthDecayRate>1</HealthDecayRate>
     78    <DecayRate>1</DecayRate>
    7979  </TerritoryDecay>
    8080  <TerritoryInfluence>
    8181    <Radius>80</Radius>
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    2020    <PlacementType>land</PlacementType>
    2121    <Territory>own</Territory>
    2222  </BuildRestrictions>
     23  <Capturable>
     24    <CapturePoints>1000</CapturePoints>
     25  </Capturable>
    2326  <Cost>
    2427    <Population>0</Population>
    2528    <PopulationBonus>0</PopulationBonus>
     
    100103    <HeightOffset>12.0</HeightOffset>
    101104  </StatusBars>
    102105  <TerritoryDecay>
    103     <HealthDecayRate>5</HealthDecayRate>
     106    <DecayRate>5</DecayRate>
    104107  </TerritoryDecay>
    105108  <Visibility>
    106109    <RetainInFog>true</RetainInFog>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml

     
    9090    <HeightOffset>18.0</HeightOffset>
    9191  </StatusBars>
    9292  <TerritoryDecay>
    93     <HealthDecayRate>2</HealthDecayRate>
     93    <DecayRate>2</DecayRate>
    9494  </TerritoryDecay>
    9595  <Vision>
    9696    <Range>80</Range>
  • binaries/data/mods/public/simulation/templates/template_unit_infantry.xml

     
    66    <Crush>15</Crush>
    77  </Armour>
    88  <Attack>
     9    <Capture>
     10      <Value>10</Value>
     11      <MaxRange>4</MaxRange>
     12      <RepeatTime>1000</RepeatTime>
     13    </Capture>
    914    <Slaughter>
    1015      <Hack>50.0</Hack>
    1116      <Pierce>0.0</Pierce>