Ticket #563: AnimalAImm-16-02-2011.patch

File AnimalAImm-16-02-2011.patch, 33.0 KB (added by Badmadblacksad, 13 years ago)
  • binaries/data/mods/public/simulation/components/Foundation.js

     
    9090                    if (cmpUnitAI)
    9191                        cmpUnitAI.LeaveFoundation(this.entity);
    9292
    93                     var cmpAnimalAI = Engine.QueryInterface(ent, IID_AnimalAI);
    94                     if (cmpAnimalAI)
    95                         cmpAnimalAI.LeaveFoundation(this.entity);
    96 
    97                     // TODO: What if an obstruction has no UnitAI/AnimalAI?
     93                    // TODO: What if an obstruction has no UnitAI?
    9894                }
    9995
    10096                // TODO: maybe we should tell the builder to use a special
  • binaries/data/mods/public/simulation/components/interfaces/AnimalAI.js

     
    1 Engine.RegisterInterface("AnimalAI");
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    22
    33UnitAI.prototype.Schema =
    44    "<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" +
    5     "<a:example/>" +
     5    "<a:example/>" +   
    66    "<element name='FormationController'>" +
    77        "<data type='boolean'/>" +
    8     "</element>";
     8    "</element>" +
     9    "<optional>" +
     10        "<interleave>" +
     11            "<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
     12                "<choice>" +
     13                    "<value a:help='Will actively attack any unit it encounters, even if not threatened'>violent</value>" +
     14                    "<value a:help='Will attack nearby units if it feels threatened (if they linger within LOS for too long)'>aggressive</value>" +
     15                    "<value a:help='Will attack nearby units if attacked'>defensive</value>" +
     16                    "<value a:help='Will never attack units'>passive</value>" +
     17                    "<value a:help='Will never attack units. Will typically attempt to flee for short distances when units approach'>skittish</value>" +
     18                "</choice>" +
     19            "</element>" +
     20            "<element name='RoamDistance'>" +
     21                "<ref name='positiveDecimal'/>" +
     22            "</element>" +
     23            "<element name='FleeDistance'>" +
     24                "<ref name='positiveDecimal'/>" +
     25            "</element>" +
     26            "<element name='RoamTimeMin'>" +
     27                "<ref name='positiveDecimal'/>" +
     28            "</element>" +
     29            "<element name='RoamTimeMax'>" +
     30                "<ref name='positiveDecimal'/>" +
     31            "</element>" +
     32            "<element name='FeedTimeMin'>" +
     33                "<ref name='positiveDecimal'/>" +
     34            "</element>" +
     35            "<element name='FeedTimeMax'>" +
     36                "<ref name='positiveDecimal'/>" +
     37            "</element>"+
     38        "</interleave>" +
     39    "</optional>";
    940
    1041// Very basic stance support (currently just for test maps where we don't want
    1142// everyone killing each other immediately after loading)
     
    4273
    4374    "Attacked": function(msg) {
    4475        // ignore attacker
     76    }, 
     77
     78    "HealthChanged": function(msg) {
     79        // ignore
    4580    },
    4681
    47 
    4882    // Formation handlers:
    4983
    5084    "FormationLeave": function(msg) {
     
    111145        if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
    112146        {
    113147            // We've started walking to the given point
    114             this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
     148            if (this.template.NaturalBehaviour)
     149                this.SetNextState("ANIMAL.COMBAT.APPROACHING");
     150            else
     151                this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
    115152        }
    116153        else
    117154        {
    118155            // We are already at the target, or can't move at all,
    119156            // so try attacking it from here.
    120157            // TODO: need better handling of the can't-reach-target case
    121             this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
     158            if (this.template.NaturalBehaviour)
     159                this.SetNextState("ANIMAL.COMBAT.ATTACKING");
     160            else
     161                this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
    122162        }
    123163    },
    124164
     
    454494
    455495                    // Can't reach it, or it doesn't exist any more - give up
    456496                    this.FinishOrder();
    457                            
     497
    458498                    // TODO: see if we can switch to a new nearby enemy
    459499                },
    460 
    461                 // TODO: respond to target deaths immediately, rather than waiting
    462                 // until the next Timer event
    463500            },
    464501
    465502            "CHASING": {
     
    841878        },
    842879
    843880    },
     881
     882    "ANIMAL": {
     883
     884        "HealthChanged": function(msg) {
     885            // If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
     886            if (msg.to == 0)
     887                this.SetNextState("CORPSE");
     888        },
     889
     890        "Attacked": function(msg) {
     891            if (this.template.NaturalBehaviour == "skittish" || this.template.NaturalBehaviour == "passive")
     892            {
     893                this.MoveAwayFrom(msg.data.attacker, +this.template.FleeDistance);
     894                this.SetNextState("FLEEING");
     895                this.PlaySound("panic");
     896            }
     897            else if(this.template.NaturalBehaviour == "violent" || this.template.NaturalBehaviour == "aggressive" || this.template.NaturalBehaviour == "defensive")
     898            {
     899                print("violent attack\n");
     900                if(this.CanAttack(msg.data.attacker))
     901                    this.PushOrderFront("Attack", { "target": msg.data.attacker });
     902            }
     903        },
     904
     905        "LeaveFoundation": function(msg) {
     906            // Run away from the foundation
     907            this.MoveAwayFrom(msg.target, +this.template.FleeDistance);
     908            this.SetNextState("FLEEING");
     909            this.PlaySound("panic");
     910        },
     911
     912        "CORPSE": {
     913            "enter": function() {
     914                this.StopMoving();
     915            },
     916
     917            "Attacked": function(msg) {
     918                // Do nothing, because we're dead already
     919            },
     920
     921            "LeaveFoundation": function(msg) {
     922                // We can't walk away from the foundation (since we're dead),
     923                // but we mustn't block its construction (since the builders would get stuck),
     924                // and we don't want to trick gatherers into trying to reach us when
     925                // we're stuck in the middle of a building, so just delete our corpse.
     926                Engine.DestroyEntity(this.entity);
     927            },
     928        },
     929
     930        "ROAMING": {
     931            "enter": function() {
     932                // Walk in a random direction
     933                this.SelectAnimation("walk", false, this.GetWalkSpeed());
     934                this.MoveRandomly(+this.template.RoamDistance);
     935                // Set a random timer to switch to feeding state
     936                this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
     937            },
     938
     939            "leave": function() {
     940                this.StopTimer();
     941            },
     942
     943            "LosRangeUpdate": function(msg) {
     944                if (this.template.NaturalBehaviour == "skittish")
     945                {
     946                    if (msg.data.added.length > 0)
     947                    {
     948                        this.MoveAwayFrom(msg.data.added[0], +this.template.FleeDistance);
     949                        this.SetNextState("FLEEING");
     950                        this.PlaySound("panic");
     951                        return;
     952                    }
     953                }
     954                // Start attacking one of the newly-seen enemy (if any)
     955                else if (this.template.NaturalBehaviour == "violent" || this.template.NaturalBehaviour == "aggressive")
     956                    this.AttackVisibleEntity(msg.data.added);
     957            },
     958
     959            "Timer": function(msg) {
     960                this.SetNextState("FEEDING");
     961            },
     962
     963            "MoveCompleted": function() {
     964                this.MoveRandomly(+this.template.RoamDistance);
     965            },
     966        },
     967
     968        "FEEDING": {
     969            "enter": function() {
     970                // Stop and eat for a while
     971                this.SelectAnimation("feeding");
     972                this.StopMoving();
     973                this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
     974            },
     975
     976            "leave": function() {
     977                this.StopTimer();
     978            },
     979
     980            "LosRangeUpdate": function(msg) {
     981                if (this.template.NaturalBehaviour == "skittish")
     982                {
     983                    if (msg.data.added.length > 0)
     984                    {
     985                        this.MoveAwayFrom(msg.data.added[0], +this.template.FleeDistance);
     986                        this.SetNextState("FLEEING");
     987                        this.PlaySound("panic");
     988                        return;
     989                    }
     990                }
     991                // Start attacking one of the newly-seen enemy (if any)
     992                else if (this.template.NaturalBehaviour == "violent")
     993                    this.AttackVisibleEntity(msg.data.added);
     994            },
     995
     996            "MoveCompleted": function() { },
     997
     998            "Timer": function(msg) {
     999                this.SetNextState("ROAMING");
     1000            },
     1001        },
     1002
     1003        "FLEEING": {
     1004            "enter": function() {
     1005                // Run quickly
     1006                var speed = this.GetRunSpeed();
     1007                this.SelectAnimation("run", false, speed);
     1008                this.SetMoveSpeed(speed);
     1009            },
     1010
     1011            "leave": function() {
     1012                // Reset normal speed
     1013                this.SetMoveSpeed(this.GetWalkSpeed());
     1014            },
     1015
     1016            "MoveCompleted": function() {
     1017                // When we've run far enough, go back to the roaming state
     1018                this.SetNextState("ROAMING");
     1019            },
     1020        },
     1021
     1022        "COMBAT": {
     1023            "Attacked": function(msg) {
     1024                // If we're already in combat mode, ignore anyone else
     1025                // who's attacking us
     1026            },
     1027
     1028            "APPROACHING": {
     1029                "enter": function () {
     1030                    this.SelectAnimation("move");
     1031                },
     1032
     1033                "MoveCompleted": function() {
     1034                    this.SetNextState("ATTACKING");
     1035                },
     1036            },
     1037
     1038            "ATTACKING": {
     1039                "enter": function() {
     1040                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     1041                    this.attackTimers = cmpAttack.GetTimers(this.attackType);
     1042
     1043                    this.SelectAnimation("melee", false, 1.0, "attack");
     1044                    this.SetAnimationSync(this.attackTimers.prepare, this.attackTimers.repeat);
     1045                    this.StartTimer(this.attackTimers.prepare, this.attackTimers.repeat);
     1046                },
     1047
     1048                "leave": function() {
     1049                    this.StopTimer();
     1050                },
     1051
     1052                "Timer": function(msg) {
     1053                    // Check the target is still alive
     1054                    if (this.TargetIsAlive(this.order.data.target))
     1055                    {
     1056                        // Check we can still reach the target
     1057                        if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1058                        {
     1059                            var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     1060                            cmpAttack.PerformAttack(this.attackType, this.order.data.target);
     1061                            return;
     1062                        }
     1063
     1064                        // Can't reach it - try to chase after it
     1065                        if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1066                        {
     1067                            this.SetNextState("COMBAT.CHASING");
     1068                            return;
     1069                        }
     1070                    }
     1071
     1072                    // Can't reach it, or it doesn't exist any more - give up
     1073                    this.SetNextState("ANIMAL.ROAMING");
     1074                },
     1075
     1076                // TODO: respond to target deaths immediately, rather than waiting
     1077                // until the next Timer event
     1078            },
     1079
     1080            "CHASING": {
     1081                "enter": function () {
     1082                    this.SelectAnimation("move");
     1083                },
     1084
     1085                "MoveCompleted": function() {
     1086                    this.SetNextState("ATTACKING");
     1087                },
     1088            },
     1089        },
     1090
     1091
     1092    },
    8441093};
    8451094
    8461095var UnitFsm = new FSM(UnitFsmSpec);
     
    8601109    return (this.template.FormationController == "true");
    8611110};
    8621111
     1112UnitAI.prototype.IsAnAnimal = function()
     1113{
     1114return (this.template.NaturalBehaviour ? true : false);
     1115};
     1116
    8631117UnitAI.prototype.IsIdle = function()
    8641118{
    8651119    return this.isIdle;
     
    8671121
    8681122UnitAI.prototype.OnCreate = function()
    8691123{
    870     if (this.IsFormationController())
     1124    if(this.IsAnAnimal())
     1125        UnitFsm.Init(this, "ANIMAL.FEEDING");
     1126    else if (this.IsFormationController())
    8711127        UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
    8721128    else
    8731129        UnitFsm.Init(this, "INDIVIDUAL.IDLE");
     
    9211177        {
    9221178            // Exclude gaia, allies, and self
    9231179            // TODO: How to handle neutral players - Special query to attack military only?
    924             if (i != owner && diplomacy[i - 1] < 0)
     1180            if (i != owner && (diplomacy[i - 1] < 0 || owner == 0))
    9251181                players.push(i);
    9261182        }
    9271183    }
     
    16141870    return true;
    16151871};
    16161872
     1873//Animal specific functions
     1874UnitAI.prototype.OnHealthChanged = function(msg)
     1875{
     1876    UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
     1877};
    16171878
     1879UnitAI.prototype.MoveRandomly = function(distance)
     1880{
     1881    // We want to walk in a random direction, but avoid getting stuck
     1882    // in obstacles or narrow spaces.
     1883    // So pick a circular range from approximately our current position,
     1884    // and move outwards to the nearest point on that circle, which will
     1885    // lead to us avoiding obstacles and moving towards free space.
     1886
     1887    // TODO: we probably ought to have a 'home' point, and drift towards
     1888    // that, so we don't spread out all across the whole map
     1889
     1890    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     1891    if (!cmpPosition)
     1892        return;
     1893
     1894    if (!cmpPosition.IsInWorld())
     1895        return;
     1896
     1897    var pos = cmpPosition.GetPosition();
     1898
     1899    var jitter = 0.5;
     1900
     1901    // Randomly adjust the range's center a bit, so we tend to prefer
     1902    // moving in random directions (if there's nothing in the way)
     1903    var tx = pos.x + (2*Math.random()-1)*jitter;
     1904    var tz = pos.z + (2*Math.random()-1)*jitter;
     1905
     1906    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1907    cmpMotion.MoveToPointRange(tx, tz, distance, distance);
     1908};
     1909
     1910UnitAI.prototype.MoveAwayFrom = function(ent, distance)
     1911{
     1912    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1913    cmpMotion.MoveToTargetRange(ent, distance, distance);
     1914};
     1915
     1916UnitAI.prototype.SetMoveSpeed = function(speed)
     1917{
     1918    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1919    cmpMotion.SetSpeed(speed);
     1920};
     1921
     1922
     1923
     1924
    16181925Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
  • binaries/data/mods/public/simulation/components/AnimalAI.js

     
    1 function AnimalAI() {}
    2 
    3 AnimalAI.prototype.Schema =
    4     "<a:example/>" +
    5     "<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
    6         "<choice>" +
    7             "<value a:help='Will actively attack any unit it encounters, even if not threatened'>violent</value>" +
    8             "<value a:help='Will attack nearby units if it feels threatened (if they linger within LOS for too long)'>aggressive</value>" +
    9             "<value a:help='Will attack nearby units if attacked'>defensive</value>" +
    10             "<value a:help='Will never attack units'>passive</value>" +
    11             "<value a:help='Will never attack units. Will typically attempt to flee for short distances when units approach'>skittish</value>" +
    12         "</choice>" +
    13     "</element>" +
    14     "<element name='RoamDistance'>" +
    15         "<ref name='positiveDecimal'/>" +
    16     "</element>" +
    17     "<element name='FleeDistance'>" +
    18         "<ref name='positiveDecimal'/>" +
    19     "</element>" +
    20     "<element name='RoamTimeMin'>" +
    21         "<ref name='positiveDecimal'/>" +
    22     "</element>" +
    23     "<element name='RoamTimeMax'>" +
    24         "<ref name='positiveDecimal'/>" +
    25     "</element>" +
    26     "<element name='FeedTimeMin'>" +
    27         "<ref name='positiveDecimal'/>" +
    28     "</element>" +
    29     "<element name='FeedTimeMax'>" +
    30         "<ref name='positiveDecimal'/>" +
    31     "</element>";
    32 
    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         "LeaveFoundation": function(msg) {
    61             // We can't walk away from the foundation (since we're dead),
    62             // but we mustn't block its construction (since the builders would get stuck),
    63             // and we don't want to trick gatherers into trying to reach us when
    64             // we're stuck in the middle of a building, so just delete our corpse.
    65             Engine.DestroyEntity(this.entity);
    66         },
    67     },
    68 
    69     "SKITTISH": {
    70 
    71         "Attacked": function(msg) {
    72             // If someone's attacking us, then run away
    73             this.MoveAwayFrom(msg.data.attacker, +this.template.FleeDistance);
    74             this.SetNextState("FLEEING");
    75             this.PlaySound("panic");
    76         },
    77 
    78         "LeaveFoundation": function(msg) {
    79             // Run away from the foundation
    80             this.MoveAwayFrom(msg.target, +this.template.FleeDistance);
    81             this.SetNextState("FLEEING");
    82             this.PlaySound("panic");
    83         },
    84 
    85         "ROAMING": {
    86             "enter": function() {
    87                 // Walk in a random direction
    88                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    89                 this.MoveRandomly(+this.template.RoamDistance);
    90                 // Set a random timer to switch to feeding state
    91                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    92             },
    93 
    94             "leave": function() {
    95                 this.StopTimer();
    96             },
    97 
    98             "Timer": function(msg) {
    99                 this.SetNextState("FEEDING");
    100             },
    101 
    102             "MoveCompleted": function() {
    103                 this.MoveRandomly(+this.template.RoamDistance);
    104             },
    105         },
    106 
    107         "FEEDING": {
    108             "enter": function() {
    109                 // Stop and eat for a while
    110                 this.SelectAnimation("feeding");
    111                 this.StopMoving();
    112                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    113             },
    114            
    115             "leave": function() {
    116                 this.StopTimer();
    117             },
    118 
    119             "MoveCompleted": function() { },
    120 
    121             "Timer": function(msg) {
    122                 this.SetNextState("ROAMING");
    123             },
    124         },
    125 
    126         "FLEEING": {
    127             "enter": function() {
    128                 // Run quickly
    129                 var speed = this.GetRunSpeed();
    130                 this.SelectAnimation("run", false, speed);
    131                 this.SetMoveSpeed(speed);
    132             },
    133 
    134             "leave": function() {
    135                 // Reset normal speed
    136                 this.SetMoveSpeed(this.GetWalkSpeed());
    137             },
    138 
    139             "MoveCompleted": function() {
    140                 // When we've run far enough, go back to the roaming state
    141                 this.SetNextState("ROAMING");
    142             },
    143         },
    144     },
    145 
    146     "PASSIVE": {
    147 
    148         "Attacked": function(msg) {
    149             // Do nothing, just let them kill us
    150         },
    151    
    152         "LeaveFoundation": function(msg) {
    153             // Walk away from the foundation
    154             this.MoveAwayFrom(msg.target, 4);
    155             this.SetNextState("FLEEING");
    156         },
    157 
    158         "ROAMING": {
    159             "enter": function() {
    160                 // Walk in a random direction
    161                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    162                 this.MoveRandomly(+this.template.RoamDistance);
    163                 // Set a random timer to switch to feeding state
    164                 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    165             },
    166 
    167             "leave": function() {
    168                 this.StopTimer();
    169             },
    170 
    171             "Timer": function(msg) {
    172                 this.SetNextState("FEEDING");
    173             },
    174 
    175             "MoveCompleted": function() {
    176                 this.MoveRandomly(+this.template.RoamDistance);
    177             },
    178         },
    179 
    180         "FEEDING": {
    181             "enter": function() {
    182                 // Stop and eat for a while
    183                 this.SelectAnimation("feeding");
    184                 this.StopMoving();
    185                 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
    186             },
    187            
    188             "leave": function() {
    189                 this.StopTimer();
    190             },
    191 
    192             "MoveCompleted": function() { },
    193 
    194             "Timer": function(msg) {
    195                 this.SetNextState("ROAMING");
    196             },
    197         },
    198 
    199         "FLEEING": {
    200             "enter": function() {
    201                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    202             },
    203 
    204             "MoveCompleted": function() {
    205                 this.SetNextState("ROAMING");
    206             },
    207         },
    208     },
    209 
    210 };
    211 
    212 var AnimalFsm = new FSM(AnimalFsmSpec);
    213 
    214 AnimalAI.prototype.Init = function()
    215 {
    216 };
    217 
    218 // FSM linkage functions:
    219 
    220 AnimalAI.prototype.OnCreate = function()
    221 {
    222     var startingState = this.template.NaturalBehaviour;
    223     startingState = startingState.toUpperCase(startingState);
    224 
    225     if (startingState == "SKITTISH")
    226         startingState = startingState + ".FEEDING";
    227     else
    228         startingState = "PASSIVE.FEEDING";
    229 
    230     AnimalFsm.Init(this, startingState);
    231 };
    232 
    233 AnimalAI.prototype.SetNextState = function(state)
    234 {
    235     AnimalFsm.SetNextState(this, state);
    236 };
    237 
    238 AnimalAI.prototype.DeferMessage = function(msg)
    239 {
    240     AnimalFsm.DeferMessage(this, msg);
    241 };
    242 
    243 AnimalAI.prototype.OnMotionChanged = function(msg)
    244 {
    245     if (msg.starting && !msg.error)
    246     {
    247         AnimalFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
    248     }
    249     else if (!msg.starting || msg.error)
    250     {
    251         AnimalFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
    252     }
    253 };
    254 
    255 AnimalAI.prototype.OnAttacked = function(msg)
    256 {
    257     AnimalFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
    258 };
    259 
    260 AnimalAI.prototype.OnHealthChanged = function(msg)
    261 {
    262     AnimalFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
    263 };
    264 
    265 AnimalAI.prototype.TimerHandler = function(data, lateness)
    266 {
    267     AnimalFsm.ProcessMessage(this, {"type": "Timer", "data": data, "lateness": lateness});
    268 };
    269 
    270 AnimalAI.prototype.LeaveFoundation = function(target)
    271 {
    272     AnimalFsm.ProcessMessage(this, {"type": "LeaveFoundation", "target": target});
    273 };
    274 
    275 // Functions to be called by the FSM:
    276 
    277 AnimalAI.prototype.GetWalkSpeed = function()
    278 {
    279     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    280     return cmpMotion.GetWalkSpeed();
    281 };
    282 
    283 AnimalAI.prototype.GetRunSpeed = function()
    284 {
    285     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    286     return cmpMotion.GetRunSpeed();
    287 };
    288 
    289 AnimalAI.prototype.PlaySound = function(name)
    290 {
    291     PlaySound(name, this.entity);
    292 };
    293 
    294 AnimalAI.prototype.SelectAnimation = function(name, once, speed, sound)
    295 {
    296     var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    297     if (!cmpVisual)
    298         return;
    299 
    300     var soundgroup;
    301     if (sound)
    302     {
    303         var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
    304         if (cmpSound)
    305             soundgroup = cmpSound.GetSoundGroup(sound);
    306     }
    307 
    308     // Set default values if unspecified
    309     if (typeof once == "undefined")
    310         once = false;
    311     if (typeof speed == "undefined")
    312         speed = 1.0;
    313     if (typeof soundgroup == "undefined")
    314         soundgroup = "";
    315 
    316     cmpVisual.SelectAnimation(name, once, speed, soundgroup);
    317 };
    318 
    319 AnimalAI.prototype.MoveRandomly = function(distance)
    320 {
    321     // We want to walk in a random direction, but avoid getting stuck
    322     // in obstacles or narrow spaces.
    323     // So pick a circular range from approximately our current position,
    324     // and move outwards to the nearest point on that circle, which will
    325     // lead to us avoiding obstacles and moving towards free space.
    326 
    327     // TODO: we probably ought to have a 'home' point, and drift towards
    328     // that, so we don't spread out all across the whole map
    329 
    330     var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    331     if (!cmpPosition)
    332         return;
    333 
    334     if (!cmpPosition.IsInWorld())
    335         return;
    336 
    337     var pos = cmpPosition.GetPosition();
    338 
    339     var jitter = 0.5;
    340 
    341     // Randomly adjust the range's center a bit, so we tend to prefer
    342     // moving in random directions (if there's nothing in the way)
    343     var tx = pos.x + (2*Math.random()-1)*jitter;
    344     var tz = pos.z + (2*Math.random()-1)*jitter;
    345 
    346     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    347     cmpMotion.MoveToPointRange(tx, tz, distance, distance);
    348 };
    349 
    350 AnimalAI.prototype.MoveAwayFrom = function(ent, distance)
    351 {
    352     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    353     cmpMotion.MoveToTargetRange(ent, distance, distance);
    354 };
    355 
    356 AnimalAI.prototype.StopMoving = function()
    357 {
    358     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    359     cmpMotion.StopMoving();
    360 };
    361 
    362 AnimalAI.prototype.SetMoveSpeed = function(speed)
    363 {
    364     var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    365     cmpMotion.SetSpeed(speed);
    366 };
    367 
    368 AnimalAI.prototype.StartTimer = function(interval, data)
    369 {
    370     if (this.timer)
    371         error("Called StartTimer when there's already an active timer");
    372 
    373     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    374     this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);
    375 };
    376 
    377 AnimalAI.prototype.StopTimer = function()
    378 {
    379     if (!this.timer)
    380         return;
    381 
    382     var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    383     cmpTimer.CancelTimer(this.timer);
    384     this.timer = undefined;
    385 };
    386 
    387 Engine.RegisterComponentType(IID_AnimalAI, "AnimalAI", AnimalAI);
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_defensive.xml

     
    22<Entity parent="template_unit_fauna_wild">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>defensive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml

     
    22<Entity parent="template_unit_fauna_hunt">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>aggressive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
     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/gaia/fauna_chicken.xml

     
    2222    <Circle radius="0.75"/>
    2323    <Height>1.5</Height>
    2424  </Footprint>
    25   <AnimalAI>
     25  <UnitAI>
    2626    <RoamDistance>4.0</RoamDistance>
    2727    <FleeDistance>12.0</FleeDistance>
    2828    <RoamTimeMin>2000</RoamTimeMin>
    2929    <RoamTimeMax>8000</RoamTimeMax>
    3030    <FeedTimeMin>10000</FeedTimeMin>
    3131    <FeedTimeMax>40000</FeedTimeMax>
    32   </AnimalAI>
     32  </UnitAI>
    3333  <UnitMotion>
    3434    <WalkSpeed>1.0</WalkSpeed>
    3535    <Run>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_whale_humpback.xml

     
    3535    <Altitude>-4.0</Altitude>
    3636    <Floating>true</Floating>
    3737  </Position>
    38   <AnimalAI>
     38  <UnitAI>
    3939    <NaturalBehaviour>skittish</NaturalBehaviour>
    4040    <RoamDistance>20.0</RoamDistance>
    4141    <FleeDistance>40.0</FleeDistance>
     
    4343    <RoamTimeMax>30000</RoamTimeMax>
    4444    <FeedTimeMin>1000</FeedTimeMin>
    4545    <FeedTimeMax>2000</FeedTimeMax>
    46   </AnimalAI>
     46  </UnitAI>
    4747  <ResourceSupply>
    4848    <KillBeforeGather>true</KillBeforeGather>
    4949    <Amount>2000</Amount>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml

     
    22<Entity parent="template_unit_fauna_wild">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>violent</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
     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_passive.xml

     
    22<Entity parent="template_unit_fauna_wild">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>passive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_breed_passive.xml

     
    22<Entity parent="template_unit_fauna_breed">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>passive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_herd_passive.xml

     
    22<Entity parent="template_unit_fauna_herd">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>passive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml

     
    22<Entity parent="template_unit_fauna_wild">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
    6     <NaturalBehaviour>violent</NaturalBehaviour>
    7   </AnimalAI>
     5  <UnitAI>
     6    <NaturalBehaviour>aggressive</NaturalBehaviour>
     7  </UnitAI>
     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

     
    22<Entity parent="template_unit_fauna_hunt">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>violent</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
     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_passive.xml

     
    22<Entity parent="template_unit_fauna_hunt">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>passive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna.xml

     
    2525      <Speed>6.0</Speed>
    2626    </Run>
    2727  </UnitMotion>
    28   <UnitAI disable=""/>
    29   <AnimalAI>
     28  <UnitAI>
    3029    <RoamDistance>8.0</RoamDistance>
    3130    <FleeDistance>32.0</FleeDistance>
    3231    <RoamTimeMin>2000</RoamTimeMin>
    3332    <RoamTimeMax>8000</RoamTimeMax>
    3433    <FeedTimeMin>15000</FeedTimeMin>
    3534    <FeedTimeMax>60000</FeedTimeMax>
    36   </AnimalAI>
     35  </UnitAI>
    3736</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_fish.xml

     
    1818    <Floating>true</Floating>
    1919  </Position>
    2020  <UnitMotion disable=""/>
    21   <AnimalAI disable=""/>
     21  <UnitAI disable=""/>
    2222  <ResourceSupply>
    2323    <KillBeforeGather>false</KillBeforeGather>
    2424    <Amount>1000</Amount>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_skittish.xml

     
    22<Entity parent="template_unit_fauna_hunt">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>skittish</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_defensive.xml

     
    22<Entity parent="template_unit_fauna_hunt">
    33  <Identity>
    44  </Identity>
    5   <AnimalAI>
     5  <UnitAI>
    66    <NaturalBehaviour>defensive</NaturalBehaviour>
    7   </AnimalAI>
     7  </UnitAI>
    88</Entity>