Ticket #3406: decay_field_v1.patch

File decay_field_v1.patch, 42.4 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'>Militar</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    if (this.buildingType === 'Militar')
     41    {
     42        this.currentRound = 0;
     43        this.archersGarrisoned = 0;
     44        this.arrowsLeft = 0;
     45        this.targetUnits = [];
     46    }
    3147};
    3248
    3349BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
    3450{
     51    //
     52    // If the building is not of militar type, nothing should be done
     53    //
     54    if(this.buildingType !== 'Militar')
     55        return;
     56
    3557    let classes = this.template.GarrisonArrowClasses;
    3658
    3759    for (let ent of msg.added)
     
    5577
    5678BuildingAI.prototype.OnOwnershipChanged = function(msg)
    5779{
     80    // If the building is of civil type, then the moment it is finished
     81    // being constructed, start the timer for the decaying process.
     82    //
     83    // This covers the scenario when the building is built in a queue where the  builder
     84    // goes for other structures leaving the buildind behind. If they don't
     85    // return then the building should decay, otherwise the timer would be
     86    // stopped inside the FSM in UnitAI
     87    if(msg.from == -1 && this.buildingType === 'Civil')
     88    {
     89        this.DecayBuilding();
     90        return;
     91    }
     92
    5893    this.targetUnits = [];
    5994    this.SetupRangeQuery();
    6095    this.SetupGaiaRangeQuery();
     
    6297
    6398BuildingAI.prototype.OnDiplomacyChanged = function(msg)
    6499{
     100    //
     101    // If the building is not of militar type, nothing should be done
     102    //
     103    if(this.buildingType !== 'Militar')
     104        return;
     105
    65106    if (!IsOwnedByPlayer(msg.player, this.entity))
    66107        return;
    67108
     
    73114
    74115BuildingAI.prototype.OnDestroy = function()
    75116{
     117    //
     118    // If the building is not of militar type, nothing should be done
     119    //
     120    if(this.buildingType !== 'Militar')
     121        return;
     122
    76123    if (this.timer)
    77124    {
    78125        let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     
    93140 */
    94141BuildingAI.prototype.OnValueModification = function(msg)
    95142{
     143    //
     144    // If the building is not of militar type, nothing should be done
     145    //
     146    if(this.buildingType !== 'Militar')
     147        return;
     148
    96149    if (msg.component != "Attack")
    97150        return;
    98151
     
    106159 */
    107160BuildingAI.prototype.SetupRangeQuery = function()
    108161{
    109     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     162    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    110163    if (!cmpAttack)
    111164        return;
    112165
    113     var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     166    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    114167    if (this.enemyUnitsQuery)
    115168    {
    116169        cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
     
    117170        this.enemyUnitsQuery = undefined;
    118171    }
    119172
    120     var cmpPlayer = QueryOwnerInterface(this.entity);
     173    let cmpPlayer = QueryOwnerInterface(this.entity);
    121174    if (!cmpPlayer)
    122175        return;
    123176
    124     var enemies = [];
    125     var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     177    let enemies = [];
     178    let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
    126179    for (let i = 1; i < numPlayers; ++i)
    127180        if (cmpPlayer.IsEnemy(i))
    128181            enemies.push(i);
     
    130183    if (!enemies.length)
    131184        return;
    132185
    133     var range = cmpAttack.GetRange(attackType);
     186    let range = cmpAttack.GetRange(attackType);
    134187    this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
    135188            this.entity, range.min, range.max, range.elevationBonus,
    136189            enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
     
    142195// This should be called whenever our ownership changes.
    143196BuildingAI.prototype.SetupGaiaRangeQuery = function()
    144197{
    145     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     198    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    146199    if (!cmpAttack)
    147200        return;
    148201
    149     var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     202    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    150203    if (this.gaiaUnitsQuery)
    151204    {
    152205        cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
     
    153206        this.gaiaUnitsQuery = undefined;
    154207    }
    155208
    156     var cmpPlayer = QueryOwnerInterface(this.entity);
     209    let cmpPlayer = QueryOwnerInterface(this.entity);
    157210    if (!cmpPlayer || !cmpPlayer.IsEnemy(0))
    158211        return;
    159212
    160     var range = cmpAttack.GetRange(attackType);
     213    let range = cmpAttack.GetRange(attackType);
    161214
    162215    // This query is only interested in Gaia entities that can attack.
    163216    this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
     
    172225 */
    173226BuildingAI.prototype.OnRangeUpdate = function(msg)
    174227{
     228    //
     229    // If the building is not of militar type, nothing should be done
     230    //
     231    if(this.buildingType !== 'Militar')
     232        return;
    175233
    176     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     234    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    177235    if (!cmpAttack)
    178236        return;
    179237
     
    202260    }
    203261
    204262    if (this.targetUnits.length)
    205         this.StartTimer();
     263        this.AttackNearEntities();
    206264};
    207265
    208 BuildingAI.prototype.StartTimer = function()
     266BuildingAI.prototype.AttackNearEntities = function()
    209267{
    210268    if (this.timer)
    211269        return;
    212270
    213     var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     271    let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    214272    if (!cmpAttack)
    215273        return;
     274    let attackTimers = cmpAttack.GetTimers(attackType);
     275    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    216276
    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);
     277    this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null);
    222278};
    223279
    224280BuildingAI.prototype.GetDefaultArrowCount = function()
    225281{
    226     var arrowCount = +this.template.DefaultArrowCount;
     282    let arrowCount = +this.template.DefaultArrowCount;
    227283    return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity);
    228284};
    229285
     
    238294
    239295BuildingAI.prototype.GetGarrisonArrowMultiplier = function()
    240296{
    241     var arrowMult = +this.template.GarrisonArrowMultiplier;
     297    let arrowMult = +this.template.GarrisonArrowMultiplier;
    242298    return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity);
    243299};
    244300
    245301BuildingAI.prototype.GetGarrisonArrowClasses = function()
    246302{
    247     var string = this.template.GarrisonArrowClasses;
     303    let string = this.template.GarrisonArrowClasses;
    248304    if (string)
    249305        return string.split(/\s+/);
    250306    return [];
     
    267323{
    268324    this.unitAITarget = ent;
    269325    if (ent)
    270         this.StartTimer();
     326        this.AttackNearEntities();
    271327};
    272328
    273329/**
     
    302358        arrowsToFire = this.arrowsLeft;
    303359    else
    304360        arrowsToFire = Math.min(
    305             Math.round(2 * Math.random() * this.GetArrowCount() / roundCount),
    306             this.arrowsLeft
     361            Math.round(2 * Math.random() * this.GetArrowCount() / roundCount),
     362            this.arrowsLeft
    307363        );
    308364
    309365    if (arrowsToFire <= 0)
     
    364420 */
    365421BuildingAI.prototype.CheckTargetVisible = function(target)
    366422{
    367     var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     423    let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    368424    if (!cmpOwnership)
    369425        return false;
    370426
    371427    // Entities that are hidden and miraged are considered visible
    372     var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
     428    let cmpFogging = Engine.QueryInterface(target, IID_Fogging);
    373429    if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
    374430        return true;
    375431
     
    378434    return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden";
    379435};
    380436
     437/**
     438 * Some buildings (like fields) are expected to decline their health when no farmer is
     439 * working on them. This increases the realism of the game and adds new levels of
     440 * complexity into the game.
     441 *
     442 * The way is expected to work is simple. When the number of workers in a field reaches
     443 * zero, a timer is launched with an offset of 25 seconds, and after that the health of
     444 * the field starts decaying with the values given by the template element <IdleDecayAmount></IdleDecayAmount>
     445 * which is accesed through the IID_Health the field
     446 */
     447BuildingAI.prototype.DecayBuilding = function()
     448{
     449    if(this.timer)
     450        return;
     451
     452    let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     453    if(!cmpHealth || cmpHealth.GetIdleDecayRate === 0 || cmpHealth.GetIdleDecayAmount === 0)
     454        return;
     455
     456    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     457    this.timer = cmpTimer.SetInterval(this.entity, IID_Health, "Reduce", 15000, 15000 / cmpHealth.GetIdleDecayRate(), cmpHealth.GetIdleDecayAmount());
     458}
     459
     460/**
     461 * Stop the current BuildingAI timer.
     462 */
     463BuildingAI.prototype.StopTimer = function()
     464{
     465    if (!this.timer)
     466        return;
     467
     468    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     469    cmpTimer.CancelTimer(this.timer);
     470    this.timer = undefined;
     471};
     472
    381473Engine.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);
     2284                    let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    22642285                    if (cmpSupply)
     2286                    {
    22652287                        cmpSupply.RemoveGatherer(this.entity);
     2288
     2289                        // Every time a gather leaves the farmer, check how many are left inside the field
     2290                        // If the number of gathers is zero, start a timer after certain offset to decay the
     2291                        // health of the field
     2292                        let supplyType = cmpSupply.GetType();
     2293                        if(supplyType.generic === 'food' && supplyType.specific === 'grain' && cmpSupply.GetNumGatherers() === 0)
     2294                        {
     2295                            let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI);
     2296                            cmpBuildingAI.DecayBuilding();
     2297                        }
     2298                    }
    22662299                    delete this.gatheringTarget;
    22672300
    22682301                    // Show the carried resource, if we've gathered anything.
     
    22702303                },
    22712304
    22722305                "Timer": function(msg) {
    2273                     var resourceTemplate = this.order.data.template;
    2274                     var resourceType = this.order.data.type;
     2306                    let resourceTemplate = this.order.data.template;
     2307                    let resourceType = this.order.data.type;
    22752308
    2276                     var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     2309                    let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    22772310                    if (!cmpOwnership)
    22782311                        return;
    22792312
    2280                     var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2313                    let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
     2314                    let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    22812315                    if (cmpSupply && cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity))
    22822316                    {
    22832317                        // Check we can still reach and gather from the target
     
    22852319                        {
    22862320                            // Gather the resources:
    22872321
    2288                             var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    2289 
    22902322                            // Try to gather treasure
    22912323                            if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget))
    22922324                                return;
     
    22972329                                cmpResourceGatherer.DropResources();
    22982330
    22992331                            // Collect from the target
    2300                             var status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
     2332                            let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
    23012333
    23022334                            // If we've collected as many resources as possible,
    23032335                            // return to the nearest dropsite
    23042336                            if (status.filled)
    23052337                            {
    2306                                 var nearby = this.FindNearestDropsite(resourceType.generic);
     2338                                let nearby = this.FindNearestDropsite(resourceType.generic);
    23072339                                if (nearby)
    23082340                                {
    23092341                                    // (Keep this Gather order on the stack so we'll
     
    23362368                            // the old one. So try to get close to the old resource's
    23372369                            // last known position
    23382370
    2339                             var maxRange = 8; // get close but not too close
     2371                            let maxRange = 8; // get close but not too close
    23402372                            if (this.order.data.lastPos &&
    23412373                                this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z,
    23422374                                    0, maxRange))
     
    23492381
    23502382                    // We're already in range, can't get anywhere near it or the target is exhausted.
    23512383
    2352                     var herdPos = this.order.data.initPos;
     2384                    let herdPos = this.order.data.initPos;
    23532385
    23542386                    // Give up on this order and try our next queued order
    23552387                    // but first check what is our next order and, if needed, insert a returnResource order
    2356                     var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    23572388                    if (cmpResourceGatherer.IsCarrying(resourceType.generic) &&
    23582389                        this.orderQueue.length > 1 && this.orderQueue[1] !== "ReturnResource" &&
    23592390                        (this.orderQueue[1].type !== "Gather" || this.orderQueue[1].data.type.generic !== resourceType.generic))
     
    23692400
    23702401                    // Try to find a new resource of the same specific type near our current position:
    23712402                    // Also don't switch to a different type of huntable animal
    2372                     var nearby = this.FindNearbyResource(function (ent, type, template) {
     2403                    let nearby = this.FindNearbyResource(function (ent, type, template) {
    23732404                        return (
    23742405                            (type.generic == "treasure" && resourceType.generic == "treasure")
    23752406                            || (type.specific == resourceType.specific
     
    23932424                    // drop it off, and if not then we might as well head to the dropsite
    23942425                    // anyway because that's a nice enough place to congregate and idle
    23952426
    2396                     var nearby = this.FindNearestDropsite(resourceType.generic);
     2427                    let nearby = this.FindNearestDropsite(resourceType.generic);
    23972428                    if (nearby)
    23982429                    {
    23992430                        this.PushOrderFront("ReturnResource", { "target": nearby, "force": false });
     
    26362667
    26372668                    this.order.data.force = false;
    26382669
    2639                     this.repairTarget = this.order.data.target; // temporary, deleted in "leave".
     2670                    this.repairTarget = this.order.data.target; // temporary, deleted in "leave".
    26402671                    // Check we can still reach and repair the target
    26412672                    if (!this.CanRepair(this.repairTarget))
    26422673                    {
     
    27242755                    let dropsiteTypes = cmpResourceDropsite.GetTypes();
    27252756                    cmpResourceGatherer.CommitResources(dropsiteTypes);
    27262757                    this.SetGathererAnimationOverride();
    2727                 } 
     2758                }
    27282759
    27292760                // We finished building it.
    27302761                // Switch to the next order (if any)
     
    27652796                    var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite);
    27662797                    var types = cmpResourceDropsite.GetTypes();
    27672798                    // 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!
     2799                    //  may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that!
    27692800                    var nearby = this.FindNearbyResource(function (ent, type, template) {
    27702801                        return (types.indexOf(type.generic) != -1);
    27712802                    });
     
    27992830                var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
    28002831                if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
    28012832                {
    2802                     this.pickup = this.order.data.target;       // temporary, deleted in "leave"
     2833                    this.pickup = this.order.data.target;       // temporary, deleted in "leave"
    28032834                    Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
    28042835                }
    28052836            },
     
    30543085    "ANIMAL": {
    30553086        "Attacked": function(msg) {
    30563087            if (this.template.NaturalBehaviour == "skittish" ||
    3057                 this.template.NaturalBehaviour == "passive")
     3088                this.template.NaturalBehaviour == "passive")
    30583089            {
    30593090                this.Flee(msg.data.attacker, false);
    30603091            }
     
    34363467    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    34373468    if (this.losRangeQuery)
    34383469        this.SetupRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losRangeQuery));
    3439  
     3470
    34403471    if (this.IsHealer() && this.losHealRangeQuery)
    34413472        this.SetupHealRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losHealRangeQuery));
    34423473};
     
    35543585{
    35553586    if (!this.orderQueue.length)
    35563587    {
    3557         var stack = new Error().stack.trimRight().replace(/^/mg, '  '); // indent each line
     3588        var stack = new Error().stack.trimRight().replace(/^/mg, '  '); // indent each line
    35583589        var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
    35593590        var template = cmpTemplateManager.GetCurrentTemplateName(this.entity);
    35603591        error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack);
     
    57755806    if (cmpOwnership && IsOwnedByPlayer(cmpOwnership.GetOwner(), target))
    57765807        return true;
    57775808    return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() &&
    5778            cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target);
     5809           cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target);
    57795810};
    57805811
    57815812UnitAI.prototype.CanTrade = function(target)
  • binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml

     
    1313    </Ranged>
    1414  </Attack>
    1515  <BuildingAI>
     16    <Type>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</Type>
    2930    <DefaultArrowCount>1</DefaultArrowCount>
    3031    <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier>
    3132    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>