Ticket #1144: restricted_and_preferred_classes.diff

File restricted_and_preferred_classes.diff, 14.4 KB (added by Zsolt Dollenstein, 12 years ago)

Add RestrictedClasses and PreferredClasses to the attack component

  • binaries/data/mods/public/simulation/components/tests/test_UnitAI.js

     
    8585    AddMock(unit, IID_Attack, {
    8686        GetRange: function() { return 10; },
    8787        GetBestAttack: function() { return "melee"; },
     88        GetBestAttackAgainst: function(t) { return "melee"; },
    8889        GetTimers: function() { return { "prepare": 500, "repeat": 1000 }; },
     90        CanAttack: function(v) { return true; },
     91        CompareEntitiesByPreference: function(a, b) { return 0; },
    8992    });
    9093
    9194    unitAI.OnCreate();
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    249249        }
    250250
    251251        // Work out how to attack the given target
    252         var type = this.GetBestAttack();
     252        var type = this.GetBestAttackAgainst(this.order.data.target);
    253253        if (!type)
    254254        {
    255255            // Oops, we can't attack at all
     
    336336        if (this.MustKillGatherTarget(this.order.data.target) && this.CheckTargetVisible(this.order.data.target))
    337337        {
    338338            // Make sure we can attack the target, else we'll get very stuck
    339             if (!this.GetBestAttack())
     339            if (!this.GetBestAttackAgainst(this.order.data.target))
    340340            {
    341341                // Oops, we can't attack at all - give up
    342342                // TODO: should do something so the player knows why this failed
     
    644644                if (this.GetStance().targetVisibleEnemies)
    645645                {
    646646                    // Start attacking one of the newly-seen enemy (if any)
    647                     this.RespondToTargetedEntities(msg.data.added);
     647                    var ents = this.GetAttackableEntitiesByPreference(msg.data.added);
     648                    this.RespondToTargetedEntities(ents);
    648649                }
    649650            },
    650651           
     
    24372438    return cmpAttack.GetBestAttack();
    24382439};
    24392440
     2441UnitAI.prototype.GetBestAttackAgainst = function(target)
     2442{
     2443    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     2444    if (!cmpAttack)
     2445        return undefined;
     2446    return cmpAttack.GetBestAttackAgainst(target);
     2447};
     2448
    24402449/**
    24412450 * Try to find one of the given entities which can be attacked,
    24422451 * and start attacking it.
     
    24622471 */
    24632472UnitAI.prototype.AttackEntityInZone = function(ents)
    24642473{
    2465     var type = this.GetBestAttack();
    24662474    for each (var target in ents)
    24672475    {
    2468         if (this.CanAttack(target) && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, type))
     2476        if (this.CanAttack(target))
    24692477        {
    2470             this.PushOrderFront("Attack", { "target": target, "force": false });
    2471             return true;
     2478            var type = this.GetBestAttackAgainst(target);
     2479            if (this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, type))
     2480            {
     2481                this.PushOrderFront("Attack", { "target": target, "force": false });
     2482                return true;
     2483            }
    24722484        }
    24732485    }
    24742486    return false;
     
    29282940    if (!this.GetStance().targetVisibleEnemies)
    29292941        return false;
    29302942
    2931     SortEntitiesByPriority(ents);
    2932     return this.RespondToTargetedEntities(ents);
     2943    return this.RespondToTargetedEntities(this.GetAttackableEntitiesByPreference(ents));
    29332944};
    29342945
    29352946/**
     
    30233034    // Verify that we're able to respond to Attack commands
    30243035    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    30253036    if (!cmpAttack)
     3037        return false;   
     3038 
     3039  if (!cmpAttack.CanAttack(target))
    30263040        return false;
    30273041
    30283042    // Verify that the target is alive
     
    32663280    return false;
    32673281};
    32683282
     3283UnitAI.prototype.GetAttackableEntitiesByPreference = function(ents)
     3284{
     3285    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     3286    if (!cmpAttack)
     3287        return [];
     3288
     3289    return ents
     3290        .filter(function (v, i, a) { return cmpAttack.CanAttack(v); })
     3291        .sort(function (a, b) { return cmpAttack.CompareEntitiesByPreference(a, b); });
     3292};
     3293
    32693294Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
  • binaries/data/mods/public/simulation/components/Attack.js

     
    1818        "</element>" +
    1919    "</optional>";
    2020
     21var preferredClassesSchema =
     22    "<optional>" +
     23        "<element name='PreferredClasses' a:help='Space delimited list of classes preferred for attacking. If an entity has any of theses classes, it is preferred.'>" +
     24            "<attribute name='datatype'>" +
     25                "<value>tokens</value>" +
     26            "</attribute>" +
     27            "<text/>" +
     28        "</element>" +
     29    "</optional>";
     30
     31var restrictedClassesSchema =
     32    "<optional>" +
     33        "<element name='RestrictedClasses' a:help='Space delimited list of classes that cannot be attacked by this entity. If target entity has any of these classes, it cannot be attacked'>" +
     34            "<attribute name='datatype'>" +
     35                "<value>tokens</value>" +
     36            "</attribute>" +
     37            "<text/>" +
     38        "</element>" +
     39    "</optional>";
     40
    2141Attack.prototype.Schema =
    2242    "<a:help>Controls the attack abilities and strengths of the unit.</a:help>" +
    2343    "<a:example>" +
     
    3858                    "<Multiplier>1.5</Multiplier>" +
    3959                "</BonusCavMelee>" +
    4060            "</Bonuses>" +
     61            "<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
     62            "<PreferredClasses datatype=\"tokens\">Cavalry Infantry</PreferredClasses>" +
    4163        "</Melee>" +
    4264        "<Ranged>" +
    4365            "<Hack>0.0</Hack>" +
     
    5476                    "<Multiplier>2</Multiplier>" +
    5577                "</Bonus1>" +
    5678            "</Bonuses>" +
     79            "<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
    5780        "</Ranged>" +
    5881        "<Charge>" +
    5982            "<Hack>10.0</Hack>" +
     
    7497                    "<data type='positiveInteger'/>" +
    7598                "</element>" +
    7699                bonusesSchema +
     100                preferredClassesSchema +
     101                restrictedClassesSchema +
    77102            "</interleave>" +
    78103        "</element>" +
    79104    "</optional>" +
     
    95120                    "<ref name='nonNegativeDecimal'/>" +
    96121                "</element>" +
    97122                bonusesSchema +
     123                preferredClassesSchema +
     124                restrictedClassesSchema +
    98125            "</interleave>" +
    99126        "</element>" +
    100127    "</optional>" +
     
    107134                "<element name='MaxRange'><ref name='nonNegativeDecimal'/></element>" + // TODO: how do these work?
    108135                "<element name='MinRange'><ref name='nonNegativeDecimal'/></element>" +
    109136                bonusesSchema +
     137                preferredClassesSchema +
     138                restrictedClassesSchema +
    110139            "</interleave>" +
    111140        "</element>" +
    112141    "</optional>";
     
    117146
    118147Attack.prototype.Serialize = null; // we have no dynamic state to save
    119148
     149Attack.prototype.GetAttackTypes = function()
     150{
     151    var ret = [];
     152    if (this.template.Charge) ret.push("Charge");
     153    if (this.template.Melee) ret.push("Melee");
     154    if (this.template.Ranged) ret.push("Ranged");
     155    return ret;
     156};
     157
     158Attack.prototype.GetPreferredClasses = function(type)
     159{
     160    if (this.template[type] && this.template[type].PreferredClasses)
     161    {
     162        return this.template[type].PreferredClasses["_string"].split(/\s+/);
     163    }
     164    return [];
     165};
     166
     167Attack.prototype.GetRestrictedClasses = function(type)
     168{
     169    if (this.template[type] && this.template[type].RestrictedClasses)
     170    {
     171        return this.template[type].RestrictedClasses["_string"].split(/\s+/);
     172    }
     173    return [];
     174};
     175
     176Attack.prototype.CanAttack = function(target)
     177{
     178    const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     179    if (!cmpIdentity)
     180        return undefined;
     181
     182    const targetClasses = cmpIdentity.GetClassesList();
     183
     184    for each (var type in this.GetAttackTypes())
     185    {
     186        var canAttack = true;
     187        var restrictedClasses = this.GetRestrictedClasses(type);
     188
     189        for each (var targetClass in targetClasses)
     190        {
     191            if (restrictedClasses.indexOf(targetClass) != -1)
     192            {
     193                canAttack = false;
     194                break;
     195            }
     196        }
     197        if (canAttack)
     198        {
     199            return true;
     200        }
     201    }
     202
     203    return false;
     204};
     205
     206Attack.prototype.GetPreference = function(target)
     207{
     208    const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     209    if (!cmpIdentity)
     210        return undefined;
     211
     212    const targetClasses = cmpIdentity.GetClassesList();
     213   
     214    var minPref = null;
     215    for each (var type in this.GetAttackTypes())
     216    {
     217        for each (var targetClass in targetClasses)
     218        {
     219            var pref = this.GetPreferredClasses(type).indexOf(targetClass);
     220            if (pref != -1 && (minPref === null || minPref > pref))
     221            {
     222                error("preference for target " + target + " with type " + type + " " +pref);
     223                minPref = pref;
     224            }
     225        }
     226    }
     227    return minPref;
     228};
     229
    120230/**
    121231 * Return the type of the best attack.
    122232 * TODO: this should probably depend on range, target, etc,
     
    124234 */
    125235Attack.prototype.GetBestAttack = function()
    126236{
    127     if (this.template.Ranged)
    128         return "Ranged";
    129     else if (this.template.Melee)
    130         return "Melee";
    131     else if (this.template.Charge)
    132         return "Charge";
    133     else
     237    return this.GetAttackTypes().pop();
     238};
     239
     240Attack.prototype.GetBestAttackAgainst = function(target)
     241{
     242    const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     243    if (!cmpIdentity)
    134244        return undefined;
     245
     246    const targetClasses = cmpIdentity.GetClassesList();
     247    const isTargetClass = function (value, i, a) { return targetClasses.indexOf(value) != -1; };
     248    const types = this.GetAttackTypes();
     249    const attack = this;
     250    const isAllowed = function (value, i, a) { return !attack.GetRestrictedClasses(value).some(isTargetClass); }
     251    const isPreferred = function (value, i, a) { return attack.GetPreferredClasses(value).some(isTargetClass); }
     252    const byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
     253
     254    return types.filter(isAllowed).sort(byPreference).pop();
    135255};
    136256
     257Attack.prototype.CompareEntitiesByPreference = function(a, b)
     258{
     259    var aPreference = this.GetPreference(a);
     260    var bPreference = this.GetPreference(b);
     261
     262    if (aPreference === null && bPreference === null) return 0;
     263    if (aPreference === null) return bPreference;
     264    if (bPreference === null) return aPreference;
     265    return aPreference - bPreference; // 0 is most preferred
     266};
     267
    137268Attack.prototype.GetTimers = function(type)
    138269{
    139270    var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager);
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml

     
    77      <Crush>0.0</Crush>
    88      <MaxRange>4.0</MaxRange>
    99      <RepeatTime>1000</RepeatTime>
     10      <RestrictedClasses datatype="tokens">Structure</RestrictedClasses>
    1011    </Melee>
    1112  </Attack>
    1213  <Identity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml

     
    77      <Crush>0.0</Crush>
    88      <MaxRange>4.0</MaxRange>
    99      <RepeatTime>1000</RepeatTime>
     10      <RestrictedClasses datatype="tokens">Structure</RestrictedClasses>
    1011    </Melee>
    1112  </Attack>
    1213  <Identity>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml

     
    1212      <Crush>0.0</Crush>
    1313      <MaxRange>5.0</MaxRange>
    1414      <RepeatTime>1000</RepeatTime>
     15      <RestrictedClasses datatype="tokens">Ship</RestrictedClasses>
    1516    </Melee>
    1617  </Attack>
    1718  <Footprint>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml

     
    77      <Crush>0.0</Crush>
    88      <MaxRange>4.0</MaxRange>
    99      <RepeatTime>1000</RepeatTime>
     10      <RestrictedClasses datatype="tokens">Structure</RestrictedClasses>
    1011    </Melee>
    1112  </Attack>
    1213  <Identity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml

     
    77      <Crush>0.0</Crush>
    88      <MaxRange>4.0</MaxRange>
    99      <RepeatTime>1000</RepeatTime>
     10      <RestrictedClasses datatype="tokens">Structure</RestrictedClasses>
    1011    </Melee>
    1112  </Attack>
    1213  <Identity>
  • binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml

     
    1212      <Crush>0.0</Crush>
    1313      <MaxRange>4.0</MaxRange>
    1414      <RepeatTime>1000</RepeatTime>
     15      <RestrictedClasses datatype="tokens">Infantry Cavalry Champion CitizenSoldier</RestrictedClasses>
    1516    </Melee>
    1617  </Attack>
    1718  <Auras>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ram.xml

     
    2121          <Multiplier>3.0</Multiplier>
    2222        </BonusGates>
    2323      </Bonuses>
     24    <RestrictedClasses datatype="tokens">Organic</RestrictedClasses>
     25    <PreferredClasses datatype="tokens">Gates Structure</PreferredClasses>
    2426    </Melee>
    2527    <Charge>
    2628      <Hack>0.0</Hack>
     
    3840          <Multiplier>3.0</Multiplier>
    3941        </BonusGates>
    4042      </Bonuses>
     43    <RestrictedClasses datatype="tokens">Organic</RestrictedClasses>
     44    <PreferredClasses datatype="tokens">Gates Structure</PreferredClasses>
    4145    </Charge>
    4246  </Attack>
    4347  <Cost>