Changeset View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
function UnitAI() {} | function UnitAI() {} | ||||
UnitAI.prototype.Schema = | UnitAI.prototype.Schema = | ||||
"<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" + | "<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" + | ||||
"<a:example/>" + | "<a:example/>" + | ||||
"<element name='DefaultStance'>" + | "<element name='DefaultStance'>" + | ||||
"<choice>" + | "<choice>" + | ||||
"<value>violent</value>" + | "<value>violent</value>" + | ||||
"<value>aggressive</value>" + | "<value>aggressive</value>" + | ||||
"<value>defensive</value>" + | "<value>defensive</value>" + | ||||
"<value>passive</value>" + | "<value>passive</value>" + | ||||
"<value>standground</value>" + | "<value>standground</value>" + | ||||
"</choice>" + | "</choice>" + | ||||
"</element>" + | "</element>" + | ||||
Nescio: What does this mean? And why is it listed at the bottom? | |||||
Not Done Inline ActionsThis means that the entity will retaliate but not initiate in any attack. It list listed at the bottom because I added it lastly ^^ Freagarach: This means that the entity will retaliate but not initiate in any attack. It list listed at the… | |||||
Not Done Inline ActionsThe idea is that if you don't bother it, it won't bother you. wraitii: The idea is that if you don't bother it, it won't bother you.
I feel like English must have a… | |||||
Not Done Inline ActionsTechnically it's neutral, one might also use indifferent https://www.synonyms.com/synonym/Neutral#:~:text=Princeton%27s%20WordNet%20%281.86%20%2F%2013%20votes%29Rate%20these%20synonyms%3A,impersonal%2C%20apathetic%2C%20electroneutral%2C%20deaf%20...%20More%20items...%20 Stan: Technically it's neutral, one might also use indifferent https://www.synonyms. | |||||
"<element name='FormationController'>" + | "<element name='FormationController'>" + | ||||
"<data type='boolean'/>" + | "<data type='boolean'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='FleeDistance'>" + | "<element name='FleeDistance'>" + | ||||
"<ref name='positiveDecimal'/>" + | "<ref name='positiveDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='CanGuard'>" + | "<element name='CanGuard'>" + | ||||
"<data type='boolean'/>" + | "<data type='boolean'/>" + | ||||
▲ Show 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"FormationLeave": function(msg) { | "FormationLeave": function(msg) { | ||||
// ignore when we're not in FORMATIONMEMBER | // ignore when we're not in FORMATIONMEMBER | ||||
}, | }, | ||||
// Called when being told to walk as part of a formation | // Called when being told to walk as part of a formation | ||||
"Order.FormationWalk": function(msg) { | "Order.FormationWalk": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move. | // 1. If packed, we can move. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
Show All 21 Lines | "Order.LeaveFoundation": function(msg) { | ||||
this.SetNextState("INDIVIDUAL.WALKING"); | this.SetNextState("INDIVIDUAL.WALKING"); | ||||
}, | }, | ||||
// Individual orders: | // Individual orders: | ||||
// (these will switch the unit out of formation mode) | // (these will switch the unit out of formation mode) | ||||
"Order.Stop": function(msg) { | "Order.Stop": function(msg) { | ||||
// We have no control over non-domestic animals. | // We have no control over non-domestic animals. | ||||
if (this.IsAnimal() && !this.IsDomestic()) | if (!this.IsDomestic()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// Stop moving immediately. | // Stop moving immediately. | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// No orders left, we're an individual now | // No orders left, we're an individual now | ||||
if (this.IsAnimal()) | if (this.IsFormationMember()) | ||||
Done Inline Actionscould ternary this Stan: could ternary this | |||||
Done Inline ActionsDoesn't improve readability. Freagarach: Doesn't improve readability. | |||||
this.SetNextState("ANIMAL.IDLE"); | |||||
else if (this.IsFormationMember()) | |||||
this.SetNextState("FORMATIONMEMBER.IDLE"); | this.SetNextState("FORMATIONMEMBER.IDLE"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.IDLE"); | this.SetNextState("INDIVIDUAL.IDLE"); | ||||
}, | }, | ||||
"Order.Walk": function(msg) { | "Order.Walk": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move. | // 1. If packed, we can move. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetHeldPosition(this.order.data.x, this.order.data.z); | this.SetHeldPosition(this.order.data.x, this.order.data.z); | ||||
// It's not too bad if we don't arrive at exactly the right position. | // It's not too bad if we don't arrive at exactly the right position. | ||||
this.order.data.relaxed = true; | this.order.data.relaxed = true; | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.WALKING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.WALKING"); | this.SetNextState("INDIVIDUAL.WALKING"); | ||||
}, | }, | ||||
"Order.WalkAndFight": function(msg) { | "Order.WalkAndFight": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move. | // 1. If packed, we can move. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetHeldPosition(this.order.data.x, this.order.data.z); | this.SetHeldPosition(this.order.data.x, this.order.data.z); | ||||
// It's not too bad if we don't arrive at exactly the right position. | // It's not too bad if we don't arrive at exactly the right position. | ||||
this.order.data.relaxed = true; | this.order.data.relaxed = true; | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals | |||||
else | |||||
this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING"); | this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING"); | ||||
}, | }, | ||||
"Order.WalkToTarget": function(msg) { | "Order.WalkToTarget": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move. | // 1. If packed, we can move. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
Show All 9 Lines | if (this.CheckRange(this.order.data)) | ||||
// We are already at the target, or can't move at all | // We are already at the target, or can't move at all | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
// It's not too bad if we don't arrive at exactly the right position. | // It's not too bad if we don't arrive at exactly the right position. | ||||
this.order.data.relaxed = true; | this.order.data.relaxed = true; | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.WALKING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.WALKING"); | this.SetNextState("INDIVIDUAL.WALKING"); | ||||
}, | }, | ||||
"Order.PickupUnit": function(msg) { | "Order.PickupUnit": function(msg) { | ||||
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
Show All 30 Lines | "Order.Guard": function(msg) { | ||||
if (!this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | if (!this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | ||||
this.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); | this.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.GUARD.GUARDING"); | this.SetNextState("INDIVIDUAL.GUARD.GUARDING"); | ||||
}, | }, | ||||
"Order.Flee": function(msg) { | "Order.Flee": function(msg) { | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.FLEEING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.FLEEING"); | this.SetNextState("INDIVIDUAL.FLEEING"); | ||||
}, | }, | ||||
"Order.Attack": function(msg) { | "Order.Attack": function(msg) { | ||||
// Check the target is alive | // Check the target is alive | ||||
if (!this.TargetIsAlive(this.order.data.target)) | if (!this.TargetIsAlive(this.order.data.target)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
Show All 24 Lines | if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
this.PushOrderFront("Unpack", { "force": true }); | this.PushOrderFront("Unpack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
// Cancel any current packing order. | // Cancel any current packing order. | ||||
if (!this.EnsureCorrectPackStateForAttack(false)) | if (!this.EnsureCorrectPackStateForAttack(false)) | ||||
return; | return; | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.COMBAT.ATTACKING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); | this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); | ||||
return; | return; | ||||
} | } | ||||
// If we can't reach the target, but are standing ground, then abandon this attack order. | // If we can't reach the target, but are standing ground, then abandon this attack order. | ||||
// Unless we're hunting, that's a special case where we should continue attacking our target. | // Unless we're hunting, that's a special case where we should continue attacking our target. | ||||
if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret()) | if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units out of attack range: | // For packable units out of attack range: | ||||
// 1. If packed, we need to move to attack range and then unpack. | // 1. If packed, we need to move to attack range and then unpack. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
// If we're currently packing/unpacking, make sure we are packed, so we can move. | // If we're currently packing/unpacking, make sure we are packed, so we can move. | ||||
if (!this.EnsureCorrectPackStateForAttack(true)) | if (!this.EnsureCorrectPackStateForAttack(true)) | ||||
return; | return; | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.COMBAT.APPROACHING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); | this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); | ||||
}, | }, | ||||
"Order.Patrol": function(msg) { | "Order.Patrol": function(msg) { | ||||
if (this.IsAnimal() || this.IsTurret()) | if (this.IsTurret()) | ||||
Done Inline ActionsShouldn't the patrol order override the roaming? It's basically ordered roaming Stan: Shouldn't the patrol order override the roaming? It's basically ordered roaming | |||||
Done Inline ActionsI'm not sure what you mean here, but I guess this check can be ditched. Freagarach: I'm not sure what you mean here, but I guess this check can be ditched. | |||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
▲ Show 20 Lines • Show All 143 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"Order.Garrison": function(msg) { | "Order.Garrison": function(msg) { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.SetNextState("IDLE"); | this.SetNextState("IDLE"); | ||||
return; | return; | ||||
} | } | ||||
else if (this.IsGarrisoned()) | else if (this.IsGarrisoned()) | ||||
{ | { | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.GARRISON.GARRISONED"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); | this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move to the garrison target. | // 1. If packed, we can move to the garrison target. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.GARRISON.APPROACHING"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); | this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); | ||||
}, | }, | ||||
"Order.Ungarrison": function() { | "Order.Ungarrison": function() { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
this.isGarrisoned = false; | this.isGarrisoned = false; | ||||
}, | }, | ||||
"Order.Cheering": function(msg) { | "Order.Cheering": function(msg) { | ||||
▲ Show 20 Lines • Show All 630 Lines • ▼ Show 20 Lines | "Order.LeaveFoundation": function(msg) { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
this.order.data.min = g_LeaveFoundationRange; | this.order.data.min = g_LeaveFoundationRange; | ||||
this.SetNextState("WALKINGTOPOINT"); | this.SetNextState("WALKINGTOPOINT"); | ||||
}, | }, | ||||
"enter": function() { | "enter": function() { | ||||
if (this.IsAnimal()) | |||||
{ | |||||
// Animals can't go in formation. | |||||
warn("Entity " + this.entity + " was put in FORMATIONMEMBER state but is an animal"); | |||||
this.FinishOrder(); | |||||
this.SetNextState("ANIMAL.IDLE"); | |||||
return true; | |||||
} | |||||
let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
{ | { | ||||
this.formationAnimationVariant = cmpFormation.GetFormationAnimation(this.entity); | this.formationAnimationVariant = cmpFormation.GetFormationAnimation(this.entity); | ||||
if (this.formationAnimationVariant) | if (this.formationAnimationVariant) | ||||
this.SetAnimationVariant(this.formationAnimationVariant); | this.SetAnimationVariant(this.formationAnimationVariant); | ||||
else | else | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | "FORMATIONMEMBER": { | ||||
}, | }, | ||||
}, | }, | ||||
// States for entities not part of a formation: | // States for entities not part of a formation: | ||||
"INDIVIDUAL": { | "INDIVIDUAL": { | ||||
"enter": function() { | "enter": function() { | ||||
// Sanity-checking | |||||
if (this.IsAnimal()) | |||||
error("Animal got moved into INDIVIDUAL.* state"); | |||||
return false; | return false; | ||||
}, | }, | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// Respond to attack if we always target attackers or during unforced orders | // Respond to attack if we always target attackers or during unforced orders | ||||
if (this.GetStance().targetAttackersAlways || !this.order || !this.order.data || !this.order.data.force) | if (this.GetStance().targetAttackersAlways || !this.order || !this.order.data || !this.order.data.force) | ||||
this.RespondToTargetedEntities([msg.data.attacker]); | this.RespondToTargetedEntities([msg.data.attacker]); | ||||
if (!this.template.NaturalBehaviour) | |||||
return; | |||||
if (this.template.NaturalBehaviour == "skittish" || | |||||
this.template.NaturalBehaviour == "passive") | |||||
{ | |||||
this.Flee(msg.data.attacker, false); | |||||
} | |||||
else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | |||||
{ | |||||
if (this.CanAttack(msg.data.attacker)) | |||||
this.Attack(msg.data.attacker, false); | |||||
} | |||||
else if (this.template.NaturalBehaviour == "domestic") | |||||
{ | |||||
// Never flee, stop what we were doing | |||||
this.SetNextState("IDLE"); | |||||
} | |||||
}, | }, | ||||
"GuardedAttacked": function(msg) { | "GuardedAttacked": function(msg) { | ||||
// do nothing if we have a forced order in queue before the guard order | // do nothing if we have a forced order in queue before the guard order | ||||
for (var i = 0; i < this.orderQueue.length; ++i) | for (var i = 0; i < this.orderQueue.length; ++i) | ||||
{ | { | ||||
if (this.orderQueue[i].type == "Guard") | if (this.orderQueue[i].type == "Guard") | ||||
break; | break; | ||||
▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | "IDLE": { | ||||
// Start attacking one of the newly-seen enemy (if any). | // Start attacking one of the newly-seen enemy (if any). | ||||
// We check for idleness to prevent an entity to attack only newly seen enemies | // We check for idleness to prevent an entity to attack only newly seen enemies | ||||
// when receiving a LosRangeUpdate on the same turn as the entity becomes idle | // when receiving a LosRangeUpdate on the same turn as the entity becomes idle | ||||
// since this.FindNewTargets is called in the timer. | // since this.FindNewTargets is called in the timer. | ||||
if (this.isIdle && msg.data.added.length && this.GetStance().targetVisibleEnemies) | if (this.isIdle && msg.data.added.length && this.GetStance().targetVisibleEnemies) | ||||
this.AttackEntitiesByPreference(msg.data.added); | this.AttackEntitiesByPreference(msg.data.added); | ||||
}, | }, | ||||
"LosHealRangeUpdate": function(msg) { | "LosHealRangeUpdate": function(msg) { | ||||
Done Inline ActionsRefs. D1880. Freagarach: Refs. D1880. | |||||
// We check for idleness to prevent an entity to heal only newly seen entities | // We check for idleness to prevent an entity to heal only newly seen entities | ||||
// when receiving a LosHealRangeUpdate on the same turn as the entity becomes idle | // when receiving a LosHealRangeUpdate on the same turn as the entity becomes idle | ||||
// since this.FindNewHealTargets is called in the timer. | // since this.FindNewHealTargets is called in the timer. | ||||
if (this.isIdle && msg.data.added.length) | if (this.isIdle && msg.data.added.length) | ||||
this.RespondToHealableEntities(msg.data.added); | this.RespondToHealableEntities(msg.data.added); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
if (this.HasNaturalBehaviour()) | |||||
{ | |||||
this.SetNextState("FEEDING"); | |||||
return; | |||||
} | |||||
// If the unit is guarding/escorting, go back to its duty. | // If the unit is guarding/escorting, go back to its duty. | ||||
if (this.isGuardOf) | if (this.isGuardOf) | ||||
{ | { | ||||
this.Guard(this.isGuardOf, false); | this.Guard(this.isGuardOf, false); | ||||
return; | return; | ||||
} | } | ||||
// If a unit can heal and attack we first want to heal wounded units, | // If a unit can heal and attack we first want to heal wounded units, | ||||
Show All 22 Lines | "IDLE": { | ||||
if (!this.isIdle) | if (!this.isIdle) | ||||
{ | { | ||||
this.isIdle = true; | this.isIdle = true; | ||||
Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
"WALKING": { | "WALKING": { | ||||
Done Inline Actionsthis should go before if (!this.isIdle) Silier: this should go before if (!this.isIdle) | |||||
Done Inline ActionsWhy? Aren't we considered idle when merely lingering around? Freagarach: Why? Aren't we considered idle when merely lingering around? | |||||
Done Inline Actionshmm, depends on definition of idle but ok Silier: hmm, depends on definition of idle but ok
ah. else would not work ranges, I did not notice that… | |||||
"enter": function() { | "enter": function() { | ||||
if (!this.MoveTo(this.order.data)) | if (!this.MoveTo(this.order.data)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
}, | }, | ||||
Show All 13 Lines | "INDIVIDUAL": { | ||||
"WALKINGANDFIGHTING": { | "WALKINGANDFIGHTING": { | ||||
"enter": function() { | "enter": function() { | ||||
if (!this.MoveTo(this.order.data)) | if (!this.MoveTo(this.order.data)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
Not Done Inline ActionsQuestion 1: How big is that patch? Else we could tackle it before this one. Stan: Question 1: How big is that patch? Else we could tackle it before this one.
Question 2: Does it… | |||||
Not Done Inline ActionsA1: No clue yet, but the patch makes no sense without this one I guess ^^ Freagarach: A1: No clue yet, but the patch makes no sense without this one I guess ^^
A2: I guess not… | |||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
return false; | return false; | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
▲ Show 20 Lines • Show All 359 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
// 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; | this.resyncAnimation = prepare != this.attackTimers.prepare; | ||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | ||||
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; | ||||
Done Inline Actionspoint is to not cheer after killing chicken or any non-attacking animal, removal is wrong Silier: point is to not cheer after killing chicken or any non-attacking animal, removal is wrong | |||||
Done Inline ActionsAgreed. You can test though, entities won't cheer after killing those kind of animals :) Freagarach: Agreed. You can test though, entities won't cheer after killing those kind of animals :) | |||||
}, | }, | ||||
"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.StopTimer(); | ||||
this.ResetAnimation(); | this.ResetAnimation(); | ||||
▲ Show 20 Lines • Show All 1,185 Lines • ▼ Show 20 Lines | "PICKUP": { | ||||
return false; | return false; | ||||
}, | }, | ||||
"PickupCanceled": function() { | "PickupCanceled": function() { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
}, | |||||
"ANIMAL": { | |||||
"Attacked": function(msg) { | |||||
if (this.template.NaturalBehaviour == "skittish" || | |||||
this.template.NaturalBehaviour == "passive") | |||||
{ | |||||
this.Flee(msg.data.attacker, false); | |||||
} | |||||
else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | |||||
{ | |||||
if (this.CanAttack(msg.data.attacker)) | |||||
this.Attack(msg.data.attacker, false); | |||||
} | |||||
else if (this.template.NaturalBehaviour == "domestic") | |||||
Done Inline ActionsNote that in real life domestic animals would run if hurt. It is just that slaughtering is so quick they don't need to. (Hence I propose to increase slaughtering damage to kill domestic animals in one go.) Freagarach: Note that in real life domestic animals would run if hurt. It is just that slaughtering is so… | |||||
{ | |||||
// Never flee, stop what we were doing | |||||
this.SetNextState("IDLE"); | |||||
} | |||||
}, | |||||
"Order.LeaveFoundation": function(msg) { | |||||
// Move a tile outside the building | |||||
if (this.CheckTargetRangeExplicit(msg.data.target, g_LeaveFoundationRange, -1)) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
this.order.data.min = g_LeaveFoundationRange; | |||||
this.SetNextState("WALKING"); | |||||
}, | |||||
"IDLE": { | |||||
// (We need an IDLE state so that FinishOrder works) | |||||
"enter": function() { | |||||
// Start feeding immediately | |||||
this.SetNextState("FEEDING"); | |||||
return true; | |||||
}, | |||||
}, | |||||
"ROAMING": { | "ROAMING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Walk in a random direction | // Walk in a random direction | ||||
this.SetFacePointAfterMove(false); | this.SetFacePointAfterMove(false); | ||||
this.MoveRandomly(+this.template.RoamDistance); | this.MoveRandomly(+this.template.RoamDistance); | ||||
// Set a random timer to switch to feeding state | // Set a random timer to switch to feeding state | ||||
this.StartTimer(randIntInclusive(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); | this.StartTimer(randIntInclusive(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetFacePointAfterMove(true); | this.SetFacePointAfterMove(true); | ||||
}, | }, | ||||
"LosRangeUpdate": function(msg) { | "LosRangeUpdate": function(msg) { | ||||
if (this.template.NaturalBehaviour == "skittish") | if (this.template.NaturalBehaviour && | ||||
this.template.NaturalBehaviour == "skittish") | |||||
{ | { | ||||
if (msg.data.added.length > 0) | if (msg.data.added.length > 0) | ||||
{ | { | ||||
this.Flee(msg.data.added[0], false); | this.Flee(msg.data.added[0], false); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
// Start attacking one of the newly-seen enemy (if any) | // Start attacking one of the newly-seen enemy (if any) | ||||
else if (this.IsDangerousAnimal()) | else if (this.IsDangerousAnimal()) | ||||
{ | { | ||||
this.AttackVisibleEntity(msg.data.added); | this.AttackVisibleEntity(msg.data.added); | ||||
} | } | ||||
// TODO: if two units enter our range together, we'll attack the | // TODO: if two units enter our range together, we'll attack the | ||||
// first and then the second won't trigger another LosRangeUpdate | // first and then the second won't trigger another LosRangeUpdate | ||||
// so we won't notice it. Probably we should do something with | // so we won't notice it. Probably we should do something with | ||||
// ResetActiveQuery in ROAMING.enter/FEEDING.enter in order to | // ResetActiveQuery in ROAMING.enter/FEEDING.enter in order to | ||||
// find any units that are already in range. | // find any units that are already in range. | ||||
Done Inline ActionsIs fixed by moving it. Freagarach: Is fixed by moving it. | |||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.SetNextState("FEEDING"); | this.SetNextState("FEEDING"); | ||||
}, | }, | ||||
"MovementUpdate": function() { | "MovementUpdate": function() { | ||||
this.MoveRandomly(+this.template.RoamDistance); | this.MoveRandomly(+this.template.RoamDistance); | ||||
Show All 29 Lines | "FEEDING": { | ||||
this.AttackVisibleEntity(msg.data.added); | this.AttackVisibleEntity(msg.data.added); | ||||
} | } | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.SetNextState("ROAMING"); | this.SetNextState("ROAMING"); | ||||
}, | }, | ||||
}, | }, | ||||
"FLEEING": "INDIVIDUAL.FLEEING", // reuse the same fleeing behaviour for animals | |||||
"COMBAT": "INDIVIDUAL.COMBAT", // reuse the same combat behaviour for animals | |||||
"WALKING": "INDIVIDUAL.WALKING", // reuse the same walking behaviour for animals | |||||
// only used for domestic animals | |||||
// Reuse the same garrison behaviour for animals. | |||||
"GARRISON": "INDIVIDUAL.GARRISON", | |||||
}, | }, | ||||
}; | }; | ||||
UnitAI.prototype.Init = function() | UnitAI.prototype.Init = function() | ||||
{ | { | ||||
this.orderQueue = []; // current order is at the front of the list | this.orderQueue = []; // current order is at the front of the list | ||||
this.order = undefined; // always == this.orderQueue[0] | this.order = undefined; // always == this.orderQueue[0] | ||||
this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to | this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to | ||||
Show All 40 Lines | UnitAI.prototype.HasFinishedOrder = function() | ||||
return this.finishedOrder; | return this.finishedOrder; | ||||
}; | }; | ||||
UnitAI.prototype.ResetFinishOrder = function() | UnitAI.prototype.ResetFinishOrder = function() | ||||
{ | { | ||||
this.finishedOrder = false; | this.finishedOrder = false; | ||||
}; | }; | ||||
UnitAI.prototype.IsAnimal = function() | UnitAI.prototype.HasNaturalBehaviour = function() | ||||
Done Inline ActionsCould become isRoaming or something like that? Stan: Could become isRoaming or something like that? | |||||
Done Inline ActionsI want to be explicit why this function is here. Freagarach: I want to be explicit why this function is here. | |||||
{ | { | ||||
return (this.template.NaturalBehaviour ? true : false); | return (this.template.NaturalBehaviour ? true : false); | ||||
}; | }; | ||||
UnitAI.prototype.IsAnimal = function() | |||||
{ | |||||
Done Inline ActionsDo we have a convention for TODO (ToDo, Todo, toDo..) Stan: Do we have a convention for TODO (ToDo, Todo, toDo..) | |||||
return this.HasNaturalBehaviour(); | |||||
Done Inline Actionsseems a bit of a weird function tbh. I'm guessing it's part of the WIP, but don't remove animal only stuff just to add it back with an if wraitii: seems a bit of a weird function tbh. I'm guessing it's part of the WIP, but don't remove animal… | |||||
Done Inline ActionsYeah, we want animals to move from foundations, but I'm not sure what a proper way of checking that would be. Perhaps just check if the entity stance is passive (if it is non-allied) and if that is true move from the foundation? Would mean lions and such would stay, which seems okay to me. Freagarach: Yeah, we want animals to move from foundations, but I'm not sure what a proper way of checking… | |||||
}; | |||||
UnitAI.prototype.IsDangerousAnimal = function() | UnitAI.prototype.IsDangerousAnimal = function() | ||||
{ | { | ||||
return (this.IsAnimal() && (this.template.NaturalBehaviour == "violent" || | return (this.IsAnimal() && (this.template.NaturalBehaviour == "violent" || | ||||
this.template.NaturalBehaviour == "aggressive")); | this.template.NaturalBehaviour == "aggressive")); | ||||
Done Inline Actionsthis.template.RoamDistance > 0; ? Stan: ```lang=js
this.template.RoamDistance > 0;
```
? | |||||
Done Inline ActionsBut if there is no roaming distance at all in the template, this will error out, right? Freagarach: But if there is no roaming distance at all in the template, this will error out, right? | |||||
}; | }; | ||||
UnitAI.prototype.IsDomestic = function() | UnitAI.prototype.IsDomestic = function() | ||||
Done Inline ActionsTODO: ? Stan: TODO: ? | |||||
{ | { | ||||
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); | var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); | ||||
return cmpIdentity && cmpIdentity.HasClass("Domestic"); | return cmpIdentity && cmpIdentity.HasClass("Domestic"); | ||||
}; | }; | ||||
UnitAI.prototype.IsHealer = function() | UnitAI.prototype.IsHealer = function() | ||||
{ | { | ||||
return Engine.QueryInterface(this.entity, IID_Heal); | return Engine.QueryInterface(this.entity, IID_Heal); | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | UnitAI.prototype.IsWalkingAndFighting = function() | ||||
if (this.IsFormationMember()) | if (this.IsFormationMember()) | ||||
return false; | return false; | ||||
return this.orderQueue.length > 0 && (this.orderQueue[0].type == "WalkAndFight" || this.orderQueue[0].type == "Patrol"); | return this.orderQueue.length > 0 && (this.orderQueue[0].type == "WalkAndFight" || this.orderQueue[0].type == "Patrol"); | ||||
}; | }; | ||||
UnitAI.prototype.OnCreate = function() | UnitAI.prototype.OnCreate = function() | ||||
{ | { | ||||
if (this.IsAnimal()) | if (this.HasNaturalBehaviour()) | ||||
this.UnitFsm.Init(this, "ANIMAL.FEEDING"); | this.UnitFsm.Init(this, "INDIVIDUAL.FEEDING"); | ||||
else if (this.IsFormationController()) | else if (this.IsFormationController()) | ||||
this.UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE"); | this.UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE"); | ||||
else | else | ||||
this.UnitFsm.Init(this, "INDIVIDUAL.IDLE"); | this.UnitFsm.Init(this, "INDIVIDUAL.IDLE"); | ||||
this.isIdle = true; | this.isIdle = true; | ||||
}; | }; | ||||
UnitAI.prototype.OnDiplomacyChanged = function(msg) | UnitAI.prototype.OnDiplomacyChanged = function(msg) | ||||
▲ Show 20 Lines • Show All 390 Lines • ▼ Show 20 Lines | UnitAI.prototype.EnsureCorrectPackStateForAttack = function(requirePacked) | ||||
this.orderQueue[0] = this.orderQueue[1]; | this.orderQueue[0] = this.orderQueue[1]; | ||||
this.orderQueue[1] = tmp; | this.orderQueue[1] = tmp; | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
return false; | return false; | ||||
}; | }; | ||||
UnitAI.prototype.WillMoveFromFoundation = function(target, checkPacking = true) | UnitAI.prototype.WillMoveFromFoundation = function(target, checkPacking = true) | ||||
{ | { | ||||
if (this.IsAnimal()) | |||||
return true; | |||||
// If foundation is not ally of entity, or if entity is unpacked siege, | // If foundation is not ally of entity, or if entity is unpacked siege, | ||||
// ignore the order. | // ignore the order. | ||||
if (!IsOwnedByAllyOfEntity(this.entity, target) && | if (!IsOwnedByAllyOfEntity(this.entity, target) && | ||||
!Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | ||||
checkPacking && this.IsPacking() || | checkPacking && this.IsPacking() || | ||||
this.CanPack() || this.IsTurret()) | this.CanPack() || this.IsTurret()) | ||||
return false; | return false; | ||||
▲ Show 20 Lines • Show All 1,899 Lines • ▼ Show 20 Lines | UnitAI.prototype.GetTargetsFromUnit = function() | ||||
if (!this.GetStance().targetVisibleEnemies) | if (!this.GetStance().targetVisibleEnemies) | ||||
return []; | return []; | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return []; | return []; | ||||
var attackfilter = function(e) { | var attackfilter = function(e) { | ||||
var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | ||||
Done Inline ActionsThis introduces a rather early check. I'm not saying it's bad per se, but it might be quite inconsistent, I know some other functions run rather late ones instead. wraitii: This introduces a rather early check. I'm not saying it's bad per se, but it might be quite… | |||||
Done Inline ActionsIt's only called below, so it's not so early ? Also prevents the creation of an extra function and more indirection see l5925 Stan: It's only called below, so it's not so early ? Also prevents the creation of an extra function… | |||||
if (cmpOwnership && cmpOwnership.GetOwner() > 0) | if (cmpOwnership && cmpOwnership.GetOwner() > 0) | ||||
return true; | return true; | ||||
var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | ||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | ||||
Done Inline ActionsSame problem as BuildingAI. Freagarach: Same problem as `BuildingAI`. | |||||
Done Inline ActionsMaybe this should be a function of it's own since you use it thrice? Stan: Maybe this should be a function of it's own since you use it thrice? | |||||
}; | }; | ||||
Done Inline ActionsRefs. rP14494. Freagarach: Refs. rP14494. | |||||
Done Inline ActionsNotice the duplication with below. One could make this stance dependant to only attack entities with cmpAttack of neutral players. (For future reference.) Freagarach: Notice the duplication with below. One could make this stance dependant to only attack entities… | |||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
Done Inline Actionsinline ? Maybe could also merge with above, although might hurt readability Stan: inline ? Maybe could also merge with above, although might hurt readability | |||||
var entities = cmpRangeManager.ResetActiveQuery(this.losRangeQuery); | var entities = cmpRangeManager.ResetActiveQuery(this.losRangeQuery); | ||||
var targets = entities.filter(function(v) { return cmpAttack.CanAttack(v) && attackfilter(v); }) | var targets = entities.filter(function(v) { return cmpAttack.CanAttack(v) && attackfilter(v); }) | ||||
.sort(function(a, b) { return cmpAttack.CompareEntitiesByPreference(a, b); }); | .sort(function(a, b) { return cmpAttack.CompareEntitiesByPreference(a, b); }); | ||||
Done Inline ActionsWhy is cmpAttack not inside the attack filter function? Stan: Why is cmpAttack not inside the attack filter function? | |||||
Done Inline ActionsCan't you just do: entities.filter(attackfilter); Stan: Can't you just do:
```lang=js
entities.filter(attackfilter);
``` | |||||
return targets; | return targets; | ||||
}; | }; | ||||
/** | /** | ||||
* Resets losHealRangeQuery, and if there are some targets in range that we can heal | * Resets losHealRangeQuery, and if there are some targets in range that we can heal | ||||
* then we start healing and this returns true; otherwise, returns false. | * then we start healing and this returns true; otherwise, returns false. | ||||
*/ | */ | ||||
UnitAI.prototype.FindNewHealTargets = function() | UnitAI.prototype.FindNewHealTargets = function() | ||||
▲ Show 20 Lines • Show All 400 Lines • ▼ Show 20 Lines | UnitAI.prototype.AttackEntitiesByPreference = function(ents) | ||||
var attackfilter = function(e) { | var attackfilter = function(e) { | ||||
var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); | ||||
if (cmpOwnership && cmpOwnership.GetOwner() > 0) | if (cmpOwnership && cmpOwnership.GetOwner() > 0) | ||||
return true; | return true; | ||||
var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | ||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | ||||
}; | }; | ||||
Done Inline ActionsSame ^^ Stan: Same ^^ | |||||
let entsByPreferences = {}; | let entsByPreferences = {}; | ||||
Done Inline ActionsIdem. Freagarach: Idem. | |||||
let preferences = []; | let preferences = []; | ||||
let entsWithoutPref = []; | let entsWithoutPref = []; | ||||
for (let ent of ents) | for (let ent of ents) | ||||
{ | { | ||||
if (!attackfilter(ent)) | if (!attackfilter(ent)) | ||||
continue; | continue; | ||||
let pref = cmpAttack.GetPreference(ent); | let pref = cmpAttack.GetPreference(ent); | ||||
if (pref === null || pref === undefined) | if (pref === null || pref === undefined) | ||||
▲ Show 20 Lines • Show All 55 Lines • Show Last 20 Lines |
What does this mean? And why is it listed at the bottom?