Ticket #996: capture.2.diff

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

     
    166166// ====================================================================
    167167
    168168// Convert integer color values to string (for use in GUI objects)
    169 function rgbToGuiColor(color)
     169function rgbToGuiColor(color, alpha)
    170170{
     171    var ret;
    171172    if (color && ("r" in color) && ("g" in color) && ("b" in color))
    172         return color.r + " " + color.g + " " + color.b;
    173 
    174     return "0 0 0";
     173        ret = color.r + " " + color.g + " " + color.b;
     174    else
     175        ret = "0 0 0";
     176    if (alpha)
     177        ret += " " + alpha;
     178    return ret;
    175179}
    176180
    177181// ====================================================================
  • 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            unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[i].color, 128);
     103        }
     104
     105        Engine.GetGUIObjectByName("captureStats").caption = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
     106            capturePoints: Math.ceil(entState.capturePoints[entState.player]),
     107            maxCapturePoints: entState.maxCapturePoints
     108        });
     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            <repeat count="9">
     32                <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar[n]"/>
     33            </repeat>
    3334            <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
    3435        </object>
    3536        </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 as 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 meters)'><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);
     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
     
    585639        // If friendlyFire isn't enabled, get all player enemies to pass to "Damage.CauseSplashDamage".
    586640        if (friendlyFire == "false")
    587641        {
    588             var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
     642            var cmpPlayer = QueryPlayerIDInterface(data.playerId);
    589643            playersToDamage = cmpPlayer.GetEnemies();
    590644        }
    591645        // Damage the units.
     
    607661    else
    608662    {
    609663        // If we didn't hit the main target look for nearby units
    610         var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
     664        var cmpPlayer = QueryPlayerIDInterface(data.playerId);
    611665        var ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
    612666
    613667        for (var i = 0; i < ents.length; i++)
  • binaries/data/mods/public/simulation/components/Builder.js

     
    77        "<Entities datatype='tokens'>" +
    88            "\n    structures/{civ}_barracks\n    structures/{civ}_civil_centre\n    structures/pers_apadana\n  " +
    99        "</Entities>" +
     10        "<AllowForeignBuilder>false</AllowForeignBuilder>" +
    1011    "</a:example>" +
    1112    "<element name='Rate' a:help='Construction speed multiplier (1.0 is normal speed, higher values are faster)'>" +
    1213        "<ref name='positiveDecimal'/>" +
     
    1617            "<value>tokens</value>" +
    1718        "</attribute>" +
    1819        "<text/>" +
    19     "</element>";
     20    "</element>" +
     21    "<optional>" +
     22        "<element name='AllowForeignBuilder' a:help='Allow this entity to build buildings also when this civ doesn&apos;t match the owner&apos;s civ, so when this entity is considered a foreigner.'>" +
     23            "<data type='boolean'/>" +
     24        "</element>" +
     25    "</optional>";
    2026
    2127Builder.prototype.Init = function()
    2228{
     
    2632
    2733Builder.prototype.GetEntitiesList = function()
    2834{
    29     var entities = [];
    3035    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;
     36    if (!string)
     37        return [];
     38
     39    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     40    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player)
     41    if (!cmpIdentity || !cmpPlayer)
     42        return [];
     43
     44    // possibly disable building of structures from other civs
     45    if (cmpIdentity.GetCiv() != cmpPlayer.GetCiv() && !this.AllowForeignBuilder())
     46        return [];
     47   
     48    // Replace the "{civ}" codes with this entity's civ ID
     49    var entities = string.replace(/\{civ\}/g, cmpIdentity.GetCiv()).split(/\s+/);
     50    // Remove disabled entities
     51    var disabledEntities = cmpPlayer.GetDisabledTemplates();
     52    return entities.filter(function(e) { return !disabledEntities[e]; });
    4853};
    4954
     55Builder.prototype.AllowForeignBuilder = function()
     56{
     57    var allow = this.template.AllowForeignBuilder || false;
     58    return allow == "true";
     59};
     60
    5061Builder.prototype.GetRange = function()
    5162{
    5263    var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
  • 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    this.cp = [];
     13};
     14
     15//// Interface functions ////
     16
     17/**
     18 * Returns the current capture points array
     19 */
     20Capturable.prototype.GetCapturePoints = function()
     21{
     22    return this.cp;
     23};
     24
     25Capturable.prototype.GetMaxCapturePoints = function()
     26{
     27    return this.maxCp;
     28};
     29
     30/**
     31 * Reduces the amount of capture points of an entity,
     32 * in the favour of the player of the source
     33 * Returns true when captured
     34 */
     35Capturable.prototype.Reduce = function(amount, player)
     36{
     37    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     38    if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
     39        return;
     40
     41    // Before changing the value, activate Fogging if necessary to hide changes
     42    var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
     43    if (cmpFogging)
     44        cmpFogging.Activate();
     45
     46    var cmpPlayerSource = QueryPlayerIDInterface(player);
     47
     48    if (!cmpPlayerSource)
     49        warn(source + " has no player component defined on its owner ");
     50
     51    var enemiesWithCp = 0;
     52    for (let i in this.cp)
     53        if (!cmpPlayerSource.IsAlly(i) && this.cp[i] > 0)
     54            enemiesWithCp++;
     55
     56    var distributedAmount = amount / enemiesWithCp;
     57    for (let i in this.cp)
     58    {
     59        if (cmpPlayerSource.IsAlly(i))
     60            continue;
     61        if (this.cp[i] > distributedAmount)
     62            this.cp[i] -= distributedAmount;
     63        else
     64            this.cp[i] = 0;
     65    }
     66
     67    // give all cp taken to the player
     68    var currentCp = this.cp.reduce(function(a, b) { return a + b; });
     69    this.cp[player] = this.GetMaxCapturePoints() - currentCp;
     70
     71    // if all cp has been taken from the owner, convert it
     72    if (this.cp[cmpOwnership.GetOwner()] > 0)
     73        return;
     74
     75    var bestPlayer = 0;
     76    for (let i in this.cp)
     77        if (this.cp[i] >= this.cp[bestPlayer])
     78            bestPlayer = +i;
     79
     80    cmpOwnership.SetOwner(bestPlayer);
     81
     82    return;
     83};
     84
     85/**
     86 * Check if the source can (re)capture points from this building
     87 */
     88Capturable.prototype.CanCapture = function(playerID)
     89{
     90    var cmpPlayerSource = QueryPlayerIDInterface(playerID);
     91
     92    if (!cmpPlayerSource)
     93        warn(source + " has no player component defined on its owner ");
     94    var cp = this.GetCapturePoints()
     95    var sourceEnemyCp = 0;
     96    for (let i in this.GetCapturePoints())
     97        if (!cmpPlayerSource.IsAlly(i))
     98            sourceEnemyCp += cp[i];
     99    return sourceEnemyCp > 0;
     100};
     101
     102//// Private functions ////
     103
     104Capturable.prototype.OnValueModification = function(msg)
     105{
     106    if (msg.component != "Capturable")
     107        return;
     108
     109    var oldMaxCp = this.GetMaxCapturePoints();
     110    this.maxCp = Math.round(ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity));
     111    if (oldMaxCp == this.maxCp)
     112        return;
     113
     114    var scale = this.maxCp / oldMaxCp;
     115    for (let i in this.cp)
     116        this.cp[i] *= scale;
     117};
     118
     119Capturable.prototype.OnOwnershipChanged = function(msg)
     120{
     121    // initialise the capture points when created
     122    if (msg.from != -1)
     123        return;
     124
     125    this.cp = [];
     126    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     127    // FIXME: check if this works with observers
     128    for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
     129        if (i == msg.to)
     130            this.cp[i] = this.maxCp;
     131        else
     132            this.cp[i] = 0;
     133};
     134
     135Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
  • binaries/data/mods/public/simulation/components/Fogging.js

     
    125125            cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
    126126        );
    127127
     128    var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
     129    if (cmpCapturable)
     130        cmpMirage.CopyCapturable(
     131            cmpCapturable.GetMaxCapturePoints(),
     132            cmpCapturable.GetCapturePoints()
     133        );
     134
    128135    var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
    129136    if (cmpResourceSupply)
    130137        cmpMirage.CopyResourceSupply(
  • 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    if (cmpMirage && cmpMirage.Capturable())
     277    {
     278        ret.capturePoints = cmpMirage.GetCapturePoints();
     279        ret.maxCapturePoints = cmpMirage.GetMaxCapturePoints();
     280    }
     281
    270282    var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
    271283    if (cmpBuilder)
    272284        ret.builder = true;
     
    17011713    var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
    17021714    if (!cmpAttack)
    17031715        return false;
     1716    var cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
     1717    var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
     1718    if (!cmpEntityPlayer || !cmpTargetPlayer)
     1719        return false;
    17041720
    1705     return cmpAttack.CanAttack(data.target);
     1721
     1722    // if the owner is an enemy, it's up to the attack component to decide
     1723    if (!cmpEntityPlayer.IsAlly(cmpTargetPlayer.GetPlayerID()))
     1724        return cmpAttack.CanAttack(data.target);
     1725
     1726    // if the owner is an ally, we could still want to capture some capture points back
     1727    var cmpCapturable = Engine.QueryInterface(data.target, IID_Capturable);
     1728    if (cmpCapturable && cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID()))
     1729        return cmpAttack.CanAttack(data.target);
     1730
     1731    return false;
    17061732};
    17071733
    17081734/*
  • binaries/data/mods/public/simulation/components/Mirage.js

     
    2121    this.hitpoints = null;
    2222    this.needsRepair = null;
    2323
     24    this.capturable = false;
     25    this.capturePoints = [];
     26    this.maxCapturePoints = 0;
     27
    2428    this.resourceSupply = false;
    2529    this.maxAmount = null;
    2630    this.amount = null;
     
    9498    return this.needsRepair;
    9599};
    96100
     101// Capture data
     102
     103Mirage.prototype.CopyCapturable = function(capturePoints, maxCapturePoints)
     104{
     105    this.capturable = true;
     106    this.capturePoints = capturePoints;
     107    this.maxCapturePoints = maxCapturePoints;
     108}
     109
     110Mirage.prototype.Capturable = function()
     111{
     112    return this.capturable;
     113}
     114
     115Mirage.prototype.GetMaxCapturePoints = function()
     116{
     117    return this.maxCapturePoints;
     118}
     119
     120Mirage.prototype.GetCapturePoints = function()
     121{
     122    return this.capturePoints;
     123}
     124
    97125// ResourceSupply data
    98126
    99127Mirage.prototype.CopyResourceSupply = function(maxAmount, amount, type, isInfinite)
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

     
    2929            "</attribute>" +
    3030            "<text/>" +
    3131        "</element>" +
    32     "</optional>";
     32    "</optional>" +
     33    "<optional>" +
     34        "<element name='AllowForeignUnitRecruiter' a:help='Allow this entity to produce units also when this civ doesn&apos;t match the owner&apos;s civ, so when this entity is considered a foreign.'>" +
     35            "<data type='boolean'/>" +
     36        "</element>" +
     37    "</optional>" +
     38    "<optional>" +
     39        "<element name='AllowForeignTechnologyResearcher' a:help='Allow this entity to research technologies also when this civ doesn&apos;t match the owner&apos;s civ, so when this entity is considered a foreign.'>" +
     40            "<data type='boolean'/>" +
     41        "</element>" +
     42    "</optional>";
    3343
    3444ProductionQueue.prototype.Init = function()
    3545{
     
    6979    this.alertRaiser = undefined;
    7080};
    7181
     82ProductionQueue.prototype.AllowForeignUnitRecruiter = function()
     83{
     84    var allow = this.template.AllowForeignUnitRecruiter || false;
     85    return allow == "true";
     86};
     87
     88ProductionQueue.prototype.AllowForeignTechnologyResearcher = function()
     89{
     90    var allow = this.template.AllowForeignTechnologyResearcher || false;
     91    return allow == "true";
     92};
     93
    7294ProductionQueue.prototype.PutUnderAlert = function(raiser)
    7395{
    7496    this.alertRaiser = raiser;
     
    97119    if (!string)
    98120        return;
    99121   
     122    var cmpPlayer = QueryOwnerInterface(this.entity);
     123    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     124    if (!cmpPlayer || !cmpIdentity)
     125        return;
     126
     127    // possibly don't allow recruiting units of other civs
     128    if (cmpIdentity.GetCiv() != cmpPlayer.GetCiv() && !this.AllowForeignUnitRecruiter())
     129        return;
     130
    100131    // Replace the "{civ}" codes with this entity's civ ID
    101     var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    102     if (cmpIdentity)
    103         string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());
     132    string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());
    104133   
    105134    var entitiesList = string.split(/\s+/);
    106135   
    107136    // Remove disabled entities
    108     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    109137    var disabledEntities = cmpPlayer.GetDisabledTemplates();
    110138   
    111139    for (var i = entitiesList.length - 1; i >= 0; --i)
     
    154182    if (!cmpTechnologyManager)
    155183        return [];
    156184   
     185    var cmpPlayer = QueryOwnerInterface(this.entity);
     186    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     187    if (!cmpPlayer || !cmpIdentity)
     188        return [];
     189
     190    // possibly don't allow researching techs of other civs
     191    if (cmpIdentity.GetCiv() != cmpPlayer.GetCiv() && !this.AllowForeignTechnologyResearcher())
     192        return [];
     193
    157194    var techs = string.split(/\s+/);
    158195    var techList = [];
    159196    var superseded = {}; // Stores the tech which supersedes the key
    160197
    161     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    162     if (cmpPlayer)
    163         var disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
     198    var disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
    164199   
    165200    // Add any top level technologies to an array which corresponds to the displayed icons
    166201    // Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...}
     
    237272    // TODO: there should probably be a limit on the number of queued batches
    238273    // TODO: there should be a way for the GUI to determine whether it's going
    239274    // to be possible to add a batch (based on resource costs and length limits)
    240     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     275    var cmpPlayer = QueryOwnerInterface(this.entity);
    241276
    242277    if (this.queue.length < MAX_QUEUE_SIZE)
    243278    {
     
    314349            var template = cmpTechTempMan.GetTemplate(templateName);
    315350            if (!template)
    316351                return;
    317             var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     352            var cmpPlayer = QueryOwnerInterface(this.entity);
    318353            var time = template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
    319354
    320355            var cost = {};
     
    479514 */
    480515ProductionQueue.prototype.GetBatchTime = function(batchSize)
    481516{
    482     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     517    var cmpPlayer = QueryOwnerInterface(this.entity);
    483518
    484519    var batchTimeModifier = ApplyValueModificationsToEntity("ProductionQueue/BatchTimeModifier", +this.template.BatchTimeModifier, this.entity);
    485520
     
    651686    // until we've used up all the time (so that we work accurately
    652687    // with items that take fractions of a second)
    653688    var time = g_ProgressInterval;
    654     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     689    var cmpPlayer = QueryOwnerInterface(this.entity);
    655690
    656691    while (time > 0 && this.queue.length)
    657692    {
     
    729764               
    730765                if (!this.spawnNotified)
    731766                {
    732                     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     767                    var cmpPlayer = QueryOwnerInterface(this.entity);
    733768                    var notification = {"players": [cmpPlayer.GetPlayerID()], "message": "Can't find free space to spawn trained units" };
    734769                    var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
    735770                    cmpGUIInterface.PushNotification(notification);
  • 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
     
    3333    var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
    3434    if (tileOwner != cmpOwnership.GetOwner())
    3535        return false;
    36     // TODO: this should probably use the same territory restriction
    37     // logic as BuildRestrictions, to handle allies etc
    3836
    3937    return cmpTerritoryManager.IsConnected(pos.x, pos.y);
    4038};
     
    6462    if (connected)
    6563        var decaying = false;
    6664    else
    67         var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity)) > 0);
     65        var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/DecayRate", +this.template.DecayRate, this.entity)) > 0);
    6866    if (decaying === this.decaying)
    6967        return;
    7068    this.decaying = decaying;
     
    8886
    8987TerritoryDecay.prototype.Decay = function()
    9088{
    91     var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    92     if (!cmpHealth)
     89    var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
     90    if (!cmpCapturable)
    9391        return; // error
    9492
    95     var decayRate = ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity);
     93    var decayRate = ApplyValueModificationsToEntity(
     94        "TerritoryDecay/DecayRate",
     95        +this.template.DecayRate,
     96        this.entity);
    9697
    97     cmpHealth.Reduce(Math.round(decayRate));
     98    // Reduce capture points in favour of Gaia
     99    cmpCapturable.Reduce(decayRate, 0);
    98100};
    99101
    100102Engine.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>