Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/Attack.js
Show First 20 Lines • Show All 489 Lines • ▼ Show 20 Lines | Attack.prototype.GetRange = function(type) | ||||||||||||||
min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity); | min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity); | ||||||||||||||
let elevationBonus = +(this.template[type].ElevationBonus || 0); | let elevationBonus = +(this.template[type].ElevationBonus || 0); | ||||||||||||||
elevationBonus = ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity); | elevationBonus = ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity); | ||||||||||||||
return { "max": max, "min": min, "elevationBonus": elevationBonus }; | return { "max": max, "min": min, "elevationBonus": elevationBonus }; | ||||||||||||||
}; | }; | ||||||||||||||
/** | /** | ||||||||||||||
Stan: You should use doxygen style comments. I'm not sure this function even needs a comment as it's… | |||||||||||||||
Done Inline Actionsdoxygen = C++, JSdoc = JS. GetElevationDamageBonus would indeed remove the last bit of ambiguity making the comment unneeded. elexis: doxygen = C++, JSdoc = JS.
GetElevationDamageBonus would indeed remove the last bit of… | |||||||||||||||
* Attack the target entity. This should only be called after a successful range check, | * Attack the target entity. This should only be called after a successful range check, | ||||||||||||||
* and should only be called after GetTimers().repeat msec has passed since the last | * and should only be called after GetTimers().repeat msec has passed since the last | ||||||||||||||
* call to PerformAttack. | * call to PerformAttack. | ||||||||||||||
*/ | */ | ||||||||||||||
Done Inline Actions-> Damage component I guess elexis: -> Damage component I guess | |||||||||||||||
Attack.prototype.PerformAttack = function(type, target) | Attack.prototype.PerformAttack = function(type, target) | ||||||||||||||
{ | { | ||||||||||||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||||||||||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||||||||||||
return; | |||||||||||||||
let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); | let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); | ||||||||||||||
let data = { | let data = { | ||||||||||||||
"type": type, | "type": type, | ||||||||||||||
Done Inline ActionsNot sure we need that temp. variable. Stan: Not sure we need that temp. variable. | |||||||||||||||
Done Inline ActionsIt is needed further up in the code as well :) For the TTT calculation. Freagarach: It is needed further up in the code as well :) For the TTT calculation. | |||||||||||||||
"attackData": this.GetAttackEffectsData(type), | "attackData": this.GetAttackEffectsData(type), | ||||||||||||||
"target": target, | "target": target, | ||||||||||||||
"attacker": this.entity, | "attacker": this.entity, | ||||||||||||||
"attackerOwner": attackerOwner, | "attackerOwner": attackerOwner, | ||||||||||||||
"attackerPosition": cmpPosition.GetPosition(), | |||||||||||||||
"attackHeightOffset": this.GetRange(type).elevationBonus | |||||||||||||||
}; | }; | ||||||||||||||
// If this is a ranged attack, then launch a projectile | // If this is a ranged attack, then launch a projectile | ||||||||||||||
if (type == "Ranged") | if (type == "Ranged") | ||||||||||||||
{ | { | ||||||||||||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||||||||||||
let turnLength = cmpTimer.GetLatestTurnLength()/1000; | let turnLength = cmpTimer.GetLatestTurnLength()/1000; | ||||||||||||||
// In the future this could be extended: | // In the future this could be extended: | ||||||||||||||
// * Obstacles like trees could reduce the probability of the target being hit | // * Obstacles like trees could reduce the probability of the target being hit | ||||||||||||||
// * Obstacles like walls should block projectiles entirely | // * Obstacles like walls should block projectiles entirely | ||||||||||||||
let horizSpeed = +this.template[type].Projectile.Speed; | let horizSpeed = +this.template[type].Projectile.Speed; | ||||||||||||||
let gravity = +this.template[type].Projectile.Gravity; | let gravity = +this.template[type].Projectile.Gravity; | ||||||||||||||
// horizSpeed /= 2; gravity /= 2; // slow it down for testing | // horizSpeed /= 2; gravity /= 2; // slow it down for testing | ||||||||||||||
// We will try to estimate the position of the target, where we can hit it. | // We will try to estimate the position of the target, where we can hit it. | ||||||||||||||
// We first estimate the time-till-hit by extrapolating linearly the movement | // We first estimate the time-till-hit by extrapolating linearly the movement | ||||||||||||||
// of the last turn. We compute the time till an arrow will intersect the target. | // of the last turn. We compute the time till an arrow will intersect the target. | ||||||||||||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||||||||||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||||||||||||
return; | |||||||||||||||
let selfPosition = cmpPosition.GetPosition(); | |||||||||||||||
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); | let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); | ||||||||||||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) | if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) | ||||||||||||||
return; | return; | ||||||||||||||
let targetPosition = cmpTargetPosition.GetPosition(); | let targetPosition = cmpTargetPosition.GetPosition(); | ||||||||||||||
let targetVelocity = Vector3D.sub(targetPosition, cmpTargetPosition.GetPreviousPosition()).div(turnLength); | let targetVelocity = Vector3D.sub(targetPosition, cmpTargetPosition.GetPreviousPosition()).div(turnLength); | ||||||||||||||
let timeToTarget = PositionHelper.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity); | let timeToTarget = PositionHelper.PredictTimeToTarget(data.attackerPosition, horizSpeed, targetPosition, targetVelocity); | ||||||||||||||
// 'Cheat' and use UnitMotion to predict the position in the near-future. | // 'Cheat' and use UnitMotion to predict the position in the near-future. | ||||||||||||||
// This avoids 'dancing' issues with units zigzagging over very short distances. | // This avoids 'dancing' issues with units zigzagging over very short distances. | ||||||||||||||
// However, this could fail if the player gives several short move orders, so | // However, this could fail if the player gives several short move orders, so | ||||||||||||||
// occasionally fall back to basic interpolation. | // occasionally fall back to basic interpolation. | ||||||||||||||
let predictedPosition = targetPosition; | let predictedPosition = targetPosition; | ||||||||||||||
if (timeToTarget !== false) | if (timeToTarget !== false) | ||||||||||||||
{ | { | ||||||||||||||
Show All 16 Lines | if (timeToTarget !== false) | ||||||||||||||
else | else | ||||||||||||||
predictedPosition = Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition); | predictedPosition = Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition); | ||||||||||||||
} | } | ||||||||||||||
let predictedHeight = cmpTargetPosition.GetHeightAt(predictedPosition.x, predictedPosition.z); | let predictedHeight = cmpTargetPosition.GetHeightAt(predictedPosition.x, predictedPosition.z); | ||||||||||||||
// Add inaccuracy based on spread. | // Add inaccuracy based on spread. | ||||||||||||||
let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.Spread, this.entity) * | let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.Spread, this.entity) * | ||||||||||||||
predictedPosition.horizDistanceTo(selfPosition) / 100; | predictedPosition.horizDistanceTo(data.attackerPosition) / 100; | ||||||||||||||
let randNorm = randomNormal2D(); | let randNorm = randomNormal2D(); | ||||||||||||||
let offsetX = randNorm[0] * distanceModifiedSpread; | let offsetX = randNorm[0] * distanceModifiedSpread; | ||||||||||||||
let offsetZ = randNorm[1] * distanceModifiedSpread; | let offsetZ = randNorm[1] * distanceModifiedSpread; | ||||||||||||||
let realTargetPosition = new Vector3D(predictedPosition.x + offsetX, predictedHeight, predictedPosition.z + offsetZ); | let realTargetPosition = new Vector3D(predictedPosition.x + offsetX, predictedHeight, predictedPosition.z + offsetZ); | ||||||||||||||
// Recalculate when the missile will hit the target position. | // Recalculate when the missile will hit the target position. | ||||||||||||||
let realHorizDistance = realTargetPosition.horizDistanceTo(selfPosition); | let realHorizDistance = realTargetPosition.horizDistanceTo(data.attackerPosition); | ||||||||||||||
timeToTarget = realHorizDistance / horizSpeed; | timeToTarget = realHorizDistance / horizSpeed; | ||||||||||||||
let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance); | let missileDirection = Vector3D.sub(realTargetPosition, data.attackerPosition).div(realHorizDistance); | ||||||||||||||
// Launch the graphical projectile. | // Launch the graphical projectile. | ||||||||||||||
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); | let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); | ||||||||||||||
let actorName = ""; | let actorName = ""; | ||||||||||||||
Done Inline Actionswold be better to only pass atomic values rather than doing any kind of computation (logic) here elexis: wold be better to only pass atomic values rather than doing any kind of computation (logic) here | |||||||||||||||
let impactActorName = ""; | let impactActorName = ""; | ||||||||||||||
let impactAnimationLifetime = 0; | let impactAnimationLifetime = 0; | ||||||||||||||
actorName = this.template[type].Projectile.ActorName || ""; | actorName = this.template[type].Projectile.ActorName || ""; | ||||||||||||||
impactActorName = this.template[type].Projectile.ImpactActorName || ""; | impactActorName = this.template[type].Projectile.ImpactActorName || ""; | ||||||||||||||
impactAnimationLifetime = this.template[type].Projectile.ImpactAnimationLifetime || 0; | impactAnimationLifetime = this.template[type].Projectile.ImpactAnimationLifetime || 0; | ||||||||||||||
// TODO: Use unit rotation to implement x/z offsets. | // TODO: Use unit rotation to implement x/z offsets. | ||||||||||||||
let deltaLaunchPoint = new Vector3D(0, +this.template[type].Projectile.LaunchPoint["@y"], 0); | let deltaLaunchPoint = new Vector3D(0, +this.template[type].Projectile.LaunchPoint["@y"], 0); | ||||||||||||||
let launchPoint = Vector3D.add(selfPosition, deltaLaunchPoint); | let launchPoint = Vector3D.add(data.attackerPosition, deltaLaunchPoint); | ||||||||||||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | ||||||||||||||
if (cmpVisual) | if (cmpVisual) | ||||||||||||||
{ | { | ||||||||||||||
// if the projectile definition is missing from the template | // if the projectile definition is missing from the template | ||||||||||||||
// then fallback to the projectile name and launchpoint in the visual actor | // then fallback to the projectile name and launchpoint in the visual actor | ||||||||||||||
if (!actorName) | if (!actorName) | ||||||||||||||
actorName = cmpVisual.GetProjectileActor(); | actorName = cmpVisual.GetProjectileActor(); | ||||||||||||||
Show All 15 Lines | if (type == "Ranged") | ||||||||||||||
data.projectileId = id; | data.projectileId = id; | ||||||||||||||
data.attackImpactSound = attackImpactSound; | data.attackImpactSound = attackImpactSound; | ||||||||||||||
data.splash = this.GetSplashData(type); | data.splash = this.GetSplashData(type); | ||||||||||||||
data.friendlyFire = this.template[type].Projectile.FriendlyFire == "true"; | data.friendlyFire = this.template[type].Projectile.FriendlyFire == "true"; | ||||||||||||||
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data); | cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data); | ||||||||||||||
} | } | ||||||||||||||
else | else | ||||||||||||||
Attacking.HandleAttackEffects(target, data); | Attacking.HandleAttackEffects(target, data); | ||||||||||||||
Done Inline Actions
(or even go the whole way, and: Attacking.HandleAttackEffects(target, { "type": type, "attackData": this.GetAttackEffectsData(type), "attacker": this.entity, "attackerOwner": attackerOwner, "attackerPosition": selfPosition, "attackHeightOffset": this.GetRange(type).elevationBonus }); ) wraitii: (or even go the whole way, and:
```
Attacking.HandleAttackEffects(target, {
"type": type… | |||||||||||||||
}; | }; | ||||||||||||||
Attack.prototype.OnValueModification = function(msg) | Attack.prototype.OnValueModification = function(msg) | ||||||||||||||
{ | { | ||||||||||||||
if (msg.component != "Attack") | if (msg.component != "Attack") | ||||||||||||||
return; | return; | ||||||||||||||
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); | let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); | ||||||||||||||
Show All 27 Lines |
Wildfire Games · Phabricator
You should use doxygen style comments. I'm not sure this function even needs a comment as it's self explicit but maybe for parameters.