diff --git a/binaries/data/mods/public/simulation/components/Attack.js b/binaries/data/mods/public/simulation/components/Attack.js index 8b75917aa5..4708318e45 100644 --- a/binaries/data/mods/public/simulation/components/Attack.js +++ b/binaries/data/mods/public/simulation/components/Attack.js @@ -179,6 +179,11 @@ Attack.prototype.Schema = "" + "" + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -660,7 +665,7 @@ Attack.prototype.PerformAttack = function(type, target) "target": target, }; - let delay = +(this.template[type].EffectDelay || 0); + const delay = +(this.template[type].EffectDelay || 0); if (this.template[type].Projectile) { @@ -679,7 +684,7 @@ Attack.prototype.PerformAttack = function(type, target) // of the last turn. We compute the time till an arrow will intersect the target. const targetVelocity = Vector3D.sub(targetPosition, cmpTargetPosition.GetPreviousPosition()).div(turnLength); - let timeToTarget = PositionHelper.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity); + const timeToTarget = PositionHelper.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity); // 'Cheat' and use UnitMotion to predict the position in the near-future. // This avoids 'dancing' issues with units zigzagging over very short distances. @@ -714,18 +719,6 @@ Attack.prototype.PerformAttack = function(type, target) const distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/" + type + "/Projectile/Spread", +this.template[type].Projectile.Spread, this.entity) * predictedPosition.horizDistanceTo(selfPosition) / 100; - const randNorm = randomNormal2D(); - const offsetX = randNorm[0] * distanceModifiedSpread; - const offsetZ = randNorm[1] * distanceModifiedSpread; - - data.position = new Vector3D(predictedPosition.x + offsetX, predictedHeight, predictedPosition.z + offsetZ); - - const realHorizDistance = data.position.horizDistanceTo(selfPosition); - timeToTarget = realHorizDistance / horizSpeed; - delay += timeToTarget * 1000; - - data.direction = Vector3D.sub(data.position, selfPosition).div(realHorizDistance); - let actorName = this.template[type].Projectile.ActorName || ""; const impactActorName = this.template[type].Projectile.ImpactActorName || ""; const impactAnimationLifetime = this.template[type].Projectile.ImpactAnimationLifetime || 0; @@ -748,18 +741,53 @@ Attack.prototype.PerformAttack = function(type, target) } const cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); - data.projectileId = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, data.position, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime); - const cmpSound = Engine.QueryInterface(this.entity, IID_Sound); - data.attackImpactSound = cmpSound ? cmpSound.GetSoundGroup("attack_impact_" + type.toLowerCase()) : ""; + const attackImpactSound = cmpSound ? cmpSound.GetSoundGroup("attack_impact_" + type.toLowerCase()) : ""; + const friendlyFire = this.template[type].Projectile.FriendlyFire == "true"; + const count = Math.max(1, Math.floor( + ApplyValueModificationsToEntity( + "Attack/" + type + "/Projectile/Count", + this.template[type].Projectile.Count ? +this.template[type].Projectile.Count : 1, + this.entity + ) + )); - data.friendlyFire = this.template[type].Projectile.FriendlyFire == "true"; - } - else - { - data.position = targetPosition; - data.direction = Vector3D.sub(targetPosition, selfPosition); + for (let i = 0; i < count; ++i) + { + const randNorm = randomNormal2D(); + const offsetX = randNorm[0] * distanceModifiedSpread; + const offsetZ = randNorm[1] * distanceModifiedSpread; + + const projectileData = { + "type": type, + "attackData": this.GetAttackEffectsData(type), + "splash": this.GetSplashData(type), + "attacker": this.entity, + "attackerOwner": attackerOwner, + "target": target, + "position": new Vector3D(predictedPosition.x + offsetX, predictedHeight, predictedPosition.z + offsetZ), + "friendlyFire": friendlyFire, + "attackImpactSound": attackImpactSound + }; + + const realHorizDistance = projectileData.position.horizDistanceTo(selfPosition); + const projectileTimeToTarget = realHorizDistance / horizSpeed; + const projectileDelay = delay + projectileTimeToTarget * 1000; + + projectileData.direction = Vector3D.sub(projectileData.position, selfPosition).div(realHorizDistance); + projectileData.projectileId = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, projectileData.position, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime); + + if (projectileDelay) + cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "Hit", projectileDelay, projectileData); + else + Engine.QueryInterface(SYSTEM_ENTITY, IID_DelayedDamage).Hit(projectileData, 0); + } + return; } + + data.position = targetPosition; + data.direction = Vector3D.sub(targetPosition, selfPosition); + if (delay) { const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);