forked from mirrors/0ad
Fix #1496 with a revised version of my patch. Also moves damage-related functions from Attack.js to a seperate file in the global namespace.
This was SVN commit r14231.
This commit is contained in:
@@ -261,7 +261,7 @@ Attack.prototype.GetPreference = function(target)
|
||||
return undefined;
|
||||
|
||||
const targetClasses = cmpIdentity.GetClassesList();
|
||||
|
||||
|
||||
var minPref = null;
|
||||
for each (var type in this.GetAttackTypes())
|
||||
{
|
||||
@@ -300,7 +300,7 @@ Attack.prototype.GetBestAttackAgainst = function(target)
|
||||
const isAllowed = function (value, i, a) { return !attack.GetRestrictedClasses(value).some(isTargetClass); }
|
||||
const isPreferred = function (value, i, a) { return attack.GetPreferredClasses(value).some(isTargetClass); }
|
||||
const byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
|
||||
|
||||
|
||||
// Always slaughter domestic animals instead of using a normal attack
|
||||
if (isTargetClass("Domestic") && this.template.Slaughter)
|
||||
return "Slaughter";
|
||||
@@ -334,7 +334,7 @@ Attack.prototype.GetAttackStrengths = function(type)
|
||||
{
|
||||
// Work out the attack values with technology effects
|
||||
var self = this;
|
||||
|
||||
|
||||
var template = this.template[type];
|
||||
var splash = "";
|
||||
if (!template)
|
||||
@@ -376,18 +376,18 @@ Attack.prototype.GetAttackBonus = function(type, target)
|
||||
var template = this.template[type];
|
||||
if (!template)
|
||||
template = this.template[type.split(".")[0]].Splash;
|
||||
|
||||
|
||||
if (template.Bonuses)
|
||||
{
|
||||
var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
return 1;
|
||||
|
||||
|
||||
// Multiply the bonuses for all matching classes
|
||||
for (var key in template.Bonuses)
|
||||
{
|
||||
var bonus = template.Bonuses[key];
|
||||
|
||||
|
||||
var hasClasses = true;
|
||||
if (bonus.Classes){
|
||||
var classes = bonus.Classes.split(/\s+/);
|
||||
@@ -399,21 +399,21 @@ Attack.prototype.GetAttackBonus = function(type, target)
|
||||
attackBonus *= bonus.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return attackBonus;
|
||||
};
|
||||
|
||||
// Returns a 2d random distribution scaled for a spread of scale 1.
|
||||
// The current implementation is a 2d gaussian with sigma = 1
|
||||
Attack.prototype.GetNormalDistribution = function(){
|
||||
|
||||
|
||||
// Use the Box-Muller transform to get a gaussian distribution
|
||||
var a = Math.random();
|
||||
var b = Math.random();
|
||||
|
||||
|
||||
var c = Math.sqrt(-2*Math.log(a)) * Math.cos(2*Math.PI*b);
|
||||
var d = Math.sqrt(-2*Math.log(a)) * Math.sin(2*Math.PI*b);
|
||||
|
||||
|
||||
return [c, d];
|
||||
};
|
||||
|
||||
@@ -434,12 +434,12 @@ Attack.prototype.PerformAttack = function(type, target)
|
||||
// Get some data about the entity
|
||||
var horizSpeed = +this.template[type].ProjectileSpeed;
|
||||
var gravity = 9.81; // this affects the shape of the curve; assume it's constant for now
|
||||
|
||||
|
||||
var spread = +this.template.Ranged.Spread;
|
||||
spread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", spread, this.entity);
|
||||
|
||||
|
||||
//horizSpeed /= 2; gravity /= 2; // slow it down for testing
|
||||
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||
return;
|
||||
@@ -448,74 +448,57 @@ Attack.prototype.PerformAttack = function(type, target)
|
||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
|
||||
return;
|
||||
var targetPosition = cmpTargetPosition.GetPosition();
|
||||
|
||||
|
||||
var relativePosition = {"x": targetPosition.x - selfPosition.x, "z": targetPosition.z - selfPosition.z}
|
||||
var previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition();
|
||||
|
||||
|
||||
var targetVelocity = {"x": (targetPosition.x - previousTargetPosition.x) / this.turnLength, "z": (targetPosition.z - previousTargetPosition.z) / this.turnLength}
|
||||
// the component of the targets velocity radially away from the archer
|
||||
var radialSpeed = this.VectorDot(relativePosition, targetVelocity) / this.VectorLength(relativePosition);
|
||||
|
||||
|
||||
var horizDistance = this.VectorDistance(targetPosition, selfPosition);
|
||||
|
||||
|
||||
// This is an approximation of the time ot the target, it assumes that the target has a constant radial
|
||||
// velocity, but since units move in straight lines this is not true. The exact value would be more
|
||||
// difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
|
||||
// about 5% of the units radius out in the worst case)
|
||||
var timeToTarget = horizDistance / (horizSpeed - radialSpeed);
|
||||
|
||||
|
||||
// Predict where the unit is when the missile lands.
|
||||
var predictedPosition = {"x": targetPosition.x + targetVelocity.x * timeToTarget,
|
||||
"z": targetPosition.z + targetVelocity.z * timeToTarget};
|
||||
|
||||
|
||||
// Compute the real target point (based on spread and target speed)
|
||||
var randNorm = this.GetNormalDistribution();
|
||||
var offsetX = randNorm[0] * spread * (1 + this.VectorLength(targetVelocity) / 20);
|
||||
var offsetZ = randNorm[1] * spread * (1 + this.VectorLength(targetVelocity) / 20);
|
||||
|
||||
var realTargetPosition = { "x": predictedPosition.x + offsetX, "y": targetPosition.y, "z": predictedPosition.z + offsetZ };
|
||||
|
||||
|
||||
// Calculate when the missile will hit the target position
|
||||
var realHorizDistance = this.VectorDistance(realTargetPosition, selfPosition);
|
||||
var timeToTarget = realHorizDistance / horizSpeed;
|
||||
|
||||
|
||||
var missileDirection = {"x": (realTargetPosition.x - selfPosition.x) / realHorizDistance, "z": (realTargetPosition.z - selfPosition.z) / realHorizDistance};
|
||||
|
||||
|
||||
// Make the arrow appear to land slightly behind the target so that arrows landing next to a guys foot don't count but arrows that go through the torso do
|
||||
var graphicalPosition = {"x": realTargetPosition.x + 2*missileDirection.x, "y": realTargetPosition.y + 2*missileDirection.y};
|
||||
// Launch the graphical projectile
|
||||
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||
var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
|
||||
|
||||
|
||||
var playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner()
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id});
|
||||
cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id, "playerId":playerId});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Melee attack - hurt the target immediately
|
||||
this.CauseDamage({"type": type, "target": target});
|
||||
Damage.CauseDamage({"strengths":this.GetAttackStrengths(type), "target":target, "attacker":this.entity, "multiplier":this.GetAttackBonus(type, target), "type":type});
|
||||
}
|
||||
// TODO: charge attacks (need to design how they work)
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when some units kills something (another unit, building, animal etc)
|
||||
*/
|
||||
Attack.prototype.TargetKilled = function(killerEntity, targetEntity)
|
||||
{
|
||||
var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
|
||||
if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
|
||||
var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
|
||||
if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
|
||||
|
||||
// if unit can collect loot, lets try to collect it
|
||||
var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
|
||||
if (cmpLooter)
|
||||
{
|
||||
cmpLooter.Collect(targetEntity);
|
||||
}
|
||||
};
|
||||
|
||||
Attack.prototype.InterpolatedLocation = function(ent, lateness)
|
||||
{
|
||||
var cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
@@ -538,11 +521,6 @@ Attack.prototype.VectorDot = function(p1, p2)
|
||||
return (p1.x * p2.x + p1.z * p2.z);
|
||||
};
|
||||
|
||||
Attack.prototype.VectorCross = function(p1, p2)
|
||||
{
|
||||
return (p1.x * p2.z - p1.z * p2.x);
|
||||
};
|
||||
|
||||
Attack.prototype.VectorLength = function(p)
|
||||
{
|
||||
return Math.sqrt(p.x*p.x + p.z*p.z);
|
||||
@@ -558,10 +536,10 @@ Attack.prototype.testCollision = function(ent, point, lateness)
|
||||
if (!cmpFootprint)
|
||||
return false;
|
||||
var targetShape = cmpFootprint.GetShape();
|
||||
|
||||
|
||||
if (!targetShape || !targetPosition)
|
||||
return false;
|
||||
|
||||
|
||||
if (targetShape.type === 'circle')
|
||||
{
|
||||
return (this.VectorDistance(point, targetPosition) < targetShape.radius);
|
||||
@@ -569,13 +547,13 @@ Attack.prototype.testCollision = function(ent, point, lateness)
|
||||
else
|
||||
{
|
||||
var targetRotation = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
|
||||
|
||||
|
||||
var dx = point.x - targetPosition.x;
|
||||
var dz = point.z - targetPosition.z;
|
||||
|
||||
|
||||
var dxr = Math.cos(targetRotation) * dx - Math.sin(targetRotation) * dz;
|
||||
var dzr = Math.sin(targetRotation) * dx + Math.cos(targetRotation) * dz;
|
||||
|
||||
|
||||
return (-targetShape.width/2 <= dxr && dxr < targetShape.width/2 && -targetShape.depth/2 <= dzr && dzr < targetShape.depth/2);
|
||||
}
|
||||
};
|
||||
@@ -591,55 +569,25 @@ Attack.prototype.MissileHit = function(data, lateness)
|
||||
var friendlyFire = this.template.Ranged.Splash.FriendlyFire;
|
||||
var splashRadius = this.template.Ranged.Splash.Range;
|
||||
var splashShape = this.template.Ranged.Splash.Shape;
|
||||
|
||||
var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2 + splashRadius, friendlyFire);
|
||||
ents.push(data.target); // Add the original unit to the list of splash damage targets
|
||||
|
||||
for (var i = 0; i < ents.length; i++)
|
||||
var playersToDamage;
|
||||
// If friendlyFire isn't enabled, get all player enemies to pass to "Damage.CauseSplashDamage".
|
||||
if (friendlyFire == false)
|
||||
{
|
||||
var entityPosition = this.InterpolatedLocation(ents[i], lateness);
|
||||
var radius = this.VectorDistance(data.position, entityPosition);
|
||||
|
||||
if (radius < splashRadius)
|
||||
{
|
||||
var multiplier = 1;
|
||||
if (splashShape == "Circular") // quadratic falloff
|
||||
{
|
||||
multiplier *= 1 - ((radius * radius) / (splashRadius * splashRadius));
|
||||
}
|
||||
else if (splashShape == "Linear")
|
||||
{
|
||||
// position of entity relative to where the missile hit
|
||||
var relPos = {"x": entityPosition.x - data.position.x, "z": entityPosition.z - data.position.z};
|
||||
|
||||
var splashWidth = splashRadius / 5;
|
||||
var parallelDist = this.VectorDot(relPos, data.direction);
|
||||
var perpDist = Math.abs(this.VectorCross(relPos, data.direction));
|
||||
|
||||
// Check that the unit is within the distance splashWidth of the line starting at the missile's
|
||||
// landing point which extends in the direction of the missile for length splashRadius.
|
||||
if (parallelDist > -splashWidth && perpDist < splashWidth)
|
||||
{
|
||||
// Use a quadratic falloff in both directions
|
||||
multiplier = (splashRadius*splashRadius - parallelDist*parallelDist) / (splashRadius*splashRadius)
|
||||
* (splashWidth*splashWidth - perpDist*perpDist) / (splashWidth*splashWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
multiplier = 0;
|
||||
}
|
||||
}
|
||||
var newData = {"type": data.type + ".Splash", "target": ents[i], "damageMultiplier": multiplier};
|
||||
this.CauseDamage(newData);
|
||||
}
|
||||
var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
|
||||
playersToDamage = cmpPlayer.GetEnemies();
|
||||
}
|
||||
// Damage the units.
|
||||
Damage.CauseSplashDamage({"attacker":this.entity, "origin":data.position, "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":playersToDamage, "type":data.type});
|
||||
}
|
||||
|
||||
|
||||
if (this.testCollision(data.target, data.position, lateness))
|
||||
{
|
||||
data.attacker = this.entity
|
||||
data.multiplier = this.GetAttackBonus(data.type, data.target)
|
||||
data.strengths = this.GetAttackStrengths(data.type)
|
||||
// Hit the primary target
|
||||
this.CauseDamage(data);
|
||||
|
||||
Damage.CauseDamage(data);
|
||||
|
||||
// Remove the projectile
|
||||
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||
cmpProjectileManager.RemoveProjectile(data.projectileId);
|
||||
@@ -647,15 +595,15 @@ Attack.prototype.MissileHit = function(data, lateness)
|
||||
else
|
||||
{
|
||||
// If we didn't hit the main target look for nearby units
|
||||
var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2);
|
||||
|
||||
var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
|
||||
var ents = Damage.EntitiesNearPoint(data.position, this.VectorDistance(data.position, targetPosition) * 2, cmpPlayer.GetEnemies());
|
||||
|
||||
for (var i = 0; i < ents.length; i++)
|
||||
{
|
||||
if (this.testCollision(ents[i], data.position, lateness))
|
||||
{
|
||||
var newData = {"type": data.type, "target": ents[i]};
|
||||
this.CauseDamage(newData);
|
||||
|
||||
var newData = {"strengths":this.GetAttackStrengths(data.type), "target":ents[i], "attacker":this.entity, "multiplier":this.GetAttackBonus(data.type, ents[i]), "type":data.type};
|
||||
Damage.CauseDamage(newData);
|
||||
// Remove the projectile
|
||||
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||
cmpProjectileManager.RemoveProjectile(data.projectileId);
|
||||
@@ -664,53 +612,6 @@ Attack.prototype.MissileHit = function(data, lateness)
|
||||
}
|
||||
};
|
||||
|
||||
Attack.prototype.GetNearbyEntities = function(startEnt, range, friendlyFire)
|
||||
{
|
||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
var owner = cmpOwnership.GetOwner();
|
||||
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);
|
||||
var numPlayers = cmpPlayerManager.GetNumPlayers();
|
||||
var players = [];
|
||||
|
||||
for (var i = 1; i < numPlayers; ++i)
|
||||
{
|
||||
// Only target enemies unless friendly fire is on
|
||||
if (cmpPlayer.IsEnemy(i) || friendlyFire)
|
||||
players.push(i);
|
||||
}
|
||||
|
||||
var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
return rangeManager.ExecuteQuery(startEnt, 0, range, players, IID_DamageReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflict damage on the target
|
||||
*/
|
||||
Attack.prototype.CauseDamage = function(data)
|
||||
{
|
||||
var strengths = this.GetAttackStrengths(data.type);
|
||||
|
||||
var damageMultiplier = this.GetAttackBonus(data.type, data.target);
|
||||
if (data.damageMultiplier !== undefined)
|
||||
damageMultiplier *= data.damageMultiplier;
|
||||
|
||||
var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
|
||||
if (!cmpDamageReceiver)
|
||||
return;
|
||||
var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier, this.entity);
|
||||
// if target killed pick up loot and credit experience
|
||||
if (targetState.killed == true)
|
||||
{
|
||||
this.TargetKilled(this.entity, data.target);
|
||||
}
|
||||
|
||||
Engine.PostMessage(data.target, MT_Attacked,
|
||||
{ "attacker": this.entity, "target": data.target, "type": data.type, "damage": -targetState.change });
|
||||
|
||||
PlaySound("attack_impact", this.entity);
|
||||
};
|
||||
|
||||
Attack.prototype.OnUpdate = function(msg)
|
||||
{
|
||||
this.turnLength = msg.turnLength;
|
||||
|
||||
@@ -450,6 +450,18 @@ Player.prototype.SetEnemy = function(id)
|
||||
this.SetDiplomacyIndex(id, -1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all enemies of a given player.
|
||||
*/
|
||||
Player.prototype.GetEnemies = function()
|
||||
{
|
||||
var enemies = [];
|
||||
for (var i = 0; i < this.diplomacy.length; i++)
|
||||
if (this.diplomacy[i] < 0)
|
||||
enemies.push(i);
|
||||
return enemies;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if given player is our enemy
|
||||
*/
|
||||
|
||||
@@ -46,4 +46,8 @@ PlayerManager.prototype.RemoveAllPlayers = function()
|
||||
this.playerEntities = [];
|
||||
};
|
||||
|
||||
PlayerManager.prototype.GetAllPlayerEntities = function()
|
||||
{
|
||||
return this.playerEntities;
|
||||
};
|
||||
Engine.RegisterComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Create global Damage object.
|
||||
var Damage = {};
|
||||
|
||||
/**
|
||||
* Damages units around a given origin.
|
||||
* data.attacker = <entity id>
|
||||
* data.origin = {'x':<int>, 'z':<int>}
|
||||
* data.radius = <int>
|
||||
* data.shape = <string>
|
||||
* data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
|
||||
* data.type = <string>
|
||||
* ***Optional Variables***
|
||||
* data.direction = <unit vector>
|
||||
* data.playersToDamage = <array of player ids>
|
||||
*/
|
||||
Damage.CauseSplashDamage = function(data)
|
||||
{
|
||||
// Get nearby entities and define variables
|
||||
var nearEnts = Damage.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
|
||||
var damageMultiplier = 1;
|
||||
// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
|
||||
for each (var entity in nearEnts)
|
||||
{
|
||||
var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition();
|
||||
if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction
|
||||
{
|
||||
var squaredDistanceFromOrigin = Damage.VectorDistanceSquared(data.origin, entityPosition);
|
||||
damageMultiplier == 1 - squaredDistanceFromOrigin / (data.radius * data.radius);
|
||||
}
|
||||
else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
|
||||
{
|
||||
// Get position of entity relative to splash origin.
|
||||
var relativePos = {"x":entityPosition.x - data.origin.x, "z":entityPosition.z - data.origin.z};
|
||||
|
||||
// The width of linear splash is one fifth of the normal splash radius.
|
||||
var width = data.radius/5;
|
||||
|
||||
// Effectivly rotate the axis to align with the missile direction.
|
||||
var parallelDist = Damage.VectorDot(relativePos, data.direction); // z axis
|
||||
var perpDist = Math.abs(Damage.VectorCross(relativePos, data.direction)); // y axis
|
||||
|
||||
// Check that the unit is within the distance at which it will get damaged.
|
||||
if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
|
||||
damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius)
|
||||
* (width * width - perpDist * perpDist) / (width * width);
|
||||
else
|
||||
damageMultiplier = 0;
|
||||
}
|
||||
else // In case someone calls this function with an invalid shape.
|
||||
{
|
||||
warn("The " + data.shape + " splash damage shape is not implemented!");
|
||||
}
|
||||
// Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
|
||||
Damage.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"})
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Causes damage on a given unit
|
||||
* data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
|
||||
* data.target = <entity id>
|
||||
* data.attacker = <entity id>
|
||||
* data.multiplier = <float between 1 and 0>
|
||||
* data.type = <string>
|
||||
*/
|
||||
Damage.CauseDamage = function(data)
|
||||
{
|
||||
// Check the target can be damaged otherwise don't do anything.
|
||||
var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
|
||||
if (!cmpDamageReceiver)
|
||||
return;
|
||||
|
||||
// Damage the target
|
||||
var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier, data.attacker);
|
||||
|
||||
// If the target was killed run some cleanup
|
||||
if (targetState.killed)
|
||||
Damage.TargetKilled(data.attacker, data.target);
|
||||
|
||||
// Post the network command (make it work in multiplayer)
|
||||
Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change});
|
||||
|
||||
// Play attacking sounds
|
||||
PlaySound("attack_impact", data.attacker);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets entities near a give point for given players.
|
||||
* origin = {'x':<int>, 'z':<int>}
|
||||
* radius = <int>
|
||||
* players = <array>
|
||||
* If players is not included, entities from all players are used.
|
||||
*/
|
||||
Damage.EntitiesNearPoint = function(origin, radius, players)
|
||||
{
|
||||
// If there is insufficient data return an empty array.
|
||||
if (!origin || !radius)
|
||||
return [];
|
||||
// Create the dummy entity used for range calculations if it doesn't exist.
|
||||
if (!Damage.dummyTargetEntity)
|
||||
Damage.dummyTargetEntity = Engine.AddEntity('special/dummy');
|
||||
// Move the dummy entity to the origin of the query.
|
||||
var cmpDummyPosition = Engine.QueryInterface(Damage.dummyTargetEntity, IID_Position);
|
||||
if (!cmpDummyPosition)
|
||||
return [];
|
||||
cmpDummyPosition.JumpTo(origin.x, origin.z);
|
||||
|
||||
// If the players parameter is not specified use all players.
|
||||
if (!players)
|
||||
{
|
||||
var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
|
||||
players = [];
|
||||
for (var entity in playerEntities)
|
||||
players.append(Engine.QueryInterface(entity, IID_Player).GetPlayerID());
|
||||
}
|
||||
|
||||
// Call RangeManager with dummy entity and return the result.
|
||||
var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
var rangeQuery = rangeManager.ExecuteQuery(Damage.dummyTargetEntity, 0, radius, players, IID_DamageReceiver);
|
||||
return rangeQuery;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when some units kills something (another unit, building, animal etc)
|
||||
* killerEntity = <entity id>
|
||||
* targetEntity = <entity id>
|
||||
*/
|
||||
Damage.TargetKilled = function(killerEntity, targetEntity)
|
||||
{
|
||||
// Add to killer statistics.
|
||||
var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
|
||||
if (cmpKillerPlayerStatisticsTracker)
|
||||
cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
|
||||
// Add to loser statistics.
|
||||
var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
|
||||
if (cmpTargetPlayerStatisticsTracker)
|
||||
cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
|
||||
|
||||
// If killer can collect loot, let's try to collect it.
|
||||
var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
|
||||
if (cmpLooter)
|
||||
cmpLooter.Collect(targetEntity);
|
||||
};
|
||||
|
||||
// Gets the straight line distance between p1 and p2
|
||||
Damage.VectorDistanceSquared = function(p1, p2)
|
||||
{
|
||||
return (p1.x - p2.x) * (p1.x - p2.x) + (p1.z - p2.z) * (p1.z - p2.z);
|
||||
};
|
||||
|
||||
// Gets the dot product of two vectors.
|
||||
Damage.VectorDot = function(p1, p2)
|
||||
{
|
||||
return p1.x * p2.x + p1.z * p2.z;
|
||||
};
|
||||
|
||||
// Gets the 2D interpreted version of the cross product of two vectors.
|
||||
Damage.VectorCross = function(p1, p2)
|
||||
{
|
||||
return p1.x * p2.z - p1.z * p2.x;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
<Entity>
|
||||
<Position>
|
||||
<Altitude>0</Altitude>
|
||||
<Anchor>upright</Anchor>
|
||||
<Floating>false</Floating>
|
||||
<TurnRate>6.0</TurnRate>
|
||||
</Position>
|
||||
</Entity>
|
||||
Reference in New Issue
Block a user