Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 377 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"Order.Flee": function(msg) { | "Order.Flee": function(msg) { | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextState("ANIMAL.FLEEING"); | this.SetNextState("ANIMAL.FLEEING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.FLEEING"); | this.SetNextState("INDIVIDUAL.FLEEING"); | ||||
}, | }, | ||||
"Order.Attack": function(msg) { | "Order.Attack": function(msg) { | ||||
let type = this.GetBestAttackAgainst(this.order.data.target, this.order.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) | ||||
{ | { | ||||
// Oops, we can't attack at all | // Oops, we can't attack at all | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
this.order.data.attackType = type; | this.order.data.attackType = type; | ||||
▲ Show 20 Lines • Show All 114 Lines • ▼ Show 20 Lines | if (msg.data.type.generic !== "treasure" && cmpResourceGatherer && !cmpResourceGatherer.CanCarryMore(msg.data.type.generic)) | ||||
this.WalkToTarget(msg.data.target, false); | this.WalkToTarget(msg.data.target, false); | ||||
return; | return; | ||||
} | } | ||||
if (this.MustKillGatherTarget(this.order.data.target)) | if (this.MustKillGatherTarget(this.order.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(this.order.data.target, false)) | if (!this.GetBestAttackAgainst(this.order.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 | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// 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. | ||||
Show All 14 Lines | if (this.MustKillGatherTarget(this.order.data.target)) | ||||
"z": data.lastPos.z, | "z": data.lastPos.z, | ||||
"type": data.type, | "type": data.type, | ||||
"template": data.template | "template": data.template | ||||
}); | }); | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
this.PushOrderFront("Attack", { "target": this.order.data.target, "force": !!this.order.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; | return; | ||||
} | } | ||||
this.RememberTargetPosition(); | this.RememberTargetPosition(); | ||||
if (!this.order.data.initPos) | if (!this.order.data.initPos) | ||||
this.order.data.initPos = this.order.data.lastPos; | this.order.data.initPos = this.order.data.lastPos; | ||||
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) | if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | "Order.Stop": function(msg) { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// 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.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; | ||||
} | } | ||||
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"); | ||||
}, | }, | ||||
"Order.Garrison": function(msg) { | "Order.Garrison": function(msg) { | ||||
Show All 40 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; | return; | ||||
} | } | ||||
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; | return; | ||||
} | } | ||||
// 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)) | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
▲ Show 20 Lines • Show All 153 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 155 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 222 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 174 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 285 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
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; | ||||
if (!this.CanAttack(target)) | if (!this.CanAttack(target, false, {}, [this.order.data.attackType])) | ||||
{ | { | ||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | this.SetNextState("COMBAT.FINDINGNEWTARGET"); | ||||
return true; | return true; | ||||
} | } | ||||
if (!this.CheckTargetAttackRange(target, this.order.data.attackType)) | if (!this.CheckTargetAttackRange(target, this.order.data.attackType)) | ||||
{ | { | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.ResetAnimation(); | this.ResetAnimation(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
let target = this.order.data.target; | let target = this.order.data.target; | ||||
let attackType = this.order.data.attackType; | let attackType = this.order.data.attackType; | ||||
if (!this.CanAttack(target)) | if (!this.CanAttack(target, false, {}, [attackType])) | ||||
{ | { | ||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | this.SetNextState("COMBAT.FINDINGNEWTARGET"); | ||||
return; | return; | ||||
} | } | ||||
this.RememberTargetPosition(); | this.RememberTargetPosition(); | ||||
if (this.order.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") | if (this.order.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 41 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | ||||
}, | }, | ||||
// TODO: respond to target deaths immediately, rather than waiting | // TODO: respond to target deaths immediately, rather than waiting | ||||
// until the next Timer event | // until the next Timer event | ||||
"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.GetBestAttackAgainst(msg.data.attacker, true)) | |||||
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 { "discardOrder": true }; | return { "discardOrder": true }; | ||||
this.SetNextState("CHEERING"); | this.SetNextState("CHEERING"); | ||||
return false; | return false; | ||||
}, | }, | ||||
"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, (t) => this.CanAttack(t, this.GetStance().respondStandGround || !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 1,141 Lines • ▼ Show 20 Lines | "ANIMAL": { | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
if (this.template.NaturalBehaviour == "skittish" || | if (this.template.NaturalBehaviour == "skittish" || | ||||
this.template.NaturalBehaviour == "passive") | this.template.NaturalBehaviour == "passive") | ||||
{ | { | ||||
this.Flee(msg.data.attacker, false); | this.Flee(msg.data.attacker, false); | ||||
} | } | ||||
else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | ||||
{ | { | ||||
if (this.CanAttack(msg.data.attacker)) | if (this.CanAttack(msg.data.attacker, this.GetStance().respondStandGround || !this.AbleToMove())) | ||||
this.Attack(msg.data.attacker, false); | this.Attack(msg.data.attacker, { "Capture": true }); | ||||
} | } | ||||
else if (this.template.NaturalBehaviour == "domestic") | else if (this.template.NaturalBehaviour == "domestic") | ||||
{ | { | ||||
// Never flee, stop what we were doing | // Never flee, stop what we were doing | ||||
this.SetNextState("IDLE"); | this.SetNextState("IDLE"); | ||||
} | } | ||||
}, | }, | ||||
▲ Show 20 Lines • Show All 1,323 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, (t) => this.CanAttack(t, 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); | let range = this.GetRange(IID_Attack, type); | ||||
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 this' CheckTargetAttackRange. | ||||
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, (t) => this.CanAttack(t)); | ||||
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 20 Lines • Show All 50 Lines • ▼ Show 20 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, (t) => this.CanAttack(t, false, {}, [type])); | ||||
if (type != "Ranged") | |||||
return this.CheckTargetRange(target, IID_Attack, type); | |||||
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; | ||||
let range = this.GetRange(IID_Attack, type); | let range = this.GetRange(IID_Attack, type); | ||||
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 || !thisCmpPosition.IsInWorld()) | ||||
return false; | return false; | ||||
let s = thisCmpPosition.GetPosition(); | // Parabolic range compuation is the same as in BuildingAI's FireArrows, Attack's GetAllowedAttackTypes and this' MoveToTargetAttackRange. | ||||
// h is positive when I'm higher than the target. | |||||
let t = targetCmpPosition.GetPosition(); | let h = thisCmpPosition.GetHeightOffset() - targetCmpPosition.GetHeightOffset() + range.elevationBonus; | ||||
let h = s.y - t.y + range.elevationBonus; | |||||
let maxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); | |||||
if (maxRange < 0) | // In case the target is too much higher, we are out of range. | ||||
if (h <= -range.max / 2) | |||||
return false; | return false; | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | return Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).IsInTargetRange( | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, maxRange, false); | this.entity, | ||||
target, | |||||
range.min, | |||||
Math.sqrt(Math.square(range.max) + 2 * range.max * h), | |||||
false); | |||||
}; | }; | ||||
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, (t) => this.CanAttack(t)); | ||||
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 125 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); | let 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)); | let target = ents.find(t => this.CanAttack(t, 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 => | let target = ents.find(t => | ||||
this.CanAttack(target) | this.CanAttack(t, this.GetStance().respondStandGround || !this.AbleToMove()) && | ||||
&& this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true)) | this.CheckTargetDistanceFromHeldPosition(t, IID_Attack, this.GetBestAttackAgainst(t, this.GetStance().respondStandGround || !this.AbleToMove())) && | ||||
&& (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) | (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(t)) | ||||
); | ); | ||||
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 385 Lines • ▼ Show 20 Lines | UnitAI.prototype.WalkToTarget = function(target, queued) | ||||
this.AddOrder("WalkToTarget", { "target": target, "force": true }, queued); | this.AddOrder("WalkToTarget", { "target": target, "force": true }, queued); | ||||
}; | }; | ||||
/** | /** | ||||
* 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) | UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = false) | ||||
{ | { | ||||
this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "force": true }, queued); | this.AddOrder("WalkAndFight", { | ||||
"x": x, | |||||
"z": z, | |||||
"targetClasses": targetClasses, | |||||
"ignoreAttackEffects": ignoreAttackEffects, | |||||
"prefAttackTypes": prefAttackTypes, | |||||
"projectile": projectile, | |||||
"force": true | |||||
}, queued); | |||||
}; | }; | ||||
UnitAI.prototype.Patrol = function(x, z, targetClasses, allowCapture = true, queued = false) | UnitAI.prototype.Patrol = function(x, z, targetClasses, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = 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); | this.AddOrder("Patrol", { | ||||
"x": x, | |||||
"z": z, | |||||
"targetClasses": targetClasses, | |||||
"ignoreAttackEffects": ignoreAttackEffects, | |||||
"prefAttackTypes": prefAttackTypes, | |||||
"projectile": projectile, | |||||
"force": true | |||||
}, queued); | |||||
}; | }; | ||||
/** | /** | ||||
* 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) | UnitAI.prototype.Attack = function(target, ignoreAttackEffects = {}, prefAttackTypes = [], projectile = undefined, queued = false) | ||||
{ | { | ||||
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); | this.WalkToTarget(target, queued); | ||||
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); | ||||
this.AddOrder("Attack", order, queued); | this.AddOrder("Attack", order, queued); | ||||
}; | }; | ||||
/** | /** | ||||
▲ Show 20 Lines • Show All 446 Lines • ▼ Show 20 Lines | if (this.IsFormationController()) | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
for (var ent of cmpFormation.members) | for (var ent of cmpFormation.members) | ||||
{ | { | ||||
if (!(cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI))) | if (!(cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI))) | ||||
continue; | continue; | ||||
var targets = cmpUnitAI.GetTargetsFromUnit(); | var targets = cmpUnitAI.GetTargetsFromUnit(); | ||||
for (var targ of targets) | for (var targ of targets) | ||||
{ | { | ||||
if (!cmpUnitAI.CanAttack(targ)) | |||||
continue; | |||||
if (this.order.data.targetClasses) | if (this.order.data.targetClasses) | ||||
{ | { | ||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | ||||
var targetClasses = this.order.data.targetClasses; | var targetClasses = this.order.data.targetClasses; | ||||
if (targetClasses.attack && cmpIdentity | if (targetClasses.attack && cmpIdentity | ||||
&& !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | && !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | ||||
continue; | continue; | ||||
if (targetClasses.avoid && cmpIdentity | if (targetClasses.avoid && cmpIdentity | ||||
&& MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | && MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | ||||
continue; | continue; | ||||
// Only used by the AIs to prevent some choices of targets | // Only used by the AIs to prevent some choices of targets | ||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) | if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) | ||||
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; | ||||
} | } | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
var targets = this.GetTargetsFromUnit(); | var targets = this.GetTargetsFromUnit(); | ||||
for (var targ of targets) | for (var targ of targets) | ||||
{ | { | ||||
if (!this.CanAttack(targ)) | |||||
continue; | |||||
if (this.order.data.targetClasses) | if (this.order.data.targetClasses) | ||||
{ | { | ||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | ||||
var targetClasses = this.order.data.targetClasses; | var targetClasses = this.order.data.targetClasses; | ||||
if (cmpIdentity && targetClasses.attack | if (cmpIdentity && targetClasses.attack | ||||
&& !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | && !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | ||||
continue; | continue; | ||||
if (cmpIdentity && targetClasses.avoid | if (cmpIdentity && targetClasses.avoid | ||||
&& MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | && MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | ||||
continue; | continue; | ||||
// Only used by the AIs to prevent some choices of targets | // Only used by the AIs to prevent some choices of targets | ||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) | if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) | ||||
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 All 16 Lines | let attackfilter = function(e) { | ||||
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()); | ||||
}; | }; | ||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
let entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery); | let entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery); | ||||
let targets = entities.filter(function(v) { return cmpAttack.CanAttack(v) && attackfilter(v); }) | let targets = entities.filter(ent => cmpAttack.CanAttack(ent, this.GetStance().respondStandGround || !this.AbleToMove()) && attackfilter(ent)) | ||||
.sort(function(a, b) { return cmpAttack.CompareEntitiesByPreference(a, b); }); | .sort((a, b) => cmpAttack.CompareEntitiesByPreference(a, b)); | ||||
return targets; | return targets; | ||||
}; | }; | ||||
UnitAI.prototype.GetQueryRange = function(iid) | UnitAI.prototype.GetQueryRange = function(iid) | ||||
{ | { | ||||
let ret = { "min": 0, "max": 0 }; | let ret = { "min": 0, "max": 0 }; | ||||
▲ Show 20 Lines • Show All 143 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); | return component.GetRange(type); | ||||
} | } | ||||
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 317 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator