1
1
forked from 0ad/0ad

# Improve animal AI, based on patch from Badmadblacksad.

Fixes #563.

This was SVN commit r8995.
This commit is contained in:
Ykkrosh 2011-02-27 13:34:22 +00:00
parent 83d0492fca
commit 18b317bc19
21 changed files with 392 additions and 432 deletions

View File

@ -1,387 +0,0 @@
function AnimalAI() {}
AnimalAI.prototype.Schema =
"<a:example/>" +
"<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
"<choice>" +
"<value a:help='Will actively attack any unit it encounters, even if not threatened'>violent</value>" +
"<value a:help='Will attack nearby units if it feels threatened (if they linger within LOS for too long)'>aggressive</value>" +
"<value a:help='Will attack nearby units if attacked'>defensive</value>" +
"<value a:help='Will never attack units'>passive</value>" +
"<value a:help='Will never attack units. Will typically attempt to flee for short distances when units approach'>skittish</value>" +
"</choice>" +
"</element>" +
"<element name='RoamDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FleeDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='RoamTimeMin'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='RoamTimeMax'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FeedTimeMin'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FeedTimeMax'>" +
"<ref name='positiveDecimal'/>" +
"</element>";
var AnimalFsmSpec = {
"MoveCompleted": function() {
// ignore spurious movement messages
// (these can happen when stopping moving at the same time
// as switching states)
},
"MoveStarted": function() {
// ignore spurious movement messages
},
"HealthChanged": function(msg) {
// If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
if (msg.to == 0)
this.SetNextState("CORPSE");
},
"CORPSE": {
"enter": function() {
this.StopMoving();
},
"Attacked": function(msg) {
// Do nothing, because we're dead already
},
"LeaveFoundation": function(msg) {
// We can't walk away from the foundation (since we're dead),
// but we mustn't block its construction (since the builders would get stuck),
// and we don't want to trick gatherers into trying to reach us when
// we're stuck in the middle of a building, so just delete our corpse.
Engine.DestroyEntity(this.entity);
},
},
"SKITTISH": {
"Attacked": function(msg) {
// If someone's attacking us, then run away
this.MoveAwayFrom(msg.data.attacker, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
},
"LeaveFoundation": function(msg) {
// Run away from the foundation
this.MoveAwayFrom(msg.target, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
},
"ROAMING": {
"enter": function() {
// Walk in a random direction
this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
this.SetNextState("FEEDING");
},
"MoveCompleted": function() {
this.MoveRandomly(+this.template.RoamDistance);
},
},
"FEEDING": {
"enter": function() {
// Stop and eat for a while
this.SelectAnimation("feeding");
this.StopMoving();
this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
},
"leave": function() {
this.StopTimer();
},
"MoveCompleted": function() { },
"Timer": function(msg) {
this.SetNextState("ROAMING");
},
},
"FLEEING": {
"enter": function() {
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("run", false, speed);
this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed
this.SetMoveSpeed(this.GetWalkSpeed());
},
"MoveCompleted": function() {
// When we've run far enough, go back to the roaming state
this.SetNextState("ROAMING");
},
},
},
"PASSIVE": {
"Attacked": function(msg) {
// Do nothing, just let them kill us
},
"LeaveFoundation": function(msg) {
// Walk away from the foundation
this.MoveAwayFrom(msg.target, 4);
this.SetNextState("FLEEING");
},
"ROAMING": {
"enter": function() {
// Walk in a random direction
this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
this.SetNextState("FEEDING");
},
"MoveCompleted": function() {
this.MoveRandomly(+this.template.RoamDistance);
},
},
"FEEDING": {
"enter": function() {
// Stop and eat for a while
this.SelectAnimation("feeding");
this.StopMoving();
this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
},
"leave": function() {
this.StopTimer();
},
"MoveCompleted": function() { },
"Timer": function(msg) {
this.SetNextState("ROAMING");
},
},
"FLEEING": {
"enter": function() {
this.SelectAnimation("walk", false, this.GetWalkSpeed());
},
"MoveCompleted": function() {
this.SetNextState("ROAMING");
},
},
},
};
var AnimalFsm = new FSM(AnimalFsmSpec);
AnimalAI.prototype.Init = function()
{
};
// FSM linkage functions:
AnimalAI.prototype.OnCreate = function()
{
var startingState = this.template.NaturalBehaviour;
startingState = startingState.toUpperCase(startingState);
if (startingState == "SKITTISH")
startingState = startingState + ".FEEDING";
else
startingState = "PASSIVE.FEEDING";
AnimalFsm.Init(this, startingState);
};
AnimalAI.prototype.SetNextState = function(state)
{
AnimalFsm.SetNextState(this, state);
};
AnimalAI.prototype.DeferMessage = function(msg)
{
AnimalFsm.DeferMessage(this, msg);
};
AnimalAI.prototype.OnMotionChanged = function(msg)
{
if (msg.starting && !msg.error)
{
AnimalFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
}
else if (!msg.starting || msg.error)
{
AnimalFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
}
};
AnimalAI.prototype.OnAttacked = function(msg)
{
AnimalFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
};
AnimalAI.prototype.OnHealthChanged = function(msg)
{
AnimalFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
};
AnimalAI.prototype.TimerHandler = function(data, lateness)
{
AnimalFsm.ProcessMessage(this, {"type": "Timer", "data": data, "lateness": lateness});
};
AnimalAI.prototype.LeaveFoundation = function(target)
{
AnimalFsm.ProcessMessage(this, {"type": "LeaveFoundation", "target": target});
};
// Functions to be called by the FSM:
AnimalAI.prototype.GetWalkSpeed = function()
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.GetWalkSpeed();
};
AnimalAI.prototype.GetRunSpeed = function()
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.GetRunSpeed();
};
AnimalAI.prototype.PlaySound = function(name)
{
PlaySound(name, this.entity);
};
AnimalAI.prototype.SelectAnimation = function(name, once, speed, sound)
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
var soundgroup;
if (sound)
{
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
if (cmpSound)
soundgroup = cmpSound.GetSoundGroup(sound);
}
// Set default values if unspecified
if (typeof once == "undefined")
once = false;
if (typeof speed == "undefined")
speed = 1.0;
if (typeof soundgroup == "undefined")
soundgroup = "";
cmpVisual.SelectAnimation(name, once, speed, soundgroup);
};
AnimalAI.prototype.MoveRandomly = function(distance)
{
// We want to walk in a random direction, but avoid getting stuck
// in obstacles or narrow spaces.
// So pick a circular range from approximately our current position,
// and move outwards to the nearest point on that circle, which will
// lead to us avoiding obstacles and moving towards free space.
// TODO: we probably ought to have a 'home' point, and drift towards
// that, so we don't spread out all across the whole map
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition)
return;
if (!cmpPosition.IsInWorld())
return;
var pos = cmpPosition.GetPosition();
var jitter = 0.5;
// Randomly adjust the range's center a bit, so we tend to prefer
// moving in random directions (if there's nothing in the way)
var tx = pos.x + (2*Math.random()-1)*jitter;
var tz = pos.z + (2*Math.random()-1)*jitter;
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.MoveToPointRange(tx, tz, distance, distance);
};
AnimalAI.prototype.MoveAwayFrom = function(ent, distance)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.MoveToTargetRange(ent, distance, distance);
};
AnimalAI.prototype.StopMoving = function()
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.StopMoving();
};
AnimalAI.prototype.SetMoveSpeed = function(speed)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.SetSpeed(speed);
};
AnimalAI.prototype.StartTimer = function(interval, data)
{
if (this.timer)
error("Called StartTimer when there's already an active timer");
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);
};
AnimalAI.prototype.StopTimer = function()
{
if (!this.timer)
return;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
};
Engine.RegisterComponentType(IID_AnimalAI, "AnimalAI", AnimalAI);

View File

@ -90,11 +90,7 @@ Foundation.prototype.Build = function(builderEnt, work)
if (cmpUnitAI)
cmpUnitAI.LeaveFoundation(this.entity);
var cmpAnimalAI = Engine.QueryInterface(ent, IID_AnimalAI);
if (cmpAnimalAI)
cmpAnimalAI.LeaveFoundation(this.entity);
// TODO: What if an obstruction has no UnitAI/AnimalAI?
// TODO: What if an obstruction has no UnitAI?
}
// TODO: maybe we should tell the builder to use a special

View File

@ -5,7 +5,38 @@ UnitAI.prototype.Schema =
"<a:example/>" +
"<element name='FormationController'>" +
"<data type='boolean'/>" +
"</element>";
"</element>" +
"<optional>" +
"<interleave>" +
"<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
"<choice>" +
"<value a:help='Will actively attack any unit it encounters, even if not threatened'>violent</value>" +
"<value a:help='Will attack nearby units if it feels threatened (if they linger within LOS for too long)'>aggressive</value>" +
"<value a:help='Will attack nearby units if attacked'>defensive</value>" +
"<value a:help='Will never attack units'>passive</value>" +
"<value a:help='Will never attack units. Will typically attempt to flee for short distances when units approach'>skittish</value>" +
"</choice>" +
"</element>" +
"<element name='RoamDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FleeDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='RoamTimeMin'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='RoamTimeMax'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FeedTimeMin'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FeedTimeMax'>" +
"<ref name='positiveDecimal'/>" +
"</element>"+
"</interleave>" +
"</optional>";
// Very basic stance support (currently just for test maps where we don't want
// everyone killing each other immediately after loading)
@ -18,6 +49,7 @@ var g_Stances = {
},
};
// See ../helpers/FSM.js for some documentation of this FSM specification syntax
var UnitFsmSpec = {
// Default event handlers:
@ -44,6 +76,9 @@ var UnitFsmSpec = {
// ignore attacker
},
"HealthChanged": function(msg) {
// ignore
},
// Formation handlers:
@ -53,6 +88,13 @@ var UnitFsmSpec = {
// Called when being told to walk as part of a formation
"Order.FormationWalk": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z);
@ -71,11 +113,25 @@ var UnitFsmSpec = {
// (these will switch the unit out of formation mode)
"Order.Walk": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
this.MoveToPoint(this.order.data.x, this.order.data.z);
this.SetNextState("INDIVIDUAL.WALKING");
},
"Order.WalkToTarget": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
var ok = this.MoveToTarget(this.order.data.target);
if (ok)
{
@ -111,14 +167,20 @@ var UnitFsmSpec = {
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.APPROACHING");
else
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
}
else
{
// We are already at the target, or can't move at all,
// so try attacking it from here.
// TODO: need better handling of the can't-reach-target case
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.ATTACKING");
else
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
}
},
@ -299,6 +361,12 @@ var UnitFsmSpec = {
// States for entities not part of a formation:
"INDIVIDUAL": {
"enter": function() {
// Sanity-checking
if (this.IsAnimal())
error("Animal got moved into INDIVIDUAL.* state");
},
"Attacked": function(msg) {
// Default behaviour: attack back at our attacker
if (this.CanAttack(msg.data.attacker))
@ -454,7 +522,7 @@ var UnitFsmSpec = {
// Can't reach it, or it doesn't exist any more - give up
this.FinishOrder();
// TODO: see if we can switch to a new nearby enemy
},
@ -841,6 +909,178 @@ var UnitFsmSpec = {
},
},
"ANIMAL": {
"HealthChanged": function(msg) {
// If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
if (msg.to == 0)
this.SetNextState("CORPSE");
},
"Attacked": function(msg) {
if (this.template.NaturalBehaviour == "skittish" ||
this.template.NaturalBehaviour == "passive")
{
this.MoveToTargetRangeExplicit(msg.data.attacker, +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
}
else if (this.template.NaturalBehaviour == "violent" ||
this.template.NaturalBehaviour == "aggressive" ||
this.template.NaturalBehaviour == "defensive")
{
if (this.CanAttack(msg.data.attacker))
this.ReplaceOrder("Attack", { "target": msg.data.attacker });
}
},
"Order.LeaveFoundation": function(msg) {
// Run away from the foundation
this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
},
"IDLE": {
// (We need an IDLE state so that FinishOrder works)
"enter": function() {
// Start feeding immediately
this.SetNextState("FEEDING");
return true;
},
},
"CORPSE": {
"enter": function() {
this.StopMoving();
},
// Ignore all orders that animals might otherwise respond to
"Order.FormationWalk": function() { },
"Order.Walk": function() { },
"Order.WalkToTarget": function() { },
"Order.Attack": function() { },
"Attacked": function(msg) {
// Do nothing, because we're dead already
},
"Order.LeaveFoundation": function(msg) {
// We can't walk away from the foundation (since we're dead),
// but we mustn't block its construction (since the builders would get stuck),
// and we don't want to trick gatherers into trying to reach us when
// we're stuck in the middle of a building, so just delete our corpse.
Engine.DestroyEntity(this.entity);
},
},
"ROAMING": {
"enter": function() {
// Walk in a random direction
this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
},
"leave": function() {
this.StopTimer();
},
"LosRangeUpdate": function(msg) {
if (this.template.NaturalBehaviour == "skittish")
{
if (msg.data.added.length > 0)
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
return;
}
}
// Start attacking one of the newly-seen enemy (if any)
else if (this.template.NaturalBehaviour == "violent" ||
this.template.NaturalBehaviour == "aggressive")
{
this.AttackVisibleEntity(msg.data.added);
}
// TODO: if two units enter our range together, we'll attack the
// first and then the second won't trigger another LosRangeUpdate
// so we won't notice it. Probably we should do something with
// ResetActiveQuery in ROAMING.enter/FEEDING.enter in order to
// find any units that are already in range.
},
"Timer": function(msg) {
this.SetNextState("FEEDING");
},
"MoveCompleted": function() {
this.MoveRandomly(+this.template.RoamDistance);
},
},
"FEEDING": {
"enter": function() {
// Stop and eat for a while
this.SelectAnimation("feeding");
this.StopMoving();
this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
},
"leave": function() {
this.StopTimer();
},
"LosRangeUpdate": function(msg) {
if (this.template.NaturalBehaviour == "skittish")
{
if (msg.data.added.length > 0)
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
return;
}
}
// Start attacking one of the newly-seen enemy (if any)
else if (this.template.NaturalBehaviour == "violent")
{
this.AttackVisibleEntity(msg.data.added);
}
},
"MoveCompleted": function() { },
"Timer": function(msg) {
this.SetNextState("ROAMING");
},
},
"FLEEING": {
"enter": function() {
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("run", false, speed);
this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed
this.SetMoveSpeed(this.GetWalkSpeed());
},
"MoveCompleted": function() {
// When we've run far enough, go back to the roaming state
this.SetNextState("ROAMING");
},
},
"COMBAT": "INDIVIDUAL.COMBAT", // reuse the same combat behaviour for animals
},
};
var UnitFsm = new FSM(UnitFsmSpec);
@ -860,6 +1100,11 @@ UnitAI.prototype.IsFormationController = function()
return (this.template.FormationController == "true");
};
UnitAI.prototype.IsAnimal = function()
{
return (this.template.NaturalBehaviour ? true : false);
};
UnitAI.prototype.IsIdle = function()
{
return this.isIdle;
@ -867,7 +1112,9 @@ UnitAI.prototype.IsIdle = function()
UnitAI.prototype.OnCreate = function()
{
if (this.IsFormationController())
if (this.IsAnimal())
UnitFsm.Init(this, "ANIMAL.FEEDING");
else if (this.IsFormationController())
UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
else
UnitFsm.Init(this, "INDIVIDUAL.IDLE");
@ -916,7 +1163,7 @@ UnitAI.prototype.SetupRangeQuery = function(owner)
// Get our diplomacy array
var diplomacy = player.GetDiplomacy();
var numPlayers = playerMan.GetNumPlayers();
for (var i = 1; i < numPlayers; ++i)
{
// Exclude gaia, allies, and self
@ -1076,6 +1323,11 @@ UnitAI.prototype.OnAttacked = function(msg)
UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
};
UnitAI.prototype.OnHealthChanged = function(msg)
{
UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
};
UnitAI.prototype.OnRangeUpdate = function(msg)
{
if (msg.tag == this.losRangeQuery)
@ -1544,7 +1796,13 @@ UnitAI.prototype.CanGarrison = function(target)
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
// Don't let animals garrison for now
// (If we want to support that, we'll need to change Order.Garrison so it
// doesn't move the animal into an INVIDIDUAL.* state)
if (this.IsAnimal())
return false;
return true;
};
@ -1614,5 +1872,43 @@ UnitAI.prototype.CanRepair = function(target)
return true;
};
//// Animal specific functions ////
UnitAI.prototype.MoveRandomly = function(distance)
{
// We want to walk in a random direction, but avoid getting stuck
// in obstacles or narrow spaces.
// So pick a circular range from approximately our current position,
// and move outwards to the nearest point on that circle, which will
// lead to us avoiding obstacles and moving towards free space.
// TODO: we probably ought to have a 'home' point, and drift towards
// that, so we don't spread out all across the whole map
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition)
return;
if (!cmpPosition.IsInWorld())
return;
var pos = cmpPosition.GetPosition();
var jitter = 0.5;
// Randomly adjust the range's center a bit, so we tend to prefer
// moving in random directions (if there's nothing in the way)
var tx = pos.x + (2*Math.random()-1)*jitter;
var tz = pos.z + (2*Math.random()-1)*jitter;
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.MoveToPointRange(tx, tz, distance, distance);
};
UnitAI.prototype.SetMoveSpeed = function(speed)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.SetSpeed(speed);
};
Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);

View File

@ -1 +0,0 @@
Engine.RegisterInterface("AnimalAI");

View File

@ -70,6 +70,9 @@ var FsmSpec = {
},
},
// Define a new state which is an exact copy of another
// state that is defined elsewhere in this FSM:
"OTHERSUBSTATENAME": "STATENAME.SUBSTATENAME",
}
}
@ -143,6 +146,23 @@ function FSM(spec)
function process(fsm, node, path, handlers)
{
// Handle string references to nodes defined elsewhere in the FSM spec
if (typeof node === "string")
{
var refpath = node.split(".");
var refd = spec;
for each (var p in refpath)
{
refd = refd[p];
if (!refd)
{
error("FSM node "+path.join(".")+" referred to non-defined node "+node);
return {};
}
}
node = refd;
}
var state = {};
fsm.states[path.join(".")] = state;

View File

@ -53,7 +53,7 @@ function LoadPlayerSettings(settings)
teams[team].push(i);
}
}
for (var i = 0; i < numPlayers; ++i)
{
// Add player entity to engine
@ -112,6 +112,7 @@ function LoadPlayerSettings(settings)
player.SetName(pDefs.Name);
player.SetCiv(pDefs.Civ);
player.SetColour(pDefs.Colour.r, pDefs.Colour.g, pDefs.Colour.b);
player.SetDiplomacy(diplomacy);
}
// Add player to player manager

View File

@ -22,14 +22,14 @@
<Circle radius="0.75"/>
<Height>1.5</Height>
</Footprint>
<AnimalAI>
<UnitAI>
<RoamDistance>4.0</RoamDistance>
<FleeDistance>12.0</FleeDistance>
<RoamTimeMin>2000</RoamTimeMin>
<RoamTimeMax>8000</RoamTimeMax>
<FeedTimeMin>10000</FeedTimeMin>
<FeedTimeMax>40000</FeedTimeMax>
</AnimalAI>
</UnitAI>
<UnitMotion>
<WalkSpeed>1.0</WalkSpeed>
<Run>

View File

@ -35,7 +35,7 @@
<Altitude>-4.0</Altitude>
<Floating>true</Floating>
</Position>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>skittish</NaturalBehaviour>
<RoamDistance>20.0</RoamDistance>
<FleeDistance>40.0</FleeDistance>
@ -43,7 +43,7 @@
<RoamTimeMax>30000</RoamTimeMax>
<FeedTimeMin>1000</FeedTimeMin>
<FeedTimeMax>2000</FeedTimeMax>
</AnimalAI>
</UnitAI>
<ResourceSupply>
<KillBeforeGather>true</KillBeforeGather>
<Amount>2000</Amount>

View File

@ -25,13 +25,12 @@
<Speed>6.0</Speed>
</Run>
</UnitMotion>
<UnitAI disable=""/>
<AnimalAI>
<UnitAI>
<RoamDistance>8.0</RoamDistance>
<FleeDistance>32.0</FleeDistance>
<RoamTimeMin>2000</RoamTimeMin>
<RoamTimeMax>8000</RoamTimeMax>
<FeedTimeMin>15000</FeedTimeMin>
<FeedTimeMax>60000</FeedTimeMax>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_breed">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>passive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -18,7 +18,7 @@
<Floating>true</Floating>
</Position>
<UnitMotion disable=""/>
<AnimalAI disable=""/>
<UnitAI disable=""/>
<ResourceSupply>
<KillBeforeGather>false</KillBeforeGather>
<Amount>1000</Amount>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_herd">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>passive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,16 @@
<Entity parent="template_unit_fauna_hunt">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>aggressive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
<Attack>
<Melee>
<Hack>1.0</Hack>
<Pierce>1.0</Pierce>
<Crush>0.0</Crush>
<MaxRange>4.0</MaxRange>
<RepeatTime>1000</RepeatTime>
</Melee>
</Attack>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_hunt">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>defensive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_hunt">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>passive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_hunt">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>skittish</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,16 @@
<Entity parent="template_unit_fauna_hunt">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>violent</NaturalBehaviour>
</AnimalAI>
</UnitAI>
<Attack>
<Melee>
<Hack>1.0</Hack>
<Pierce>1.0</Pierce>
<Crush>0.0</Crush>
<MaxRange>4.0</MaxRange>
<RepeatTime>1000</RepeatTime>
</Melee>
</Attack>
</Entity>

View File

@ -2,7 +2,16 @@
<Entity parent="template_unit_fauna_wild">
<Identity>
</Identity>
<AnimalAI>
<NaturalBehaviour>violent</NaturalBehaviour>
</AnimalAI>
<UnitAI>
<NaturalBehaviour>aggressive</NaturalBehaviour>
</UnitAI>
<Attack>
<Melee>
<Hack>1.0</Hack>
<Pierce>1.0</Pierce>
<Crush>0.0</Crush>
<MaxRange>4.0</MaxRange>
<RepeatTime>1000</RepeatTime>
</Melee>
</Attack>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_wild">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>defensive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_fauna_wild">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>passive</NaturalBehaviour>
</AnimalAI>
</UnitAI>
</Entity>

View File

@ -2,7 +2,16 @@
<Entity parent="template_unit_fauna_wild">
<Identity>
</Identity>
<AnimalAI>
<UnitAI>
<NaturalBehaviour>violent</NaturalBehaviour>
</AnimalAI>
</UnitAI>
<Attack>
<Melee>
<Hack>1.0</Hack>
<Pierce>1.0</Pierce>
<Crush>0.0</Crush>
<MaxRange>4.0</MaxRange>
<RepeatTime>1000</RepeatTime>
</Melee>
</Attack>
</Entity>