1
0
forked from 0ad/0ad

Attempt minor improvements to stances code (see #865).

Don't chase units that are no longer visible, beyond where they were
last seen (fixes #595).

This was SVN commit r9657.
This commit is contained in:
Ykkrosh 2011-06-24 12:35:15 +00:00
parent 4e809a02e8
commit 92fcb737cd
7 changed files with 163 additions and 127 deletions

View File

@ -274,7 +274,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
}
else if (guiName == "Stance")
{
var stanceSelected = Engine.GuiInterfaceCall("StanceSelected", {
var stanceSelected = Engine.GuiInterfaceCall("IsStanceSelected", {
"ents": g_Selection.toList(),
"stance": item
});
@ -385,13 +385,19 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
var formations = getEntityFormationsList(entState);
if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && formations.length)
{
setupUnitPanel("Formation", usedPanels, entState, formations,
function (item) { performFormation(entState.id, item); } );
}
var stances = ["violent","aggressive","passive","defensive","stand"];
// TODO: probably should load the stance list from a data file,
// and/or vary depending on what units are selected
var stances = ["violent", "aggressive", "passive", "defensive", "stand"];
if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && stances.length)
{
setupUnitPanel("Stance", usedPanels, entState, stances,
function (item) { performStance(entState.id, item); } );
}
if (entState.buildEntities && entState.buildEntities.length)
{

View File

@ -298,7 +298,7 @@ GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
return CanMoveEntsIntoFormation(data.ents, data.formationName);
};
GuiInterface.prototype.StanceSelected = function(player, data)
GuiInterface.prototype.IsStanceSelected = function(player, data)
{
for each (var ent in data.ents)
{
@ -567,7 +567,7 @@ var exposedFunctions = {
"GetNextNotification": 1,
"CanMoveEntsIntoFormation": 1,
"StanceSelected": 1,
"IsStanceSelected": 1,
"SetSelectionHighlight": 1,
"SetStatusBars": 1,

View File

@ -5,9 +5,11 @@ UnitAI.prototype.Schema =
"<a:example/>" +
"<element name='DefaultStance'>" +
"<choice>" +
"<value>violent</value>" +
"<value>aggressive</value>" +
"<value>defensive</value>" +
"<value>passive</value>" +
"<value>stand</value>" +
"</choice>" +
"</element>" +
"<element name='FormationController'>" +
@ -45,60 +47,64 @@ UnitAI.prototype.Schema =
"</interleave>" +
"</optional>";
// Very basic stance support (currently just for test maps where we don't want
// everyone killing each other immediately after loading, and for female citizens)
// Unit stances.
// 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).
// respondChaseBeyondVision: start chasing, and don't stop even if it's out
// of this unit's vision range (though still visible to the player)
// respondStandGround: attack enemy but don't move at all
// respondHoldGround: attack enemy but don't move far from current position
// TODO: maybe add targetAggressiveEnemies (don't worry about lone scouts,
// do worry around armies slaughtering the guy standing next to you), etc.
var g_Stances = {
"violent": {
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: true,
respondStandGround : false,
respondHoldGround : false,
respondChaseToHell : true,
respondChaseBeyondVision: true,
respondStandGround: false,
respondHoldGround: false,
},
"aggressive": {
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: true,
respondStandGround : false,
respondHoldGround : false,
respondChaseBeyondVision: false,
respondStandGround: false,
respondHoldGround: false,
},
"defensive": {
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: false,
respondStandGround : false,
respondHoldGround : true,
respondChaseBeyondVision: false,
respondStandGround: false,
respondHoldGround: true,
},
"passive": {
targetVisibleEnemies: false,
targetAttackers: true,
respondFlee: true,
respondChase: false,
respondStandGround : false,
respondHoldGround : false,
respondChaseBeyondVision: false,
respondStandGround: false,
respondHoldGround: false,
},
"stand": {
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: false,
respondStandGround : true,
respondHoldGround : false,
respondChaseBeyondVision: false,
respondStandGround: true,
respondHoldGround: false,
},
};
@ -137,11 +143,6 @@ var UnitFsmSpec = {
// ignore
},
"StanceChanged": function(msg) {
if (this.StanceSpecificQuery(this.stance,true))
return;
},
// Formation handlers:
"FormationLeave": function(msg) {
@ -246,27 +247,39 @@ var UnitFsmSpec = {
}
this.attackType = type;
// If we are already at the target, try attacking it from here
if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
// We are already at the target
// so try attacking it from here.
this.StopMoving();
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.ATTACKING");
else
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
return;
}
// If we can't reach the target, but are standing ground,
// then abandon this attack order
if (this.GetStance().respondStandGround && !this.order.data.force)
{
this.FinishOrder();
return;
}
// Try to move within attack range
else if ((!this.GetStance().respondStandGround || this.order.data.force) && this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
// We've started walking to the given point
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.APPROACHING");
else
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
return;
}
else
this.FinishOrder();
// We can't reach the target, and can't move towards it,
// so abandon this attack order
this.FinishOrder();
},
"Order.Gather": function(msg) {
@ -514,8 +527,8 @@ var UnitFsmSpec = {
// If we entered the idle state we must have nothing better to do,
// so immediately check whether there's anybody nearby to attack.
// (If anyone approaches later, it'll be handled via LosRangeUpdate.)
if (this.StanceSpecificQuery(this.stance))
return true;
if (this.FindNewTargets())
return true; // (abort the FSM transition since we may have already switched state)
// Nobody to attack - stay in idle
return false;
@ -572,11 +585,6 @@ var UnitFsmSpec = {
this.FinishOrder();
}
},
"StanceChanged": function(msg) {
if (this.StanceSpecificQuery(this.stance))
return;
},
},
"WALKING": {
@ -634,12 +642,12 @@ var UnitFsmSpec = {
},
"Timer": function(msg) {
if (!this.ChaseTargetedEntity(this.order.data.target, this.order.data.force))
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
{
this.StopMoving();
this.FinishOrder();
// we return to our position
// Return to our original position
if (this.GetStance().respondHoldGround)
this.WalkToHeldPosition();
}
@ -691,7 +699,8 @@ var UnitFsmSpec = {
return;
}
if (this.ChaseTargetedEntity(this.order.data.target))
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(this.order.data.target, this.order.data.force))
{
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
@ -706,16 +715,13 @@ var UnitFsmSpec = {
if (this.FinishOrder())
return;
// see if we can switch to a new nearby enemy
if (this.StanceSpecificQuery(this.stance, true))
// See if we can switch to a new nearby enemy
if (this.FindNewTargets())
return;
// we return to our position
// Return to our original position
if (this.GetStance().respondHoldGround)
{
if (this.WalkToHeldPosition())
return;
}
this.WalkToHeldPosition();
},
// TODO: respond to target deaths immediately, rather than waiting
@ -733,12 +739,12 @@ var UnitFsmSpec = {
},
"Timer": function(msg) {
if (!this.ChaseTargetedEntity(this.order.data.target))
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
{
this.StopMoving();
this.FinishOrder();
// we return to our position
// Return to our original position
if (this.GetStance().respondHoldGround)
this.WalkToHeldPosition();
}
@ -1365,7 +1371,7 @@ UnitAI.prototype.OnCreate = function()
UnitAI.prototype.OnOwnershipChanged = function(msg)
{
this.SetupRangeQuery(msg.to);
this.SetupRangeQuery();
};
UnitAI.prototype.OnDestroy = function()
@ -1382,8 +1388,11 @@ UnitAI.prototype.OnDestroy = function()
// Set up a range query for all enemy units within LOS range
// which can be attacked.
// This should be called whenever our ownership changes.
UnitAI.prototype.SetupRangeQuery = function(owner)
UnitAI.prototype.SetupRangeQuery = function()
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var owner = cmpOwnership.GetOwner();
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
@ -1410,11 +1419,11 @@ UnitAI.prototype.SetupRangeQuery = function(owner)
}
}
var range = this.StanceSpecificRange();
var range = this.GetQueryRange();
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, 0, range.max, players, IID_DamageReceiver);
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver);
rangeMan.EnableActiveQuery(this.losRangeQuery);
}
};
//// FSM linkage functions ////
@ -1861,12 +1870,18 @@ UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
UnitAI.prototype.MoveToTarget = function(target)
{
if (!this.CheckTargetVisible(target))
return false;
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
};
UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
{
if (!this.CheckTargetVisible(target))
return false;
var cmpRanged = Engine.QueryInterface(this.entity, iid);
var range = cmpRanged.GetRange(type);
@ -1876,6 +1891,9 @@ UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
{
if (!this.CheckTargetVisible(target))
return false;
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, min, max);
};
@ -1889,6 +1907,26 @@ UnitAI.prototype.CheckTargetRange = function(target, iid, type)
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
};
/**
* Returns true if the target entity is visible through the FoW/SoD.
*/
UnitAI.prototype.CheckTargetVisible = function(target)
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return false;
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
return false;
if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) == "hidden")
return false;
// Either visible directly, or visible in fog
return true;
};
UnitAI.prototype.FaceTowardsTarget = function(target)
{
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
@ -1942,7 +1980,7 @@ UnitAI.prototype.CheckTargetIsInVisionRange = function(target)
var distance = DistanceBetweenEntities(this.entity,target);
return distance < range;
}
};
UnitAI.prototype.GetBestAttack = function()
{
@ -1971,8 +2009,8 @@ UnitAI.prototype.AttackVisibleEntity = function(ents)
};
/**
* Try to find one of the given entities which can be attacked,
* within zone.
* Try to find one of the given entities which can be attacked
* and which is close to the hold position, and start attacking it.
* Returns true if it found something to attack.
*/
UnitAI.prototype.AttackEntityInZone = function(ents)
@ -2017,38 +2055,45 @@ UnitAI.prototype.RespondToTargetedEntities = function(ents)
return false;
};
/*
* Try to chase targeted entity given our current stance
/**
* Returns true if we should stop following the target entity.
*/
UnitAI.prototype.ChaseTargetedEntity = function(ent, force)
UnitAI.prototype.ShouldAbandonChase = function(target, force)
{
if (this.GetStance().respondChase)
{
if (this.CheckTargetIsInVisionRange(ent))
{
return true;
}
}
// Stop if we're in hold-ground mode and it's too far from the holding point
if (this.GetStance().respondHoldGround)
{
if (!this.CheckTargetDistanceFromHeldPosition(target, this.attackType))
return true;
}
if (this.GetStance().respondChaseToHell || force)
{
//if ( TODO : Check ent is not in Shroud of Darkness)
{
return true;
}
}
// Stop if it's left our vision range, unless we're especially persistent
if (!force && !this.GetStance().respondChaseBeyondVision)
{
if (!this.CheckTargetIsInVisionRange(target))
return true;
}
if (this.GetStance().respondHoldGround)
{
if (this.CheckTargetDistanceFromHeldPosition(ent, this.attackType))
{
return true;
}
}
// (Note that CCmpUnitMotion will detect if the target is lost in FoW,
// and will continue moving to its last seen position and then stop)
return false;
return false;
};
}
/*
* Returns whether we should chase the targeted entity,
* given our current stance.
*/
UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force)
{
if (this.GetStance().respondChase)
return true;
if (force)
return true;
return false;
};
//// External interface functions ////
@ -2289,32 +2334,29 @@ UnitAI.prototype.SwitchToStance = function(stance)
if (stance == "stand" || stance == "defensive" || stance == "passive")
this.StopMoving();
UnitFsm.ProcessMessage(this, {"type": "StanceChanged", "stance": stance});
// Reset the range query, since the range depends on stance
this.SetupRangeQuery();
}
UnitAI.prototype.StanceSpecificQuery = function(stance, disable)
/**
* Resets losRangeQuery, and if there are some targets in range that we can
* attack then we start attacking and this returns true; otherwise, returns false.
*/
UnitAI.prototype.FindNewTargets = function()
{
if (!g_Stances[stance])
{
error("UnitAI: Setting to invalid stance '"+stance+"'");
return false;
}
if (!this.losRangeQuery)
return false;
var range = this.StanceSpecificRange();
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var ents = rangeMan.ModifyRangeActiveQuery(this.losRangeQuery, range.min, range.max);
var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
if (disable && !this.IsAnimal())
rangeMan.DisableActiveQuery(this.losRangeQuery);
if (!this.GetStance().targetVisibleEnemies)
return false;
return this.RespondToTargetedEntities(ents);
};
UnitAI.prototype.StanceSpecificRange = function()
UnitAI.prototype.GetQueryRange = function()
{
var ret = { "min": 0, "max": 0 };
if (this.GetStance().respondStandGround)

View File

@ -459,23 +459,6 @@ public:
return (tag_t)id;
}
virtual std::vector<entity_id_t> ModifyRangeActiveQuery(tag_t tag,
entity_pos_t minRange, entity_pos_t maxRange)
{
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: ModifyRangeActiveQuery called with invalid tag %d", tag);
std::vector<entity_id_t> r;
return r;
}
Query& q = it->second;
q.minRange = minRange;
q.maxRange = maxRange;
return ResetActiveQuery(tag);
}
virtual void DestroyActiveQuery(tag_t tag)
{
if (m_Queries.find(tag) == m_Queries.end())

View File

@ -22,8 +22,10 @@
#include "ICmpObstruction.h"
#include "ICmpObstructionManager.h"
#include "ICmpOwnership.h"
#include "ICmpPosition.h"
#include "ICmpPathfinder.h"
#include "ICmpRangeManager.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Render.h"
@ -1003,6 +1005,20 @@ bool CCmpUnitMotion::CheckTargetMovement(CFixedVector2D from, entity_pos_t minDe
if (!PathIsShort(m_LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
return false;
// Fail if the target is no longer visible to this entity's owner
// (in which case we'll continue moving to its last known location,
// unless it comes back into view before we reach that location)
CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), GetEntityId());
if (!cmpOwnership.null())
{
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpRangeManager.null())
{
if (cmpRangeManager->GetLosVisibility(m_TargetEntity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
return false;
}
}
// The target moved and we need to update our current path;
// change the goal here and expect our caller to start the path request
m_FinalGoal.x = targetPos.X;

View File

@ -36,7 +36,6 @@ std::string ICmpRangeManager::GetLosVisibility_wrapper(entity_id_t ent, int play
BEGIN_INTERFACE_WRAPPER(RangeManager)
DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
DEFINE_INTERFACE_METHOD_5("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
DEFINE_INTERFACE_METHOD_3("ModifyRangeActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ModifyRangeActiveQuery, ICmpRangeManager::tag_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)

View File

@ -101,16 +101,6 @@ public:
virtual tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
/**
* Modify an active query's range parameters and execute a ResetActiveQuery.
* @param tag identifier of query.
* @param minRange non-negative minimum distance in metres (inclusive).
* @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance.
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector<entity_id_t> ModifyRangeActiveQuery(tag_t tag,
entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* Destroy a query and clean up resources. This must be called when an entity no longer needs its
* query (e.g. when the entity is destroyed).