Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 2,083 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
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; | ||||
} | } | ||||
this.shouldCheer = false; | this.shouldCheer = false; | ||||
if (!this.CanAttack(target)) | |||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
if (!cmpAttack) | |||||
{ | { | ||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | this.FinishOrder(); | ||||
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()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return true; | return true; | ||||
} | } | ||||
this.SetNextState("COMBAT.APPROACHING"); | this.ProcessMessage("OutOfRange"); | ||||
return true; | return true; | ||||
} | } | ||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
this.attackTimers = cmpAttack.GetTimers(this.order.data.attackType); | |||||
// If the repeat time since the last attack hasn't elapsed, | |||||
// delay this attack to avoid attacking too fast. | |||||
let prepare = this.attackTimers.prepare; | |||||
if (this.lastAttacked) | |||||
{ | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
let repeatLeft = this.lastAttacked + this.attackTimers.repeat - cmpTimer.GetTime(); | |||||
prepare = Math.max(prepare, repeatLeft); | |||||
} | |||||
if (!this.formationAnimationVariant) | if (!this.formationAnimationVariant) | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.oldAttackType = this.order.data.attackType; | this.FaceTowardsTarget(this.order.data.target); | ||||
this.SelectAnimation("attack_" + this.order.data.attackType.toLowerCase()); | |||||
this.SetAnimationSync(prepare, this.attackTimers.repeat); | |||||
this.StartTimer(prepare, this.attackTimers.repeat); | |||||
// 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. | this.RememberTargetPosition(); | ||||
this.resyncAnimation = prepare != this.attackTimers.prepare; | if (this.order.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") | ||||
this.RememberTargetPosition(this.orderQueue[1].data); | |||||
this.FaceTowardsTarget(this.order.data.target); | if (!cmpAttack.StartAttacking(this.order.data.target, this.order.data.attackType, IID_UnitAI)) | ||||
{ | |||||
this.ProcessMessage("TargetInvalidated"); | |||||
return true; | |||||
} | |||||
let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | ||||
if (cmpBuildingAI) | if (cmpBuildingAI) | ||||
{ | { | ||||
cmpBuildingAI.SetUnitAITarget(this.order.data.target); | cmpBuildingAI.SetUnitAITarget(this.order.data.target); | ||||
return false; | return false; | ||||
} | } | ||||
let cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | let cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | ||||
// Units with no cheering time do not cheer. | // Units with no cheering time do not cheer. | ||||
this.shouldCheer = cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()) && this.cheeringTime > 0; | this.shouldCheer = cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()) && this.cheeringTime > 0; | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
let 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.ResetAnimation(); | |||||
}, | |||||
"Timer": function(msg) { | |||||
let target = this.order.data.target; | |||||
let attackType = this.order.data.attackType; | |||||
if (!this.CanAttack(target)) | |||||
{ | |||||
this.SetNextState("COMBAT.FINDINGNEWTARGET"); | |||||
return; | |||||
} | |||||
this.RememberTargetPosition(); | |||||
if (this.order.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") | |||||
this.RememberTargetPosition(this.orderQueue[1].data); | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
this.lastAttacked = cmpTimer.GetTime() - msg.lateness; | |||||
this.FaceTowardsTarget(target); | |||||
// BuildingAI has it's own attack-routine | |||||
let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | |||||
if (!cmpBuildingAI) | |||||
{ | |||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
cmpAttack.PerformAttack(attackType, target); | if (cmpAttack) | ||||
} | cmpAttack.StopAttacking(); | ||||
}, | |||||
// PerformAttack might have triggered messages that moved us to another state. | |||||
// (use 'ends with' to handle formation members copying our state). | |||||
if (!this.GetCurrentState().endsWith("COMBAT.ATTACKING")) | |||||
return; | |||||
// Check we can still reach the target for the next attack | |||||
if (this.CheckTargetAttackRange(target, attackType)) | |||||
{ | |||||
if (this.resyncAnimation) | |||||
{ | |||||
this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat); | |||||
this.resyncAnimation = false; | |||||
} | |||||
return; | |||||
} | |||||
if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | "OutOfRange": function() { | ||||
if (this.ShouldChaseTargetedEntity(this.order.data.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("CHASING"); | ||||
return; | return; | ||||
} | } | ||||
this.SetNextState("FINDINGNEWTARGET"); | |||||
}, | |||||
"TargetInvalidated": function() { | |||||
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.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force) && | ||||
▲ Show 20 Lines • Show All 1,171 Lines • ▼ Show 20 Lines | UnitAI.prototype.Init = function() | ||||
this.heldPosition = undefined; | this.heldPosition = undefined; | ||||
// Queue of remembered works | // Queue of remembered works | ||||
this.workOrders = []; | this.workOrders = []; | ||||
this.isGuardOf = undefined; | this.isGuardOf = undefined; | ||||
// For preventing increased action rate due to Stop orders or target death. | |||||
this.lastAttacked = undefined; | |||||
this.formationAnimationVariant = undefined; | this.formationAnimationVariant = undefined; | ||||
this.cheeringTime = +(this.template.CheeringTime || 0); | this.cheeringTime = +(this.template.CheeringTime || 0); | ||||
this.SetStance(this.template.DefaultStance); | this.SetStance(this.template.DefaultStance); | ||||
}; | }; | ||||
UnitAI.prototype.IsTurret = function() | UnitAI.prototype.IsTurret = function() | ||||
{ | { | ||||
if (!this.isGarrisoned) | if (!this.isGarrisoned) | ||||
▲ Show 20 Lines • Show All 1,371 Lines • ▼ Show 20 Lines | 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); | ||||
if (type != "Ranged") | let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
return this.CheckTargetRange(target, IID_Attack, type); | return cmpAttack && cmpAttack.IsTargetInRange(target, type); | ||||
let targetCmpPosition = Engine.QueryInterface(target, IID_Position); | |||||
if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) | |||||
return false; | |||||
let range = this.GetRange(IID_Attack, type, target); | |||||
if (!range) | |||||
return false; | |||||
let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!thisCmpPosition.IsInWorld()) | |||||
return false; | |||||
let s = thisCmpPosition.GetPosition(); | |||||
let t = targetCmpPosition.GetPosition(); | |||||
let h = s.y - t.y + range.elevationBonus; | |||||
let maxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); | |||||
if (maxRange < 0) | |||||
return false; | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, maxRange, 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); | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 1,648 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator