mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Trigger an exit-reentry when the target entity of an order is renamed
This lets sunit AI FSM states correctly handle target entity renaming by
processing a message when that happens. The default behaviour is to
leave-reenter the state, which re-runs sanity checks and optionally
picks a better behaviour.
UnitMotion is still not made aware of entity renaming, as the
leave-enter makes it irrelevant in practice. It still may be a good idea
to implement that someday.
Fixes the concern raised at de1bb8a766.
Fixes #5584
Comments by: bb, Freagarach
Reported by: minohaka, bb, Freagarach
Differential Revision: https://code.wildfiregames.com/D2735
This was SVN commit r23708.
This commit is contained in:
parent
131590927f
commit
0363202a20
2 changed files with 110 additions and 7 deletions
|
|
@ -193,6 +193,13 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
// ignore
|
||||
},
|
||||
|
||||
"OrderTargetRenamed": function() {
|
||||
// By default, trigger an exit-reenter
|
||||
// so that state preconditions are checked against the new entity
|
||||
// (there is no reason to assume the target is still valid).
|
||||
this.SetNextState(this.GetCurrentState());
|
||||
},
|
||||
|
||||
// Formation handlers:
|
||||
|
||||
"FormationLeave": function(msg) {
|
||||
|
|
@ -1978,6 +1985,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
|
||||
"Timer": function(msg) {
|
||||
let target = this.order.data.target;
|
||||
let attackType = this.order.data.attackType;
|
||||
|
||||
// Check the target is still alive and attackable
|
||||
if (!this.CanAttack(target))
|
||||
|
|
@ -2000,11 +2008,16 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
if (!cmpBuildingAI)
|
||||
{
|
||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
cmpAttack.PerformAttack(this.order.data.attackType, target);
|
||||
cmpAttack.PerformAttack(attackType, target);
|
||||
}
|
||||
|
||||
// PerformAttack might have triggered messages that moved us to another state.
|
||||
if (this.GetCurrentState() != "INDIVIDUAL.COMBAT.ATTACKING")
|
||||
return;
|
||||
|
||||
|
||||
// Check we can still reach the target for the next attack
|
||||
if (this.CheckTargetAttackRange(target, this.order.data.attackType))
|
||||
if (this.CheckTargetAttackRange(target, attackType))
|
||||
{
|
||||
if (this.resyncAnimation)
|
||||
{
|
||||
|
|
@ -4144,24 +4157,32 @@ UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
|
|||
UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
|
||||
{
|
||||
let changed = false;
|
||||
for (let order of this.orderQueue)
|
||||
let currentOrderChanged = false;
|
||||
for (let i = 0; i < this.orderQueue.length; ++i)
|
||||
{
|
||||
let order = this.orderQueue[i];
|
||||
if (order.data && order.data.target && order.data.target == msg.entity)
|
||||
{
|
||||
changed = true;
|
||||
if (i == 0)
|
||||
currentOrderChanged = true;
|
||||
order.data.target = msg.newentity;
|
||||
}
|
||||
if (order.data && order.data.formationTarget && order.data.formationTarget == msg.entity)
|
||||
{
|
||||
changed = true;
|
||||
if (i == 0)
|
||||
currentOrderChanged = true;
|
||||
order.data.formationTarget = msg.newentity;
|
||||
}
|
||||
}
|
||||
if (this.repairTarget && this.repairTarget == msg.entity)
|
||||
this.repairTarget = msg.newentity;
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
if (changed)
|
||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
|
||||
if (currentOrderChanged)
|
||||
this.UnitFsm.ProcessMessage(this, { "type": "OrderTargetRenamed", "data": msg });
|
||||
|
||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
|
||||
};
|
||||
|
||||
UnitAI.prototype.OnAttacked = function(msg)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
Engine.LoadHelperScript("FSM.js");
|
||||
Engine.LoadHelperScript("Entity.js");
|
||||
Engine.LoadHelperScript("Player.js");
|
||||
Engine.LoadHelperScript("Sound.js");
|
||||
Engine.LoadComponentScript("interfaces/Attack.js");
|
||||
Engine.LoadComponentScript("interfaces/Auras.js");
|
||||
Engine.LoadComponentScript("interfaces/Builder.js");
|
||||
Engine.LoadComponentScript("interfaces/BuildingAI.js");
|
||||
Engine.LoadComponentScript("interfaces/Capturable.js");
|
||||
Engine.LoadComponentScript("interfaces/Resistance.js");
|
||||
|
|
@ -17,6 +19,86 @@ Engine.LoadComponentScript("interfaces/UnitAI.js");
|
|||
Engine.LoadComponentScript("Formation.js");
|
||||
Engine.LoadComponentScript("UnitAI.js");
|
||||
|
||||
/**
|
||||
* Fairly straightforward test that entity renaming is handled
|
||||
* by unitAI states. These ought to be augmented with integration tests, ideally.
|
||||
*/
|
||||
function TestTargetEntityRenaming(init_state, post_state, setup)
|
||||
{
|
||||
ResetState();
|
||||
const player_ent = 5;
|
||||
const target_ent = 6;
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
||||
"SetInterval": () => {},
|
||||
"SetTimeout": () => {}
|
||||
});
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => false
|
||||
});
|
||||
|
||||
let unitAI = ConstructComponent(player_ent, "UnitAI", {
|
||||
"FormationController": "false",
|
||||
"DefaultStance": "aggressive",
|
||||
"FleeDistance": 10
|
||||
});
|
||||
unitAI.OnCreate();
|
||||
|
||||
setup(unitAI, player_ent, target_ent);
|
||||
|
||||
TS_ASSERT_EQUALS(unitAI.GetCurrentState(), init_state);
|
||||
|
||||
unitAI.OnGlobalEntityRenamed({
|
||||
"entity": target_ent,
|
||||
"newentity": target_ent + 1
|
||||
});
|
||||
|
||||
TS_ASSERT_EQUALS(unitAI.GetCurrentState(), post_state);
|
||||
}
|
||||
|
||||
TestTargetEntityRenaming(
|
||||
"INDIVIDUAL.GARRISON.APPROACHING", "INDIVIDUAL.IDLE",
|
||||
(unitAI, player_ent, target_ent) => {
|
||||
unitAI.CanGarrison = (target) => target == target_ent;
|
||||
unitAI.MoveToGarrisonRange = (target) => target == target_ent;
|
||||
|
||||
AddMock(target_ent, IID_GarrisonHolder, {
|
||||
"CanPickup": () => false
|
||||
});
|
||||
|
||||
unitAI.Garrison(target_ent, false);
|
||||
}
|
||||
);
|
||||
|
||||
TestTargetEntityRenaming(
|
||||
"INDIVIDUAL.REPAIR.REPAIRING", "INDIVIDUAL.IDLE",
|
||||
(unitAI, player_ent, target_ent) => {
|
||||
QueryBuilderListInterface = () => {};
|
||||
unitAI.CheckTargetRange = () => true;
|
||||
unitAI.CanRepair = (target) => target == target_ent;
|
||||
|
||||
unitAI.Repair(target_ent, false, false);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
TestTargetEntityRenaming(
|
||||
"INDIVIDUAL.FLEEING", "INDIVIDUAL.FLEEING",
|
||||
(unitAI, player_ent, target_ent) => {
|
||||
DistanceBetweenEntities = () => 10;
|
||||
unitAI.CheckTargetRangeExplicit = () => false;
|
||||
|
||||
AddMock(player_ent, IID_UnitMotion, {
|
||||
"MoveToTargetRange": () => true,
|
||||
"GetRunMultiplier": () => 1,
|
||||
"SetSpeedMultiplier": () => {},
|
||||
"StopMoving": () => {}
|
||||
});
|
||||
|
||||
unitAI.Flee(target_ent, false);
|
||||
}
|
||||
);
|
||||
|
||||
/* Regression test.
|
||||
* Tests the FSM behaviour of a unit when walking as part of a formation,
|
||||
* then exiting the formation.
|
||||
|
|
|
|||
Loading…
Reference in a new issue