1
1
forked from 0ad/0ad

Make non-combat units flee when under melee attack, and not attack enemies automatically.

Fixes #703.

This was SVN commit r9010.
This commit is contained in:
Ykkrosh 2011-03-04 14:36:41 +00:00
parent 3e22cf4cae
commit bec4c6437b
7 changed files with 123 additions and 28 deletions

View File

@ -3,9 +3,19 @@ function UnitAI() {}
UnitAI.prototype.Schema =
"<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" +
"<a:example/>" +
"<element name='DefaultStance'>" +
"<choice>" +
"<value>aggressive</value>" +
"<value>holdfire</value>" +
"<value>noncombat</value>" +
"</choice>" +
"</element>" +
"<element name='FormationController'>" +
"<data type='boolean'/>" +
"</element>" +
"<element name='FleeDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<optional>" +
"<interleave>" +
"<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
@ -20,9 +30,6 @@ UnitAI.prototype.Schema =
"<element name='RoamDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='FleeDistance'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='RoamTimeMin'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
@ -39,13 +46,36 @@ UnitAI.prototype.Schema =
"</optional>";
// Very basic stance support (currently just for test maps where we don't want
// everyone killing each other immediately after loading)
// everyone killing each other immediately after loading, and for female citizens)
// There some targeting options:
// targetVisibleEnemies: anything in vision range is a viable target
// targetAttackers: anything that hurts us is a viable target
// There are some response options, triggered when targets are detected:
// respondFlee: run away
// respondChase: start chasing after the enemy
// TODO: maybe add respondStandGround, respondHoldGround (allow chasing a short distance then return),
// targetAggressiveEnemies (don't worry about lone scouts, do worry around armies slaughtering
// the guy standing next to you), etc.
// TODO: even this limited version isn't implemented properly yet (e.g. it can't handle
// dynamic stance changes).
var g_Stances = {
"aggressive": {
attackOnSight: true,
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: true,
},
"holdfire": {
attackOnSight: false,
targetVisibleEnemies: false,
targetAttackers: true,
respondFlee: false,
respondChase: true,
},
"noncombat": {
targetVisibleEnemies: false,
targetAttackers: true,
respondFlee: true,
respondChase: false,
},
};
@ -145,6 +175,21 @@ var UnitFsmSpec = {
}
},
"Order.Flee": function(msg) {
// TODO: if we were attacked by a ranged unit, we need to flee much further away
var ok = this.MoveToTargetRangeExplicit(this.order.data.target, +this.template.FleeDistance, -1);
if (ok)
{
// We've started fleeing from the given target
this.SetNextState("INDIVIDUAL.FLEEING");
}
else
{
// We are already at the target, or can't move at all
this.FinishOrder();
}
},
"Order.Attack": function(msg) {
// Check the target is alive
if (!this.TargetIsAlive(this.order.data.target))
@ -368,14 +413,9 @@ var UnitFsmSpec = {
},
"Attacked": function(msg) {
// Default behaviour: attack back at our attacker
if (this.CanAttack(msg.data.attacker))
if (this.GetStance().targetAttackers)
{
this.PushOrderFront("Attack", { "target": msg.data.attacker });
}
else
{
// TODO: If unit can't attack, run away
this.RespondToTargetedEntities([msg.data.attacker]);
}
},
@ -399,8 +439,11 @@ var UnitFsmSpec = {
{
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
if (this.GetStance().attackOnSight && this.AttackVisibleEntity(ents))
return true; // (abort the transition since we may have already switched state)
if (this.GetStance().targetVisibleEnemies)
{
if (this.RespondToTargetedEntities(ents))
return true; // (abort the FSM transition since we may have already switched state)
}
}
// Nobody to attack - stay in idle
@ -421,10 +464,10 @@ var UnitFsmSpec = {
},
"LosRangeUpdate": function(msg) {
if (this.GetStance().attackOnSight)
if (this.GetStance().targetVisibleEnemies)
{
// Start attacking one of the newly-seen enemy (if any)
this.AttackVisibleEntity(msg.data.added);
this.RespondToTargetedEntities(msg.data.added);
}
},
@ -466,6 +509,29 @@ var UnitFsmSpec = {
},
},
"FLEEING": {
"enter": function() {
this.PlaySound("panic");
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("move");
this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed
this.SetMoveSpeed(this.GetWalkSpeed());
},
"MoveCompleted": function() {
// When we've run far enough, stop fleeing
this.FinishOrder();
},
// TODO: what if we run into more enemies while fleeing?
},
"COMBAT": {
"Attacked": function(msg) {
// If we're already in combat mode, ignore anyone else
@ -924,7 +990,6 @@ var UnitFsmSpec = {
{
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" ||
@ -939,7 +1004,6 @@ var UnitFsmSpec = {
// Run away from the foundation
this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
},
"IDLE": {
@ -996,7 +1060,6 @@ var UnitFsmSpec = {
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
return;
}
}
@ -1042,7 +1105,6 @@ var UnitFsmSpec = {
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
this.PlaySound("panic");
return;
}
}
@ -1060,8 +1122,10 @@ var UnitFsmSpec = {
},
},
"FLEEING": {
"FLEEING": { // TODO: would be nice to share more of this with non-animal units
"enter": function() {
this.PlaySound("panic");
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("run", false, speed);
@ -1092,7 +1156,7 @@ UnitAI.prototype.Init = function()
this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to
this.isIdle = false;
this.SetStance("aggressive");
this.SetStance(this.template.DefaultStance);
};
UnitAI.prototype.IsFormationController = function()
@ -1572,6 +1636,28 @@ UnitAI.prototype.AttackVisibleEntity = function(ents)
return false;
};
/**
* Try to respond appropriately given our current stance,
* given a list of entities that match our stance's target criteria.
* Returns true if it responded.
*/
UnitAI.prototype.RespondToTargetedEntities = function(ents)
{
if (!ents.length)
return false;
if (this.GetStance().respondChase)
return this.AttackVisibleEntity(ents);
if (this.GetStance().respondFlee)
{
this.PushOrderFront("Flee", { "target": ents[0] });
return true;
}
return false;
};
//// External interface functions ////
UnitAI.prototype.SetFormationController = function(ent)
@ -1633,6 +1719,7 @@ UnitAI.prototype.ComputeWalkingDistance = function()
break; // and continue the loop
case "WalkToTarget":
case "Flee":
case "LeaveFoundation":
case "Attack":
case "Gather":

View File

@ -7,6 +7,8 @@
</Position>
<Formation/>
<UnitAI>
<DefaultStance>aggressive</DefaultStance>
<FleeDistance>12.0</FleeDistance>
<FormationController>true</FormationController>
</UnitAI>
<UnitMotion>

View File

@ -8,6 +8,8 @@
<Type>unit</Type>
</Minimap>
<UnitAI>
<DefaultStance>aggressive</DefaultStance>
<FleeDistance>12.0</FleeDistance>
<FormationController>false</FormationController>
</UnitAI>
<Cost>

View File

@ -7,6 +7,9 @@
<Minimap>
<Type>support</Type>
</Minimap>
<UnitAI>
<DefaultStance>noncombat</DefaultStance>
</UnitAI>
<Cost>
<BuildTime>15</BuildTime>
</Cost>

View File

@ -1213,7 +1213,7 @@ bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos
{
goalDistance = minRange + g_GoalDelta;
}
else if (distance > maxRange)
else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
{
goalDistance = maxRange - g_GoalDelta;
}
@ -1328,7 +1328,7 @@ bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange
goal.hw = obstruction.hw + delta;
goal.hh = obstruction.hh + delta;
}
else if (distance < maxRange)
else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
{
// We're already in range - no need to move anywhere
FaceTowardsPoint(pos, goal.x, goal.z);
@ -1436,7 +1436,7 @@ bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange,
return false;
// See if we're close enough to the target square
if (distance <= maxRange)
if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
return true;
entity_pos_t circleRadius = halfSize.Length();
@ -1464,7 +1464,7 @@ bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange,
entity_pos_t distance = (pos - targetPos).Length();
if (minRange <= distance && distance <= maxRange)
if (minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange))
return true;
return false;

View File

@ -41,6 +41,7 @@ public:
* some other error, then returns false.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
* If maxRange is negative, then the maximum range is treated as infinity.
*/
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
@ -56,6 +57,7 @@ public:
* some other error, then returns false.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
* If maxRange is negative, then the maximum range is treated as infinity.
*/
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;

View File

@ -113,7 +113,6 @@ sub find_entities
return $File::Find::prune = 1 if $_ eq '.svn';
my $n = $File::Find::name;
return if /~$/;
return if $n =~ /\/special\//;
return unless -f $_;
$n =~ s~\Q$vfsroot\E/(public|internal)/simulation/templates/~~;
$n =~ s/\.xml$//;