Ticket #3406: decay_field_v2.patch

File decay_field_v2.patch, 42.8 KB (added by otero, 8 years ago)
  • binaries/data/mods/public/simulation/components/BuildingAI.js

     
    55function BuildingAI() {}
    66
    77BuildingAI.prototype.Schema =
    8     "<element name='DefaultArrowCount'>" +
    9         "<data type='nonNegativeInteger'/>" +
     8    "<element name='Type'>" +
     9        "<choice>" +
     10            "<value a:help='Represents buildings with the capability to attack other units'>Military</value>" +
     11            "<value a:help='Represents buildings which cannot attack'>Civil</value>" +
     12        "</choice>" +
    1013    "</element>" +
     14    "<optional>"+
     15        "<element name='DefaultArrowCount'>" +
     16            "<data type='nonNegativeInteger'/>" +
     17        "</element>" +
     18    "</optional>"+
    1119    "<optional>" +
    1220        "<element name='MaxArrowCount' a:help='Limit the number of arrows to a certain amount'>" +
    1321            "<data type='nonNegativeInteger'/>" +
    1422        "</element>" +
    1523    "</optional>" +
    16     "<element name='GarrisonArrowMultiplier'>" +
    17         "<ref name='nonNegativeDecimal'/>" +
    18     "</element>" +
    19     "<element name='GarrisonArrowClasses' a:help='Add extra arrows for this class list'>" +
    20         "<text/>" +
    21     "</element>";
     24    "<optional>" +
     25        "<element name='GarrisonArrowMultiplier'>" +
     26            "<ref name='nonNegativeDecimal'/>" +
     27        "</element>" +
     28    "</optional>" +
     29    "<optional>" +
     30        "<element name='GarrisonArrowClasses' a:help='Add extra arrows for this class list'>" +
     31            "<text/>" +
     32        "</element>" +
     33    "</optional>";
    2234
    2335BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2;
    2436
    2537BuildingAI.prototype.Init = function()
    2638{
    27     this.currentRound = 0;
    28     this.archersGarrisoned = 0;
    29     this.arrowsLeft = 0;
    30     this.targetUnits = [];
     39    this.buildingType = this.template.Type;
     40    this.currentRound = 0;
     41    this.archersGarrisoned = 0;
     42    this.arrowsLeft = 0;
     43    this.targetUnits = [];
    3144};
    3245
    3346BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
    3447{
     48    //
     49    // If the building is not of militar type, nothing should be done
     50    //
     51    if(this.buildingType !== 'Military')
     52        return;
     53
    3554    let classes = this.template.GarrisonArrowClasses;
    3655
    3756    for (let ent of msg.added)
     
    5574
    5675BuildingAI.prototype.OnOwnershipChanged = function(msg)
    5776{
     77    // If the building is of civil type, then the moment it is finished
     78    // being constructed, start the timer for the decaying process.
     79    //
     80    // This covers the scenario when the building is built in a queue where the  builder
     81    // goes for other structures leaving the buildind behind. If they don't
     82    // return then the building should decay, otherwise the timer would be
     83    // stopped inside the FSM in UnitAI
     84    if(msg.from == -1 && this.buildingType === 'Civil')
     85    {
     86        this.DecayBuilding();
     87        return;
     88    }
     89
    5890    this.targetUnits = [];
    5991    this.SetupRangeQuery();
    6092    this.SetupGaiaRangeQuery();
     
    6294
    6395BuildingAI.prototype.OnDiplomacyChanged = function(msg)
    6496{
     97    //
     98    // If the building is not of militar type, nothing should be done
     99    //
     100    if(this.buildingType !== 'Military')
     101        return;
     102
    65103    if (!IsOwnedByPlayer(msg.player, this.entity))
    66104        return;
    67105
     
    73111
    74112BuildingAI.prototype.OnDestroy = function()
    75113{
     114    //
     115    // If the building is not of militar type, nothing should be done
     116    //
     117    if(this.buildingType !== 'Military')
     118        return;
     119
    76120    if (this.timer)
    77121    {
    78122        let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     
    93137 */
    94138BuildingAI.prototype.OnValueModification = function(msg)
    95139{
     140    //
     141    // If the building is not of militar type, nothing should be done
     142    //
     143    if(this.buildingType !== 'Military')
     144        return;
     145
    96146    if (msg.component != "Attack")
    97147        return;
    98148
     
    106156 */
    107157BuildingAI.prototype.SetupRangeQuery = function()
    108158{
    109     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     159    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    110160    if (!cmpAttack)
    111161        return;
    112162
    113     var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     163    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    114164    if (this.enemyUnitsQuery)
    115165    {
    116166        cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
     
    117167        this.enemyUnitsQuery = undefined;
    118168    }
    119169
    120     var cmpPlayer = QueryOwnerInterface(this.entity);
     170    let cmpPlayer = QueryOwnerInterface(this.entity);
    121171    if (!cmpPlayer)
    122172        return;
    123173
    124     var enemies = [];
    125     var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     174    let enemies = [];
     175    let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
    126176    for (let i = 1; i < numPlayers; ++i)
    127177        if (cmpPlayer.IsEnemy(i))
    128178            enemies.push(i);
     
    130180    if (!enemies.length)
    131181        return;
    132182
    133     var range = cmpAttack.GetRange(attackType);
     183    let range = cmpAttack.GetRange(attackType);
    134184    this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
    135185            this.entity, range.min, range.max, range.elevationBonus,
    136186            enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
     
    142192// This should be called whenever our ownership changes.
    143193BuildingAI.prototype.SetupGaiaRangeQuery = function()
    144194{
    145     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     195    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    146196    if (!cmpAttack)
    147197        return;
    148198
    149     var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     199    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    150200    if (this.gaiaUnitsQuery)
    151201    {
    152202        cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
     
    153203        this.gaiaUnitsQuery = undefined;
    154204    }
    155205
    156     var cmpPlayer = QueryOwnerInterface(this.entity);
     206    let cmpPlayer = QueryOwnerInterface(this.entity);
    157207    if (!cmpPlayer || !cmpPlayer.IsEnemy(0))
    158208        return;
    159209
    160     var range = cmpAttack.GetRange(attackType);
     210    let range = cmpAttack.GetRange(attackType);
    161211
    162212    // This query is only interested in Gaia entities that can attack.
    163213    this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
     
    172222 */
    173223BuildingAI.prototype.OnRangeUpdate = function(msg)
    174224{
     225    //
     226    // If the building is not of militar type, nothing should be done
     227    //
     228    if(this.buildingType !== 'Military')
     229        return;
    175230
    176     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     231    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    177232    if (!cmpAttack)
    178233        return;
    179234
     
    202257    }
    203258
    204259    if (this.targetUnits.length)
    205         this.StartTimer();
     260        this.AttackNearEntities();
    206261};
    207262
    208 BuildingAI.prototype.StartTimer = function()
     263BuildingAI.prototype.AttackNearEntities = function()
    209264{
    210265    if (this.timer)
    211266        return;
    212267
    213     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     268    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    214269    if (!cmpAttack)
    215270        return;
     271    let attackTimers = cmpAttack.GetTimers(attackType);
     272    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    216273
    217     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    218     var attackTimers = cmpAttack.GetTimers(attackType);
    219 
    220     this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows",
    221         attackTimers.prepare, attackTimers.repeat / roundCount, null);
     274    this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null);
    222275};
    223276
    224277BuildingAI.prototype.GetDefaultArrowCount = function()
    225278{
    226     var arrowCount = +this.template.DefaultArrowCount;
     279    let arrowCount = +this.template.DefaultArrowCount;
    227280    return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity);
    228281};
    229282
     
    238291
    239292BuildingAI.prototype.GetGarrisonArrowMultiplier = function()
    240293{
    241     var arrowMult = +this.template.GarrisonArrowMultiplier;
     294    let arrowMult = +this.template.GarrisonArrowMultiplier;
    242295    return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity);
    243296};
    244297
    245298BuildingAI.prototype.GetGarrisonArrowClasses = function()
    246299{
    247     var string = this.template.GarrisonArrowClasses;
     300    let string = this.template.GarrisonArrowClasses;
    248301    if (string)
    249302        return string.split(/\s+/);
    250303    return [];
     
    267320{
    268321    this.unitAITarget = ent;
    269322    if (ent)
    270         this.StartTimer();
     323        this.AttackNearEntities();
    271324};
    272325
    273326/**
     
    302355        arrowsToFire = this.arrowsLeft;
    303356    else
    304357        arrowsToFire = Math.min(
    305             Math.round(2 * Math.random() * this.GetArrowCount() / roundCount),
    306             this.arrowsLeft
     358            Math.round(2 * Math.random() * this.GetArrowCount() / roundCount),
     359            this.arrowsLeft
    307360        );
    308361
    309362    if (arrowsToFire <= 0)
     
    364417 */
    365418BuildingAI.prototype.CheckTargetVisible = function(target)
    366419{
    367     var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     420    let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    368421    if (!cmpOwnership)
    369422        return false;
    370423
    371424    // Entities that are hidden and miraged are considered visible
    372     var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
     425    let cmpFogging = Engine.QueryInterface(target, IID_Fogging);
    373426    if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
    374427        return true;
    375428
     
    378431    return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden";
    379432};
    380433
     434/**
     435 * Some buildings (like fields) are expected to decline their health when no farmer is
     436 * working on them. This increases the realism of the game and adds new levels of
     437 * complexity into the game.
     438 *
     439 * The way is expected to work is simple. When the number of workers in a field reaches
     440 * zero, a timer is launched with an offset of 25 seconds, and after that the health of
     441 * the field starts decaying with the values given by the template element <IdleDecayAmount></IdleDecayAmount>
     442 * which is accesed through the IID_Health the field
     443 */
     444BuildingAI.prototype.DecayBuilding = function()
     445{
     446    if(this.timer)
     447        return;
     448
     449    let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     450    if(!cmpHealth || cmpHealth.GetIdleDecayRate === 0 || cmpHealth.GetIdleDecayAmount === 0)
     451        return;
     452
     453    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     454    this.timer = cmpTimer.SetInterval(this.entity, IID_Health, "Reduce", 15000, 25000 / cmpHealth.GetIdleDecayRate(), cmpHealth.GetIdleDecayAmount());
     455}
     456
     457/**
     458 * Stop the current BuildingAI timer.
     459 */
     460BuildingAI.prototype.StopTimer = function()
     461{
     462    if (!this.timer)
     463        return;
     464
     465    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     466    cmpTimer.CancelTimer(this.timer);
     467    this.timer = undefined;
     468};
     469
    381470Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI);
  • binaries/data/mods/public/simulation/components/Health.js

     
    2727    "<element name='IdleRegenRate' a:help='Hitpoint regeneration rate per second when idle or garrisoned.'>" +
    2828        "<data type='decimal'/>" +
    2929    "</element>" +
     30    "<optional>" +
     31        "<element name='IdleDecayRate' a:help='Some structures (like fields) are supposed to decay in health when they are not been used, this variable is used to declare the frequency'>" +
     32            "<data type='decimal'/>" +
     33        "</element>" +
     34    "</optional>" +
     35    "<optional>" +
     36        "<element name='IdleDecayAmount' a:help='Represents the amount of health that would be taken each time the dacying process is called'>" +
     37            "<data type='decimal'/>" +
     38        "</element>" +
     39    "</optional>" +
    3040    "<element name='DeathType' a:help='Behaviour when the unit dies'>" +
    3141        "<choice>" +
    3242            "<value a:help='Disappear instantly'>vanish</value>" +
     
    5060    this.hitpoints = +(this.template.Initial || this.GetMaxHitpoints());
    5161    this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity);
    5262    this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity);
     63    // Some entities lose points when iddle, but if it is not defined, make it zero
     64    this.idleDecayRate = +(this.template.IdleDecayRate || 0);
     65    this.idleDecayAmount = +(this.template.IdleDecayAmount || 0);
    5366    this.CheckRegenTimer();
    5467};
    5568
     
    6982    return this.maxHitpoints;
    7083};
    7184
     85/**
     86 * Returns the current frecuency at which the health of the unit should
     87 * diminish each second when is idle (mainly used by fields)
     88 */
     89Health.prototype.GetIdleDecayRate = function()
     90{
     91    return this.idleDecayRate;
     92};
     93
     94/**
     95 * Returns the amount of healt points this unit should lost while the
     96 * unit is idle (mainly used by fields)
     97 */
     98Health.prototype.GetIdleDecayAmount = function()
     99{
     100    return this.idleDecayAmount;
     101};
     102
    72103Health.prototype.SetHitpoints = function(value)
    73104{
    74105    // If we're already dead, don't allow resurrection
     
    82113
    83114    var old = this.hitpoints;
    84115    this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value));
    85    
     116
    86117    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    87118    if (cmpRangeManager)
    88119    {
     
    134165
    135166    if (regen > 0)
    136167        this.Increase(regen);
    137     else 
     168    else
    138169        this.Reduce(-regen);
    139170};
    140171
     
    145176{
    146177    // check if we need a timer
    147178    if (this.GetRegenRate() == 0 && this.GetIdleRegenRate() == 0 ||
    148         this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 ||
    149         this.GetHitpoints() == 0)
     179        this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 ||
     180        this.GetHitpoints() == 0)
    150181    {
    151182        // we don't need a timer, disable if one exists
    152183        if (this.regenTimer)
     
    254285
    255286    var old = this.hitpoints;
    256287    this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints());
    257    
     288
    258289    if (this.hitpoints == this.GetMaxHitpoints())
    259290    {
    260291        var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     
    353384
    354385    let oldRegenRate = this.regenRate;
    355386    this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity);
    356    
     387
    357388    let oldIdleRegenRate = this.idleRegenRate;
    358389    this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity);
    359    
     390
    360391    if (this.regenRate != oldRegenRate || this.idleRegenRate != oldIdleRegenRate)
    361392        this.CheckRegenTimer();
    362393};
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    5656
    5757// Unit stances.
    5858// There some targeting options:
    59 //  targetVisibleEnemies: anything in vision range is a viable target
    60 //  targetAttackersAlways: anything that hurts us is a viable target,
    61 //     possibly overriding user orders!
    62 //  targetAttackersPassive: anything that hurts us is a viable target,
    63 //     if we're on a passive/unforced order (e.g. gathering/building)
     59//  targetVisibleEnemies: anything in vision range is a viable target
     60//  targetAttackersAlways: anything that hurts us is a viable target,
     61//     possibly overriding user orders!
     62//  targetAttackersPassive: anything that hurts us is a viable target,
     63//     if we're on a passive/unforced order (e.g. gathering/building)
    6464// There are some response options, triggered when targets are detected:
    65 //  respondFlee: run away
    66 //  respondChase: start chasing after the enemy
    67 //  respondChaseBeyondVision: start chasing, and don't stop even if it's out
    68 //     of this unit's vision range (though still visible to the player)
    69 //  respondStandGround: attack enemy but don't move at all
    70 //  respondHoldGround: attack enemy but don't move far from current position
     65//  respondFlee: run away
     66//  respondChase: start chasing after the enemy
     67//  respondChaseBeyondVision: start chasing, and don't stop even if it's out
     68//     of this unit's vision range (though still visible to the player)
     69//  respondStandGround: attack enemy but don't move at all
     70//  respondHoldGround: attack enemy but don't move far from current position
    7171// TODO: maybe add targetAggressiveEnemies (don't worry about lone scouts,
    7272// do worry around armies slaughtering the guy standing next to you), etc.
    7373var g_Stances = {
     
    349349            return;
    350350        }
    351351
    352         // Check if we need to move     TODO implement a better way to know if we are on the shoreline
     352        // Check if we need to move     TODO implement a better way to know if we are on the shoreline
    353353        var needToMove = true;
    354354        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    355355        if (this.lastShorelinePosition && cmpPosition && (this.lastShorelinePosition.x == cmpPosition.GetPosition().x)
    356             && (this.lastShorelinePosition.z == cmpPosition.GetPosition().z))
     356            && (this.lastShorelinePosition.z == cmpPosition.GetPosition().z))
    357357        {
    358358            // we were already on the shoreline, and have not moved since
    359359            if (DistanceBetweenEntities(this.entity, this.order.data.target) < 50)
     
    10801080                var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
    10811081                if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
    10821082                {
    1083                     this.pickup = this.order.data.target;       // temporary, deleted in "leave"
     1083                    this.pickup = this.order.data.target;       // temporary, deleted in "leave"
    10841084                    Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
    10851085                }
    10861086            },
     
    13871387            var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    13881388            var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health);
    13891389            if (cmpIdentity && cmpIdentity.HasClass("Support") &&
    1390                 cmpHealth && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
     1390                cmpHealth && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
    13911391            {
    13921392                if (this.CanHeal(this.isGuardOf))
    13931393                    this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false });
     
    17431743
    17441744                "Attacked": function(msg) {
    17451745                    // If we're attacked by a close enemy, we should try to defend ourself
    1746                     //  but only if we're not forced to target something else
     1746                    //  but only if we're not forced to target something else
    17471747                    if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !this.order.data.force)))
    17481748                    {
    17491749                        this.RespondToTargetedEntities([msg.data.attacker]);
     
    17951795                    }
    17961796                    // Check the target is still alive and attackable
    17971797                    if (this.TargetIsAlive(target) &&
    1798                         this.CanAttack(target, this.order.data.forceResponse || null) &&
    1799                         !this.CheckTargetAttackRange(target, this.order.data.attackType))
     1798                        this.CanAttack(target, this.order.data.forceResponse || null) &&
     1799                        !this.CheckTargetAttackRange(target, this.order.data.attackType))
    18001800                    {
    18011801                        // Can't reach it - try to chase after it
    18021802                        if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
     
    19601960                    if (this.order.data.target != msg.data.attacker)
    19611961                    {
    19621962                        // If we're attacked by a close enemy, stronger than our current target,
    1963                         //  we choose to attack it, but only if we're not forced to target something else
     1963                        //  we choose to attack it, but only if we're not forced to target something else
    19641964                        if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !this.order.data.force)))
    19651965                        {
    19661966                            var ents = [this.order.data.target, msg.data.attacker];
     
    20372037                    var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    20382038                    var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage);
    20392039                    if ((!cmpMirage || !cmpMirage.Mirages(IID_ResourceSupply)) &&
    2040                         (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)))
     2040                        (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)))
    20412041                    {
    20422042                        // Save the current order's data in case we need it later
    20432043                        var oldType = this.order.data.type;
     
    22012201                    {
    22022202                        // Check that we can gather from the resource we're supposed to gather from.
    22032203                        // Will only be added if we're not already in.
    2204                         var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    2205                         var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2204                        let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     2205                        let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    22062206                        if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
    22072207                        {
    22082208                            this.gatheringTarget = INVALID_ENTITY;
     
    22092209                            this.StartTimer(0);
    22102210                            return false;
    22112211                        }
     2212
     2213                        // If the building is a field and is decaying, stop the timer from BuildingAI
     2214                        let supplyType = cmpSupply.GetType();
     2215                        if(supplyType.generic === 'food' && supplyType.specific === 'grain')
     2216                        {
     2217                            let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI);
     2218                            let cmpBuildingHealth = Engine.QueryInterface(this.gatheringTarget, IID_Health);
     2219                            if(cmpBuildingAI && cmpBuildingHealth)
     2220                            {
     2221                                cmpBuildingAI.StopTimer();
     2222
     2223                                // If the field has been decaying its heatpoints should be less than the maximum
     2224                                // therefore it should be repared before continue with the gathering
     2225                                if(cmpBuildingHealth.GetHitpoints() < cmpBuildingHealth.GetMaxHitpoints())
     2226                                {
     2227                                    this.PushOrderFront("Repair", { "target": this.gatheringTarget, "force": true });
     2228                                    this.SetNextState('INDIVIDUAL.REPAIR.REPAIRING');
     2229                                    return false;
     2230                                }
     2231                            }
     2232                        }
    22122233                    }
    22132234
    22142235                    // If this order was forced, the player probably gave it, but now we've reached the target
     
    22182239
    22192240                    // Calculate timing based on gather rates
    22202241                    // This allows the gather rate to control how often we gather, instead of how much.
    2221                     var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    2222                     var rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);
     2242                    let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
     2243                    let rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);
    22232244
    22242245                    if (!rate)
    22252246                    {
     
    22382259
    22392260                    // Scale timing interval based on rate, and start timer
    22402261                    // The offset should be at least as long as the repeat time so we use the same value for both.
    2241                     var offset = 1000/rate;
    2242                     var repeat = offset;
     2262                    let offset = 1000/rate;
     2263                    let repeat = offset;
    22432264                    this.StartTimer(offset, repeat);
    22442265
    22452266                    // We want to start the gather animation as soon as possible,
     
    22492270                    // off to a different target.)
    22502271                    if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer))
    22512272                    {
    2252                         var typename = "gather_" + this.order.data.type.specific;
     2273                        let typename = "gather_" + this.order.data.type.specific;
    22532274                        this.SelectAnimation(typename, false, 1.0, typename);
    22542275                    }
    22552276                    return false;
     
    22602281
    22612282                    // don't use ownership because this is called after a conversion/resignation
    22622283                    // and the ownership would be invalid then.
    2263                     var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    2264                     if (cmpSupply)
    2265                         cmpSupply.RemoveGatherer(this.entity);
    2266                     delete this.gatheringTarget;
     2284                    //
     2285                    // As javascript manages references instead of copies, when the field
     2286                    // is repaired, the reference to this.gatherinTarget could be deleted
     2287                    // creating an error when the engine is queried
     2288                    if(this.gatheringTarget)
     2289                    {
     2290                        let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2291                        if (cmpSupply)
     2292                        {
     2293                            cmpSupply.RemoveGatherer(this.entity);
    22672294
     2295                            // Every time a gather leaves the farmer, check how many are left inside the field
     2296                            // If the number of gathers is zero, start a timer after certain offset to decay the
     2297                            // health of the field
     2298                            let supplyType = cmpSupply.GetType();
     2299                            if(supplyType.generic === 'food' && supplyType.specific === 'grain' && cmpSupply.GetNumGatherers() === 0)
     2300                            {
     2301                                let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI);
     2302                                cmpBuildingAI.DecayBuilding();
     2303                            }
     2304                        }
     2305                        delete this.gatheringTarget;
     2306                    }
     2307
    22682308                    // Show the carried resource, if we've gathered anything.
    22692309                    this.SetGathererAnimationOverride();
    22702310                },
    22712311
    22722312                "Timer": function(msg) {
    2273                     var resourceTemplate = this.order.data.template;
    2274                     var resourceType = this.order.data.type;
     2313                    let resourceTemplate = this.order.data.template;
     2314                    let resourceType = this.order.data.type;
    22752315
    2276                     var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     2316                    let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    22772317                    if (!cmpOwnership)
    22782318                        return;
    22792319
    2280                     var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2320                    let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2321                    let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    22812322                    if (cmpSupply && cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity))
    22822323                    {
    22832324                        // Check we can still reach and gather from the target
     
    22852326                        {
    22862327                            // Gather the resources:
    22872328
    2288                             var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    2289 
    22902329                            // Try to gather treasure
    22912330                            if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget))
    22922331                                return;
     
    22972336                                cmpResourceGatherer.DropResources();
    22982337
    22992338                            // Collect from the target
    2300                             var status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
     2339                            let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
    23012340
    23022341                            // If we've collected as many resources as possible,
    23032342                            // return to the nearest dropsite
    23042343                            if (status.filled)
    23052344                            {
    2306                                 var nearby = this.FindNearestDropsite(resourceType.generic);
     2345                                let nearby = this.FindNearestDropsite(resourceType.generic);
    23072346                                if (nearby)
    23082347                                {
    23092348                                    // (Keep this Gather order on the stack so we'll
     
    23362375                            // the old one. So try to get close to the old resource's
    23372376                            // last known position
    23382377
    2339                             var maxRange = 8; // get close but not too close
     2378                            let maxRange = 8; // get close but not too close
    23402379                            if (this.order.data.lastPos &&
    23412380                                this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z,
    23422381                                    0, maxRange))
     
    23492388
    23502389                    // We're already in range, can't get anywhere near it or the target is exhausted.
    23512390
    2352                     var herdPos = this.order.data.initPos;
     2391                    let herdPos = this.order.data.initPos;
    23532392
    23542393                    // Give up on this order and try our next queued order
    23552394                    // but first check what is our next order and, if needed, insert a returnResource order
    2356                     var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    23572395                    if (cmpResourceGatherer.IsCarrying(resourceType.generic) &&
    23582396                        this.orderQueue.length > 1 && this.orderQueue[1] !== "ReturnResource" &&
    23592397                        (this.orderQueue[1].type !== "Gather" || this.orderQueue[1].data.type.generic !== resourceType.generic))
     
    23692407
    23702408                    // Try to find a new resource of the same specific type near our current position:
    23712409                    // Also don't switch to a different type of huntable animal
    2372                     var nearby = this.FindNearbyResource(function (ent, type, template) {
     2410                    let nearby = this.FindNearbyResource(function (ent, type, template) {
    23732411                        return (
    23742412                            (type.generic == "treasure" && resourceType.generic == "treasure")
    23752413                            || (type.specific == resourceType.specific
     
    23932431                    // drop it off, and if not then we might as well head to the dropsite
    23942432                    // anyway because that's a nice enough place to congregate and idle
    23952433
    2396                     var nearby = this.FindNearestDropsite(resourceType.generic);
     2434                    let nearby = this.FindNearestDropsite(resourceType.generic);
    23972435                    if (nearby)
    23982436                    {
    23992437                        this.PushOrderFront("ReturnResource", { "target": nearby, "force": false });
     
    26362674
    26372675                    this.order.data.force = false;
    26382676
    2639                     this.repairTarget = this.order.data.target; // temporary, deleted in "leave".
     2677                    this.repairTarget = this.order.data.target; // temporary, deleted in "leave".
    26402678                    // Check we can still reach and repair the target
    26412679                    if (!this.CanRepair(this.repairTarget))
    26422680                    {
     
    27242762                    let dropsiteTypes = cmpResourceDropsite.GetTypes();
    27252763                    cmpResourceGatherer.CommitResources(dropsiteTypes);
    27262764                    this.SetGathererAnimationOverride();
    2727                 } 
     2765                }
    27282766
    27292767                // We finished building it.
    27302768                // Switch to the next order (if any)
     
    27652803                    var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite);
    27662804                    var types = cmpResourceDropsite.GetTypes();
    27672805                    // TODO: Slightly undefined behavior here, we don't know what type of resource will be collected,
    2768                     //  may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that!
     2806                    //  may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that!
    27692807                    var nearby = this.FindNearbyResource(function (ent, type, template) {
    27702808                        return (types.indexOf(type.generic) != -1);
    27712809                    });
     
    27992837                var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
    28002838                if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
    28012839                {
    2802                     this.pickup = this.order.data.target;       // temporary, deleted in "leave"
     2840                    this.pickup = this.order.data.target;       // temporary, deleted in "leave"
    28032841                    Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
    28042842                }
    28052843            },
     
    30543092    "ANIMAL": {
    30553093        "Attacked": function(msg) {
    30563094            if (this.template.NaturalBehaviour == "skittish" ||
    3057                 this.template.NaturalBehaviour == "passive")
     3095                this.template.NaturalBehaviour == "passive")
    30583096            {
    30593097                this.Flee(msg.data.attacker, false);
    30603098            }
     
    34363474    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    34373475    if (this.losRangeQuery)
    34383476        this.SetupRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losRangeQuery));
    3439  
     3477
    34403478    if (this.IsHealer() && this.losHealRangeQuery)
    34413479        this.SetupHealRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losHealRangeQuery));
    34423480};
     
    35543592{
    35553593    if (!this.orderQueue.length)
    35563594    {
    3557         var stack = new Error().stack.trimRight().replace(/^/mg, '  '); // indent each line
     3595        var stack = new Error().stack.trimRight().replace(/^/mg, '  '); // indent each line
    35583596        var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
    35593597        var template = cmpTemplateManager.GetCurrentTemplateName(this.entity);
    35603598        error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack);
     
    57755813    if (cmpOwnership && IsOwnedByPlayer(cmpOwnership.GetOwner(), target))
    57765814        return true;
    57775815    return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() &&
    5778            cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target);
     5816           cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target);
    57795817};
    57805818
    57815819UnitAI.prototype.CanTrade = function(target)
  • binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml

     
    1313    </Ranged>
    1414  </Attack>
    1515  <BuildingAI>
     16    <Type>Military</Type>
    1617    <DefaultArrowCount>3</DefaultArrowCount>
    1718    <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier>
    1819  </BuildingAI>
  • binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml

     
    1313    </Ranged>
    1414  </Attack>
    1515  <BuildingAI>
     16    <Type>Military</Type>
    1617    <DefaultArrowCount>5</DefaultArrowCount>
    1718    <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier>
    1819  </BuildingAI>
  • binaries/data/mods/public/simulation/templates/other/plane.xml

     
    1414    </Ranged>
    1515  </Attack>
    1616  <BuildingAI>
     17    <Type>Military</Type>
    1718    <DefaultArrowCount>3</DefaultArrowCount>
    1819    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    1920    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
     
    2425    <List datatype="tokens">Support Infantry</List>
    2526    <BuffHeal>1</BuffHeal>
    2627    <LoadingRange>5</LoadingRange>
    27     <EjectClassesOnDestroy datatype="tokens" /> 
     28    <EjectClassesOnDestroy datatype="tokens" />
    2829  </GarrisonHolder>
    2930  <Decay>
    3031    <SinkingAnim>true</SinkingAnim>
  • binaries/data/mods/public/simulation/templates/structures/brit_crannog.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22<Entity parent="template_structure_civic_civil_centre">
    33  <BuildingAI>
     4    <Type>Military</Type>
    45    <DefaultArrowCount>3</DefaultArrowCount>
    56    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    67  </BuildingAI>
  • binaries/data/mods/public/simulation/templates/structures/iber_defense_tower.xml

     
    66    </Ranged>
    77  </Attack>
    88  <BuildingAI>
     9    <Type>Military</Type>
    910    <DefaultArrowCount>2</DefaultArrowCount>
    1011  </BuildingAI>
    1112  <Cost>
  • binaries/data/mods/public/simulation/templates/structures/ptol_military_colony.xml

     
    1414    </Ranged>
    1515  </Attack>
    1616  <BuildingAI>
     17    <Type>Military</Type>
    1718    <DefaultArrowCount>1</DefaultArrowCount>
    1819    <MaxArrowCount>15</MaxArrowCount>
    1920    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml

     
    2424    </Ranged>
    2525  </Attack>
    2626  <BuildingAI>
     27    <Type>Military</Type>
    2728    <DefaultArrowCount>1</DefaultArrowCount>
    2829    <MaxArrowCount>15</MaxArrowCount>
    2930    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/structures/sele_military_colony.xml

     
    1414    </Ranged>
    1515  </Attack>
    1616  <BuildingAI>
     17    <Type>Military</Type>
    1718    <DefaultArrowCount>1</DefaultArrowCount>
    1819    <MaxArrowCount>15</MaxArrowCount>
    1920    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    1111    </Foundation>
    1212  </Armour>
    1313  <BuildingAI>
     14    <Type>Military</Type>
    1415    <DefaultArrowCount>0</DefaultArrowCount>
    1516    <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier>
    1617    <GarrisonArrowClasses>Ranged Infantry</GarrisonArrowClasses>
  • binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

     
    2424    </Ranged>
    2525  </Attack>
    2626  <BuildingAI>
     27    <Type>Military</Type>
    2728    <DefaultArrowCount>3</DefaultArrowCount>
    2829    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2930  </BuildingAI>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml

     
    1616    </Ranged>
    1717  </Attack>
    1818  <BuildingAI>
     19    <Type>Military</Type>
    1920    <DefaultArrowCount>1</DefaultArrowCount>
    2021    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2122    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml

     
    2424    </Ranged>
    2525  </Attack>
    2626  <BuildingAI>
     27    <Type>Military</Type>
    2728    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2829  </BuildingAI>
    2930  <BuildRestrictions>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml

     
    2020    </Ranged>
    2121  </Attack>
    2222  <BuildingAI>
     23    <Type>Military</Type>
    2324    <DefaultArrowCount>0</DefaultArrowCount>
    2425    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2526    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
  • binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml

     
    1515    </Ranged>
    1616  </Attack>
    1717  <BuildingAI>
     18    <Type>Military</Type>
    1819    <DefaultArrowCount>3</DefaultArrowCount>
    1920    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2021    <GarrisonArrowClasses>Infantry Ranged</GarrisonArrowClasses>
  • binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml

     
    55    <Pierce>40</Pierce>
    66    <Crush>5</Crush>
    77  </Armour>
     8  <BuildingAI>
     9    <Type>Civil</Type>
     10  </BuildingAI>
    811  <BuildRestrictions>
    912    <Category>Field</Category>
    1013  </BuildRestrictions>
     
    2225  <Health>
    2326    <Max>250</Max>
    2427    <SpawnEntityOnDeath>rubble/rubble_field</SpawnEntityOnDeath>
     28    <IdleDecayRate>1</IdleDecayRate>
     29    <IdleDecayAmount>5</IdleDecayAmount>
    2530  </Health>
    2631  <Identity>
    2732    <GenericName>Field</GenericName>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml

     
    1414    </Ranged>
    1515  </Attack>
    1616  <BuildingAI>
     17    <Type>Military</Type>
    1718    <DefaultArrowCount>2</DefaultArrowCount>
    1819    <MaxArrowCount>10</MaxArrowCount>
    1920    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml

     
    2222    </Ranged>
    2323  </Attack>
    2424  <BuildingAI>
     25    <Type>Military</Type>
    2526    <DefaultArrowCount>1</DefaultArrowCount>
    2627    <MaxArrowCount>10</MaxArrowCount>
    2728    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml

     
    1414    </Ranged>
    1515  </Attack>
    1616  <BuildingAI>
     17    <Type>Military</Type>
    1718    <DefaultArrowCount>3</DefaultArrowCount>
    1819    <MaxArrowCount>13</MaxArrowCount>
    1920    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml

     
    2626    </Capture>
    2727  </Attack>
    2828  <BuildingAI>
     29    <Type>Military</Type>
    2930    <DefaultArrowCount>1</DefaultArrowCount>
    3031    <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier>
    3132    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>