Changeset View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 396 Lines • ▼ Show 20 Lines | "Order.Attack": function(msg) { | ||||
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; | ||||
this.RememberTargetPosition(); | |||||
// If we are already at the target, try attacking it from here | // If we are already at the target, try attacking it from here | ||||
if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
{ | { | ||||
// For packable units within attack range: | // For packable units within attack range: | ||||
// 1. If unpacked, we can attack the target. | // 1. If unpacked, we can attack the target. | ||||
// 2. If packed, we first need to unpack, then follow case 1. | // 2. If packed, we first need to unpack, then follow case 1. | ||||
if (this.CanUnpack()) | if (this.CanUnpack()) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | if (this.MustKillGatherTarget(this.order.data.target)) | ||||
} | } | ||||
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, "allowCapture": false }); | ||||
return; | return; | ||||
} | } | ||||
this.RememberTargetPosition(); | |||||
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) | if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) | ||||
this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); | this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.GATHER.APPROACHING"); | this.SetNextState("INDIVIDUAL.GATHER.APPROACHING"); | ||||
}, | }, | ||||
"Order.GatherNearPosition": function(msg) { | "Order.GatherNearPosition": function(msg) { | ||||
this.SetNextState("INDIVIDUAL.GATHER.WALKING"); | this.SetNextState("INDIVIDUAL.GATHER.WALKING"); | ||||
▲ Show 20 Lines • Show All 1,224 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
} | } | ||||
else | |||||
this.RememberTargetPosition(); | |||||
}, | }, | ||||
"MovementUpdate": function() { | "MovementUpdate": function(msg) { | ||||
if (msg.error) | |||||
Stan: Is that intended ? | |||||
wraitiiAuthorUnsubmitted Done Inline ActionsYes? Not sure why you would think it's not. wraitii: Yes? Not sure why you would think it's not. | |||||
StanUnsubmitted Not Done Inline ActionsWell usually you abort when there is an error no ? Stan: Well usually you abort when there is an error no ? | |||||
wraitiiAuthorUnsubmitted Done Inline ActionsWell the whole point of the diff is to cleverly handle the error. wraitii: Well the whole point of the diff is to cleverly handle the error. | |||||
{ | |||||
// This also handles hunting. | |||||
if (this.orderQueue.length > 1) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
else if (!this.order.data.force) | |||||
{ | |||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | |||||
return; | |||||
} | |||||
// Go to the last known position and try to find enemies there. | |||||
let lastPos = this.order.data.lastPos; | |||||
this.PushOrder("Walk", { "x": lastPos.x, "z": lastPos.z, "force": false }); | |||||
Done Inline ActionsShouldn't happen when hunting I think. wraitii: Shouldn't happen when hunting I think. | |||||
Done Inline Actionsactually harmless since we have finishorder() above. wraitii: actually harmless since we have finishorder() above. | |||||
Not Done Inline ActionsWould be nice to use vector2D here at some point. Stan: Would be nice to use vector2D here at some point. | |||||
this.PushOrder("WalkAndFight", { "x": lastPos.x, "z": lastPos.z, "force": false }); | |||||
return; | |||||
} | |||||
if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
return; | return; | ||||
// If the unit needs to unpack, do so | // If the unit needs to unpack, do so | ||||
if (this.CanUnpack()) | if (this.CanUnpack()) | ||||
{ | { | ||||
this.PushOrderFront("Unpack", { "force": true }); | this.PushOrderFront("Unpack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetNextState("ATTACKING"); | this.SetNextState("ATTACKING"); | ||||
}, | }, | ||||
}, | }, | ||||
"ATTACKING": { | "ATTACKING": { | ||||
"enter": function() { | "enter": function() { | ||||
var target = this.order.data.target; | let target = this.order.data.target; | ||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | let cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
// if the target is a formation, save the attacking formation, and pick a member | // if the target is a formation, save the attacking formation, and pick a member | ||||
if (cmpFormation) | if (cmpFormation) | ||||
{ | { | ||||
this.order.data.formationTarget = target; | this.order.data.formationTarget = target; | ||||
target = cmpFormation.GetClosestMember(this.entity); | target = cmpFormation.GetClosestMember(this.entity); | ||||
this.order.data.target = target; | this.order.data.target = target; | ||||
} | } | ||||
// Check the target is still alive and attackable | |||||
if (this.CanAttack(target) && !this.CheckTargetAttackRange(target, this.order.data.attackType)) | if (!this.CanAttack(target)) | ||||
{ | { | ||||
// Can't reach it - try to chase after it | this.SetNextState("COMBAT.FINDINGNEWTARGET"); | ||||
if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | return true; | ||||
Done Inline ActionsThe chase logic is only handled in Timer, since if we're failing distance checks in "enter" we were never in-range in the first place. wraitii: The chase logic is only handled in Timer, since if we're failing distance checks in "enter" we… | |||||
} | |||||
if (!this.CheckTargetAttackRange(target, this.order.data.attackType)) | |||||
{ | { | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return true; | ||||
} | } | ||||
this.SetNextState("COMBAT.CHASING"); | this.SetNextState("COMBAT.APPROACHING"); | ||||
return true; | return true; | ||||
} | } | ||||
} | |||||
this.StopMoving(); | this.StopMoving(); | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
this.attackTimers = cmpAttack.GetTimers(this.order.data.attackType); | this.attackTimers = cmpAttack.GetTimers(this.order.data.attackType); | ||||
// If the repeat time since the last attack hasn't elapsed, | // If the repeat time since the last attack hasn't elapsed, | ||||
// delay this attack to avoid attacking too fast. | // delay this attack to avoid attacking too fast. | ||||
var prepare = this.attackTimers.prepare; | let prepare = this.attackTimers.prepare; | ||||
if (this.lastAttacked) | if (this.lastAttacked) | ||||
{ | { | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
var repeatLeft = this.lastAttacked + this.attackTimers.repeat - cmpTimer.GetTime(); | let repeatLeft = this.lastAttacked + this.attackTimers.repeat - cmpTimer.GetTime(); | ||||
prepare = Math.max(prepare, repeatLeft); | prepare = Math.max(prepare, repeatLeft); | ||||
} | } | ||||
this.oldAttackType = this.order.data.attackType; | this.oldAttackType = this.order.data.attackType; | ||||
// add prefix + no capital first letter for attackType | // add prefix + no capital first letter for attackType | ||||
var animationName = "attack_" + this.order.data.attackType.toLowerCase(); | let animationName = "attack_" + this.order.data.attackType.toLowerCase(); | ||||
if (this.IsFormationMember()) | if (this.IsFormationMember()) | ||||
{ | { | ||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | let cmpFormationController = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormationController) | ||||
animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); | animationName = cmpFormationController.GetFormationAnimation(this.entity, animationName); | ||||
} | } | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.StartTimer(prepare, this.attackTimers.repeat); | |||||
let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | |||||
if (cmpBuildingAI) | |||||
cmpBuildingAI.SetUnitAITarget(this.order.data.target); | |||||
this.SelectAnimation(animationName); | this.SelectAnimation(animationName); | ||||
this.SetAnimationSync(prepare, this.attackTimers.repeat); | this.SetAnimationSync(prepare, this.attackTimers.repeat); | ||||
this.StartTimer(prepare, this.attackTimers.repeat); | |||||
// TODO: we should probably only bother syncing projectile attacks, not melee | // TODO: we should probably only bother syncing projectile attacks, not melee | ||||
// If using a non-default prepare time, re-sync the animation when the timer runs. | // If using a non-default prepare time, re-sync the animation when the timer runs. | ||||
this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false; | this.resyncAnimation = prepare != this.attackTimers.prepare; | ||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | ||||
var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | |||||
if (cmpBuildingAI) | |||||
cmpBuildingAI.SetUnitAITarget(this.order.data.target); | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | ||||
if (cmpBuildingAI) | if (cmpBuildingAI) | ||||
cmpBuildingAI.SetUnitAITarget(0); | cmpBuildingAI.SetUnitAITarget(0); | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
var target = this.order.data.target; | let target = this.order.data.target; | ||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | let cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
// if the target is a formation, save the attacking formation, and pick a member | // if the target is a formation, save the attacking formation, and pick a member | ||||
if (cmpFormation) | if (cmpFormation) | ||||
{ | { | ||||
var thisObject = this; | let thisObject = this; | ||||
var filter = function(t) { | let filter = function(t) { | ||||
return thisObject.CanAttack(t); | return thisObject.CanAttack(t); | ||||
}; | }; | ||||
this.order.data.formationTarget = target; | this.order.data.formationTarget = target; | ||||
target = cmpFormation.GetClosestMember(this.entity, filter); | target = cmpFormation.GetClosestMember(this.entity, filter); | ||||
this.order.data.target = target; | this.order.data.target = target; | ||||
} | } | ||||
// Check the target is still alive and attackable | // Check the target is still alive and attackable | ||||
if (this.CanAttack(target)) | if (!this.CanAttack(target)) | ||||
{ | |||||
// If we are hunting, first update the target position of the gather order so we know where will be the killed animal | |||||
if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos) | |||||
{ | |||||
var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position); | |||||
if (cmpPosition && cmpPosition.IsInWorld()) | |||||
{ | { | ||||
// Store the initial position, so that we can find the rest of the herd later | this.SetNextState("COMBAT.FINDINGNEWTARGET"); | ||||
if (!this.orderQueue[1].data.initPos) | return; | ||||
this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos; | |||||
this.orderQueue[1].data.lastPos = cmpPosition.GetPosition(); | |||||
// We still know where the animal is, so we shouldn't give up before going there | |||||
this.orderQueue[1].data.secondTry = undefined; | |||||
} | |||||
} | } | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | this.RememberTargetPosition(); | ||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
this.lastAttacked = cmpTimer.GetTime() - msg.lateness; | this.lastAttacked = cmpTimer.GetTime() - msg.lateness; | ||||
this.FaceTowardsTarget(target); | this.FaceTowardsTarget(target); | ||||
// BuildingAI has it's own attack-routine | // BuildingAI has it's own attack-routine | ||||
var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | ||||
if (!cmpBuildingAI) | if (!cmpBuildingAI) | ||||
{ | { | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
cmpAttack.PerformAttack(this.order.data.attackType, target); | cmpAttack.PerformAttack(this.order.data.attackType, target); | ||||
} | } | ||||
// Check we can still reach the target for the next attack | // Check we can still reach the target for the next attack | ||||
if (this.CheckTargetAttackRange(target, this.order.data.attackType)) | if (this.CheckTargetAttackRange(target, this.order.data.attackType)) | ||||
{ | { | ||||
if (this.resyncAnimation) | if (this.resyncAnimation) | ||||
{ | { | ||||
this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat); | this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat); | ||||
this.resyncAnimation = false; | this.resyncAnimation = false; | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
// Can't reach it - try to chase after it | // Can't reach it - try to chase after it | ||||
if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | ||||
{ | { | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetNextState("COMBAT.CHASING"); | this.SetNextState("COMBAT.CHASING"); | ||||
return; | return; | ||||
} | } | ||||
} | }, | ||||
// TODO: respond to target deaths immediately, rather than waiting | |||||
// until the next Timer event | |||||
// if we're targetting a formation, find a new member of that formation | "Attacked": function(msg) { | ||||
var cmpTargetFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); | // If we are capturing and are attacked by something that we would not capture, attack that entity instead | ||||
if (this.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force) | |||||
&& this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, true) != "Capture") | |||||
this.RespondToTargetedEntities([msg.data.attacker]); | |||||
}, | |||||
}, | |||||
"FINDINGNEWTARGET": { | |||||
"enter": function() { | |||||
// If we're targetting a formation, find a new member of that formation. | |||||
let cmpTargetFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); | |||||
// if there is no target, it means previously searching for the target inside the target formation failed, so don't repeat the search | // if there is no target, it means previously searching for the target inside the target formation failed, so don't repeat the search | ||||
if (target && cmpTargetFormation) | if (this.order.data.target && cmpTargetFormation) | ||||
{ | { | ||||
this.order.data.target = this.order.data.formationTarget; | this.order.data.target = this.order.data.formationTarget; | ||||
this.TimerHandler(msg.data, msg.lateness); | this.TimerHandler(msg.data, msg.lateness); | ||||
return; | return; | ||||
} | } | ||||
// 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 ennemies 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()) | ||||
{ | { | ||||
if (this.IsWalkingAndFighting()) | if (this.IsWalkingAndFighting()) | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
return; | return true; | ||||
} | } | ||||
// See if we can switch to a new nearby enemy | // See if we can switch to a new nearby enemy | ||||
if (this.FindNewTargets()) | if (this.FindNewTargets()) | ||||
{ | return true; | ||||
// Attempt to immediately re-enter the timer function, to avoid wasting the attack. | |||||
// Packable units may have switched to PACKING state, thus canceling the timer and having order.data.attackType undefined. | |||||
if (this.orderQueue.length > 0 && this.orderQueue[0].data && this.orderQueue[0].data.attackType && | |||||
this.orderQueue[0].data.attackType == this.oldAttackType) | |||||
this.TimerHandler(msg.data, msg.lateness); | |||||
return; | |||||
} | |||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
}, | |||||
// TODO: respond to target deaths immediately, rather than waiting | |||||
// until the next Timer event | |||||
"Attacked": function(msg) { | return true; | ||||
// If we are capturing and are attacked by something that we would not capture, attack that entity instead | |||||
if (this.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force) | |||||
&& this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, true) != "Capture") | |||||
this.RespondToTargetedEntities([msg.data.attacker]); | |||||
}, | }, | ||||
}, | }, | ||||
"CHASING": { | "CHASING": { | ||||
"enter": function() { | "enter": function() { | ||||
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(); | ||||
▲ Show 20 Lines • Show All 2,415 Lines • ▼ Show 20 Lines | UnitAI.prototype.FaceTowardsTarget = function(target) | ||||
if (Math.abs(delta) > 0.2) | if (Math.abs(delta) > 0.2) | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (cmpUnitMotion) | if (cmpUnitMotion) | ||||
cmpUnitMotion.FaceTowardsPoint(targetpos.x, targetpos.y); | cmpUnitMotion.FaceTowardsPoint(targetpos.x, targetpos.y); | ||||
} | } | ||||
}; | }; | ||||
UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, iid, type) | UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, iid, type) | ||||
Done Inline ActionsShould capitalise this, I missed that this function existed. wraitii: Should capitalise this, I missed that this function existed. | |||||
{ | { | ||||
var cmpRanged = Engine.QueryInterface(this.entity, iid); | var cmpRanged = Engine.QueryInterface(this.entity, iid); | ||||
var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(type); | var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(type); | ||||
var cmpPosition = Engine.QueryInterface(target, IID_Position); | var cmpPosition = Engine.QueryInterface(target, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
return false; | return false; | ||||
▲ Show 20 Lines • Show All 495 Lines • ▼ Show 20 Lines | 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; | ||||
} | } | ||||
this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued); | |||||
let order = { | |||||
"target": target, | |||||
"force": true, | |||||
"allowCapture": allowCapture, | |||||
}; | |||||
this.RememberTargetPosition(order); | |||||
this.AddOrder("Attack", order, queued); | |||||
}; | }; | ||||
/** | /** | ||||
* Adds garrison order to the queue, forced by the player. | * Adds garrison order to the queue, forced by the player. | ||||
*/ | */ | ||||
UnitAI.prototype.Garrison = function(target, queued) | UnitAI.prototype.Garrison = function(target, queued) | ||||
{ | { | ||||
if (target == this.entity) | if (target == this.entity) | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | UnitAI.prototype.PerformGather = function(target, queued, force) | ||||
// we won't go from hunting slow safe animals to dangerous fast ones | // we won't go from hunting slow safe animals to dangerous fast ones | ||||
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | ||||
var template = cmpTemplateManager.GetCurrentTemplateName(target); | var template = cmpTemplateManager.GetCurrentTemplateName(target); | ||||
// Remove "resource|" prefix from template name, if present. | // Remove "resource|" prefix from template name, if present. | ||||
if (template.indexOf("resource|") != -1) | if (template.indexOf("resource|") != -1) | ||||
template = template.slice(9); | template = template.slice(9); | ||||
// Remember the position of our target, if any, in case it disappears | let order = { | ||||
// later and we want to head to its last known position | "target": target, | ||||
var lastPos = undefined; | "type": type, | ||||
var cmpPosition = Engine.QueryInterface(target, IID_Position); | "template": template, | ||||
if (cmpPosition && cmpPosition.IsInWorld()) | "force": force, | ||||
lastPos = cmpPosition.GetPosition(); | }; | ||||
this.RememberTargetPosition(order); | |||||
this.AddOrder("Gather", { "target": target, "type": type, "template": template, "lastPos": lastPos, "force": force }, queued); | this.AddOrder("Gather", order, queued); | ||||
}; | }; | ||||
/** | /** | ||||
* Adds gather-near-position order to the queue, not forced, so it can be | * Adds gather-near-position order to the queue, not forced, so it can be | ||||
* interrupted by attacks. | * interrupted by attacks. | ||||
*/ | */ | ||||
UnitAI.prototype.GatherNearPosition = function(x, z, type, template, queued) | UnitAI.prototype.GatherNearPosition = function(x, z, type, template, queued) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 501 Lines • ▼ Show 20 Lines | |||||
UnitAI.prototype.SetSpeedMultiplier = function(speed) | UnitAI.prototype.SetSpeedMultiplier = function(speed) | ||||
{ | { | ||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (cmpUnitMotion) | if (cmpUnitMotion) | ||||
cmpUnitMotion.SetSpeedMultiplier(speed); | cmpUnitMotion.SetSpeedMultiplier(speed); | ||||
}; | }; | ||||
/* | |||||
* Remember the position of the target (in lastPos), if any, in case it disappears later | |||||
* and we want to head to its last known position. | |||||
* @param orderData - The order data to set this on. Defaults to this.order.data | |||||
*/ | |||||
UnitAI.prototype.RememberTargetPosition = function(orderData) | |||||
{ | |||||
if (!orderData) | |||||
orderData = this.order.data; | |||||
let cmpPosition = Engine.QueryInterface(orderData.target, IID_Position); | |||||
if (cmpPosition && cmpPosition.IsInWorld()) | |||||
orderData.lastPos = cmpPosition.GetPosition(); | |||||
}; | |||||
UnitAI.prototype.SetHeldPosition = function(x, z) | UnitAI.prototype.SetHeldPosition = function(x, z) | ||||
{ | { | ||||
this.heldPosition = {"x": x, "z": z}; | this.heldPosition = {"x": x, "z": z}; | ||||
}; | }; | ||||
UnitAI.prototype.SetHeldPositionOnEntity = function(entity) | UnitAI.prototype.SetHeldPositionOnEntity = function(entity) | ||||
{ | { | ||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
Done Inline ActionsI believe this.entity before was a typo dating from the writing of this function. wraitii: I believe `this.entity` before was a typo dating from the writing of this function. | |||||
return; | return; | ||||
var pos = cmpPosition.GetPosition(); | var pos = cmpPosition.GetPosition(); | ||||
this.SetHeldPosition(pos.x, pos.z); | this.SetHeldPosition(pos.x, pos.z); | ||||
}; | }; | ||||
UnitAI.prototype.GetHeldPosition = function() | UnitAI.prototype.GetHeldPosition = function() | ||||
{ | { | ||||
return this.heldPosition; | return this.heldPosition; | ||||
}; | }; | ||||
UnitAI.prototype.WalkToHeldPosition = function() | UnitAI.prototype.WalkToHeldPosition = function() | ||||
{ | { | ||||
if (this.heldPosition) | if (this.heldPosition) | ||||
{ | { | ||||
this.AddOrder("Walk", { "x": this.heldPosition.x, "z": this.heldPosition.z, "force": false }, false); | this.AddOrder("Walk", { "x": this.heldPosition.x, "z": this.heldPosition.z, "force": false }, false); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
Done Inline ActionsI'm calling FinishOrder here and only moving back if we don't have another order. I've checked, and it shouldn't change our existing behaviour anywhere (in fact one caller already did that), so we're good. wraitii: I'm calling FinishOrder here and only moving back if we don't have another order. I've checked… | |||||
}; | }; | ||||
//// Helper functions //// | //// Helper functions //// | ||||
UnitAI.prototype.CanAttack = function(target) | UnitAI.prototype.CanAttack = 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) | ||||
▲ Show 20 Lines • Show All 335 Lines • Show Last 20 Lines |
Is that intended ?