Ticket #2577: turrets_scripted.2.diff

File turrets_scripted.2.diff, 21.0 KB (added by sanderd17, 10 years ago)
  • binaries/data/mods/public/art/actors/units/persians/cavalry_archer_a.xml

     
    1414        <prop actor="units/persians/pers_chariot_archer_a_h2.xml" attachpoint="horse2"/>
    1515        <prop actor="units/persians/pers_chariot_archer_a_h3.xml" attachpoint="horse3"/>
    1616        <prop actor="units/persians/pers_chariot_archer_a_h4.xml" attachpoint="horse4"/>
    17         <prop actor="units/persians/cavalry_archer_a_r.xml" attachpoint="rider1"/>
    1817        <prop actor="units/persians/cavalry_archer_a_d.xml" attachpoint="rider2"/>
    1918      </props>
    2019      <textures><texture file="structural/pers_chariot_a.png" name="baseTex"/></textures>
  • binaries/data/mods/public/art/actors/units/persians/cavalry_archer_e.xml

     
    1515        <prop actor="units/persians/pers_chariot_archer_e_h2.xml" attachpoint="horse2"/>
    1616        <prop actor="units/persians/pers_chariot_archer_e_h3.xml" attachpoint="horse3"/>
    1717        <prop actor="units/persians/pers_chariot_archer_e_h4.xml" attachpoint="horse4"/>
    18         <prop actor="units/persians/cavalry_archer_e_r.xml" attachpoint="rider1"/>
    1918        <prop actor="units/persians/cavalry_archer_e_d.xml" attachpoint="rider2"/>
    2019      </props>
    2120      <textures><texture file="structural/pers_chariot_e.png" name="baseTex"/></textures>
  • binaries/data/mods/public/simulation/components/Attack.js

     
    155155                        "</interleave>" +
    156156                    "</element>" +
    157157                "</optional>" +
     158                "<optional>"+
     159                    "<element name='TurretsOnly' a:help='When set to \"true\", will only accept attacks coming from turrets.'><text/></element>" +
     160                "</optional>" +
    158161            "</interleave>" +
    159162        "</element>" +
    160163    "</optional>" +
     
    188191
    189192Attack.prototype.Init = function()
    190193{
     194    this.latestTarget = INVALID_ENTITY;
    191195};
    192196
    193 Attack.prototype.Serialize = null; // we have no dynamic state to save
     197Attack.prototype.GetLatestTarget = function()
     198{
     199    return this.latestTarget;
     200};
    194201
    195202Attack.prototype.GetAttackTypes = function()
    196203{
     
    443450 * and should only be called after GetTimers().repeat msec has passed since the last
    444451 * call to PerformAttack.
    445452 */
    446 Attack.prototype.PerformAttack = function(type, target)
     453Attack.prototype.PerformAttack = function(type, target, turretId)
    447454{
     455    if (this.template[type].TurretsOnly == "true" && !turretId)
     456        return;
    448457    // If this is a ranged attack, then launch a projectile
    449458    if (type == "Ranged")
    450459    {
     
    512521        var graphicalPosition = Vector3D.mult(missileDirection, 2).add(realTargetPosition);
    513522        // Launch the graphical projectile
    514523        var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
    515         var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
     524        var id = cmpProjectileManager.LaunchProjectileAtPoint(turretId || this.entity, realTargetPosition, horizSpeed, gravity);
    516525
    517526        var playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner()
    518527        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
  • binaries/data/mods/public/simulation/components/TurretAI.js

     
     1function TurretAI() {}
     2
     3TurretAI.prototype.Schema =
     4    "<empty/>";
     5
     6TurretAI.prototype.MAX_PREFERENCE_BONUS = 2;
     7
     8/**
     9 * Initialize TurretAI Component
     10 */
     11TurretAI.prototype.Init = function()
     12{
     13};
     14
     15TurretAI.prototype.GetTurretParent = function()
     16{
     17    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     18    if (!cmpPosition)
     19        return INVALID_ENTITY;
     20    return cmpPosition.GetTurretParent();
     21};
     22
     23TurretAI.prototype.GetCmpAttack = function()
     24{
     25    // use own attack, or turretHolder's attack
     26    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     27    if (cmpAttack)
     28        return cmpAttack;
     29    return Engine.QueryInterface(this.GetTurretParent(), IID_Attack);
     30};
     31
     32TurretAI.prototype.OnOwnershipChanged = function(msg)
     33{
     34    var cmpAttack = this.GetCmpAttack();
     35    if (!cmpAttack)
     36        return;
     37    this.bestAttack = cmpAttack.GetBestAttack();
     38    // Remove current targets, to prevent them from being added twice
     39    this.targetUnits = [];
     40
     41    if (msg.to != -1)
     42        this.SetupRangeQuery(msg.to);
     43
     44    // Non-Gaia buildings should attack certain Gaia units.
     45    if (msg.to != 0 || this.gaiaUnitsQuery)
     46        this.SetupGaiaRangeQuery(msg.to);
     47};
     48
     49TurretAI.prototype.OnDiplomacyChanged = function(msg)
     50{
     51    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     52    if (cmpOwnership && cmpOwnership.GetOwner() == msg.player)
     53    {
     54        // Remove maybe now allied/neutral units
     55        this.targetUnits = [];
     56        this.SetupRangeQuery(msg.player);
     57    }
     58};
     59
     60/**
     61 * Cleanup on destroy
     62 */
     63TurretAI.prototype.OnDestroy = function()
     64{
     65    if (this.timer)
     66    {
     67        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     68        cmpTimer.CancelTimer(this.timer);
     69        this.timer = undefined;
     70    }
     71
     72    // Clean up range queries
     73    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     74    if (this.enemyUnitsQuery)
     75        cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
     76    if (this.gaiaUnitsQuery)
     77        cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
     78};
     79
     80/**
     81 * Setup the Range Query to detect units coming in & out of range
     82 */
     83TurretAI.prototype.SetupRangeQuery = function(owner)
     84{
     85    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     86    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     87    var cmpAttack = this.GetCmpAttack();
     88    if (!cmpAttack)
     89        return;
     90
     91    if (this.enemyUnitsQuery)
     92    {
     93        cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
     94        this.enemyUnitsQuery = undefined;
     95    }
     96    var players = [];
     97
     98    var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);
     99    var numPlayers = cmpPlayerManager.GetNumPlayers();
     100
     101    for (var i = 1; i < numPlayers; ++i)
     102    {   // Exclude gaia, allies, and self
     103        // TODO: How to handle neutral players - Special query to attack military only?
     104        if (cmpPlayer.IsEnemy(i))
     105            players.push(i);
     106    }
     107
     108    var range = cmpAttack.GetRange(this.bestAttack);
     109    this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
     110    cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
     111};
     112
     113// Set up a range query for Gaia units within LOS range which can be attacked.
     114// This should be called whenever our ownership changes.
     115TurretAI.prototype.SetupGaiaRangeQuery = function()
     116{
     117    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     118    var owner = cmpOwnership.GetOwner();
     119
     120    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     121    var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     122    var cmpAttack = this.GetCmpAttack();
     123    if (!cmpAttack)
     124        return;
     125
     126    if (this.gaiaUnitsQuery)
     127    {
     128        cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
     129        this.gaiaUnitsQuery = undefined;
     130    }
     131
     132    if (owner == -1)
     133        return;
     134
     135    var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player);
     136    if (!cmpPlayer.IsEnemy(0))
     137        return;
     138
     139    var range = cmpAttack.GetRange(this.bestAttack);
     140
     141    // This query is only interested in Gaia entities that can attack.
     142    this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal"));
     143    cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery);
     144};
     145
     146/**
     147 * Called when units enter or leave range
     148 */
     149TurretAI.prototype.OnRangeUpdate = function(msg)
     150{
     151
     152    var cmpAttack = this.GetCmpAttack();
     153    if (!cmpAttack)
     154        return;
     155
     156    if (msg.tag == this.gaiaUnitsQuery)
     157    {
     158        const filter = function(e) {
     159            var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
     160            return (cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()));
     161        };
     162
     163        if (msg.added.length)
     164            msg.added = msg.added.filter(filter);
     165
     166        // Removed entities may not have cmpUnitAI.
     167        for (var i = 0; i < msg.removed.length; ++i)
     168            if (this.targetUnits.indexOf(msg.removed[i]) == -1)
     169                msg.removed.splice(i--, 1);
     170    }
     171    else if (msg.tag != this.enemyUnitsQuery)
     172        return;
     173
     174    if (msg.added.length > 0)
     175    {
     176        for each (var entity in msg.added)
     177        {
     178            if (cmpAttack.CanAttack(entity))
     179                this.targetUnits.push(entity);
     180        }
     181    }
     182    if (msg.removed.length > 0)
     183    {
     184        for each (var entity in msg.removed)
     185        {   
     186            var index = this.targetUnits.indexOf(entity);
     187            if (index > -1)
     188                this.targetUnits.splice(index, 1);
     189        }
     190    }
     191
     192    if (!this.targetUnits.length || this.timer)
     193        return;
     194
     195    var attackTimers = cmpAttack.GetTimers(this.bestAttack);
     196    this.SelectAnimation("attack_" + this.bestAttack.toLowerCase(), false, 1.0, "attack");
     197    this.SetAnimationSync(attackTimers.prepare, attackTimers.repeat);
     198    // units entered the range, prepare to shoot
     199    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     200    this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null);
     201};
     202
     203TurretAI.prototype.GetPreviousTarget = function()
     204{
     205    var cmpAttack = this.GetCmpAttack();
     206    if (!cmpAttack)
     207        return INVALID_ENTITY;
     208    var previousTarget = cmpAttack.GetLatestTarget();
     209    var turretParent = this.GetTurretParent();
     210    var cmpUnitAI = Engine.QueryInterface(turretParent, IID_UnitAI);
     211    // if a unit ai, use that to overcome the difference between rangeManager range and unitMotion range
     212    if (cmpUnitAI)
     213    {
     214        if (cmpUnitAI.CheckTargetAttackRange(previousTarget, this.bestAttack))
     215            return previousTarget;
     216        return INVALID_ENTITY;
     217    }
     218    // else just use the range manager (which gives us the list of possible targets);
     219    if (this.targetUnits.indexOf(previousTarget) != -1)
     220        return previousTarget;
     221    return INVALID_ENTITY;
     222};
     223
     224TurretAI.prototype.Attack = function()
     225{
     226    if (!this.targetUnits.length)
     227    {
     228        if (this.timer)
     229        {
     230            // stop the timer
     231            var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     232            cmpTimer.CancelTimer(this.timer);
     233            this.timer = undefined;
     234        }
     235        this.SelectAnimation("idle");
     236        return;
     237    }
     238
     239    var cmpAttack = this.GetCmpAttack();
     240    if (!cmpAttack)
     241        return;
     242    var target = this.GetPreviousTarget();
     243    // if no target, select a random one
     244    if (target == INVALID_ENTITY)
     245    {
     246        var selectedIndex = -1;
     247        var targets = new WeightedList();
     248        for (var i = 0; i < this.targetUnits.length; i++)
     249        {
     250            var target = this.targetUnits[i];
     251            var preference = cmpAttack.GetPreference(target);
     252            var weight = 1;
     253            if (preference !== null && preference !== undefined)
     254            {
     255                // Lower preference scores indicate a higher preference so they
     256                // should result in a higher weight.
     257                weight = 1 + this.MAX_PREFERENCE_BONUS / (1 + preference);
     258            }
     259            targets.push(target, weight);
     260        }
     261        selectedIndex = targets.randomIndex()
     262        target = targets.itemAt(selectedIndex);
     263    }
     264    // now we hope there's a target
     265    if (target != INVALID_ENTITY)
     266    {
     267        this.TurnTowardsTarget(target);
     268        cmpAttack.PerformAttack(this.bestAttack, target, this.entity);
     269    }
     270};
     271
     272TurretAI.prototype.SelectAnimation = function(name, once, speed, sound)
     273{
     274    var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     275    if (!cmpVisual)
     276        return;
     277
     278    var soundgroup;
     279    if (sound)
     280    {
     281        var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
     282        if (cmpSound)
     283            soundgroup = cmpSound.GetSoundGroup(sound);
     284    }
     285    cmpVisual.SelectAnimation(name, once || false, speed || 1.0, soundgroup || "");
     286};
     287
     288TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime)
     289{
     290    var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     291    if (!cmpVisual)
     292        return;
     293
     294    cmpVisual.SetAnimationSyncRepeat(repeattime);
     295    cmpVisual.SetAnimationSyncOffset(actiontime);
     296};
     297
     298TurretAI.prototype.TurnTowardsTarget = function(target)
     299{
     300    var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
     301    var cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
     302    if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
     303        return;
     304
     305    var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D());
     306    cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y));
     307};
     308
     309Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI);
  • binaries/data/mods/public/simulation/components/TurretHolder.js

     
     1function TurretHolder() {}
     2
     3TurretHolder.prototype.Schema =
     4    "<element name='TurretPoints'>" +
     5        "<zeroOrMore>" +
     6            "<element a:help='Element containing the offset coordinates and the template'>" +
     7                "<anyName/>" +
     8                "<interleave>" +
     9                    "<element name='Template'>" +
     10                        "<text/>" +
     11                    "</element>" +
     12                    "<element name='X'>" +
     13                        "<data type='decimal'/>" +
     14                    "</element>" +
     15                    "<element name='Y'>" +
     16                        "<data type='decimal'/>" +
     17                    "</element>" +
     18                    "<element name='Z'>" +
     19                        "<data type='decimal'/>" +
     20                    "</element>" +
     21                "</interleave>" +
     22            "</element>" +
     23        "</zeroOrMore>" +
     24    "</element>";
     25
     26/**
     27 * Initialize TurretHolder Component
     28 */
     29TurretHolder.prototype.Init = function()
     30{
     31    this.turrets = [];
     32    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     33    // hack for atlas, don't create the turrets in Atlas, as the references get lost
     34    // TODO implement some sort of tag for the turrets so they never get saved by Atlas
     35/*  if (cmpTimer.GetTime() == 0)
     36        cmpTimer.SetTimeout(this.entity, IID_TurretHolder, "CreateTurrets", 100, null);
     37    else*/
     38        this.CreateTurrets();
     39};
     40
     41TurretHolder.prototype.CreateTurrets = function()
     42{
     43    for each (var turretPoint in this.template.TurretPoints)
     44    {
     45        var ent = Engine.AddEntity(turretPoint.Template);
     46        var offset = new Vector3D(+turretPoint.X, +turretPoint.Y, +turretPoint.Z);
     47        var cmpPosition = Engine.QueryInterface(ent, IID_Position);
     48        if (cmpPosition)
     49            cmpPosition.SetTurretParent(this.entity, offset);
     50        this.turrets.push(ent);
     51    }
     52    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     53    if (cmpOwnership && cmpOwnership.GetOwner() != -1)
     54        this.ChangeTurretOwnership(cmpOwnership.GetOwner());
     55};
     56
     57/**
     58 * Return the list of entities garrisoned inside
     59 */
     60TurretHolder.prototype.GetTurrets = function()
     61{
     62    return this.turrets;
     63};
     64
     65TurretHolder.prototype.OnDestroy = function()
     66{
     67    for (var ent of this.turrets)
     68        Engine.DestroyEntity(ent);
     69};
     70
     71TurretHolder.prototype.OnOwnershipChanged = function(msg)
     72{
     73    this.ChangeTurretOwnership(msg.to);
     74};
     75
     76/**
     77 * Set the ownership of all present turrets to the same owner
     78 */
     79TurretHolder.prototype.ChangeTurretOwnership = function(owner)
     80{
     81    for (var ent of this.turrets)
     82    {
     83        var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     84        if (cmpOwnership)
     85            cmpOwnership.SetOwner(owner);
     86    }
     87};
     88
     89Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);
     90
  • binaries/data/mods/public/simulation/components/interfaces/TurretAI.js

     
     1Engine.RegisterInterface("TurretAI");
  • binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js

     
     1Engine.RegisterInterface("TurretHolder");
     2
  • binaries/data/mods/public/simulation/templates/template_turret.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity parent="template_entity_full">
     3  <Decay>
     4    <Inactive/>
     5    <DelayTime>80.0</DelayTime>
     6    <SinkRate>0.01</SinkRate>
     7    <SinkAccel>0.0</SinkAccel>
     8  </Decay>
     9  <Minimap>
     10    <Type>unit</Type>
     11  </Minimap>
     12  <OverlayRenderer/>
     13  <Selectable disable=""/>
     14  <Sound>
     15    <SoundGroups>
     16      <attacked>interface/alarm/alarm_attackplayer.xml</attacked>
     17    </SoundGroups>
     18  </Sound>
     19  <TurretAI/>
     20  <VisualActor>
     21    <SilhouetteDisplay>true</SilhouetteDisplay>
     22    <SilhouetteOccluder>false</SilhouetteOccluder>
     23    <VisibleInAtlasOnly>false</VisibleInAtlasOnly>
     24  </VisualActor>
     25</Entity>
  • binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_a.xml

    Property changes on: binaries/data/mods/public/simulation/templates/template_turret.xml
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +text/xml
    \ No newline at end of property
     
    1010      <Spread>1.3</Spread>
    1111      <MaxRange>60.0</MaxRange>
    1212      <MinRange>16.0</MinRange>
     13      <TurretsOnly>true</TurretsOnly>
    1314    </Ranged>
    1415  </Attack>
    1516  <Footprint replace="">
     
    2829  <ResourceGatherer>
    2930    <BaseSpeed>0.75</BaseSpeed>
    3031  </ResourceGatherer>
     32  <TurretHolder>
     33    <TurretPoints>
     34      <Archer>
     35        <Template>units/pers_cavalry_archer_rider_a</Template>
     36        <X>1</X><Y>2.2</Y><Z>-4.0</Z>
     37      </Archer>
     38    </TurretPoints>
     39  </TurretHolder>
    3140  <VisualActor>
    3241    <Actor>units/persians/cavalry_archer_a.xml</Actor>
    3342  </VisualActor>
  • binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_e.xml

     
    2222  <ResourceGatherer>
    2323    <BaseSpeed>0.5</BaseSpeed>
    2424  </ResourceGatherer>
     25  <TurretHolder>
     26    <TurretPoints>
     27      <Archer>
     28        <Template>units/pers_cavalry_archer_rider_e</Template>
     29        <X>1</X><Y>2.2</Y><Z>-4.0</Z>
     30      </Archer>
     31    </TurretPoints>
     32  </TurretHolder>
    2533  <VisualActor>
    2634    <Actor>units/persians/cavalry_archer_e.xml</Actor>
    2735  </VisualActor>
  • binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_a.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity parent="template_turret">
     3  <VisualActor>
     4    <Actor>units/persians/cavalry_archer_a_r.xml</Actor>
     5  </VisualActor>
     6</Entity>
  • binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_e.xml

    Property changes on: binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_a.xml
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +text/xml
    \ No newline at end of property
     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity parent="template_turret">
     3  <VisualActor>
     4    <Actor>units/persians/cavalry_archer_e_r.xml</Actor>
     5  </VisualActor>
     6</Entity>