Changeset View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 739 Lines • ▼ Show 20 Lines | "FORMATIONCONTROLLER": { | ||||
}, | }, | ||||
"Order.Stop": function(msg) { | "Order.Stop": function(msg) { | ||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.ResetOrderVariant(); | cmpFormation.ResetOrderVariant(); | ||||
if (!this.IsAttackingAsFormation()) | if (!this.IsAttackingAsFormation()) | ||||
this.CallMemberFunction("Stop", [false]); | this.CallMemberFunction("Stop", [false]); | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
Silier: I am not sure this is safe to do.
Formation controller would not go to Idle state so it would… | |||||
Done Inline ActionsI think I might have accidentally removed this line, thanks for noticing. wraitii: I think I might have accidentally removed this line, thanks for noticing. | |||||
// Don't move the members back into formation, | |||||
// as the formation then resets and it looks odd when walk-stopping. | |||||
// TODO: this should be improved in the formation reshaping code. | |||||
}, | }, | ||||
"Order.Attack": function(msg) { | "Order.Attack": function(msg) { | ||||
let target = msg.data.target; | let target = msg.data.target; | ||||
let allowCapture = msg.data.allowCapture; | let allowCapture = msg.data.allowCapture; | ||||
let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); | let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); | ||||
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) | if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) | ||||
target = cmpTargetUnitAI.GetFormationController(); | target = cmpTargetUnitAI.GetFormationController(); | ||||
▲ Show 20 Lines • Show All 172 Lines • ▼ Show 20 Lines | "FORMATIONCONTROLLER": { | ||||
"Order.Unpack": function(msg) { | "Order.Unpack": function(msg) { | ||||
this.CallMemberFunction("Unpack", [false]); | this.CallMemberFunction("Unpack", [false]); | ||||
this.SetNextState("MEMBER"); | this.SetNextState("MEMBER"); | ||||
}, | }, | ||||
"IDLE": { | "IDLE": { | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(false); | |||||
return false; | return false; | ||||
}, | }, | ||||
}, | }, | ||||
"WALKING": { | "WALKING": { | ||||
"enter": function() { | "enter": function() { | ||||
if (!this.MoveTo(this.order.data)) | if (!this.MoveTo(this.order.data)) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 275 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
cmpFormation.SetRearrange(true); | cmpFormation.SetRearrange(true); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
"MEMBER": { | "MEMBER": { | ||||
// Wait for individual members to finish | // Wait for individual members to finish | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | // Don't rearrange the formation, as that forces all units to stop | ||||
// what they're doing. | |||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(false); | cmpFormation.SetRearrange(false); | ||||
this.StopMoving(); | // While waiting on members, the formation is more like | ||||
// a group of unit and does not have a well-defined position, | |||||
// so move the controller out of the world to enforce that. | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
Not Done Inline ActionscmpPosition check? Stan: cmpPosition check? | |||||
Done Inline ActionsI didn't bother because we can assume formations have a position otherwise, but I guess fair enough. wraitii: I didn't bother because we can assume formations have a position otherwise, but I guess fair… | |||||
cmpPosition.MoveOutOfWorld(); | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
return false; | return false; | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// Have all members finished the task? | // Have all members finished the task? | ||||
if (!this.TestAllMemberFunction("HasFinishedOrder", [])) | if (!this.TestAllMemberFunction("HasFinishedOrder", [])) | ||||
return; | return; | ||||
this.CallMemberFunction("ResetFinishOrder", []); | this.CallMemberFunction("ResetFinishOrder", []); | ||||
// Execute the next order | // Execute the next order | ||||
if (this.FinishOrder()) | if (this.FinishOrder()) | ||||
{ | { | ||||
// if WalkAndFight order, look for new target before moving again | // if WalkAndFight order, look for new target before moving again | ||||
if (this.IsWalkingAndFighting()) | if (this.IsWalkingAndFighting()) | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
return; | return; | ||||
} | } | ||||
return false; | return; | ||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
// Reform entirely as members might be all over the place now. | |||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.MoveToMembersCenter(); | if (cmpFormation) | ||||
cmpFormation.MoveMembersIntoFormation(true); | |||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
// States for entities moving as part of a formation: | // States for entities moving as part of a formation: | ||||
"FORMATIONMEMBER": { | "FORMATIONMEMBER": { | ||||
"FormationLeave": function(msg) { | "FormationLeave": function(msg) { | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | "FORMATIONMEMBER": { | ||||
}, | }, | ||||
"IDLE": "INDIVIDUAL.IDLE", | "IDLE": "INDIVIDUAL.IDLE", | ||||
"CHEERING": "INDIVIDUAL.CHEERING", | "CHEERING": "INDIVIDUAL.CHEERING", | ||||
"WALKING": { | "WALKING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.formationOffset = { "x": this.order.data.x, "z": this.order.data.z }; | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
// Prevent unit to turn when stopmoving is called. | |||||
cmpUnitMotion.SetFacePointAfterMove(false); | |||||
cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z); | cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z); | ||||
if (this.order.data.offsetsChanged) | if (this.order.data.offsetsChanged) | ||||
{ | { | ||||
let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
this.formationAnimationVariant = cmpFormation.GetFormationAnimationVariant(this.entity); | this.formationAnimationVariant = cmpFormation.GetFormationAnimationVariant(this.entity); | ||||
} | } | ||||
if (this.formationAnimationVariant) | if (this.formationAnimationVariant) | ||||
this.SetAnimationVariant(this.formationAnimationVariant); | this.SetAnimationVariant(this.formationAnimationVariant); | ||||
else if (this.order.data.variant) | else if (this.order.data.variant) | ||||
this.SetAnimationVariant(this.order.data.variant); | this.SetAnimationVariant(this.order.data.variant); | ||||
else | else | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// Don't use the logic from unitMotion, as SetInPosition | |||||
// has already given us a custom rotation | |||||
// (or we failed to move and thus don't care.) | |||||
FreagarachUnsubmitted Not Done Inline ActionsDot after ). Freagarach: Dot after `)`. | |||||
this.SetFacePointAfterMove(false); | |||||
this.StopMoving(); | this.StopMoving(); | ||||
// Reset default behaviour (TODO: actually get the previuos behaviour). | |||||
Not Done Inline Actionstypo Stan: typo | |||||
this.SetFacePointAfterMove(true); | this.SetFacePointAfterMove(true); | ||||
}, | }, | ||||
// Occurs when the unit has reached its destination and the controller | |||||
// is done moving. The controller is notified. | |||||
"MovementUpdate": function(msg) { | "MovementUpdate": function(msg) { | ||||
// We can only finish this order if the move was really completed. | // We're supposed to be walking in formation, | ||||
let cmpPosition = Engine.QueryInterface(this.formationController, IID_Position); | // but the controller has no position -> abort. | ||||
let atDestination = cmpPosition && cmpPosition.IsInWorld(); | let cmpControllerPosition = Engine.QueryInterface(this.formationController, IID_Position); | ||||
if (!atDestination && cmpPosition) | if (!cmpControllerPosition || !cmpControllerPosition.IsInWorld()) | ||||
{ | { | ||||
let pos = cmpPosition.GetPosition2D(); | this.FinishOrder(); | ||||
atDestination = this.CheckPointRangeExplicit(pos.X + this.order.data.x, pos.Y + this.order.data.z, 0, 1); | |||||
} | |||||
if (!atDestination && !msg.likelyFailure) | |||||
return; | return; | ||||
} | |||||
if (this.FinishOrder()) | if (!msg.likelyFailure && !msg.likelySuccess) | ||||
return; | return; | ||||
let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
cmpFormation.SetInPosition(this.entity); | cmpFormation.SetInPosition(this.entity); | ||||
this.FinishOrder(); | |||||
delete this.formationOffset; | |||||
}, | }, | ||||
}, | }, | ||||
// Special case used by Order.LeaveFoundation | // Special case used by Order.LeaveFoundation | ||||
"WALKINGTOPOINT": { | "WALKINGTOPOINT": { | ||||
"enter": function() { | "enter": function() { | ||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
▲ Show 20 Lines • Show All 153 Lines • ▼ Show 20 Lines | "IDLE": { | ||||
return; | return; | ||||
// If we entered the idle state we must have nothing better to do, | // If we entered the idle state we must have nothing better to do, | ||||
// so immediately check whether there's anybody nearby to attack. | // so immediately check whether there's anybody nearby to attack. | ||||
// (If anyone approaches later, it'll be handled via LosRangeUpdate.) | // (If anyone approaches later, it'll be handled via LosRangeUpdate.) | ||||
if (this.FindNewTargets()) | if (this.FindNewTargets()) | ||||
return; | return; | ||||
if (this.formationOffset && this.formationController) | |||||
{ | |||||
this.PushOrder("FormationWalk", { | |||||
"target": this.formationController, | |||||
"x": this.formationOffset.x, | |||||
"z": this.formationOffset.z | |||||
}); | |||||
return; | |||||
} | |||||
if (!this.isIdle) | if (!this.isIdle) | ||||
{ | { | ||||
this.isIdle = true; | this.isIdle = true; | ||||
Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | ||||
} | } | ||||
}, | }, | ||||
Not Done Inline Actionsthis.heldPosition is not correct check. Silier: this.heldPosition is not correct check.
It is never unset when leaving defensive stance so… | |||||
}, | }, | ||||
Not Done Inline Actionscould you please move it before setting entity to idle and sending message? Silier: could you please move it before setting entity to idle and sending message?
Now there is… | |||||
"WALKING": { | "WALKING": { | ||||
Not Done Inline ActionsIssue and question: Why I did not see this is because when leaving almost any state it is called walktoheldposition, which enters walking state correctly so entity should be around held position. In case they are not, that would mean they could not find path and here they are trying to do it again, so distance can be quite large and in case their path is no longer obstructed and they can find the path they would move while in idle. Is this intended behaviour? Silier: Issue and question:
This will move entity to point without changing state, so moving while in… | |||||
Done Inline ActionsI did do that on purpose -> these units are Idle, and they should perform the LOS check for nearby enemies. So they need to stay in Idle. Calling walktoheldposition would move the WALKING, which would no longer (afaik) trigger this auto-detection of enemy.
Actually no, this is designed to handle such cases as units having finished repairing or gathering, which don't call "WalkToHeldPosition". Arguably that could be fixed there, but I don't see it as obviously superior. Moving while in Idle is not (imo) a bug, the unit _is_ idle, it's moving but that can be stopped anytime, it's not actually following an order to do something. From the AI POV for example, this unit is indeed idle. wraitii: I did do that on purpose -> these units are Idle, and they should perform the LOS check for… | |||||
Not Done Inline Actionswhen walking to held position is not supposed to be order, then there should not be none walk to held position and handle it here, but again they should be in any walking state when moving imo. with this it is once behaving like order with custom state and once as moving unit in idle state what is inconsistent and one cannot be sure anymore by asking if unit is idle/walking or not because it does same thing in two different ways. this looks to me like lets handle this special case as entity would be idle hack. Silier: when walking to held position is not supposed to be order, then there should not be none walk… | |||||
Done Inline Actions
This is debatable if we ever want to have order order types that are moving (such as moving during GATHER or ATTACK).
One should not check if a unit is walking by calling unitAI.IsIdle. For that, one must use CmpUnitMotion.IsMoveRequested I would actually agree that MoveToHeldPosition should be removed. wraitii: > but again they should be in any walking state when moving imo.
This is debatable if we ever… | |||||
Done Inline ActionsWith that being said, this is a bigger departure from usual code. SO I will change this to call WalkToHeldPosition I think. wraitii: With that being said, this is a bigger departure from usual code. SO I will change this to call… | |||||
"enter": function() { | "enter": function() { | ||||
if (!this.MoveTo(this.order.data)) | if (!this.MoveTo(this.order.data)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
}, | }, | ||||
▲ Show 20 Lines • Show All 2,167 Lines • ▼ Show 20 Lines | if (!this.orderQueue.length) | ||||
let template = cmpTemplateManager.GetCurrentTemplateName(this.entity); | let template = cmpTemplateManager.GetCurrentTemplateName(this.entity); | ||||
error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); | error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); | ||||
} | } | ||||
this.orderQueue.shift(); | this.orderQueue.shift(); | ||||
this.order = this.orderQueue[0]; | this.order = this.orderQueue[0]; | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (this.orderQueue.length && (this.IsGarrisoned() || cmpPosition && cmpPosition.IsInWorld())) | if (this.orderQueue.length && (this.IsGarrisoned() || this.IsFormationController() || | ||||
cmpPosition && cmpPosition.IsInWorld())) | |||||
{ | { | ||||
let ret = this.UnitFsm.ProcessMessage(this, | let ret = this.UnitFsm.ProcessMessage(this, | ||||
{ "type": "Order."+this.order.type, "data": this.order.data } | { "type": "Order."+this.order.type, "data": this.order.data } | ||||
); | ); | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
// If the order was rejected then immediately take it off | // If the order was rejected then immediately take it off | ||||
▲ Show 20 Lines • Show All 2,630 Lines • Show Last 20 Lines |
I am not sure this is safe to do.
Formation controller would not go to Idle state so it would continue to be in the current state finishing whatever is doing if not stopped by unitmotion messages.
Also Order.Stop would stay as current order.