Changeset View
Standalone View
binaries/data/mods/public/simulation/components/TurretAI.js
//Number of rounds of firing per 2 seconds | function TurretAI() {} | ||||
const roundCount = 10; | |||||
const attackType = "Ranged"; | |||||
function BuildingAI() {} | TurretAI.prototype.Schema = | ||||
"<empty/>"; | |||||
BuildingAI.prototype.Schema = | TurretAI.prototype.MAX_PREFERENCE_BONUS = 2; | ||||
"<element name='DefaultArrowCount'>" + | |||||
"<data type='nonNegativeInteger'/>" + | |||||
"</element>" + | |||||
"<element name='GarrisonArrowMultiplier'>" + | |||||
"<ref name='nonNegativeDecimal'/>" + | |||||
"</element>" + | |||||
"<element name='GarrisonArrowClasses'>" + | |||||
"<text/>" + | |||||
"</element>"; | |||||
BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; | |||||
/** | /** | ||||
* Initialize BuildingAI Component | * Initialize TurretAI Component | ||||
*/ | */ | ||||
BuildingAI.prototype.Init = function() | TurretAI.prototype.Init = function() | ||||
{ | { | ||||
if (this.GetDefaultArrowCount() > 0 || this.GetGarrisonArrowMultiplier() > 0) | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
{ | if (!cmpAttack) | ||||
this.currentRound = 0; | return; | ||||
//Arrows left to fire | this.bestAttack = cmpAttack.GetBestAttack(); | ||||
this.arrowsLeft = 0; | |||||
this.targetUnits = []; | |||||
} | |||||
}; | }; | ||||
BuildingAI.prototype.OnOwnershipChanged = function(msg) | TurretAI.prototype.OnOwnershipChanged = function(msg) | ||||
{ | { | ||||
// Remove current targets, to prevent them from being added twice | // Remove current targets, to prevent them from being added twice | ||||
this.targetUnits = []; | this.targetUnits = []; | ||||
Context not available. | |||||
this.SetupGaiaRangeQuery(msg.to); | this.SetupGaiaRangeQuery(msg.to); | ||||
}; | }; | ||||
BuildingAI.prototype.OnDiplomacyChanged = function(msg) | TurretAI.prototype.OnDiplomacyChanged = function(msg) | ||||
{ | { | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) | if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) | ||||
Silier: would this work ?
```
return cmpAttack | Engine.QueryInerface
```
if not so
```
return… | |||||
Context not available. | |||||
/** | /** | ||||
* Cleanup on destroy | * Cleanup on destroy | ||||
*/ | */ | ||||
BuildingAI.prototype.OnDestroy = function() | TurretAI.prototype.OnDestroy = function() | ||||
{ | { | ||||
if (this.timer) | if (this.timer) | ||||
{ | { | ||||
Context not available. | |||||
/** | /** | ||||
* Setup the Range Query to detect units coming in & out of range | * Setup the Range Query to detect units coming in & out of range | ||||
*/ | */ | ||||
BuildingAI.prototype.SetupRangeQuery = function(owner) | TurretAI.prototype.SetupRangeQuery = function(owner) | ||||
{ | { | ||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); | var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); | ||||
Context not available. | |||||
players.push(i); | players.push(i); | ||||
} | } | ||||
var range = cmpAttack.GetRange(attackType); | var range = cmpAttack.GetRange(this.bestAttack); | ||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); | this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); | ||||
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); | cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); | ||||
}; | }; | ||||
Context not available. | |||||
// Set up a range query for Gaia units within LOS range which can be attacked. | // Set up a range query for Gaia units within LOS range which can be attacked. | ||||
// This should be called whenever our ownership changes. | // This should be called whenever our ownership changes. | ||||
BuildingAI.prototype.SetupGaiaRangeQuery = function() | TurretAI.prototype.SetupGaiaRangeQuery = function() | ||||
{ | { | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
var owner = cmpOwnership.GetOwner(); | var owner = cmpOwnership.GetOwner(); | ||||
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. | |||||
Context not available. | |||||
if (!cmpPlayer.IsEnemy(0)) | if (!cmpPlayer.IsEnemy(0)) | ||||
return; | return; | ||||
var range = cmpAttack.GetRange(attackType); | var range = cmpAttack.GetRange(this.bestAttack); | ||||
// This query is only interested in Gaia entities that can attack. | // 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")); | this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); | ||||
Context not available. | |||||
/** | /** | ||||
* Called when units enter or leave range | * Called when units enter or leave range | ||||
*/ | */ | ||||
BuildingAI.prototype.OnRangeUpdate = function(msg) | TurretAI.prototype.OnRangeUpdate = function(msg) | ||||
{ | { | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
Done Inline Actionsmove this before let owner Silier: move this before let owner | |||||
Context not available. | |||||
if (!this.targetUnits.length || this.timer) | if (!this.targetUnits.length || this.timer) | ||||
return; | 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 | // units entered the range, prepare to shoot | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
var attackTimers = cmpAttack.GetTimers(attackType); | this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null); | ||||
this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null); | |||||
}; | }; | ||||
BuildingAI.prototype.GetDefaultArrowCount = function() | TurretAI.prototype.Attack = function() | ||||
{ | { | ||||
var arrowCount = +this.template.DefaultArrowCount; | |||||
return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity); | |||||
}; | |||||
BuildingAI.prototype.GetGarrisonArrowMultiplier = function() | |||||
{ | |||||
var arrowMult = +this.template.GarrisonArrowMultiplier; | |||||
return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); | |||||
}; | |||||
BuildingAI.prototype.GetGarrisonArrowClasses = function() | |||||
{ | |||||
var string = this.template.GarrisonArrowClasses; | |||||
if (string) | |||||
return string.split(/\s+/); | |||||
return []; | |||||
}; | |||||
/** | |||||
* Returns the number of arrows which needs to be fired. | |||||
* DefaultArrowCount + Garrisoned Archers(ie., any unit capable | |||||
* of shooting arrows from inside buildings) | |||||
*/ | |||||
BuildingAI.prototype.GetArrowCount = function() | |||||
{ | |||||
var count = this.GetDefaultArrowCount(); | |||||
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | |||||
if (cmpGarrisonHolder) | |||||
{ | |||||
count += Math.round(cmpGarrisonHolder.GetGarrisonedArcherCount(this.GetGarrisonArrowClasses()) * this.GetGarrisonArrowMultiplier()); | |||||
} | |||||
return count; | |||||
}; | |||||
/** | |||||
* Fires arrows. Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range | |||||
*/ | |||||
BuildingAI.prototype.FireArrows = function() | |||||
{ | |||||
if (!this.targetUnits.length) | if (!this.targetUnits.length) | ||||
{ | { | ||||
if (this.timer) | if (this.timer) | ||||
Context not available. | |||||
cmpTimer.CancelTimer(this.timer); | cmpTimer.CancelTimer(this.timer); | ||||
this.timer = undefined; | this.timer = undefined; | ||||
} | } | ||||
this.SelectAnimation("idle"); | |||||
return; | return; | ||||
} | } | ||||
Context not available. | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return; | return; | ||||
while (true) | |||||
var arrowsToFire = 0; | |||||
if (this.currentRound > (roundCount - 1)) | |||||
{ | { | ||||
//Reached end of rounds. Reset count | var selectedIndex = -1; | ||||
this.currentRound = 0; | // if we had a target, stick to it | ||||
} | if (!this.target || this.targetUnits.indexOf(this.target) == -1) | ||||
if (this.currentRound == 0) | |||||
{ | |||||
//First round. Calculate arrows to fire | |||||
this.arrowsLeft = this.GetArrowCount(); | |||||
} | |||||
if (this.currentRound == (roundCount - 1)) | |||||
{ | |||||
//Last round. Need to fire all left-over arrows | |||||
arrowsToFire = this.arrowsLeft; | |||||
} | |||||
else | |||||
{ | |||||
//Fire N arrows, 0 <= N <= Number of arrows left | |||||
arrowsToFire = Math.min( | |||||
Math.round(2*Math.random() * this.GetArrowCount()/roundCount), | |||||
this.arrowsLeft | |||||
); | |||||
} | |||||
if (arrowsToFire <= 0) | |||||
{ | |||||
this.currentRound++; | |||||
return; | |||||
} | |||||
var targets = new WeightedList(); | |||||
for (var i = 0; i < this.targetUnits.length; i++) | |||||
{ | |||||
var target = this.targetUnits[i]; | |||||
var preference = cmpAttack.GetPreference(target); | |||||
var weight = 1; | |||||
if (preference !== null && preference !== undefined) | |||||
{ | { | ||||
// Lower preference scores indicate a higher preference so they | var targets = new WeightedList(); | ||||
// should result in a higher weight. | for (var i = 0; i < this.targetUnits.length; i++) | ||||
weight = 1 + this.MAX_PREFERENCE_BONUS / (1 + preference); | { | ||||
var target = this.targetUnits[i]; | |||||
Done Inline Actionsmaybe index != -1 ? Silier: maybe index != -1 ? | |||||
var preference = cmpAttack.GetPreference(target); | |||||
var 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.MAX_PREFERENCE_BONUS / (1 + preference); | |||||
} | |||||
targets.push(target, weight); | |||||
} | |||||
selectedIndex = targets.randomIndex() | |||||
this.target = targets.itemAt(selectedIndex); | |||||
} | } | ||||
targets.push(target, weight); | if (this.target && this.CheckTargetVisible(this.target)) | ||||
} | |||||
for (var i = 0;i < arrowsToFire;i++) | |||||
{ | |||||
var selectedIndex = targets.randomIndex(); | |||||
var selectedTarget = targets.itemAt(selectedIndex); | |||||
if (selectedTarget && this.CheckTargetVisible(selectedTarget)) | |||||
{ | { | ||||
cmpAttack.PerformAttack(attackType, selectedTarget); | this.TurnTowardsTarget(); | ||||
PlaySound("attack", this.entity); | cmpAttack.PerformAttack(this.bestAttack, this.target); | ||||
break; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
targets.removeAt(selectedIndex); | this.target = 0; | ||||
i--; // one extra arrow left to fire | if (selectedIndex != -1) | ||||
if(targets.length() < 1) | |||||
{ | { | ||||
this.arrowsLeft += arrowsToFire; | targets.removeAt(selectedIndex); | ||||
// no targets found in this round, save arrows and go to next round | if(targets.length() < 1) | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
this.arrowsLeft -= arrowsToFire; | |||||
this.currentRound++; | |||||
}; | }; | ||||
/** | /** | ||||
* Returns true if the target entity is visible through the FoW/SoD. | * Returns true if the target entity is visible through the FoW/SoD. | ||||
*/ | */ | ||||
BuildingAI.prototype.CheckTargetVisible = function(target) | TurretAI.prototype.CheckTargetVisible = function(target) | ||||
{ | { | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
Done Inline Actionslet ( + whole this sope ) Silier: let ( + whole this sope ) | |||||
if (!cmpOwnership) | if (!cmpOwnership) | ||||
Done Inline Actionsthis can be inlined Silier: this can be inlined | |||||
Done Inline ActionsNot sure how. Stan: Not sure how.
| |||||
Context not available. | |||||
return true; | return true; | ||||
}; | }; | ||||
Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); | TurretAI.prototype.SelectAnimation = function(name, once, speed, sound) | ||||
{ | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
var soundgroup; | |||||
if (sound) | |||||
{ | |||||
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound); | |||||
if (cmpSound) | |||||
soundgroup = cmpSound.GetSoundGroup(sound); | |||||
} | |||||
// Set default values if unspecified | |||||
if (once === undefined) | |||||
once = false; | |||||
if (speed === undefined) | |||||
speed = 1.0; | |||||
if (soundgroup === undefined) | |||||
soundgroup = ""; | |||||
cmpVisual.SelectAnimation(name, once, speed, soundgroup); | |||||
}; | |||||
TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime) | |||||
Done Inline Actionslet ( + whole this scope ) Silier: let ( + whole this scope ) | |||||
{ | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | |||||
return; | |||||
cmpVisual.SetAnimationSyncRepeat(repeattime); | |||||
cmpVisual.SetAnimationSyncOffset(actiontime); | |||||
}; | |||||
TurretAI.prototype.TurnTowardsTarget = function() | |||||
{ | |||||
var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
Done Inline Actionsredefinition, not needed, inline this Silier: redefinition, not needed, inline this | |||||
var cmpTargetPosition = Engine.QueryInterface(this.target, IID_Position); | |||||
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) | |||||
return; | |||||
Done Inline Actionsif (!preference) Silier: if (!preference) | |||||
Done Inline Actionsnuke { } Silier: nuke { } | |||||
var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D()); | |||||
cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y)); | |||||
}; | |||||
Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); | |||||
Context not available. | |||||
Done Inline Actionsmove this before whole picking target thing ? Silier: move this before whole picking target thing ? | |||||
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 | |||||
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