Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 391 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"Order.Flee": function(msg) { | "Order.Flee": function(msg) { | ||||
if (!this.AbleToMove()) | if (!this.AbleToMove()) | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
this.SetNextState("INDIVIDUAL.FLEEING"); | this.SetNextState("INDIVIDUAL.FLEEING"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
}, | }, | ||||
"Order.Attack": function(msg) { | "Order.Attack": function(msg) { | ||||
let type = this.GetBestAttackAgainst(msg.data.target, msg.data.allowCapture); | // We should not attack formations, but their members. However if we try to attack a formation store the formation. | ||||
let cmpFormation = Engine.QueryInterface(this.order.data.target, IID_Formation); | |||||
if (cmpFormation) | |||||
{ | |||||
this.order.data.formationTarget = this.order.data.target; | |||||
this.order.data.target = cmpFormation.GetClosestMember(this.entity, (t) => this.CanAttack( | |||||
t, | |||||
(this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove(), | |||||
this.order.data.ignoreAttackEffects, | |||||
this.order.data.prefAttackTypes, | |||||
this.order.data.projectile)); | |||||
} | |||||
let type = this.GetBestAttackAgainst( | |||||
this.order.data.target, | |||||
(this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove(), | |||||
this.order.data.ignoreAttackEffects, | |||||
this.order.data.prefAttackTypes, | |||||
this.order.data.projectile); | |||||
if (!type) | if (!type) | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
msg.data.attackType = type; | msg.data.attackType = type; | ||||
this.RememberTargetPosition(); | this.RememberTargetPosition(); | ||||
if (msg.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") | if (msg.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") | ||||
this.RememberTargetPosition(this.orderQueue[1].data); | this.RememberTargetPosition(this.orderQueue[1].data); | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | "Order.Gather": function(msg) { | ||||
{ | { | ||||
this.SetNextState("INDIVIDUAL.GATHER.FINDINGNEWTARGET"); | this.SetNextState("INDIVIDUAL.GATHER.FINDINGNEWTARGET"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
if (this.MustKillGatherTarget(msg.data.target)) | if (this.MustKillGatherTarget(msg.data.target)) | ||||
{ | { | ||||
// Make sure we can attack the target, else we'll get very stuck | // Make sure we can attack the target, else we'll get very stuck | ||||
if (!this.GetBestAttackAgainst(msg.data.target, false)) | if (!this.GetBestAttackAgainst(msg.data.target, false, { "Capture": true })) | ||||
{ | { | ||||
// Oops, we can't attack at all - give up | // Oops, we can't attack at all - give up | ||||
// TODO: should do something so the player knows why this failed | // TODO: should do something so the player knows why this failed | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
} | } | ||||
// The target was visible when this order was issued, | // The target was visible when this order was issued, | ||||
// but could now be invisible again. | // but could now be invisible again. | ||||
if (!this.CheckTargetVisible(msg.data.target)) | if (!this.CheckTargetVisible(msg.data.target)) | ||||
Show All 9 Lines | if (this.MustKillGatherTarget(msg.data.target)) | ||||
"x": msg.data.lastPos.x, | "x": msg.data.lastPos.x, | ||||
"z": msg.data.lastPos.z, | "z": msg.data.lastPos.z, | ||||
"type": msg.data.type, | "type": msg.data.type, | ||||
"template": msg.data.template | "template": msg.data.template | ||||
}); | }); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false }); | this.PushOrderFront("Attack", { | ||||
"target": this.order.data.target, | |||||
"force": !!this.order.data.force, | |||||
"hunting": true, | |||||
"ignoreAttackEffects": { "Capture": true }, | |||||
"prefAttackTypes": [], | |||||
"projectile": undefined | |||||
}); | |||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
// If the unit is full go to the nearest dropsite instead of trying to gather. | // If the unit is full go to the nearest dropsite instead of trying to gather. | ||||
if (!cmpResourceGatherer.CanCarryMore(msg.data.type.generic)) | if (!cmpResourceGatherer.CanCarryMore(msg.data.type.generic)) | ||||
{ | { | ||||
this.SetNextState("INDIVIDUAL.GATHER.RETURNINGRESOURCE"); | this.SetNextState("INDIVIDUAL.GATHER.RETURNINGRESOURCE"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
▲ Show 20 Lines • Show All 215 Lines • ▼ Show 20 Lines | "Order.Stop": function(msg) { | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
// Don't move the members back into formation, | // Don't move the members back into formation, | ||||
// as the formation then resets and it looks odd when walk-stopping. | // as the formation then resets and it looks odd when walk-stopping. | ||||
// TODO: this should be improved in the formation reshaping code. | // 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 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(); | ||||
if (!this.CheckFormationTargetAttackRange(target)) | if (!this.CheckFormationTargetAttackRange(target)) | ||||
{ | { | ||||
if (this.AbleToMove() && this.CheckTargetVisible(target)) | if (this.AbleToMove() && this.CheckTargetVisible(target)) | ||||
{ | { | ||||
this.SetNextState("COMBAT.APPROACHING"); | this.SetNextState("COMBAT.APPROACHING"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
} | } | ||||
this.CallMemberFunction("Attack", [target, allowCapture, false]); | this.CallMemberFunction("Attack", [target, msg.data.ignoreAttackEffects, msg.data.prefAttackTypes, msg.data.projectile,, false]); | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (cmpAttack && cmpAttack.CanAttackAsFormation()) | if (cmpAttack && cmpAttack.CanAttackAsFormation()) | ||||
this.SetNextState("COMBAT.ATTACKING"); | this.SetNextState("COMBAT.ATTACKING"); | ||||
else | else | ||||
this.SetNextState("MEMBER"); | this.SetNextState("MEMBER"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
}, | }, | ||||
Show All 34 Lines | "Order.Gather": function(msg) { | ||||
"x": data.lastPos.x, | "x": data.lastPos.x, | ||||
"z": data.lastPos.z, | "z": data.lastPos.z, | ||||
"type": data.type, | "type": data.type, | ||||
"template": data.template | "template": data.template | ||||
}); | }); | ||||
} | } | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false, "min": 0, "max": 10 }); | this.PushOrderFront("Attack", { | ||||
"target": msg.data.target, | |||||
"force": !!msg.data.force, | |||||
"hunting": true, | |||||
"ignoreAttackEffects": { "Capture": true }, | |||||
"prefAttackTypes": [], | |||||
"projectile": undefined, | |||||
"min": 0, | |||||
"max": 10 | |||||
}); | |||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
} | } | ||||
// TODO: on what should we base this range? | // TODO: on what should we base this range? | ||||
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | ||||
{ | { | ||||
if (!this.CanGather(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | if (!this.CanGather(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
▲ Show 20 Lines • Show All 256 Lines • ▼ Show 20 Lines | "PATROL": { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
// Memorize the origin position in case that we want to go back. | // Memorize the origin position in case that we want to go back. | ||||
if (!this.patrolStartPosOrder) | if (!this.patrolStartPosOrder) | ||||
{ | { | ||||
this.patrolStartPosOrder = cmpPosition.GetPosition(); | this.patrolStartPosOrder = cmpPosition.GetPosition(); | ||||
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | ||||
this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | this.patrolStartPosOrder.ignoreAttackEffects = this.order.data.ignoreAttackEffects; | ||||
this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes; | |||||
this.patrolStartPosOrder.projectile = this.order.data.projectile; | |||||
} | } | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
▲ Show 20 Lines • Show All 160 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
}, | }, | ||||
"MovementUpdate": function(msg) { | "MovementUpdate": function(msg) { | ||||
let target = this.order.data.target; | let target = this.order.data.target; | ||||
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(); | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
this.CallMemberFunction("Attack", [target, this.order.data.allowCapture, false]); | this.CallMemberFunction("Attack", [target, this.order.data.ignoreAttackEffects, this.order.data.prefAttackTypes, this.order.data.projectile, false]); | ||||
if (cmpAttack.CanAttackAsFormation()) | if (cmpAttack.CanAttackAsFormation()) | ||||
this.SetNextState("COMBAT.ATTACKING"); | this.SetNextState("COMBAT.ATTACKING"); | ||||
else | else | ||||
this.SetNextState("MEMBER"); | this.SetNextState("MEMBER"); | ||||
}, | }, | ||||
}, | }, | ||||
"ATTACKING": { | "ATTACKING": { | ||||
// Wait for individual members to finish | // Wait for individual members to finish | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
let target = this.order.data.target; | let target = this.order.data.target; | ||||
let allowCapture = this.order.data.allowCapture; | |||||
if (!this.CheckFormationTargetAttackRange(target)) | if (!this.CheckFormationTargetAttackRange(target)) | ||||
{ | { | ||||
if (this.CanAttack(target) && this.CheckTargetVisible(target)) | if (this.CanAttack(target, (this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove()) && this.CheckTargetVisible(target)) | ||||
{ | { | ||||
this.SetNextState("COMBAT.APPROACHING"); | this.SetNextState("COMBAT.APPROACHING"); | ||||
return true; | return true; | ||||
} | } | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
// TODO fix the rearranging while attacking as formation | // TODO fix the rearranging while attacking as formation | ||||
cmpFormation.SetRearrange(!this.IsAttackingAsFormation()); | cmpFormation.SetRearrange(!this.IsAttackingAsFormation()); | ||||
cmpFormation.MoveMembersIntoFormation(false, false, "combat"); | cmpFormation.MoveMembersIntoFormation(false, false, "combat"); | ||||
this.StartTimer(200, 200); | this.StartTimer(200, 200); | ||||
return false; | return false; | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
let target = this.order.data.target; | let target = this.order.data.target; | ||||
let allowCapture = this.order.data.allowCapture; | |||||
if (!this.CheckFormationTargetAttackRange(target)) | if (!this.CheckFormationTargetAttackRange(target)) | ||||
{ | { | ||||
if (this.CanAttack(target) && this.CheckTargetVisible(target)) | if (this.CanAttack(target, (this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove()) && this.CheckTargetVisible(target)) | ||||
{ | { | ||||
this.SetNextState("COMBAT.APPROACHING"); | this.SetNextState("COMBAT.APPROACHING"); | ||||
return; | return; | ||||
} | } | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
}, | }, | ||||
▲ Show 20 Lines • Show All 205 Lines • ▼ Show 20 Lines | "GuardedAttacked": function(msg) { | ||||
{ | { | ||||
if (this.orderQueue[i].type == "Guard") | if (this.orderQueue[i].type == "Guard") | ||||
break; | break; | ||||
if (this.orderQueue[i].data && this.orderQueue[i].data.force) | if (this.orderQueue[i].data && this.orderQueue[i].data.force) | ||||
return; | return; | ||||
} | } | ||||
// if we already are targeting another unit still alive, finish with it first | // if we already are targeting another unit still alive, finish with it first | ||||
if (this.order && (this.order.type == "WalkAndFight" || this.order.type == "Attack")) | if (this.order && (this.order.type == "WalkAndFight" || this.order.type == "Attack")) | ||||
if (this.order.data.target != msg.data.attacker && this.CanAttack(msg.data.attacker)) | if (this.order.data.target != msg.data.attacker && this.CanAttack(msg.data.attacker, this.GetStance().respondStandGround || !this.AbleToMove())) | ||||
return; | return; | ||||
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); | var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); | ||||
var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); | var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); | ||||
if (cmpIdentity && cmpIdentity.HasClass("Support") && | if (cmpIdentity && cmpIdentity.HasClass("Support") && | ||||
cmpHealth && cmpHealth.IsInjured()) | cmpHealth && cmpHealth.IsInjured()) | ||||
{ | { | ||||
if (this.CanHeal(this.isGuardOf)) | if (this.CanHeal(this.isGuardOf)) | ||||
this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); | this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); | ||||
else if (this.CanRepair(this.isGuardOf)) | else if (this.CanRepair(this.isGuardOf)) | ||||
this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | ||||
return; | return; | ||||
} | } | ||||
var cmpBuildingAI = Engine.QueryInterface(msg.data.attacker, IID_BuildingAI); | var cmpBuildingAI = Engine.QueryInterface(msg.data.attacker, IID_BuildingAI); | ||||
if (cmpBuildingAI && this.CanRepair(this.isGuardOf)) | if (cmpBuildingAI && this.CanRepair(this.isGuardOf)) | ||||
{ | { | ||||
this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | ||||
return; | return; | ||||
} | } | ||||
if (this.CheckTargetVisible(msg.data.attacker)) | if (this.CheckTargetVisible(msg.data.attacker)) | ||||
this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true }); | this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "ignoreAttackEffects": {}, "prefAttackTypes": [], "projectile": undefined }); | ||||
else | else | ||||
{ | { | ||||
var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); | var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
return; | return; | ||||
var pos = cmpPosition.GetPosition(); | var pos = cmpPosition.GetPosition(); | ||||
this.PushOrderFront("WalkAndFight", { "x": pos.x, "z": pos.z, "target": msg.data.attacker, "force": false }); | this.PushOrderFront("WalkAndFight", { "x": pos.x, "z": pos.z, "target": msg.data.attacker, "force": false }); | ||||
// if we already had a WalkAndFight, keep only the most recent one in case the target has moved | // if we already had a WalkAndFight, keep only the most recent one in case the target has moved | ||||
▲ Show 20 Lines • Show All 232 Lines • ▼ Show 20 Lines | "PATROL": { | ||||
return true; | return true; | ||||
} | } | ||||
// Memorize the origin position in case that we want to go back. | // Memorize the origin position in case that we want to go back. | ||||
if (!this.patrolStartPosOrder) | if (!this.patrolStartPosOrder) | ||||
{ | { | ||||
this.patrolStartPosOrder = cmpPosition.GetPosition(); | this.patrolStartPosOrder = cmpPosition.GetPosition(); | ||||
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | ||||
this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | this.patrolStartPosOrder.ignoreAttackEffects = this.order.data.ignoreAttackEffects; | ||||
this.patrolStartPosOrder.prefAttackTypes = this.order.data.prefAttackTypes; | |||||
this.patrolStartPosOrder.projectile = this.order.data.projectile; | |||||
} | } | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
▲ Show 20 Lines • Show All 271 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
{ | { | ||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | this.SetNextState("COMBAT.FINDINGNEWTARGET"); | ||||
return; | return; | ||||
} | } | ||||
// If the order was forced, try moving to the target position, | // If the order was forced, try moving to the target position, | ||||
// under the assumption that this is desirable if the target | // under the assumption that this is desirable if the target | ||||
// was somewhat far away - we'll likely end up closer to where | // was somewhat far away - we'll likely end up closer to where | ||||
// the player hoped we would. | // the player hoped we would. | ||||
// Don't set any prefered attack types, let the attack component handle itself. | |||||
let lastPos = this.order.data.lastPos; | let lastPos = this.order.data.lastPos; | ||||
this.PushOrder("WalkAndFight", { | this.PushOrder("WalkAndFight", { | ||||
"x": lastPos.x, "z": lastPos.z, | "x": lastPos.x, "z": lastPos.z, | ||||
"force": false, | "force": false | ||||
// Force to true - otherwise structures might be attacked instead of captured, | |||||
// which is generally not expected (attacking units usually has allowCapture false). | |||||
"allowCapture": true | |||||
}); | }); | ||||
return; | return; | ||||
} | } | ||||
if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
{ | { | ||||
if (this.CanUnpack()) | if (this.CanUnpack()) | ||||
{ | { | ||||
this.PushOrderFront("Unpack", { "force": true }); | this.PushOrderFront("Unpack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetNextState("ATTACKING"); | this.SetNextState("ATTACKING"); | ||||
} | } | ||||
else if (msg.likelySuccess) | else if (msg.likelySuccess) | ||||
// Try moving again, | // Try moving again, | ||||
// attack range uses a height-related formula and our actual max range might have changed. | // attack range uses a height-related formula and our actual max range might have changed. | ||||
if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) | if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
}, | }, | ||||
"ATTACKING": { | "ATTACKING": { | ||||
"enter": function() { | "enter": function() { | ||||
let target = this.order.data.target; | let target = this.order.data.target; | ||||
let cmpFormation = Engine.QueryInterface(target, IID_Formation); | |||||
if (cmpFormation) | |||||
{ | |||||
this.order.data.formationTarget = target; | |||||
target = cmpFormation.GetClosestMember(this.entity); | |||||
this.order.data.target = target; | |||||
} | |||||
this.shouldCheer = false; | this.shouldCheer = false; | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | ||||
}, | }, | ||||
"TargetInvalidated": function() { | "TargetInvalidated": function() { | ||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | ||||
}, | }, | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
if (this.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force) && | if ((this.GetStance().targetAttackersAlways || !this.order.data.force) && | ||||
this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, true) != "Capture") | this.order.data.target != msg.data.attacker && this.CanAttack(msg.data.attacker, (this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove())) | ||||
this.RespondToTargetedEntities([msg.data.attacker]); | this.RespondToTargetedEntities([msg.data.attacker]); | ||||
}, | }, | ||||
}, | }, | ||||
"FINDINGNEWTARGET": { | "FINDINGNEWTARGET": { | ||||
"Order.Cheer": function() { | "Order.Cheer": function() { | ||||
if (!this.cheeringTime) | if (!this.cheeringTime) | ||||
return this.FinishOrder(); | return this.FinishOrder(); | ||||
this.SetNextState("CHEERING"); | this.SetNextState("CHEERING"); | ||||
return ACCEPT_ORDER; | return ACCEPT_ORDER; | ||||
}, | }, | ||||
"enter": function() { | "enter": function() { | ||||
// Try to find the formation the target was a part of. | // Try to find the formation the target was a part of. | ||||
let cmpFormation = Engine.QueryInterface(this.order.data.target, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.order.data.target, IID_Formation); | ||||
if (!cmpFormation) | if (!cmpFormation) | ||||
cmpFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); | cmpFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); | ||||
// If the target is a formation, pick closest member. | // If the target is a formation, pick closest member. | ||||
if (cmpFormation) | if (cmpFormation) | ||||
{ | { | ||||
let filter = (t) => this.CanAttack(t); | |||||
this.order.data.formationTarget = this.order.data.target; | this.order.data.formationTarget = this.order.data.target; | ||||
let target = cmpFormation.GetClosestMember(this.entity, filter); | this.order.data.target = cmpFormation.GetClosestMember(this.entity, (target) => this.CanAttack(target, (this.GetStance().respondStandGround && !this.order.data.force) || !this.AbleToMove()));; | ||||
this.order.data.target = target; | |||||
this.SetNextState("COMBAT.ATTACKING"); | this.SetNextState("COMBAT.ATTACKING"); | ||||
return true; | return true; | ||||
} | } | ||||
// Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up | // Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up | ||||
// except if in WalkAndFight mode where we look for more enemies around before moving again. | // except if in WalkAndFight mode where we look for more enemies around before moving again. | ||||
if (this.FinishOrder()) | if (this.FinishOrder()) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 2,450 Lines • ▼ Show 20 Lines | UnitAI.prototype.MoveToTargetAttackRange = function(target, type) | ||||
} | } | ||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (!this.AbleToMove(cmpUnitMotion)) | if (!this.AbleToMove(cmpUnitMotion)) | ||||
return false; | return false; | ||||
let cmpFormation = Engine.QueryInterface(target, IID_Formation); | let cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
target = cmpFormation.GetClosestMember(this.entity); | target = cmpFormation.GetClosestMember(this.entity, (targ) => this.CanAttack(targ, false, {}, [type])); | ||||
if (type != "Ranged") | |||||
return this.MoveToTargetRange(target, IID_Attack, type); | |||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
let range = this.GetRange(IID_Attack, type, target); | let range = this.GetRange(IID_Attack, type, target); | ||||
if (!range) | if (!range) | ||||
return false; | return false; | ||||
let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (!thisCmpPosition.IsInWorld()) | if (!thisCmpPosition.IsInWorld()) | ||||
return false; | return false; | ||||
let s = thisCmpPosition.GetPosition(); | |||||
let targetCmpPosition = Engine.QueryInterface(target, IID_Position); | let targetCmpPosition = Engine.QueryInterface(target, IID_Position); | ||||
if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) | if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) | ||||
return false; | return false; | ||||
// Parabolic range compuation is the same as in BuildingAI's FireArrows. | // Parabolic range compuation is the same as in BuildingAI's FireArrows, Attack's GetAllowedAttackTypes and IsInTargetRange. | ||||
let t = targetCmpPosition.GetPosition(); | // h is positive when I'm higher than the target. | ||||
// h is positive when I'm higher than the target | let h = thisCmpPosition.GetHeightOffset() - targetCmpPosition.GetHeightOffset() + range.elevationBonus; | ||||
let h = s.y - t.y + range.elevationBonus; | |||||
// Target is too high for our range. We can't attack. | |||||
let parabolicMaxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); | // Return false? Or hope you come close enough? | ||||
// No negative roots please | let parabolicMaxRange = h <= -range.max / 2 ? 0 : Math.sqrt(Math.square(range.max) + 2 * range.max * h); | ||||
if (h <= -range.max / 2) | |||||
// return false? Or hope you come close enough? | |||||
parabolicMaxRange = 0; | |||||
// The parabole changes while walking so be cautious: | // The parabole changes while walking so be cautious: | ||||
let guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange; | let guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange; | ||||
return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange); | return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max) | UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max) | ||||
Show All 10 Lines | |||||
* | * | ||||
* @param {number} target - The target entity ID to attack. | * @param {number} target - The target entity ID to attack. | ||||
* @return {boolean} - Whether the order to move has succeeded. | * @return {boolean} - Whether the order to move has succeeded. | ||||
*/ | */ | ||||
UnitAI.prototype.MoveFormationToTargetAttackRange = function(target) | UnitAI.prototype.MoveFormationToTargetAttackRange = function(target) | ||||
{ | { | ||||
let cmpTargetFormation = Engine.QueryInterface(target, IID_Formation); | let cmpTargetFormation = Engine.QueryInterface(target, IID_Formation); | ||||
if (cmpTargetFormation) | if (cmpTargetFormation) | ||||
target = cmpTargetFormation.GetClosestMember(this.entity); | target = cmpTargetFormation.GetClosestMember(this.entity, (targ) => this.CanAttack(targ)); | ||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpFormationAttack) | if (!cmpFormationAttack) | ||||
return false; | return false; | ||||
let range = cmpFormationAttack.GetRange(target); | let range = cmpFormationAttack.GetRange(target); | ||||
Show All 36 Lines | UnitAI.prototype.CheckTargetRange = function(target, iid, type) | ||||
if (!range) | if (!range) | ||||
return false; | return false; | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); | ||||
}; | }; | ||||
/** | /** | ||||
* Check if the target is inside the attack range | * Check if the target is inside the attack range. We account for any | ||||
* For melee attacks, this goes straigt to the regular range calculation | * height difference between the entity and the target. When the target is | ||||
* For ranged attacks, the parabolic formula is used to accout for bigger ranges | * lower the range is bigger, and when the target is higher the range is smaller. | ||||
* when the target is lower, and smaller ranges when the target is higher | |||||
*/ | */ | ||||
UnitAI.prototype.CheckTargetAttackRange = function(target, type) | UnitAI.prototype.CheckTargetAttackRange = function(target, type) | ||||
{ | { | ||||
// for formation members, the formation will take care of the range check | // for formation members, the formation will take care of the range check | ||||
if (this.IsFormationMember()) | if (this.IsFormationMember()) | ||||
{ | { | ||||
let cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | let cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | ||||
if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation() && | if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation() && | ||||
cmpFormationUnitAI.order.data.target == target) | cmpFormationUnitAI.order.data.target == target) | ||||
return true; | return true; | ||||
} | } | ||||
let cmpFormation = Engine.QueryInterface(target, IID_Formation); | let cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
target = cmpFormation.GetClosestMember(this.entity); | target = cmpFormation.GetClosestMember(this.entity, (targ) => this.CanAttack(targ, false, {}, [type])); | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
return cmpAttack && cmpAttack.IsTargetInRange(target, type); | return cmpAttack && cmpAttack.IsTargetInRange(target, type); | ||||
}; | }; | ||||
UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | ||||
{ | { | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, false); | return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, false); | ||||
}; | }; | ||||
/** | /** | ||||
* Check if the target is inside the attack range of the formation. | * Check if the target is inside the attack range of the formation. | ||||
* | * | ||||
* @param {number} target - The target entity ID to attack. | * @param {number} target - The target entity ID to attack. | ||||
* @return {boolean} - Whether the entity is within attacking distance. | * @return {boolean} - Whether the entity is within attacking distance. | ||||
*/ | */ | ||||
UnitAI.prototype.CheckFormationTargetAttackRange = function(target) | UnitAI.prototype.CheckFormationTargetAttackRange = function(target) | ||||
{ | { | ||||
let cmpTargetFormation = Engine.QueryInterface(target, IID_Formation); | let cmpTargetFormation = Engine.QueryInterface(target, IID_Formation); | ||||
if (cmpTargetFormation) | if (cmpTargetFormation) | ||||
target = cmpTargetFormation.GetClosestMember(this.entity); | target = cmpTargetFormation.GetClosestMember(this.entity, (targ) => this.CanAttack(targ)); | ||||
let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpFormationAttack) | if (!cmpFormationAttack) | ||||
return false; | return false; | ||||
let range = cmpFormationAttack.GetRange(target); | let range = cmpFormationAttack.GetRange(target); | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); | ||||
▲ Show 20 Lines • Show All 115 Lines • ▼ Show 20 Lines | if (!cmpVision) | ||||
return false; | return false; | ||||
let range = cmpVision.GetRange(); | let range = cmpVision.GetRange(); | ||||
let distance = PositionHelper.DistanceBetweenEntities(this.entity, target); | let distance = PositionHelper.DistanceBetweenEntities(this.entity, target); | ||||
return distance < range; | return distance < range; | ||||
}; | }; | ||||
UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture) | UnitAI.prototype.GetBestAttackAgainst = function(target, mustBeInRange, ignoreAttackEffects, prefAttackTypes, projectile) | ||||
{ | { | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return undefined; | return undefined; | ||||
return cmpAttack.GetBestAttackAgainst(target, allowCapture); | return cmpAttack.GetBestAttackAgainst(target, mustBeInRange, ignoreAttackEffects, prefAttackTypes, projectile); | ||||
}; | }; | ||||
/** | /** | ||||
* Try to find one of the given entities which can be attacked, | * Try to find one of the given entities which can be attacked, | ||||
* and start attacking it. | * and start attacking it. | ||||
* Returns true if it found something to attack. | * Returns true if it found something to attack. | ||||
*/ | */ | ||||
UnitAI.prototype.AttackVisibleEntity = function(ents) | UnitAI.prototype.AttackVisibleEntity = function(ents) | ||||
{ | { | ||||
var target = ents.find(target => this.CanAttack(target)); | const target = ents.find(targ => this.CanAttack(targ, this.GetStance().respondStandGround || !this.AbleToMove())); | ||||
if (!target) | if (!target) | ||||
return false; | return false; | ||||
this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": true }); | this.PushOrderFront("Attack", { | ||||
"target": target, | |||||
"force": false, | |||||
"ignoreAttackEffects": {}, | |||||
"prefAttackTypes": [], | |||||
"projectile": undefined | |||||
}); | |||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* Try to find one of the given entities which can be attacked | * Try to find one of the given entities which can be attacked | ||||
* and which is close to the hold position, and start attacking it. | * and which is close to the hold position, and start attacking it. | ||||
* Returns true if it found something to attack. | * Returns true if it found something to attack. | ||||
*/ | */ | ||||
UnitAI.prototype.AttackEntityInZone = function(ents) | UnitAI.prototype.AttackEntityInZone = function(ents) | ||||
{ | { | ||||
var target = ents.find(target => | var target = ents.find(target => | ||||
this.CanAttack(target) && | this.CanAttack(target, this.GetStance().respondStandGround || !this.AbleToMove()) && | ||||
this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true)) && | this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(t, this.GetStance().respondStandGround || !this.AbleToMove())) && | ||||
(this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) | (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) | ||||
); | ); | ||||
if (!target) | if (!target) | ||||
return false; | return false; | ||||
this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": true }); | this.PushOrderFront("Attack", { | ||||
"target": target, | |||||
"force": false, | |||||
"ignoreAttackEffects": {}, | |||||
"prefAttackTypes": [], | |||||
"projectile": undefined | |||||
}); | |||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* Try to respond appropriately given our current stance, | * Try to respond appropriately given our current stance, | ||||
* given a list of entities that match our stance's target criteria. | * given a list of entities that match our stance's target criteria. | ||||
* Returns true if it responded. | * Returns true if it responded. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 414 Lines • ▼ Show 20 Lines | UnitAI.prototype.WalkToTarget = function(target, queued, pushFront) | ||||
this.AddOrder("WalkToTarget", { "target": target, "force": true }, queued, pushFront); | this.AddOrder("WalkToTarget", { "target": target, "force": true }, queued, pushFront); | ||||
}; | }; | ||||
/** | /** | ||||
* Adds walk-and-fight order to queue, this only occurs in response | * Adds walk-and-fight order to queue, this only occurs in response | ||||
* to a player order, and so is forced. | * to a player order, and so is forced. | ||||
* If targetClasses is given, only entities matching the targetClasses can be attacked. | * If targetClasses is given, only entities matching the targetClasses can be attacked. | ||||
*/ | */ | ||||
UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false) | UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = false, pushFront = false) | ||||
{ | { | ||||
this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "force": true }, queued, pushFront); | this.AddOrder("WalkAndFight", { | ||||
"x": x, | |||||
"z": z, | |||||
"targetClasses": targetClasses, | |||||
"ignoreAttackEffects": ignoreAttackEffects, | |||||
"prefAttackTypes": prefAttackTypes, | |||||
"projectile": projectile, | |||||
"force": true | |||||
}, queued, pushFront); | |||||
}; | }; | ||||
UnitAI.prototype.Patrol = function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false) | UnitAI.prototype.Patrol = function(x, z, targetClasses, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = false, pushFront = false) | ||||
{ | { | ||||
if (!this.CanPatrol()) | if (!this.CanPatrol()) | ||||
{ | { | ||||
this.Walk(x, z, queued); | this.Walk(x, z, queued); | ||||
return; | return; | ||||
} | } | ||||
this.AddOrder("Patrol", { "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "force": true }, queued, pushFront); | this.AddOrder("Patrol", { | ||||
"x": x, | |||||
"z": z, | |||||
"targetClasses": targetClasses, | |||||
"ignoreAttackEffects": ignoreAttackEffects, | |||||
"prefAttackTypes": prefAttackTypes, | |||||
"projectile": projectile, | |||||
"force": true | |||||
}, queued, pushFront); | |||||
}; | }; | ||||
/** | /** | ||||
* Adds leave foundation order to queue, treated as forced. | * Adds leave foundation order to queue, treated as forced. | ||||
*/ | */ | ||||
UnitAI.prototype.LeaveFoundation = function(target) | UnitAI.prototype.LeaveFoundation = function(target) | ||||
{ | { | ||||
// If we're already being told to leave a foundation, then | // If we're already being told to leave a foundation, then | ||||
Show All 13 Lines | if (this.IsPacking()) | ||||
return; | return; | ||||
this.PushOrderFront("LeaveFoundation", { "target": target, "force": true }); | this.PushOrderFront("LeaveFoundation", { "target": target, "force": true }); | ||||
}; | }; | ||||
/** | /** | ||||
* Adds attack order to the queue, forced by the player. | * Adds attack order to the queue, forced by the player. | ||||
*/ | */ | ||||
UnitAI.prototype.Attack = function(target, allowCapture = true, queued = false, pushFront = false) | UnitAI.prototype.Attack = function(target, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = false, pushFront = false) | ||||
{ | { | ||||
// Use ignoreAttackEffects, prefAttackTypes and projectile only for choosing the attackType here. | |||||
// We should allow to attack with other types if we can't have the prefered one. | |||||
if (!this.CanAttack(target)) | if (!this.CanAttack(target)) | ||||
{ | { | ||||
// We don't want to let healers walk to the target unit so they can be easily killed. | // We don't want to let healers walk to the target unit so they can be easily killed. | ||||
// Instead we just let them get into healing range. | // Instead we just let them get into healing range. | ||||
if (this.IsHealer()) | if (this.IsHealer()) | ||||
this.MoveToTargetRange(target, IID_Heal); | this.MoveToTargetRange(target, IID_Heal); | ||||
else | else | ||||
this.WalkToTarget(target, queued, pushFront); | this.WalkToTarget(target, queued, pushFront); | ||||
return; | return; | ||||
} | } | ||||
let order = { | let order = { | ||||
"target": target, | "target": target, | ||||
"force": true, | "force": true, | ||||
"allowCapture": allowCapture, | "ignoreAttackEffects": ignoreAttackEffects, | ||||
"prefAttackTypes": prefAttackTypes, | |||||
"projectile": projectile | |||||
}; | }; | ||||
this.RememberTargetPosition(order); | this.RememberTargetPosition(order); | ||||
if (this.order && this.order.type == "Attack" && | if (this.order && this.order.type == "Attack" && | ||||
this.order.data && | this.order.data && | ||||
this.order.data.target === order.target && | this.order.data.target === order.target && | ||||
this.order.data.allowCapture === order.allowCapture) | JSON.stringify(this.order.data.ignoreAttackEffects) === JSON.stringify(order.ignoreAttackEffects) && | ||||
this.order.data.prefAttackTypes.toString() === order.prefAttackTypes.toString() && | |||||
this.order.data.projectile === order.projectile) | |||||
{ | { | ||||
this.order.data.lastPos = order.lastPos; | this.order.data.lastPos = order.lastPos; | ||||
this.order.data.force = order.force; | this.order.data.force = order.force; | ||||
if (order.force) | if (order.force) | ||||
this.orderQueue = [this.order]; | this.orderQueue = [this.order]; | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 475 Lines • ▼ Show 20 Lines | if (!this.losAttackRangeQuery || !this.GetStance().targetVisibleEnemies || !cmpAttack) | ||||
entities = []; | entities = []; | ||||
else | else | ||||
{ | { | ||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery); | entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery); | ||||
} | } | ||||
let attackfilter = e => { | let attackfilter = e => { | ||||
if (this?.order?.data?.targetClasses) | if (this.order?.data?.targetClasses) | ||||
{ | { | ||||
let cmpIdentity = Engine.QueryInterface(e, IID_Identity); | let cmpIdentity = Engine.QueryInterface(e, IID_Identity); | ||||
let targetClasses = this.order.data.targetClasses; | let targetClasses = this.order.data.targetClasses; | ||||
if (cmpIdentity && targetClasses.attack && | if (cmpIdentity && targetClasses.attack && | ||||
!MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | ||||
return false; | return false; | ||||
if (cmpIdentity && targetClasses.avoid && | if (cmpIdentity && targetClasses.avoid && | ||||
MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | ||||
Show All 10 Lines | UnitAI.prototype.FindWalkAndFightTargets = function() | ||||
}; | }; | ||||
let prefs = {}; | let prefs = {}; | ||||
let bestPref; | let bestPref; | ||||
let targets = []; | let targets = []; | ||||
let pref; | let pref; | ||||
for (let v of entities) | for (let v of entities) | ||||
{ | { | ||||
if (this.CanAttack(v) && attackfilter(v)) | if (this.CanAttack(v, this.GetStance().respondStandGround || !this.AbleToMove()) && attackfilter(v)) | ||||
{ | { | ||||
pref = cmpAttack.GetPreference(v); | pref = cmpAttack.GetPreference(v); | ||||
if (pref === 0) | if (pref === 0) | ||||
{ | { | ||||
this.PushOrderFront("Attack", { "target": v, "force": false, "allowCapture": this?.order?.data?.allowCapture }); | this.PushOrderFront("Attack", { | ||||
"target": v, | |||||
"force": false, | |||||
"ignoreAttackEffects": this.order?.data?.ignoreAttackEffects || {}, | |||||
"prefAttackTypes": this.order?.data?.prefAttackTypes || [], | |||||
"projectile": this.order?.data?.projectile | |||||
}); | |||||
return true; | return true; | ||||
} | } | ||||
targets.push(v); | targets.push(v); | ||||
} | } | ||||
prefs[v] = pref; | prefs[v] = pref; | ||||
if (pref !== undefined && (bestPref === undefined || pref < bestPref)) | if (pref !== undefined && (bestPref === undefined || pref < bestPref)) | ||||
bestPref = pref; | bestPref = pref; | ||||
} | } | ||||
for (let targ of targets) | for (let targ of targets) | ||||
{ | { | ||||
if (prefs[targ] !== bestPref) | if (prefs[targ] !== bestPref) | ||||
continue; | continue; | ||||
this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this?.order?.data?.allowCapture }); | this.PushOrderFront("Attack", { | ||||
"target": targ, | |||||
"force": false, | |||||
"ignoreAttackEffects": this.order?.data?.ignoreAttackEffects || {}, | |||||
"prefAttackTypes": this.order?.data?.prefAttackTypes || [], | |||||
"projectile": this.order?.data?.projectile, | |||||
}); | |||||
return true; | return true; | ||||
} | } | ||||
// healers on a walk-and-fight order should heal injured units | // healers on a walk-and-fight order should heal injured units | ||||
if (this.IsHealer()) | if (this.IsHealer()) | ||||
return this.FindNewHealTargets(); | return this.FindNewHealTargets(); | ||||
return false; | return false; | ||||
▲ Show 20 Lines • Show All 150 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
let component = Engine.QueryInterface(this.entity, iid); | let component = Engine.QueryInterface(this.entity, iid); | ||||
if (!component) | if (!component) | ||||
return undefined; | return undefined; | ||||
return component.GetRange(type, target); | return component.GetRange(type, target); | ||||
}; | }; | ||||
UnitAI.prototype.CanAttack = function(target) | UnitAI.prototype.CanAttack = function(target, mustBeInRange = false, ignoreAttackEffects = {}, wantedAttackTypes = [], projectile = undefined) | ||||
{ | { | ||||
// Formation controllers should always respond to commands | // Formation controllers should always respond to commands | ||||
// (then the individual units can make up their own minds) | // (then the individual units can make up their own minds) | ||||
if (this.IsFormationController()) | if (this.IsFormationController()) | ||||
return true; | return true; | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
return cmpAttack && cmpAttack.CanAttack(target); | return cmpAttack && cmpAttack.CanAttack(target, mustBeInRange, ignoreAttackEffects, wantedAttackTypes, projectile); | ||||
}; | }; | ||||
UnitAI.prototype.CanGarrison = function(target) | UnitAI.prototype.CanGarrison = function(target) | ||||
{ | { | ||||
// Formation controllers should always respond to commands | // Formation controllers should always respond to commands | ||||
// (then the individual units can make up their own minds). | // (then the individual units can make up their own minds). | ||||
if (this.IsFormationController()) | if (this.IsFormationController()) | ||||
return true; | return true; | ||||
▲ Show 20 Lines • Show All 157 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
if (!ents.length) | if (!ents.length) | ||||
return false; | return false; | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return false; | return false; | ||||
const mustBeInRange = this.GetStance().respondStandGround || !this.AbleToMove(); | |||||
let attackfilter = function(e) { | let attackfilter = function(e) { | ||||
if (!cmpAttack.CanAttack(e)) | if (!cmpAttack.CanAttack(e, mustBeInRange)) | ||||
return false; | return false; | ||||
let cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | let cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | ||||
if (cmpOwnership && cmpOwnership.GetOwner() > 0) | if (cmpOwnership && cmpOwnership.GetOwner() > 0) | ||||
return true; | return true; | ||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | ||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | ||||
▲ Show 20 Lines • Show All 91 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator