Changeset View
Standalone View
binaries/data/mods/public/simulation/components/TurretAI.js
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
function TurretAI() {} | |||||
TurretAI.prototype.Schema = "<empty/>"; | |||||
TurretAI.prototype.g_MaxPreferenceBonus = 2; | |||||
TurretAI.prototype.g_LatestTarget = INVALID_ENTITY; | |||||
/** | |||||
* Initialize TurretAI Component. | |||||
*/ | |||||
TurretAI.prototype.Init = function() | |||||
{ | |||||
}; | |||||
/** | |||||
* Get the turrent parent. | |||||
*/ | |||||
TurretAI.prototype.GetTurretParent = function() | |||||
{ | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
return !cmpPosition || !cmpPosition.IsInWorld() ? INVALID_ENTITY : cmpPosition.GetTurretParent(); | |||||
}; | |||||
/** | |||||
* Get the attack component | |||||
*/ | |||||
TurretAI.prototype.GetCmpAttack = function() | |||||
{ | |||||
// use own attack, or turretHolder's attack | |||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
return cmpAttack ? cmpAttack : Engine.QueryInterface(this.GetTurretParent(), IID_Attack); | |||||
}; | |||||
/** | |||||
* Sends the message to change ownership | |||||
*/ | |||||
TurretAI.prototype.OnOwnershipChanged = function(msg) | |||||
{ | |||||
Silier: would this work ?
```
return cmpAttack | Engine.QueryInerface
```
if not so
```
return… | |||||
if (msg.to == INVALID_PLAYER) | |||||
return; | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
this.targetUnits = []; | |||||
this.SetupRangeQuery(); | |||||
}; | |||||
/** | |||||
* Change Diplomacy State | |||||
*/ | |||||
TurretAI.prototype.OnDiplomacyChanged = function(msg) | |||||
{ | |||||
if (!IsOwnedByPlayer(msg.player, this.entity)) | |||||
return; | |||||
this.targetUnits = []; | |||||
this.SetupRangeQuery(); | |||||
}; | |||||
/** | |||||
* Cleanup on destroy | |||||
*/ | |||||
TurretAI.prototype.OnDestroy = function() | |||||
{ | |||||
if (this.timer) | |||||
{ | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
cmpTimer.CancelTimer(this.timer); | |||||
delete this.timer; | |||||
} | |||||
// Clean up range queries | |||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
if (this.enemyUnitsQuery) | |||||
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); | |||||
}; | |||||
/** | |||||
* Setup the Range Query to detect units coming in & out of range | |||||
*/ | |||||
TurretAI.prototype.SetupRangeQuery = function() | |||||
{ | |||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
if (this.losRangeQuery) | |||||
{ | |||||
cmpRangeManager.DestroyActiveQuery(this.losRangeQuery); | |||||
delete this.losRangeQuery; | |||||
} | |||||
let cmpPlayer = QueryOwnerInterface(this.entity); | |||||
// If we are being destructed (owner -1), creating a range query is pointless | |||||
if (!cmpPlayer) | |||||
return; | |||||
// Exclude allies, and self | |||||
// TODO: How to handle neutral players - Special query to attack military only? | |||||
let players = cmpPlayer.GetEnemies(); | |||||
let range = this.GetQueryRange(IID_Attack); | |||||
this.losRangeQuery = cmpRangeManager.CreateActiveQuery( | |||||
this.entity, | |||||
range.min, | |||||
range.max, | |||||
players, | |||||
IID_DamageReceiver, | |||||
cmpRangeManager.GetEntityFlagMask("normal") | |||||
); | |||||
cmpRangeManager.EnableActiveQuery(this.losRangeQuery); | |||||
}; | |||||
TurretAI.prototype.GetStance = function() | |||||
Done Inline Actionswhy not just get owner of this entity ? Silier: why not just get owner of this entity ? | |||||
Done Inline ActionsI mean : cmpPlayer = QueryOwnerInterface(this.entity); ownership of this entity has been changed by now already Silier: I mean :
```
cmpPlayer = QueryOwnerInterface(this.entity);
```
ownership of this entity has… | |||||
Done Inline ActionsI guess the code didnt' allow that by then. Stan: I guess the code didnt' allow that by then. | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
return cmpUnitAI.GetStance(); | |||||
}; | |||||
TurretAI.prototype.GetQueryRange = function(iid) | |||||
{ | |||||
let ret = { "min": 0, "max": 0 }; | |||||
if (this.GetStance().respondStandGround) | |||||
{ | |||||
let cmpRanged = Engine.QueryInterface(this.entity, iid); | |||||
if (!cmpRanged) | |||||
return ret; | |||||
let range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetFullAttackRange(); | |||||
let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); | |||||
if (!cmpVision) | |||||
return ret; | |||||
ret.min = range.min; | |||||
ret.max = Math.min(range.max, cmpVision.GetRange()); | |||||
} | |||||
else if (this.GetStance().respondChase) | |||||
{ | |||||
let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); | |||||
if (!cmpVision) | |||||
return ret; | |||||
let range = cmpVision.GetRange(); | |||||
ret.max = range; | |||||
} | |||||
else if (this.GetStance().respondHoldGround) | |||||
{ | |||||
let cmpRanged = Engine.QueryInterface(this.entity, iid); | |||||
if (!cmpRanged) | |||||
return ret; | |||||
let range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetFullAttackRange(); | |||||
let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); | |||||
if (!cmpVision) | |||||
return ret; | |||||
let vision = cmpVision.GetRange(); | |||||
ret.max = Math.min(range.max + vision / 2, vision); | |||||
Done Inline Actionsmove this before let owner Silier: move this before let owner | |||||
} | |||||
// We probably have stance 'passive' and we wouldn't have a range, | |||||
// but as it is the default for healers we need to set it to something sane. | |||||
else if (iid === IID_Heal) | |||||
{ | |||||
let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); | |||||
if (!cmpVision) | |||||
return ret; | |||||
let range = cmpVision.GetRange(); | |||||
ret.max = range; | |||||
} | |||||
return ret; | |||||
}; | |||||
/** | |||||
* Called when units enter or leave range | |||||
*/ | |||||
TurretAI.prototype.OnRangeUpdate = function(msg) | |||||
{ | |||||
this.targetUnits = []; | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
// Target enemy units except non-dangerous animals | |||||
if (msg.tag == this.enemyUnitsQuery) | |||||
{ | |||||
msg.added = msg.added.filter(e => { | |||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | |||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | |||||
}); | |||||
} | |||||
// Add new targets | |||||
for (let entity of msg.added) | |||||
if (cmpAttack.CanAttack(entity)) | |||||
this.targetUnits.push(entity); | |||||
// Remove targets outside of vision-range | |||||
for (let entity of msg.removed) | |||||
{ | |||||
let index = this.targetUnits.indexOf(entity); | |||||
if (index != -1) | |||||
this.targetUnits.splice(index, 1); | |||||
} | |||||
if (this.targetUnits.length) | |||||
{ | |||||
this.StartTimer(); | |||||
return; | |||||
} | |||||
let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
if (!this.targetUnits.length | |||||
&& cmpUnitAI.order | |||||
&& cmpUnitAI.order.type == "Attack" | |||||
&& cmpUnitAI.order.data | |||||
&& cmpUnitAI.order.data.force | |||||
&& cmpUnitAI.order.data.target | |||||
&& cmpUnitAI.order.data.target != INVALID_ENTITY) | |||||
{ | |||||
Done Inline Actionsmaybe index != -1 ? Silier: maybe index != -1 ? | |||||
this.targetUnits.push(cmpUnitAI.order.data.target) | |||||
this.StartTimer(); | |||||
} | |||||
}; | |||||
TurretAI.prototype.StartTimer = function() | |||||
{ | |||||
if (this.timer) | |||||
return; | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
let attackTimers = cmpAttack.GetTimers(this.GetBestAttack()); | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
this.timer = cmpTimer.SetInterval( | |||||
this.entity, | |||||
IID_TurretAI, | |||||
"Attack", | |||||
attackTimers.prepare, | |||||
attackTimers.repeat, | |||||
undefined | |||||
); | |||||
}; | |||||
TurretAI.prototype.GetBestAttack = function() | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
if (cmpUnitAI | |||||
&& cmpUnitAI.order | |||||
&& cmpUnitAI.order.data | |||||
&& cmpUnitAI.order.data.force | |||||
&& cmpUnitAI.order.data.target | |||||
&& cmpUnitAI.order.data.target != INVALID_ENTITY) | |||||
return cmpUnitAI.order.data.attackType | |||||
Done Inline Actionslet ( + whole this sope ) Silier: let ( + whole this sope ) | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
Done Inline Actionsthis can be inlined Silier: this can be inlined | |||||
Done Inline ActionsNot sure how. Stan: Not sure how.
| |||||
return cmpAttack.GetAttackTypes()[0]; | |||||
}; | |||||
/** | |||||
* Attack other entities | |||||
*/ | |||||
TurretAI.prototype.Attack = function(data, lateness) | |||||
{ | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
if (!this.targetUnits.length) | |||||
{ | |||||
if (this.timer) | |||||
{ | |||||
cmpTimer.CancelTimer(this.timer); | |||||
delete this.timer; | |||||
} | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (cmpPosition && cmpPosition.IsInWorld()) | |||||
cmpPosition.SetYRotation(cmpPosition.GetRotation().y); | |||||
this.SelectAnimation("idle"); | |||||
this.SetAnimationVariant(""); | |||||
return; | |||||
} | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
let target = this.targetUnits.indexOf(this.g_LatestTarget) != -1 ? this.g_LatestTarget : INVALID_ENTITY | |||||
// if no target, select a random one | |||||
if (target == INVALID_ENTITY) | |||||
{ | |||||
Done Inline Actionslet ( + whole this scope ) Silier: let ( + whole this scope ) | |||||
let targets = new WeightedList(); | |||||
for (let weightedTarget of this.targetUnits) | |||||
{ | |||||
let preference = cmpAttack.GetPreference(weightedTarget); | |||||
// Lower preference scores indicate a higher preference so they should result in a higher weight. | |||||
let weight = !preference ? 1 + this.g_MaxPreferenceBonus / (1 + preference) : 1; | |||||
targets.push(weightedTarget, weight); | |||||
} | |||||
let selectedIndex = targets.randomIndex(); | |||||
target = targets.itemAt(selectedIndex); | |||||
} | |||||
Done Inline Actionsredefinition, not needed, inline this Silier: redefinition, not needed, inline this | |||||
if (target == INVALID_ENTITY) | |||||
return; | |||||
Done Inline Actionsif (!preference) Silier: if (!preference) | |||||
Done Inline Actionsnuke { } Silier: nuke { } | |||||
let attackType = this.GetBestAttack(); | |||||
this.lastAttacked = cmpTimer.GetTime() - lateness; | |||||
let attackTimers = cmpAttack.GetTimers(this.GetBestAttack()); | |||||
let prepare = attackTimers.prepare; | |||||
if (this.lastAttacked) | |||||
{ | |||||
let repeatLeft = this.lastAttacked + attackTimers.repeat - cmpTimer.GetTime(); | |||||
prepare = Math.max(prepare, repeatLeft); | |||||
} | |||||
this.SelectAnimation("attack_" + attackType.toLowerCase()); | |||||
this.SetAnimationVariant("combat"); | |||||
this.SetAnimationSync(prepare, attackTimers.repeat); | |||||
this.previousAttack = "attack_" + attackType.toLowerCase(); | |||||
Done Inline Actionsmove this before whole picking target thing ? Silier: move this before whole picking target thing ? | |||||
this.FaceTowardsTarget(target); | |||||
cmpAttack.PerformAttack(attackType, target, this.entity); | |||||
this.g_LatestTarget = target; | |||||
}; | |||||
/** | |||||
* Selection the animations for the turret | |||||
Done Inline Actionstarget is set to invalid entity before loop so it cannot be !target Silier: target is set to invalid entity before loop so it cannot be !target | |||||
*/ | |||||
TurretAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) | |||||
{ | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
// Special case: the "move" animation gets turned into a special | |||||
// movement mode that deals with speeds and walk/run automatically | |||||
if (name == "move") | |||||
{ | |||||
// Speed to switch from walking to running animations | |||||
cmpVisual.SelectMovementAnimation(this.GetWalkSpeed()); | |||||
return; | |||||
} | |||||
cmpVisual.SelectAnimation(name, once, speed); | |||||
}; | |||||
/** | |||||
* Synchronise Animations | |||||
*/ | |||||
TurretAI.prototype.SetAnimationSync = function(offset, repeattime) | |||||
{ | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
cmpVisual.SetAnimationSyncRepeat(repeattime); | |||||
cmpVisual.SetAnimationSyncOffset(offset); | |||||
}; | |||||
/** | |||||
* Orient the turret toward his foe. | |||||
*/ | |||||
TurretAI.prototype.FaceTowardsTarget = function(target) | |||||
{ | |||||
if (!target) | |||||
return; | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||
return; | |||||
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); | |||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) | |||||
return; | |||||
let pos = cmpTargetPosition.GetPosition2D().sub(cmpPosition.GetPosition2D()); | |||||
cmpPosition.TurnTo(Math.atan2(pos.x, pos.y)); | |||||
}; | |||||
Done Inline Actionsmaybe just switch order so we are consistant with order of parameters and I would go actiontime -> offset but as you want Silier: maybe just switch order so we are consistant with order of parameters and I would go actiontime… | |||||
/* | |||||
* Set a visualActor animation variant. | |||||
* By changing the animation variant, you can change animations based on unitAI state. | |||||
* If there are no specific variants or the variant doesn't exist in the actor, | |||||
* the actor fallbacks to any existing animation. | |||||
* @param type if present, switch to a specific animation variant. | |||||
*/ | |||||
TurretAI.prototype.SetAnimationVariant = function(type) | |||||
Done Inline ActionsIs this actually needed? Silier: Is this actually needed?
I mean will it happen that this entity has position and is in world… | |||||
Done Inline ActionsPlane going out of the map with turrets on his wings maybe. Stan: Plane going out of the map with turrets on his wings maybe. | |||||
{ | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
Done Inline ActionsI would split this after every let so we do not query not needed interfaces ? Silier: I would split this after every let so we do not query not needed interfaces ? | |||||
if (!cmpVisual) | |||||
return; | |||||
cmpVisual.SetVariant("animationVariant", type); | |||||
return; | |||||
}; | |||||
Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); |
would this work ?
if not so