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 = 0 ; | |||||
/** | |||||
* 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) | |||||
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; | |||||
return Engine.QueryInterface(this.GetTurretParent(), IID_Attack); | |||||
}; | |||||
Silier: would this work ?
```
return cmpAttack | Engine.QueryInerface
```
if not so
```
return… | |||||
/** | |||||
* Sends the message to change ownership | |||||
*/ | |||||
TurretAI.prototype.OnOwnershipChanged = function(msg) | |||||
{ | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
//Return the first possible Attack Type, Capturing being the last. | |||||
this.bestAttack = cmpAttack.GetAttackTypes()[0]; | |||||
// Remove current targets, to prevent them from being added twice | |||||
this.targetUnits = []; | |||||
if (msg.to != -1) | |||||
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) | |||||
{ | |||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | |||||
if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) | |||||
{ | |||||
// Remove now allied/neutral units | |||||
this.targetUnits = []; | |||||
this.SetupRangeQuery(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 | |||||
var 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) | |||||
{ | |||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
return; | |||||
if (this.enemyUnitsQuery) | |||||
{ | |||||
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); | |||||
this.enemyUnitsQuery = undefined; | |||||
} | |||||
var players = []; | |||||
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player); | |||||
var numPlayers = cmpPlayerManager.GetNumPlayers(); | |||||
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. | |||||
for (let i = 1; i < numPlayers; ++i) | |||||
{ | |||||
// TODO: How to handle neutral players - Special query to attack military only? | |||||
if (cmpPlayer.IsEnemy(i)) | |||||
players.push(i); | |||||
} | |||||
var range = cmpAttack.GetRange(this.bestAttack); | |||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, 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; | |||||
} | |||||
if (owner == -1) | |||||
return; | |||||
var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); | |||||
if (!cmpPlayer.IsEnemy(0)) | |||||
return; | |||||
Done Inline Actionsmove this before let owner Silier: move this before let owner | |||||
var range = cmpAttack.GetRange(this.bestAttack); | |||||
// 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; | |||||
if (msg.tag == this.gaiaUnitsQuery) | |||||
{ | |||||
const filter = function(e) { | |||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); | |||||
return (cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal())); | |||||
}; | |||||
if (msg.added.length) | |||||
msg.added = msg.added.filter(filter); | |||||
// Removed entities may not have cmpUnitAI. | |||||
for (let i = 0; i < msg.removed.length; ++i) | |||||
if (this.targetUnits.indexOf(msg.removed[i]) == -1) | |||||
msg.removed.splice(i--, 1); | |||||
} | |||||
else if (msg.tag != this.enemyUnitsQuery) | |||||
return; | |||||
if (msg.added.length > 0) | |||||
for (let entity of msg.added) | |||||
if (cmpAttack.CanAttack(entity)) | |||||
this.targetUnits.push(entity); | |||||
if (msg.removed.length > 0) | |||||
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.timer) | |||||
return; | |||||
var attackTimers = cmpAttack.GetTimers(this.bestAttack); | |||||
this.SelectAnimation("attack_" + this.bestAttack.toLowerCase(), false, 1.0, "attack"); | |||||
this.SetAnimationSync(attackTimers.prepare, attackTimers.repeat); | |||||
// units entered the range, prepare to shoot | |||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null); | |||||
}; | |||||
/** | |||||
* Get Previous Target | |||||
Done Inline Actionsmaybe index != -1 ? Silier: maybe index != -1 ? | |||||
*/ | |||||
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.bestAttack)) | |||||
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; | |||||
}; | |||||
/** | |||||
* Attack other entities | |||||
*/ | |||||
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; | |||||
} | |||||
this.SelectAnimation("idle"); | |||||
return; | |||||
} | |||||
Done Inline Actionslet ( + whole this sope ) Silier: let ( + whole this sope ) | |||||
var cmpAttack = this.GetCmpAttack(); | |||||
if (!cmpAttack) | |||||
Done Inline Actionsthis can be inlined Silier: this can be inlined | |||||
Done Inline ActionsNot sure how. Stan: Not sure how.
| |||||
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) | |||||
{ | |||||
// 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); | |||||
} | |||||
// now we hope there's a target | |||||
if (target != INVALID_ENTITY) | |||||
{ | |||||
// TODO : It should only turn if the parent has more than one turret. | |||||
this.TurnTowardsTarget(target); | |||||
cmpAttack.PerformAttack(this.bestAttack, target, this.entity); | |||||
this.g_LatestTarget = target; | |||||
} | |||||
}; | |||||
/** | |||||
* Selection the animations for the turret | |||||
*/ | |||||
Done Inline Actionslet ( + whole this scope ) Silier: let ( + whole this scope ) | |||||
TurretAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0, sound) | |||||
{ | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
var soundgroup; | |||||
if (sound) | |||||
{ | |||||
let cmpSound = Engine.QueryInterface(this.entity, IID_Sound); | |||||
if (cmpSound) | |||||
soundgroup = cmpSound.GetSoundGroup(sound); | |||||
Done Inline Actionsredefinition, not needed, inline this Silier: redefinition, not needed, inline this | |||||
} | |||||
cmpVisual.SelectAnimation(name, once, speed, soundgroup || ""); | |||||
}; | |||||
Done Inline Actionsif (!preference) Silier: if (!preference) | |||||
Done Inline Actionsnuke { } Silier: nuke { } | |||||
/** | |||||
* Synchronise Animations | |||||
*/ | |||||
TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime) | |||||
{ | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
cmpVisual.SetAnimationSyncRepeat(repeattime); | |||||
cmpVisual.SetAnimationSyncOffset(actiontime); | |||||
}; | |||||
/** | |||||
Done Inline Actionsmove this before whole picking target thing ? Silier: move this before whole picking target thing ? | |||||
* Orient the turret toward his foe. | |||||
*/ | |||||
TurretAI.prototype.TurnTowardsTarget = function(target) | |||||
{ | |||||
var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
var cmpTargetPosition = Engine.QueryInterface(target, IID_Position); | |||||
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) | |||||
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 | |||||
return; | |||||
var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D()); | |||||
cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y)); | |||||
}; | |||||
Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); | |||||
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… | |||||
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