Ticket #563: animalAI.patch

File animalAI.patch, 16.7 KB (added by Badmadblacksad, 14 years ago)
  • binaries/data/mods/public/simulation/components/AnimalAI.js

     
    3131    "</element>";
    3232
    3333var AnimalFsmSpec = {
    34 
    35     "SKITTISH": {
    36 
    37         "ResourceGather": function(msg) {
    38             // If someone's carving chunks of meat off us, then run away
     34    "ResourceGather": function(msg) {
     35        // If someone's carving chunks of meat off us, then run away
     36        if(this.behavior == "SKITTISH" || this.behavior == "PASSIVE")
     37        {
    3938            this.MoveAwayFrom(msg.gatherer, +this.template.FleeDistance);
    4039            this.SetNextState("FLEEING");
    4140            this.PlaySound("panic");
     41        }
     42        else if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE"
     43           || this.behavior == "DEFENSIVE")
     44        {
     45            this.Riposte(msg.gatherer);
     46        }
     47    },
     48
     49    "Attacked": function(msg) {
     50        if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE"
     51           || this.behavior == "DEFENSIVE")
     52        {
     53            this.Riposte(msg.data.attacker);
     54        }
     55    },
     56
     57    "LosRangeUpdate": function(msg) {       
     58        if(this.behavior == "SKITTISH")
     59        {
     60            if(msg.data.added.length>0)
     61            {
     62                this.MoveAwayFrom(msg.data.added[0], +this.template.FleeDistance);
     63                this.SetNextState("FLEEING");
     64                this.PlaySound("panic");
     65                return;
     66            }
     67        }
     68        // Start attacking one of the newly-seen enemy (if any)
     69        else if(this.behavior == "VIOLENT")
     70            this.AttackVisibleEntity(msg.data.added);
     71    },
     72
     73    "FEEDING": {
     74        "enter": function() {
     75            // Stop and eat for a while
     76            this.SelectAnimation("feeding");
     77            this.StopMoving();
     78
     79            if (this.behavior == "AGGRESSIVE" && this.losRangeQuery)
     80            {
     81                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     82                this.residEnts = rangeMan.ResetActiveQuery(this.losRangeQuery);
     83            }
     84
     85
     86            this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    4287        },
    4388
    44         "ROAMING": {
    45             "enter": function() {
    46                 // Walk in a random direction
    47                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    48                 this.MoveRandomly(+this.template.RoamDistance);
    49                 // Set a random timer to switch to feeding state
    50                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    51             },
     89        "leave": function() {
     90            this.StopTimer();
     91        },
    5292
    53             "leave": function() {
    54                 this.StopTimer();
    55             },
     93        "MoveStopped": function() { },
    5694
    57             "Timer": function(msg) {
    58                 this.SetNextState("FEEDING");
    59             },
     95        "Timer": function(msg) {
     96            if (this.behavior == "AGGRESSIVE" && this.losRangeQuery)
     97            {
     98                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     99                var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
     100                for each (var ent in this.residEnts)
     101                {
     102                    for each (var tent in ents)
     103                    {
     104                        if (ent == tent)
     105                        {
     106                            this.Riposte(ent);
     107                            this.StopTimer();
     108                            return;
     109                        }
     110                    }
     111                }
     112            }
     113            this.SetNextState("ROAMING");
     114        },
     115    },
    60116
    61             "MoveStopped": function() {
    62                 this.MoveRandomly(+this.template.RoamDistance);
    63             },
     117    "ROAMING": {
     118        "enter": function() {
     119            // Walk in a random direction
     120            this.SelectAnimation("walk", false, this.GetWalkSpeed());
     121            this.MoveRandomly(+this.template.RoamDistance);
     122            // Set a random timer to switch to feeding state
     123            this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    64124        },
    65125
    66         "FEEDING": {
    67             "enter": function() {
    68                 // Stop and eat for a while
    69                 this.SelectAnimation("feeding");
    70                 this.StopMoving();
    71                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    72             },
    73            
    74             "leave": function() {
    75                 this.StopTimer();
    76             },
     126        "leave": function() {
     127            this.StopTimer();
     128        },
    77129
    78             "MoveStopped": function() { },
     130        "Timer": function(msg) {
     131            this.SetNextState("FEEDING");
     132        },
    79133
    80             "Timer": function(msg) {
    81                 this.SetNextState("ROAMING");
    82             },
     134        "MoveStopped": function() {
     135            this.MoveRandomly(+this.template.RoamDistance);
    83136        },
     137    },
    84138
    85         "FLEEING": {
    86             "enter": function() {
    87                 // Run quickly
    88                 var speed = this.GetRunSpeed();
    89                 this.SelectAnimation("run", false, speed);
    90                 this.SetMoveSpeed(speed);
    91             },
     139    "FLEEING": {
     140        "enter": function() {
     141            // Run quickly
     142            var speed = this.GetRunSpeed();
     143            this.SelectAnimation("run", false, speed);
     144            this.SetMoveSpeed(speed);
     145        },
    92146
    93             "leave": function() {
    94                 // Reset normal speed
    95                 this.SetMoveSpeed(this.GetWalkSpeed());
    96             },
     147        "leave": function() {
     148            // Reset normal speed
     149            this.SetMoveSpeed(this.GetWalkSpeed());
     150        },
    97151
    98             "MoveStopped": function() {
    99                 // When we've run far enough, go back to the roaming state
    100                 this.SetNextState("ROAMING");
    101             },
     152        "MoveStopped": function() {
     153            // When we've run far enough, go back to the roaming state
     154            this.SetNextState("ROAMING");
    102155        },
     156
     157        "Timer": function(msg) {
     158
     159        }
    103160    },
    104161
    105     "PASSIVE": {
    106162
     163    "Order.Attack": function(msg) {
     164        // Work out how to attack the given target
     165        var type = this.GetBestAttack();
     166        if (!type)
     167        {
     168            // Oops, we can't attack at all
     169            return;
     170        }
     171        this.attackType = type;
     172
     173        // Try to move within attack range
     174        if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     175        {
     176            // We've started walking to the given point
     177            this.SetNextState("COMBAT.APPROACHING");
     178        }
     179        else
     180        {
     181            // We are already at the target, or can't move at all,
     182            // so try attacking it from here.
     183            // TODO: need better handling of the can't-reach-target case
     184            this.SetNextState("COMBAT.ATTACKING");
     185        }
     186    },
     187
     188    "COMBAT": {
    107189        "ResourceGather": function(msg) {
    108             // If someone's carving chunks of meat off us, then run away
    109 //          this.MoveAwayFrom(msg.gatherer, +this.template.FleeDistance);
    110 //          this.SetNextState("FLEEING");
    111 //          this.PlaySound("panic");
    112190        },
    113191
    114         "ROAMING": {
     192        "Attacked": function(msg) {
     193        },
     194
     195        "LosRangeUpdate": function(msg) {
     196        },
     197
     198        "APPROACHING": {
    115199            "enter": function() {
    116                 // Walk in a random direction
    117200                this.SelectAnimation("walk", false, this.GetWalkSpeed());
    118                 this.MoveRandomly(+this.template.RoamDistance);
    119                 // Set a random timer to switch to feeding state
    120                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    121201            },
    122202
    123             "leave": function() {
    124                 this.StopTimer();
    125             },
    126 
    127             "Timer": function(msg) {
    128                 this.SetNextState("FEEDING");
    129             },
    130 
    131203            "MoveStopped": function() {
    132                 this.MoveRandomly(+this.template.RoamDistance);
     204                this.SetNextState("ATTACKING");
    133205            },
    134206        },
    135207
    136         "FEEDING": {
     208        "ATTACKING": {
    137209            "enter": function() {
    138                 // Stop and eat for a while
    139                 this.SelectAnimation("feeding");
    140                 this.StopMoving();
    141                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
     210                var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     211                this.attackTimers = cmpAttack.GetTimers(this.attackType);
     212
     213                this.SelectAnimation("melee", false, 1.0, "attack");
     214                //this.SetAnimationSync(this.attackTimers.prepare, this.attackTimers.repeat);
     215                this.StartTimer(this.attackTimers.prepare, this.attackTimers.repeat);
     216                // TODO: we should probably only bother syncing projectile attacks, not melee
    142217            },
    143            
     218
    144219            "leave": function() {
    145220                this.StopTimer();
    146221            },
    147222
    148             "MoveStopped": function() { },
     223            "Timer": function(msg) {
     224                // Check we can still reach the target
     225                if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     226                {
     227                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     228                    cmpAttack.PerformAttack(this.attackType, this.order.data.target);
     229                }
     230                else
     231                {
     232                    if (this.losRangeQuery)
     233                    {
     234                        // check if the raget is still in sight
     235                        var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     236                        var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
    149237
    150             "Timer": function(msg) {
    151                 this.SetNextState("ROAMING");
     238                        for each (var ent in ents)
     239                        {
     240                            if (ent == this.order.data.target)
     241                            {
     242                                // Try to chase after it
     243                                if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     244                                {
     245                                    this.SetNextState("COMBAT.CHASING");
     246                                    this.StopTimer();
     247                                    return;
     248                                }
     249                            }
     250                        }
     251                    }
     252                    this.SetNextState("ROAMING");
     253                }
    152254            },
    153255        },
    154256
    155         "FLEEING": {
     257        "CHASING": {
    156258            "enter": function() {
    157                 // Run quickly
    158 //              var speed = this.GetRunSpeed();
    159 //              this.SelectAnimation("run", false, speed);
    160 //              this.SetMoveSpeed(speed);
     259                this.SelectAnimation("walk", false, this.GetWalkSpeed());
    161260            },
    162261
    163             "leave": function() {
    164                 // Reset normal speed
    165                 this.SetMoveSpeed(this.GetWalkSpeed());
    166             },
    167 
    168262            "MoveStopped": function() {
    169                 // When we've run far enough, go back to the roaming state
    170                 this.SetNextState("ROAMING");
     263                this.SetNextState("ATTACKING");
    171264            },
    172265        },
    173266    },
     
    188281    var startingState = this.template.NaturalBehaviour;
    189282    startingState = startingState.toUpperCase(startingState);
    190283
    191     if (startingState == "SKITTISH")
    192         startingState = startingState + ".FEEDING";
    193     else
    194         startingState = "PASSIVE.FEEDING";
     284    this.behavior = startingState;
     285    AnimalFsm.Init(this, "FEEDING");
     286};
    195287
    196     AnimalFsm.Init(this, startingState);
     288AnimalAI.prototype.OnOwnershipChanged = function(msg)
     289{
     290    this.SetupRangeQuery(msg.to);
    197291};
    198292
    199293AnimalAI.prototype.SetNextState = function(state)
     
    235329
    236330AnimalAI.prototype.TimerHandler = function(data, lateness)
    237331{
     332    // Reset the timer
     333    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     334    this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", data.timerRepeat - lateness, data);
     335
     336
    238337    this.PushMessage({"type": "Timer", "data": data, "lateness": lateness});
    239338};
    240339
     
    337436        error("Called StartTimer when there's already an active timer");
    338437
    339438    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    340     this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);
     439    this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, { "timerRepeat": data});
    341440};
    342441
    343442AnimalAI.prototype.StopTimer = function()
     
    350449    this.timer = undefined;
    351450};
    352451
     452AnimalAI.prototype.OnRangeUpdate = function(msg)
     453{
     454    if (msg.tag == this.losRangeQuery)
     455        AnimalFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg});
     456};
     457
     458AnimalAI.prototype.OnAttacked = function(msg)
     459{
     460    AnimalFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
     461};
     462
     463AnimalAI.prototype.CanAttack = function(target)
     464{
     465    // Verify that we're able to respond to Attack commands
     466    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     467    if (!cmpAttack)
     468        return false;
     469
     470    // TODO: verify that this is a valid target
     471
     472    return true;
     473};
     474
     475AnimalAI.prototype.CheckTargetRange = function(target, iid, type)
     476{
     477    var cmpRanged = Engine.QueryInterface(this.entity, iid);
     478    var range = cmpRanged.GetRange(type);
     479
     480    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     481    return cmpMotion.IsInAttackRange(target, range.min, range.max);
     482};
     483
     484AnimalAI.prototype.MoveToTargetRange = function(target, iid, type)
     485{
     486    var cmpRanged = Engine.QueryInterface(this.entity, iid);
     487    var range = cmpRanged.GetRange(type);
     488
     489    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     490    return cmpMotion.MoveToAttackRange(target, range.min, range.max);
     491};
     492
     493AnimalAI.prototype.GetBestAttack = function()
     494{
     495    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     496    if (!cmpAttack)
     497        return undefined;
     498    return cmpAttack.GetBestAttack();
     499};
     500
     501AnimalAI.prototype.OnDestroy = function()
     502{
     503    // Clean up any timers that are now obsolete
     504    this.StopTimer();
     505
     506    // Clean up range queries
     507    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     508    if (this.losRangeQuery)
     509        rangeMan.DestroyActiveQuery(this.losRangeQuery);
     510};
     511
     512// Set up a range query for all enemy units within LOS range
     513// which can be attacked.
     514// This should be called whenever our ownership changes.
     515AnimalAI.prototype.SetupRangeQuery = function(owner)
     516{
     517    var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
     518    if (!cmpVision)
     519        return;
     520
     521    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     522    var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     523
     524    if (this.losRangeQuery)
     525        rangeMan.DestroyActiveQuery(this.losRangeQuery);
     526
     527    var range = cmpVision.GetRange();
     528
     529    // Find all players (i.e. exclude Gaia)
     530    var players = [];
     531    for (var i = 1; i < playerMan.GetNumPlayers(); ++i)
     532        if (i != owner)
     533            players.push(i);
     534
     535    this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range, players, IID_DamageReceiver);
     536    rangeMan.EnableActiveQuery(this.losRangeQuery);
     537};
     538
     539/**
     540 * Try to find one of the given entities which can be attacked,
     541 * and start attacking it.
     542 * Returns true if it found something to attack.
     543 */
     544AnimalAI.prototype.AttackVisibleEntity = function(ents)
     545{
     546    for each (var target in ents)
     547    {
     548        if (this.CanAttack(target))
     549        {
     550            this.order = { "type": "Attack", "data": { "target": target }};
     551            AnimalFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
     552            return true;
     553        }
     554    }
     555    return false;
     556};
     557
     558AnimalAI.prototype.Riposte = function(attacker)
     559{
     560    // Default behaviour: attack back at our attacker
     561    if (this.CanAttack(attacker))
     562    {
     563        this.order = { "type": "Attack", "data": { "target": attacker }};
     564        AnimalFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
     565        if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     566        {
     567            // We've started walking to the given point
     568            this.SetNextState("COMBAT.APPROACHING");
     569        }
     570        else
     571        {
     572            // We are already at the target, or can't move at all,
     573            // so try attacking it from here.
     574            // TODO: need better handling of the can't-reach-target case
     575            this.SetNextState("COMBAT.ATTACKING");
     576        }
     577        return true;
     578    }
     579    return false;
     580};
     581
     582
    353583Engine.RegisterComponentType(IID_AnimalAI, "AnimalAI", AnimalAI);
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml

     
    55  <AnimalAI>
    66    <NaturalBehaviour>aggressive</NaturalBehaviour>
    77  </AnimalAI>
     8  <Attack>
     9    <Melee>
     10      <Hack>1.0</Hack>
     11      <Pierce>1.0</Pierce>
     12      <Crush>0.0</Crush>
     13      <MaxRange>4.0</MaxRange>
     14      <RepeatTime>1000</RepeatTime>
     15    </Melee>
     16  </Attack>
    817</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml

     
    55  <AnimalAI>
    66    <NaturalBehaviour>violent</NaturalBehaviour>
    77  </AnimalAI>
     8  <Attack>
     9    <Melee>
     10      <Hack>1.0</Hack>
     11      <Pierce>1.0</Pierce>
     12      <Crush>0.0</Crush>
     13      <MaxRange>4.0</MaxRange>
     14      <RepeatTime>1000</RepeatTime>
     15    </Melee>
     16  </Attack>
    817</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml

     
    33  <Identity>
    44  </Identity>
    55  <AnimalAI>
    6     <NaturalBehaviour>violent</NaturalBehaviour>
     6    <NaturalBehaviour>aggressive</NaturalBehaviour>
    77  </AnimalAI>
     8  <Attack>
     9    <Melee>
     10      <Hack>1.0</Hack>
     11      <Pierce>1.0</Pierce>
     12      <Crush>0.0</Crush>
     13      <MaxRange>4.0</MaxRange>
     14      <RepeatTime>1000</RepeatTime>
     15    </Melee>
     16  </Attack>
    817</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml

     
    55  <AnimalAI>
    66    <NaturalBehaviour>violent</NaturalBehaviour>
    77  </AnimalAI>
     8  <Attack>
     9    <Melee>
     10      <Hack>1.0</Hack>
     11      <Pierce>1.0</Pierce>
     12      <Crush>0.0</Crush>
     13      <MaxRange>4.0</MaxRange>
     14      <RepeatTime>1000</RepeatTime>
     15    </Melee>
     16  </Attack>
    817</Entity>