Changeset View
Standalone View
binaries/data/mods/public/simulation/components/TurretAI.js
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() | |||||
{ | |||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||
return INVALID_ENTITY; | |||||
return cmpPosition.GetTurretParent(); | |||||
}; | |||||
/** | |||||
* Get the attack component | |||||
*/ | |||||
TurretAI.prototype.GetCmpAttack = function() | |||||
{ | |||||
// use own attack, or turretHolder's attack | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
if (cmpAttack) | |||||
return cmpAttack; | |||||
Silier: would this work ?
```
return cmpAttack | Engine.QueryInerface
```
if not so
```
return… | |||||
return Engine.QueryInterface(this.GetTurretParent(), IID_Attack); | |||||
}; | |||||
/** | |||||
* Sends the message to change ownership | |||||
*/ | |||||
TurretAI.prototype.OnOwnershipChanged = function(msg) | |||||
{ | |||||
if (msg.to == INVALID_PLAYER) | |||||
return; | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
// Remove current targets, to prevent them from being added twice | |||||
this.targetUnits = []; | |||||
this.bestAttack = cmpAttack.GetAttackTypes()[0]; | |||||
this.SetupRangeQuery(msg.to); | |||||
// Non-Gaia buildings should attack certain Gaia units. | |||||
if (msg.to != 0 || this.gaiaUnitsQuery) | |||||
this.SetupGaiaRangeQuery(msg.to); | |||||
}; | |||||
/** | |||||
* Change Diplomacy State | |||||
*/ | |||||
TurretAI.prototype.OnDiplomacyChanged = function(msg) | |||||
{ | |||||
if (!IsOwnedByPlayer(msg.player, this.entity)) | |||||
return; | |||||
// Remove maybe now allied/neutral units | |||||
this.targetUnits = []; | |||||
this.SetupRangeQuery(msg.player); | |||||
this.SetupGaiaRangeQuery(msg.player); | |||||
}; | |||||
/** | |||||
* Cleanup on destroy | |||||
*/ | |||||
TurretAI.prototype.OnDestroy = function() | |||||
{ | |||||
if (this.timer) | |||||
{ | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
cmpTimer.CancelTimer(this.timer); | |||||
this.timer = undefined; | |||||
} | |||||
// Clean up range queries | |||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
if (this.enemyUnitsQuery) | |||||
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); | |||||
if (this.gaiaUnitsQuery) | |||||
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); | |||||
}; | |||||
/** | |||||
* Setup the Range Query to detect units coming in & out of range | |||||
*/ | |||||
TurretAI.prototype.SetupRangeQuery = function(owner) | |||||
{ | |||||
let cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
if (this.enemyUnitsQuery) | |||||
{ | |||||
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); | |||||
this.enemyUnitsQuery = undefined; | |||||
} | |||||
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); | |||||
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 cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player); | |||||
if (!cmpPlayer) | |||||
return; | |||||
let enemies = cmpPlayer.GetEnemies(); | |||||
if (enemies.length && enemies[0] == 0) | |||||
enemies.shift(); // remove gaia | |||||
if (!enemies.length) | |||||
return; | |||||
let range = cmpAttack.GetRange(this.GetBestAttack()); | |||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( | |||||
this.entity, range.min, range.max, range.elevationBonus, | |||||
enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); | |||||
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); | |||||
}; | |||||
/** | |||||
*Set up a range query for Gaia units within LOS range which can be attacked. | |||||
* This should be called whenever our ownership changes. | |||||
*/ | |||||
TurretAI.prototype.SetupGaiaRangeQuery = function() | |||||
{ | |||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | |||||
var owner = cmpOwnership.GetOwner(); | |||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
if (this.gaiaUnitsQuery) | |||||
{ | |||||
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); | |||||
this.gaiaUnitsQuery = undefined; | |||||
} | |||||
Done Inline Actionsmove this before let owner Silier: move this before let owner | |||||
if (owner == -1) | |||||
return; | |||||
var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); | |||||
if (!cmpPlayer.IsEnemy(0)) | |||||
return; | |||||
var range = cmpAttack.GetRange(this.GetBestAttack()); | |||||
// This query is only interested in Gaia entities that can attack. | |||||
this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); | |||||
cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); | |||||
}; | |||||
/** | |||||
* Called when units enter or leave range | |||||
*/ | |||||
TurretAI.prototype.OnRangeUpdate = function(msg) | |||||
{ | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
// Target enemy units except non-dangerous animals | |||||
if (msg.tag == this.gaiaUnitsQuery) | |||||
{ | |||||
msg.added = msg.added.filter(e => { | |||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | |||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | |||||
}); | |||||
} | |||||
else if (msg.tag != this.enemyUnitsQuery) | |||||
return; | |||||
// 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(); | |||||
}; | |||||
TurretAI.prototype.StartTimer = function() | |||||
{ | |||||
if (this.timer) | |||||
return; | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
Done Inline Actionsmaybe index != -1 ? Silier: maybe index != -1 ? | |||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
var attackTimers = cmpAttack.GetTimers(this.GetBestAttack()); | |||||
this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", | |||||
attackTimers.prepare, attackTimers.repeat, null); | |||||
}; | |||||
TurretAI.prototype.GetBestAttack = function() | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
if (cmpUnitAI && cmpUnitAI.order && cmpUnitAI.order.data.force && cmpUnitAI.order.data.target && cmpUnitAI.order.data.target != INVALID_ENTITY) | |||||
return cmpUnitAI.order.data.attackType | |||||
return this.bestAttack; | |||||
}; | |||||
/** | |||||
* Get Previous Target | |||||
*/ | |||||
TurretAI.prototype.GetPreviousTarget = function() | |||||
{ | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return INVALID_ENTITY; | |||||
var previousTarget = this.g_LatestTarget; | |||||
var cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
// if a unit ai, use that to overcome the difference between rangeManager range and unitMotion range | |||||
if (cmpUnitAI) | |||||
{ | |||||
if (cmpUnitAI.CheckTargetAttackRange(previousTarget, this.GetBestAttack())) | |||||
return previousTarget; | |||||
return INVALID_ENTITY; | |||||
} | |||||
// else just use the range manager (which gives us the list of possible targets); | |||||
if (this.targetUnits.indexOf(previousTarget) != -1) | |||||
return previousTarget; | |||||
return INVALID_ENTITY; | |||||
}; | |||||
Done Inline Actionslet ( + whole this sope ) Silier: let ( + whole this sope ) | |||||
/** | |||||
* Attack other entities | |||||
Done Inline Actionsthis can be inlined Silier: this can be inlined | |||||
Done Inline ActionsNot sure how. Stan: Not sure how.
| |||||
*/ | |||||
TurretAI.prototype.Attack = function() | |||||
{ | |||||
if (!this.targetUnits.length) | |||||
{ | |||||
if (this.timer) | |||||
{ | |||||
// stop the timer | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
cmpTimer.CancelTimer(this.timer); | |||||
this.timer = undefined; | |||||
// TODO Reset Rotation | |||||
} | |||||
this.SelectAnimation("idle"); | |||||
return; | |||||
} | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
var target = this.GetPreviousTarget(); | |||||
// if no target, select a random one | |||||
if (target == INVALID_ENTITY) | |||||
{ | |||||
var selectedIndex = -1; | |||||
var targets = new WeightedList(); | |||||
for (let i = 0; i < this.targetUnits.length; i++) | |||||
{ | |||||
let target = this.targetUnits[i]; | |||||
let preference = cmpAttack.GetPreference(target); | |||||
let weight = 1; | |||||
if (preference !== null && preference !== undefined) | |||||
{ | |||||
Done Inline Actionslet ( + whole this scope ) Silier: let ( + whole this scope ) | |||||
// Lower preference scores indicate a higher preference so they should result in a higher weight. | |||||
weight = 1 + this.g_MaxPreferenceBonus / (1 + preference); | |||||
} | |||||
targets.push(target, weight); | |||||
} | |||||
selectedIndex = targets.randomIndex() | |||||
target = targets.itemAt(selectedIndex); | |||||
} | |||||
let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); | |||||
warn(uneval(cmpUnitAI.order)) | |||||
Done Inline Actionsredefinition, not needed, inline this Silier: redefinition, not needed, inline this | |||||
let parentTarget = cmpUnitAI.order.data.target; | |||||
if (cmpUnitAI.order && cmpUnitAI.order.type == "Attack" && cmpUnitAI.order.data.force && parentTarget && parentTarget != INVALID_ENTITY) | |||||
{ | |||||
Done Inline Actionsif (!preference) Silier: if (!preference) | |||||
this.AttackTarget(parentTarget, cmpUnitAI.order.data.attackType) | |||||
Done Inline Actionsnuke { } Silier: nuke { } | |||||
return; | |||||
} | |||||
warn("Attack"); | |||||
// Now we hope there's a target | |||||
if (!target || target == INVALID_ENTITY) | |||||
return; | |||||
warn(this.bestAttack); | |||||
this.AttackTarget(target, this.bestAttack) | |||||
}; | |||||
TurretAI.prototype.AttackTarget = function(target, attackType) | |||||
{ | |||||
Done Inline Actionsmove this before whole picking target thing ? Silier: move this before whole picking target thing ? | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
this.TurnTowardsTarget(target); | |||||
let attackTimers = cmpAttack.GetTimers(attackType); | |||||
this.SetAnimationSync(attackTimers.prepare, attackTimers.repeat); | |||||
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 | |||||
this.SelectAnimation("attack_" + attackType.toLowerCase()); | |||||
cmpAttack.PerformAttack(attackType, target, this.entity); | |||||
this.g_LatestTarget = target; | |||||
} | |||||
/** | |||||
* Selection the animations for the turret | |||||
*/ | |||||
TurretAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) | |||||
{ | |||||
var 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(actiontime, repeattime) | |||||
{ | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
cmpVisual.SetAnimationSyncRepeat(repeattime); | |||||
cmpVisual.SetAnimationSyncOffset(actiontime); | |||||
}; | |||||
/** | |||||
* Orient the turret toward his foe. | |||||
*/ | |||||
TurretAI.prototype.TurnTowardsTarget = function(target) | |||||
{ | |||||
var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
var cmpThisPositionParent = Engine.QueryInterface(this.GetTurretParent(), IID_Position); | |||||
var cmpTargetPosition = Engine.QueryInterface(target, IID_Position); | |||||
if (!cmpThisPositionParent || !cmpThisPosition || !cmpTargetPosition || !cmpThisPositionParent.IsInWorld() || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) | |||||
return; | |||||
var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D()); | |||||
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… | |||||
cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y)); | |||||
}; | |||||
Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); | |||||
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 ? | |||||
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. |
would this work ?
if not so