Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/Attack.js
Show First 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | "<Ranged>" + | ||||
"<Hack>0.0</Hack>" + | "<Hack>0.0</Hack>" + | ||||
"<Pierce>10.0</Pierce>" + | "<Pierce>10.0</Pierce>" + | ||||
"<Crush>0.0</Crush>" + | "<Crush>0.0</Crush>" + | ||||
"<MaxRange>44.0</MaxRange>" + | "<MaxRange>44.0</MaxRange>" + | ||||
"<MinRange>20.0</MinRange>" + | "<MinRange>20.0</MinRange>" + | ||||
"<ElevationBonus>15.0</ElevationBonus>" + | "<ElevationBonus>15.0</ElevationBonus>" + | ||||
"<PrepareTime>800</PrepareTime>" + | "<PrepareTime>800</PrepareTime>" + | ||||
"<RepeatTime>1600</RepeatTime>" + | "<RepeatTime>1600</RepeatTime>" + | ||||
"<ProjectileSpeed>50.0</ProjectileSpeed>" + | |||||
"<Spread>2.5</Spread>" + | |||||
"<Delay>1000</Delay>" + | "<Delay>1000</Delay>" + | ||||
"<Bonuses>" + | "<Bonuses>" + | ||||
"<Bonus1>" + | "<Bonus1>" + | ||||
"<Classes>Cavalry</Classes>" + | "<Classes>Cavalry</Classes>" + | ||||
"<Multiplier>2</Multiplier>" + | "<Multiplier>2</Multiplier>" + | ||||
"</Bonus1>" + | "</Bonus1>" + | ||||
"</Bonuses>" + | "</Bonuses>" + | ||||
"<Projectile>" + | "<Projectile>" + | ||||
"<Speed>50.0</Speed>" + | |||||
"<Spread>2.5</Spread>" + | |||||
"<ActorName>props/units/weapons/rock_flaming.xml</ActorName>" + | "<ActorName>props/units/weapons/rock_flaming.xml</ActorName>" + | ||||
"<ImpactActorName>props/units/weapons/rock_explosion.xml</ImpactActorName>" + | "<ImpactActorName>props/units/weapons/rock_explosion.xml</ImpactActorName>" + | ||||
"<ImpactAnimationLifetime>0.1</ImpactAnimationLifetime>" + | "<ImpactAnimationLifetime>0.1</ImpactAnimationLifetime>" + | ||||
"</Projectile>" + | "</Projectile>" + | ||||
"<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" + | "<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" + | ||||
"<Splash>" + | "<Splash>" + | ||||
"<Shape>Circular</Shape>" + | "<Shape>Circular</Shape>" + | ||||
"<Range>20</Range>" + | "<Range>20</Range>" + | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | "<element name='Ranged'>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | "</optional>" + | ||||
"<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor's attack animation'>" + | "<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor's attack animation'>" + | ||||
"<data type='nonNegativeInteger'/>" + | "<data type='nonNegativeInteger'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='RepeatTime' a:help='Time between attacks (in milliseconds). The attack animation will be stretched to match this time'>" + | "<element name='RepeatTime' a:help='Time between attacks (in milliseconds). The attack animation will be stretched to match this time'>" + | ||||
"<data type='positiveInteger'/>" + | "<data type='positiveInteger'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='ProjectileSpeed' a:help='Speed of projectiles (in metres per second)'>" + | "<element name='Delay' a:help='Delay of the damage in milliseconds'><ref name='nonNegativeDecimal'/></element>" + | ||||
"<optional>" + | |||||
"<element name='Splash'>" + | |||||
"<interleave>" + | |||||
"<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" + | |||||
"<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" + | |||||
"<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" + | |||||
DamageTypes.BuildSchema("damage strength") + | |||||
Attack.prototype.bonusesSchema + | |||||
"</interleave>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"<element name='Projectile'>" + | |||||
"<interleave>" + | |||||
"<element name='Speed' a:help='Speed of projectiles (in meters per second).'>" + | |||||
"<ref name='positiveDecimal'/>" + | "<ref name='positiveDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='Spread' a:help='Standard deviation of the bivariate normal distribution of hits at 100 meters. A disk at 100 meters from the attacker with this radius (2x this radius, 3x this radius) is expected to include the landing points of 39.3% (86.5%, 98.9%) of the rounds.'><ref name='nonNegativeDecimal'/></element>" + | |||||
"<element name='Gravity' a:help='The gravity affecting the projectile. This affects the shape of the flight curve.'>" + | "<element name='Gravity' a:help='The gravity affecting the projectile. This affects the shape of the flight curve.'>" + | ||||
"<ref name='nonNegativeDecimal'/>" + | "<ref name='nonNegativeDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='Spread' a:help='Standard deviation of the bivariate normal distribution of hits at 100 meters. A disk at 100 meters from the attacker with this radius (2x this radius, 3x this radius) is expected to include the landing points of 39.3% (86.5%, 98.9%) of the rounds.'><ref name='nonNegativeDecimal'/></element>" + | |||||
"<element name='Delay' a:help='Delay of the damage in milliseconds'><ref name='nonNegativeDecimal'/></element>" + | |||||
Attack.prototype.bonusesSchema + | |||||
Attack.prototype.preferredClassesSchema + | |||||
Attack.prototype.restrictedClassesSchema + | |||||
"<optional>" + | "<optional>" + | ||||
"<element name='Projectile'>" + | "<element name='LaunchPoint' a:help='Delta from the unit position where to launch the projectile.'>" + | ||||
"<interleave>" + | "<attribute name='y'>" + | ||||
"<oneOrMore>" + | "<data type='decimal'/>" + | ||||
"<choice>" + | "</attribute>" + | ||||
"<element name='ActorName' a:help='actor of the projectile animation'>" + | "</element>" + | ||||
"</optional>" + | |||||
"<optional>" + | |||||
"<element name='ActorName' a:help='actor of the projectile animation.'>" + | |||||
"<text/>" + | "<text/>" + | ||||
"</element>" + | "</element>" + | ||||
"<interleave>" + | "</optional>" + | ||||
"<optional>" + | |||||
"<element name='ImpactActorName' a:help='actor of the projectile impact animation'>" + | "<element name='ImpactActorName' a:help='actor of the projectile impact animation'>" + | ||||
"<text/>" + | "<text/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='ImpactAnimationLifetime' a:help='length of the projectile impact animation'>" + | "<element name='ImpactAnimationLifetime' a:help='length of the projectile impact animation.'>" + | ||||
"<ref name='positiveDecimal'/>" + | "<ref name='positiveDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"</interleave>" + | |||||
"</choice>" + | |||||
"</oneOrMore>" + | |||||
"</interleave>" + | |||||
"</element>" + | |||||
"</optional>" + | "</optional>" + | ||||
"<optional>" + | |||||
"<element name='Splash'>" + | |||||
"<interleave>" + | |||||
"<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" + | |||||
"<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" + | |||||
"<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" + | |||||
DamageTypes.BuildSchema("damage strength") + | |||||
Attack.prototype.bonusesSchema + | |||||
"</interleave>" + | "</interleave>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | Attack.prototype.bonusesSchema + | ||||
Attack.prototype.preferredClassesSchema + | |||||
Attack.prototype.restrictedClassesSchema + | |||||
"</interleave>" + | "</interleave>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | "</optional>" + | ||||
"<optional>" + | "<optional>" + | ||||
"<element name='Capture'>" + | "<element name='Capture'>" + | ||||
"<interleave>" + | "<interleave>" + | ||||
"<element name='Value' a:help='Capture points value'><ref name='nonNegativeDecimal'/></element>" + | "<element name='Value' a:help='Capture points value'><ref name='nonNegativeDecimal'/></element>" + | ||||
"<element name='MaxRange' a:help='Maximum attack range (in meters)'><ref name='nonNegativeDecimal'/></element>" + | "<element name='MaxRange' a:help='Maximum attack range (in meters)'><ref name='nonNegativeDecimal'/></element>" + | ||||
▲ Show 20 Lines • Show All 290 Lines • ▼ Show 20 Lines | Attack.prototype.PerformAttack = function(type, target) | ||||
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].ProjectileSpeed; | let horizSpeed = +this.template[type].Projectile.Speed; | ||||
let gravity = +this.template[type].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 | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
return; | return; | ||||
let selfPosition = cmpPosition.GetPosition(); | 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 previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition(); | let previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition(); | ||||
let targetVelocity = Vector3D.sub(targetPosition, previousTargetPosition).div(turnLength); | let targetVelocity = Vector3D.sub(targetPosition, previousTargetPosition).div(turnLength); | ||||
let timeToTarget = this.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity); | let timeToTarget = this.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity); | ||||
let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition; | let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition; | ||||
// Add inaccuracy based on spread. | // Add inaccuracy based on spread. | ||||
let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template.Ranged.Spread, this.entity) * | let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.Spread, this.entity) * | ||||
predictedPosition.horizDistanceTo(selfPosition) / 100; | predictedPosition.horizDistanceTo(selfPosition) / 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, targetPosition.y, predictedPosition.z + offsetZ); | let realTargetPosition = new Vector3D(predictedPosition.x + offsetX, targetPosition.y, 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(selfPosition); | ||||
timeToTarget = realHorizDistance / horizSpeed; | timeToTarget = realHorizDistance / horizSpeed; | ||||
let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance); | let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).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 = ""; | ||||
let impactActorName = ""; | let impactActorName = ""; | ||||
let impactAnimationLifetime = 0; | let impactAnimationLifetime = 0; | ||||
if (this.template.Ranged.Projectile) | |||||
{ | |||||
actorName = this.template.Ranged.Projectile.ActorName || ""; | |||||
impactActorName = this.template.Ranged.Projectile.ImpactActorName || ""; | |||||
impactAnimationLifetime = this.template.Ranged.Projectile.ImpactAnimationLifetime || 0; | |||||
} | |||||
let launchPoint = selfPosition.clone(); | actorName = this.template[type].Projectile.ActorName || ""; | ||||
// TODO: remove this when all the ranged unit templates are updated with Projectile/Launchpoint | impactActorName = this.template[type].Projectile.ImpactActorName || ""; | ||||
launchPoint.y += 3; | impactAnimationLifetime = this.template[type].Projectile.ImpactAnimationLifetime || 0; | ||||
// TODO: Use unit rotation to implement x/z offsets. | |||||
let deltaLaunchPoint = new Vector3D(0, this.template[type].Projectile.LaunchPoint["@y"], 0.0); | |||||
let launchPoint = Vector3D.add(selfPosition, 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 18 Lines | let data = { | ||||
"position": realTargetPosition, | "position": realTargetPosition, | ||||
"direction": missileDirection, | "direction": missileDirection, | ||||
"projectileId": id, | "projectileId": id, | ||||
"bonus": this.GetBonusTemplate(type), | "bonus": this.GetBonusTemplate(type), | ||||
"isSplash": false, | "isSplash": false, | ||||
"attackerOwner": attackerOwner, | "attackerOwner": attackerOwner, | ||||
"attackImpactSound": attackImpactSound | "attackImpactSound": attackImpactSound | ||||
}; | }; | ||||
if (this.template.Ranged.Splash) | if (this.template[type].Splash) | ||||
{ | { | ||||
data.friendlyFire = this.template.Ranged.Splash.FriendlyFire != "false"; | data.friendlyFire = this.template[type].Splash.FriendlyFire != "false"; | ||||
data.radius = +this.template.Ranged.Splash.Range; | data.radius = +this.template[type].Splash.Range; | ||||
data.shape = this.template.Ranged.Splash.Shape; | data.shape = this.template[type].Splash.Shape; | ||||
data.isSplash = true; | data.isSplash = true; | ||||
data.splashStrengths = this.GetAttackStrengths(type + ".Splash"); | data.splashStrengths = this.GetAttackStrengths(type + ".Splash"); | ||||
data.splashBonus = this.GetBonusTemplate(type + ".Splash"); | data.splashBonus = this.GetBonusTemplate(type + ".Splash"); | ||||
} | } | ||||
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template.Ranged.Delay, data); | cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template[type].Delay, data); | ||||
} | } | ||||
else if (type == "Capture") | else if (type == "Capture") | ||||
{ | { | ||||
if (attackerOwner == INVALID_PLAYER) | if (attackerOwner == INVALID_PLAYER) | ||||
return; | return; | ||||
let multiplier = GetDamageBonus(target, this.GetBonusTemplate(type)); | let multiplier = GetDamageBonus(target, this.GetBonusTemplate(type)); | ||||
let cmpHealth = Engine.QueryInterface(target, IID_Health); | let cmpHealth = Engine.QueryInterface(target, IID_Health); | ||||
▲ Show 20 Lines • Show All 95 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator