Ticket #563: AnimalAIm-06-02-2010.patch

File AnimalAIm-06-02-2010.patch, 25.0 KB (added by Badmadblacksad, 13 years ago)
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    55    "<a:example/>" +
    66    "<element name='FormationController'>" +
    77        "<data type='boolean'/>" +
     8    "</element>" +
     9    "<element name='IsAnimal'>" +
     10        "<data type='boolean'/>" +
    811    "</element>";
    912
    1013// Very basic stance support (currently just for test maps where we don't want
     
    4447        // ignore attacker
    4548    },
    4649
     50    "HealthChanged": function(msg) {
     51        // ignore
     52    },
    4753
    4854    // Formation handlers:
    4955
     
    788794        },
    789795
    790796    },
     797
     798    "ANIMAL": {
     799        "MoveCompleted": function() {
     800            // ignore spurious movement messages
     801            // (these can happen when stopping moving at the same time
     802            // as switching states)
     803        },
     804
     805        "MoveStarted": function() {
     806            // ignore spurious movement messages
     807        },
     808
     809        "HealthChanged": function(msg) {
     810            // If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
     811            if (msg.to == 0)
     812                this.SetNextState("CORPSE");
     813        },
     814
     815        "ResourceGather": function(msg) {
     816            // If someone's carving chunks of meat off us, then run away
     817            if(this.behavior == "SKITTISH" || this.behavior == "PASSIVE")
     818            {
     819                this.MoveAwayFrom(msg.gatherer, +this.FleeDistance);
     820                this.SetNextState("FLEEING");
     821                this.PlaySound("panic");
     822            }
     823            else if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE"
     824            || this.behavior == "DEFENSIVE")
     825            {
     826                this.Riposte(msg.gatherer);
     827            }
     828        },
     829
     830        "Attacked": function(msg) {
     831            if(this.behavior == "SKITTISH" || this.behavior == "PASSIVE")
     832            {
     833                this.MoveAwayFrom(msg.data.attacker, +this.FleeDistance);
     834                this.SetNextState("FLEEING");
     835                this.PlaySound("panic");
     836            }
     837            else if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE" || this.behavior == "DEFENSIVE")
     838            {
     839                this.Riposte(msg.data.attacker);
     840            }
     841        },
     842
     843
     844        "LosRangeUpdate": function(msg) {
     845            if(this.behavior == "SKITTISH")
     846            {
     847                if(msg.data.added.length>0)
     848                {
     849                    this.MoveAwayFrom(msg.data.added[0], +this.FleeDistance);
     850                    this.SetNextState("FLEEING");
     851                    this.PlaySound("panic");
     852                    return;
     853                }
     854            }
     855            // Start attacking one of the newly-seen enemy (if any)
     856            else if(this.behavior == "VIOLENT")
     857                this.AttackVisibleEntity(msg.data.added);
     858        },
     859
     860        "CORPSE": {
     861            "enter": function() {
     862                this.StopMoving();
     863            },
     864
     865            "Attacked": function(msg) {
     866                // Do nothing, because we're dead already
     867            },
     868        },
     869
     870        "ROAMING": {
     871            "enter": function() {
     872                // Walk in a random direction
     873                this.SelectAnimation("walk", false, this.GetWalkSpeed());
     874                this.MoveRandomly(+this.RoamDistance);
     875                // Set a random timer to switch to feeding state
     876                this.StartTimer(RandomInt(+this.RoamTimeMin, +this.RoamTimeMax));
     877            },
     878
     879            "leave": function() {
     880                this.StopTimer();
     881            },
     882
     883            "Timer": function(msg) {
     884                this.SetNextState("FEEDING");
     885            },
     886
     887            "MoveCompleted": function() {
     888                this.MoveRandomly(+this.RoamDistance);
     889            },
     890        },
     891
     892        "FEEDING": {
     893            "enter": function() {
     894                // Stop and eat for a while
     895                this.SelectAnimation("feeding");
     896                this.StopMoving();
     897
     898                if (this.behavior == "AGGRESSIVE" && this.losRangeQuery)
     899                {
     900                    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     901                    this.residEnts = rangeMan.ResetActiveQuery(this.losRangeQuery);
     902                }
     903
     904                this.StartTimer(RandomInt(+this.FeedTimeMin, +this.FeedTimeMax));
     905            },
     906
     907            "leave": function() {
     908                this.StopTimer();
     909            },
     910
     911            "MoveCompleted": function() { },
     912
     913            "Timer": function(msg) {
     914                if (this.behavior == "AGGRESSIVE" && this.losRangeQuery)
     915                {
     916                    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     917                    var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
     918                    for each (var ent in this.residEnts)
     919                    {
     920                        for each (var tent in ents)
     921                        {
     922                            if (ent == tent)
     923                            {
     924                                this.Riposte(ent);
     925                                this.StopTimer();
     926                                return;
     927                            }
     928                        }
     929                    }
     930                }
     931                this.SetNextState("ROAMING");
     932            },
     933        },
     934
     935        "FLEEING": {
     936            "enter": function() {
     937                // Run quickly
     938                var speed = this.GetRunSpeed();
     939                this.SelectAnimation("run", false, speed);
     940                this.SetMoveSpeed(speed);
     941            },
     942
     943            "leave": function() {
     944                // Reset normal speed
     945                this.SetMoveSpeed(this.GetWalkSpeed());
     946            },
     947
     948            "MoveCompleted": function() {
     949                // When we've run far enough, go back to the roaming state
     950                this.SetNextState("ROAMING");
     951            },
     952
     953            "Timer": function(msg) {
     954            }
     955        },
     956
     957        "Order.Attack": function(msg) {
     958            // Work out how to attack the given target
     959            var type = this.GetBestAttack();
     960            if (!type)
     961            {
     962                // Oops, we can't attack at all
     963                return;
     964            }
     965            this.attackType = type;
     966
     967            // Try to move within attack range
     968            if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     969            {
     970                // We've started walking to the given point
     971                this.SetNextState("COMBAT.APPROACHING");
     972            }
     973            else
     974            {
     975                // We are already at the target, or can't move at all,
     976                // so try attacking it from here.
     977                // TODO: need better handling of the can't-reach-target case
     978                this.SetNextState("COMBAT.ATTACKING");
     979            }
     980        },
     981
     982        "COMBAT": {
     983            "Attacked": function(msg) {
     984            },
     985
     986            "LosRangeUpdate": function(msg) {
     987            },
     988
     989            "APPROACHING": {
     990                "enter": function() {
     991                    this.SelectAnimation("walk", false, this.GetWalkSpeed());
     992                },
     993                "MoveCompleted": function() {
     994                    this.SetNextState("ATTACKING");
     995                },
     996            },
     997
     998            "ATTACKING": {
     999                "enter": function() {
     1000                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     1001                    this.attackTimers = cmpAttack.GetTimers(this.attackType);
     1002                    this.SelectAnimation("melee", false, 1.0, "attack");
     1003                    //this.SetAnimationSync(this.attackTimers.prepare, this.attackTimers.repeat);
     1004                    this.StartTimer(this.attackTimers.prepare, this.attackTimers.repeat);
     1005                    // TODO: we should probably only bother syncing projectile attacks, not melee
     1006                },
     1007                "leave": function() {
     1008                    this.StopTimer();
     1009                },
     1010                "Timer": function(msg) {
     1011                    // Check we can still reach the target
     1012                    if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1013                    {
     1014                        var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     1015                        cmpAttack.PerformAttack(this.attackType, this.order.data.target);
     1016                    }
     1017                    else
     1018                    {
     1019                        if (this.losRangeQuery)
     1020                        {
     1021                            // check if the raget is still in sight
     1022                            var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     1023                            var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
     1024                            for each (var ent in ents)
     1025                            {
     1026                                if (ent == this.order.data.target)
     1027                                {
     1028                                    // Try to chase after it
     1029                                    if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1030                                    {
     1031                                        this.SetNextState("CHASING");
     1032                                        this.StopTimer();
     1033                                        return;
     1034                                    }
     1035                                }
     1036                            }
     1037                        }
     1038                        this.SetNextState("ANIMAL.ROAMING");
     1039                    }
     1040                },
     1041            }, //end ANIMAL.COMBAT.ATTACKING
     1042
     1043            "CHASING": {
     1044                "enter": function() {
     1045                    this.SelectAnimation("walk", false, this.GetWalkSpeed());
     1046                },
     1047                "MoveCompleted": function() {
     1048                    this.SetNextState("ATTACKING");
     1049                },
     1050            },
     1051
     1052        }, //end ANIMAL.COMBAT
     1053    }, //end ANIMAL
    7911054};
    7921055
    7931056var UnitFsm = new FSM(UnitFsmSpec);
     
    8141077
    8151078UnitAI.prototype.OnCreate = function()
    8161079{
    817     if (this.IsFormationController())
    818         UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
     1080    if (this.template.IsAnimal == "true")
     1081    {
     1082        this.InitAnimalAI();
     1083        var startingState = this.NaturalBehaviour.toUpperCase(this.NaturalBehaviour);
     1084        this.behavior = startingState;
     1085
     1086        startingState = "ANIMAL.FEEDING";
     1087        UnitFsm.Init(this, startingState);
     1088    }
    8191089    else
    820         UnitFsm.Init(this, "INDIVIDUAL.IDLE");
     1090    {
     1091        if (this.IsFormationController())
     1092            UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
     1093        else
     1094            UnitFsm.Init(this, "INDIVIDUAL.IDLE");
     1095    }
    8211096};
    8221097
    8231098UnitAI.prototype.OnOwnershipChanged = function(msg)
     
    8541129    var range = cmpVision.GetRange();
    8551130   
    8561131    var players = [];
    857    
    858     if (owner != -1)
     1132
     1133if (owner != -1)
    8591134    {
    8601135        // If unit not just killed, get enemy players via diplomacy
    8611136        var player = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player);
     
    8631138        // Get our diplomacy array
    8641139        var diplomacy = player.GetDiplomacy();
    8651140        var numPlayers = playerMan.GetNumPlayers();
    866        
     1141
    8671142        for (var i = 1; i < numPlayers; ++i)
    8681143        {
    8691144            // Exclude gaia, allies, and self
    8701145            // TODO: How to handle neutral players - Special query to attack military only?
    871             if (i != owner && diplomacy[i - 1] < 0)
     1146            if (i != owner && (diplomacy[i - 1] < 0 || owner == 0))
    8721147                players.push(i);
    8731148        }
    8741149    }
     
    15461821    return true;
    15471822};
    15481823
     1824//Animal specific functions
    15491825
     1826UnitAI.prototype.OnHealthChanged = function(msg)
     1827{
     1828    UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
     1829};
     1830
     1831UnitAI.prototype.MoveRandomly = function(distance)
     1832{
     1833    // We want to walk in a random direction, but avoid getting stuck
     1834    // in obstacles or narrow spaces.
     1835    // So pick a circular range from approximately our current position,
     1836    // and move outwards to the nearest point on that circle, which will
     1837    // lead to us avoiding obstacles and moving towards free space.
     1838
     1839    // TODO: we probably ought to have a 'home' point, and drift towards
     1840    // that, so we don't spread out all across the whole map
     1841
     1842    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     1843    if (!cmpPosition)
     1844        return;
     1845
     1846    if (!cmpPosition.IsInWorld())
     1847        return;
     1848
     1849    var pos = cmpPosition.GetPosition();
     1850
     1851    var jitter = 0.5;
     1852
     1853    // Randomly adjust the range's center a bit, so we tend to prefer
     1854    // moving in random directions (if there's nothing in the way)
     1855    var tx = pos.x + (2*Math.random()-1)*jitter;
     1856    var tz = pos.z + (2*Math.random()-1)*jitter;
     1857
     1858    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1859    cmpMotion.MoveToPointRange(tx, tz, distance, distance);
     1860};
     1861
     1862UnitAI.prototype.MoveAwayFrom = function(ent, distance)
     1863{
     1864    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1865    cmpMotion.MoveToTargetRange(ent, distance, distance);
     1866};
     1867
     1868UnitAI.prototype.SetMoveSpeed = function(speed)
     1869{
     1870    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1871    cmpMotion.SetSpeed(speed);
     1872};
     1873
     1874UnitAI.prototype.InitAnimalAI = function()
     1875{
     1876    var cmpAnimalAI = Engine.QueryInterface(this.entity, IID_AnimalAI);
     1877    if (!cmpAnimalAI)
     1878        return;
     1879
     1880    this.NaturalBehaviour = cmpAnimalAI.GetNaturalBehaviour();
     1881    this.RoamDistance = cmpAnimalAI.GetRoamDistance();
     1882    this.FleeDistance = cmpAnimalAI.GetFleeDistance();
     1883    this.RoamTimeMin = cmpAnimalAI.GetRoamTimeMin();
     1884    this.RoamTimeMax = cmpAnimalAI.GetRoamTimeMax();
     1885    this.FeedTimeMin = cmpAnimalAI.GetFeedTimeMin();
     1886    this.FeedTimeMax = cmpAnimalAI.GetFeedTimeMax();
     1887};
     1888
     1889UnitAI.prototype.Riposte = function(attacker)
     1890{
     1891  // Default behaviour: attack back at our attacker
     1892  if (this.CanAttack(attacker))
     1893  {
     1894    this.order = { "type": "Attack", "data": { "target": attacker }};
     1895    UnitFsm.ProcessMessage(this, {"type": "Order.Attack", "data": this.order.data});
     1896    return true;
     1897  }
     1898  return false;
     1899};
     1900
    15501901Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
  • binaries/data/mods/public/simulation/components/AnimalAI.js

     
    3030        "<ref name='positiveDecimal'/>" +
    3131    "</element>";
    3232
    33 var AnimalFsmSpec = {
    34 
    35     "MoveCompleted": function() {
    36         // ignore spurious movement messages
    37         // (these can happen when stopping moving at the same time
    38         // as switching states)
    39     },
    40 
    41     "MoveStarted": function() {
    42         // ignore spurious movement messages
    43     },
    44 
    45     "HealthChanged": function(msg) {
    46         // If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
    47         if (msg.to == 0)
    48             this.SetNextState("CORPSE");
    49     },
    50 
    51     "CORPSE": {
    52         "enter": function() {
    53             this.StopMoving();
    54         },
    55 
    56         "Attacked": function(msg) {
    57             // Do nothing, because we're dead already
    58         },
    59     },
    60 
    61     "SKITTISH": {
    62 
    63         "Attacked": function(msg) {
    64             // If someone's attacking us, then run away
    65             this.MoveAwayFrom(msg.data.attacker, +this.template.FleeDistance);
    66             this.SetNextState("FLEEING");
    67             this.PlaySound("panic");
    68         },
    69 
    70         "ROAMING": {
    71             "enter": function() {
    72                 // Walk in a random direction
    73                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    74                 this.MoveRandomly(+this.template.RoamDistance);
    75                 // Set a random timer to switch to feeding state
    76                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    77             },
    78 
    79             "leave": function() {
    80                 this.StopTimer();
    81             },
    82 
    83             "Timer": function(msg) {
    84                 this.SetNextState("FEEDING");
    85             },
    86 
    87             "MoveCompleted": function() {
    88                 this.MoveRandomly(+this.template.RoamDistance);
    89             },
    90         },
    91 
    92         "FEEDING": {
    93             "enter": function() {
    94                 // Stop and eat for a while
    95                 this.SelectAnimation("feeding");
    96                 this.StopMoving();
    97                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    98             },
    99            
    100             "leave": function() {
    101                 this.StopTimer();
    102             },
    103 
    104             "MoveCompleted": function() { },
    105 
    106             "Timer": function(msg) {
    107                 this.SetNextState("ROAMING");
    108             },
    109         },
    110 
    111         "FLEEING": {
    112             "enter": function() {
    113                 // Run quickly
    114                 var speed = this.GetRunSpeed();
    115                 this.SelectAnimation("run", false, speed);
    116                 this.SetMoveSpeed(speed);
    117             },
    118 
    119             "leave": function() {
    120                 // Reset normal speed
    121                 this.SetMoveSpeed(this.GetWalkSpeed());
    122             },
    123 
    124             "MoveCompleted": function() {
    125                 // When we've run far enough, go back to the roaming state
    126                 this.SetNextState("ROAMING");
    127             },
    128         },
    129     },
    130 
    131     "PASSIVE": {
    132 
    133         "Attacked": function(msg) {
    134             // Do nothing, just let them kill us
    135         },
    136    
    137         "ROAMING": {
    138             "enter": function() {
    139                 // Walk in a random direction
    140                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    141                 this.MoveRandomly(+this.template.RoamDistance);
    142                 // Set a random timer to switch to feeding state
    143                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    144             },
    145 
    146             "leave": function() {
    147                 this.StopTimer();
    148             },
    149 
    150             "Timer": function(msg) {
    151                 this.SetNextState("FEEDING");
    152             },
    153 
    154             "MoveCompleted": function() {
    155                 this.MoveRandomly(+this.template.RoamDistance);
    156             },
    157         },
    158 
    159         "FEEDING": {
    160             "enter": function() {
    161                 // Stop and eat for a while
    162                 this.SelectAnimation("feeding");
    163                 this.StopMoving();
    164                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    165             },
    166            
    167             "leave": function() {
    168                 this.StopTimer();
    169             },
    170 
    171             "MoveCompleted": function() { },
    172 
    173             "Timer": function(msg) {
    174                 this.SetNextState("ROAMING");
    175             },
    176         },
    177     },
    178 
    179 };
    180 
    181 var AnimalFsm = new FSM(AnimalFsmSpec);
    182 
    18333AnimalAI.prototype.Init = function()
    18434{
    18535};
    18636
    187 // FSM linkage functions:
    188 
    189 AnimalAI.prototype.OnCreate = function()
     37AnimalAI.prototype.GetNaturalBehaviour = function()
    19038{
    191     var startingState = this.template.NaturalBehaviour;
    192     startingState = startingState.toUpperCase(startingState);
    193 
    194     if (startingState == "SKITTISH")
    195         startingState = startingState + ".FEEDING";
    196     else
    197         startingState = "PASSIVE.FEEDING";
    198 
    199     AnimalFsm.Init(this, startingState);
     39    return this.template.NaturalBehaviour;
    20040};
    20141
    202 AnimalAI.prototype.SetNextState = function(state)
     42AnimalAI.prototype.GetRoamDistance = function()
    20343{
    204     AnimalFsm.SetNextState(this, state);
     44    return this.template.RoamDistance;
    20545};
    20646
    207 AnimalAI.prototype.DeferMessage = function(msg)
     47AnimalAI.prototype.GetFleeDistance = function()
    20848{
    209     AnimalFsm.DeferMessage(this, msg);
     49    return this.template.FleeDistance;
    21050};
    21151
    212 AnimalAI.prototype.OnMotionChanged = function(msg)
     52AnimalAI.prototype.GetRoamTimeMin = function()
    21353{
    214     if (msg.starting && !msg.error)
    215     {
    216         AnimalFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
    217     }
    218     else if (!msg.starting || msg.error)
    219     {
    220         AnimalFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
    221     }
     54    return this.template.RoamTimeMin;
    22255};
    22356
    224 AnimalAI.prototype.OnAttacked = function(msg)
     57AnimalAI.prototype.GetRoamTimeMax = function()
    22558{
    226     AnimalFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
     59    return this.template.RoamTimeMax;
    22760};
    22861
    229 AnimalAI.prototype.OnHealthChanged = function(msg)
     62AnimalAI.prototype.GetFeedTimeMin = function()
    23063{
    231     AnimalFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
     64    return this.template.FeedTimeMin;
    23265};
    23366
    234 AnimalAI.prototype.TimerHandler = function(data, lateness)
     67AnimalAI.prototype.GetFeedTimeMax = function()
    23568{
    236     AnimalFsm.ProcessMessage(this, {"type": "Timer", "data": data, "lateness": lateness});
     69    return this.template.FeedTimeMax;
    23770};
    23871
    239 // Functions to be called by the FSM:
    240 
    241 AnimalAI.prototype.GetWalkSpeed = function()
    242 {
    243     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    244     return cmpMotion.GetWalkSpeed();
    245 };
    246 
    247 AnimalAI.prototype.GetRunSpeed = function()
    248 {
    249     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    250     return cmpMotion.GetRunSpeed();
    251 };
    252 
    253 AnimalAI.prototype.PlaySound = function(name)
    254 {
    255     PlaySound(name, this.entity);
    256 };
    257 
    258 AnimalAI.prototype.SelectAnimation = function(name, once, speed, sound)
    259 {
    260     var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    261     if (!cmpVisual)
    262         return;
    263 
    264     var soundgroup;
    265     if (sound)
    266     {
    267         var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
    268         if (cmpSound)
    269             soundgroup = cmpSound.GetSoundGroup(sound);
    270     }
    271 
    272     // Set default values if unspecified
    273     if (typeof once == "undefined")
    274         once = false;
    275     if (typeof speed == "undefined")
    276         speed = 1.0;
    277     if (typeof soundgroup == "undefined")
    278         soundgroup = "";
    279 
    280     cmpVisual.SelectAnimation(name, once, speed, soundgroup);
    281 };
    282 
    283 AnimalAI.prototype.MoveRandomly = function(distance)
    284 {
    285     // We want to walk in a random direction, but avoid getting stuck
    286     // in obstacles or narrow spaces.
    287     // So pick a circular range from approximately our current position,
    288     // and move outwards to the nearest point on that circle, which will
    289     // lead to us avoiding obstacles and moving towards free space.
    290 
    291     // TODO: we probably ought to have a 'home' point, and drift towards
    292     // that, so we don't spread out all across the whole map
    293 
    294     var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    295     if (!cmpPosition)
    296         return;
    297 
    298     if (!cmpPosition.IsInWorld())
    299         return;
    300 
    301     var pos = cmpPosition.GetPosition();
    302 
    303     var jitter = 0.5;
    304 
    305     // Randomly adjust the range's center a bit, so we tend to prefer
    306     // moving in random directions (if there's nothing in the way)
    307     var tx = pos.x + (2*Math.random()-1)*jitter;
    308     var tz = pos.z + (2*Math.random()-1)*jitter;
    309 
    310     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    311     cmpMotion.MoveToPointRange(tx, tz, distance, distance);
    312 };
    313 
    314 AnimalAI.prototype.MoveAwayFrom = function(ent, distance)
    315 {
    316     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    317     cmpMotion.MoveToTargetRange(ent, distance, distance);
    318 };
    319 
    320 AnimalAI.prototype.StopMoving = function()
    321 {
    322     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    323     cmpMotion.StopMoving();
    324 };
    325 
    326 AnimalAI.prototype.SetMoveSpeed = function(speed)
    327 {
    328     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    329     cmpMotion.SetSpeed(speed);
    330 };
    331 
    332 AnimalAI.prototype.StartTimer = function(interval, data)
    333 {
    334     if (this.timer)
    335         error("Called StartTimer when there's already an active timer");
    336 
    337     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    338     this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);
    339 };
    340 
    341 AnimalAI.prototype.StopTimer = function()
    342 {
    343     if (!this.timer)
    344         return;
    345 
    346     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    347     cmpTimer.CancelTimer(this.timer);
    348     this.timer = undefined;
    349 };
    350 
    35172Engine.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/special/formation.xml

     
    88  <Formation/>
    99  <UnitAI>
    1010    <FormationController>true</FormationController>
     11    <IsAnimal>false</IsAnimal>
    1112  </UnitAI>
    1213  <UnitMotion>
    1314    <FormationController>true</FormationController>
  • 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>
  • 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.xml

     
    2525      <Speed>6.0</Speed>
    2626    </Run>
    2727  </UnitMotion>
    28   <UnitAI disable=""/>
     28  <UnitAI>
     29    <FormationController>false</FormationController>
     30    <IsAnimal>true</IsAnimal>
     31  </UnitAI>
    2932  <AnimalAI>
    3033    <RoamDistance>8.0</RoamDistance>
    3134    <FleeDistance>32.0</FleeDistance>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_fish.xml

     
    1616    <Floating>true</Floating>
    1717  </Position>
    1818  <UnitMotion disable=""/>
     19  <UnitAI disable=""/>
    1920  <AnimalAI disable=""/>
    2021  <ResourceSupply>
    2122    <KillBeforeGather>false</KillBeforeGather>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    99  </Minimap>
    1010  <UnitAI>
    1111    <FormationController>false</FormationController>
     12    <IsAnimal>false</IsAnimal>
    1213  </UnitAI>
    1314  <Cost>
    1415    <Population>1</Population>