mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-12 17:15:38 +00:00
Added randomized arrow positions with hit detection when the missile lands and splash damage. Fixes #18.
This was SVN commit r11886.
This commit is contained in:
@@ -70,6 +70,7 @@ Attack.prototype.Schema =
|
|||||||
"<PrepareTime>800</PrepareTime>" +
|
"<PrepareTime>800</PrepareTime>" +
|
||||||
"<RepeatTime>1600</RepeatTime>" +
|
"<RepeatTime>1600</RepeatTime>" +
|
||||||
"<ProjectileSpeed>50.0</ProjectileSpeed>" +
|
"<ProjectileSpeed>50.0</ProjectileSpeed>" +
|
||||||
|
"<Spread>2.5</Spread>" +
|
||||||
"<Bonuses>" +
|
"<Bonuses>" +
|
||||||
"<Bonus1>" +
|
"<Bonus1>" +
|
||||||
"<Classes>Cavalry</Classes>" +
|
"<Classes>Cavalry</Classes>" +
|
||||||
@@ -77,6 +78,14 @@ Attack.prototype.Schema =
|
|||||||
"</Bonus1>" +
|
"</Bonus1>" +
|
||||||
"</Bonuses>" +
|
"</Bonuses>" +
|
||||||
"<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
|
"<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
|
||||||
|
"<Splash>" +
|
||||||
|
"<Shape>Circular</Shape>" +
|
||||||
|
"<Range>20</Range>" +
|
||||||
|
"<FriendlyFire>false</FriendlyFire>" +
|
||||||
|
"<Hack>0.0</Hack>" +
|
||||||
|
"<Pierce>10.0</Pierce>" +
|
||||||
|
"<Crush>0.0</Crush>" +
|
||||||
|
"</Splash>" +
|
||||||
"</Ranged>" +
|
"</Ranged>" +
|
||||||
"<Charge>" +
|
"<Charge>" +
|
||||||
"<Hack>10.0</Hack>" +
|
"<Hack>10.0</Hack>" +
|
||||||
@@ -119,9 +128,23 @@ Attack.prototype.Schema =
|
|||||||
"<element name='ProjectileSpeed' a:help='Speed of projectiles (in metres per second). If unspecified, then it is a melee attack instead'>" +
|
"<element name='ProjectileSpeed' a:help='Speed of projectiles (in metres per second). If unspecified, then it is a melee attack instead'>" +
|
||||||
"<ref name='nonNegativeDecimal'/>" +
|
"<ref name='nonNegativeDecimal'/>" +
|
||||||
"</element>" +
|
"</element>" +
|
||||||
|
"<element name='Spread' a:help='Radius over which missiles will tend to land. Roughly 2/3 will land inside this radius (in metres)'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
bonusesSchema +
|
bonusesSchema +
|
||||||
preferredClassesSchema +
|
preferredClassesSchema +
|
||||||
restrictedClassesSchema +
|
restrictedClassesSchema +
|
||||||
|
"<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>" +
|
||||||
|
"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
|
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
|
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
|
bonusesSchema +
|
||||||
|
"</interleave>" +
|
||||||
|
"</element>" +
|
||||||
|
"</optional>" +
|
||||||
"</interleave>" +
|
"</interleave>" +
|
||||||
"</element>" +
|
"</element>" +
|
||||||
"</optional>" +
|
"</optional>" +
|
||||||
@@ -287,6 +310,14 @@ Attack.prototype.GetAttackStrengths = function(type)
|
|||||||
// Work out the attack values with technology effects
|
// Work out the attack values with technology effects
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
var template = this.template[type];
|
||||||
|
var splash = "";
|
||||||
|
if (!template)
|
||||||
|
{
|
||||||
|
template = this.template[type.split(".")[0]].Splash;
|
||||||
|
splash = "/Splash";
|
||||||
|
}
|
||||||
|
|
||||||
var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager);
|
var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager);
|
||||||
var applyTechs = function(damageType)
|
var applyTechs = function(damageType)
|
||||||
{
|
{
|
||||||
@@ -294,8 +325,8 @@ Attack.prototype.GetAttackStrengths = function(type)
|
|||||||
if (cmpTechMan)
|
if (cmpTechMan)
|
||||||
{
|
{
|
||||||
// All causes caching problems so disable it for now.
|
// All causes caching problems so disable it for now.
|
||||||
//var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + "/All", strength, self.entity) - self.template[type][damageType];
|
//var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/All", strength, self.entity) - self.template[type][damageType];
|
||||||
strength = cmpTechMan.ApplyModifications("Attack/" + type + "/" + damageType, strength, self.entity);
|
strength = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/" + damageType, strength, self.entity);
|
||||||
}
|
}
|
||||||
return strength;
|
return strength;
|
||||||
};
|
};
|
||||||
@@ -326,16 +357,20 @@ Attack.prototype.GetRange = function(type)
|
|||||||
Attack.prototype.GetAttackBonus = function(type, target)
|
Attack.prototype.GetAttackBonus = function(type, target)
|
||||||
{
|
{
|
||||||
var attackBonus = 1;
|
var attackBonus = 1;
|
||||||
if (this.template[type].Bonuses)
|
var template = this.template[type];
|
||||||
|
if (!template)
|
||||||
|
template = this.template[type.split(".")[0]].Splash;
|
||||||
|
|
||||||
|
if (template.Bonuses)
|
||||||
{
|
{
|
||||||
var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||||
if (!cmpIdentity)
|
if (!cmpIdentity)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// Multiply the bonuses for all matching classes
|
// Multiply the bonuses for all matching classes
|
||||||
for (var key in this.template[type].Bonuses)
|
for (var key in template.Bonuses)
|
||||||
{
|
{
|
||||||
var bonus = this.template[type].Bonuses[key];
|
var bonus = template.Bonuses[key];
|
||||||
|
|
||||||
var hasClasses = true;
|
var hasClasses = true;
|
||||||
if (bonus.Classes){
|
if (bonus.Classes){
|
||||||
@@ -352,6 +387,20 @@ Attack.prototype.GetAttackBonus = function(type, target)
|
|||||||
return attackBonus;
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -362,32 +411,18 @@ Attack.prototype.PerformAttack = function(type, target)
|
|||||||
// 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")
|
||||||
{
|
{
|
||||||
// To implement (in)accuracy, for arrows and javelins, we want to do the following:
|
// In the future this could be extended:
|
||||||
// * Compute an accuracy rating, based on the entity's characteristics and the distance to the target
|
|
||||||
// * Pick a random point 'close' to the target (based on the accuracy) which is the real target point
|
|
||||||
// * Pick a real target unit, based on their footprint's proximity to the real target point
|
|
||||||
// * If there is none, then harmlessly shoot to the real target point instead
|
|
||||||
// * If the real target unit moves after being targeted, the projectile will follow it and hit it anyway
|
|
||||||
//
|
|
||||||
// In the future this should be extended:
|
|
||||||
// * If the target unit moves too far, the projectile should 'detach' and not hit it, so that
|
|
||||||
// players can dodge projectiles. (Or it should pick a new target after detaching, so it can still
|
|
||||||
// hit somebody.)
|
|
||||||
// * 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
|
||||||
// * There should be more control over the probabilities of hitting enemy units vs friendly units vs missing,
|
|
||||||
// for gameplay balance tweaks
|
|
||||||
// * Larger, slower projectiles (catapults etc) shouldn't pick targets first, they should just
|
|
||||||
// hurt anybody near their landing point
|
|
||||||
|
|
||||||
// Get some data about the entity
|
// Get some data about the entity
|
||||||
var horizSpeed = +this.template[type].ProjectileSpeed;
|
var horizSpeed = +this.template[type].ProjectileSpeed;
|
||||||
var gravity = 9.81; // this affects the shape of the curve; assume it's constant for now
|
var gravity = 9.81; // this affects the shape of the curve; assume it's constant for now
|
||||||
var accuracy = 6; // TODO: get from entity template
|
|
||||||
|
var spread = this.template.Ranged.Spread;
|
||||||
//horizSpeed /= 8; gravity /= 8; // slow it down for testing
|
|
||||||
|
//horizSpeed /= 2; gravity /= 2; // slow it down for testing
|
||||||
// Find the distance to the target
|
|
||||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||||
return;
|
return;
|
||||||
@@ -396,48 +431,47 @@ Attack.prototype.PerformAttack = function(type, target)
|
|||||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
|
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
|
||||||
return;
|
return;
|
||||||
var targetPosition = cmpTargetPosition.GetPosition();
|
var targetPosition = cmpTargetPosition.GetPosition();
|
||||||
var horizDistance = Math.sqrt(Math.pow(targetPosition.x - selfPosition.x, 2) + Math.pow(targetPosition.z - selfPosition.z, 2));
|
|
||||||
|
var relativePosition = {"x": targetPosition.x - selfPosition.x, "z": targetPosition.z - selfPosition.z}
|
||||||
// Compute the real target point (based on accuracy)
|
var previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition();
|
||||||
var angle = Math.random() * 2*Math.PI;
|
|
||||||
var r = 1 - Math.sqrt(Math.random()); // triangular distribution [0,1] (cluster around the center)
|
var targetVelocity = {"x": (targetPosition.x - previousTargetPosition.x) / this.turnLength, "z": (targetPosition.z - previousTargetPosition.z) / this.turnLength}
|
||||||
var offset = r * accuracy; // TODO: should be affected by range
|
// the component of the targets velocity radially away from the archer
|
||||||
var offsetX = offset * Math.sin(angle);
|
var radialSpeed = this.VectorDot(relativePosition, targetVelocity) / this.VectorLength(relativePosition);
|
||||||
var offsetZ = offset * Math.cos(angle);
|
|
||||||
|
var horizDistance = this.VectorDistance(targetPosition, selfPosition);
|
||||||
var realTargetPosition = { "x": targetPosition.x + offsetX, "y": targetPosition.y, "z": targetPosition.z + offsetZ };
|
|
||||||
|
// This is an approximation of the time ot the target, it assumes that the target has a constant radial
|
||||||
// TODO: what we should really do here is select the unit whose footprint is closest to the realTargetPosition
|
// velocity, but since units move in straight lines this is not true. The exact value would be more
|
||||||
// (and harmlessly hit the ground if there's none), but as a simplification let's just randomly decide whether to
|
// difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was
|
||||||
// hit the original target or not.
|
// about 5% of the units radius out in the worst case)
|
||||||
var realTargetUnit = undefined;
|
var timeToTarget = horizDistance / (horizSpeed - radialSpeed);
|
||||||
if (Math.random() < 0.5) // TODO: this is yucky and hardcoded
|
|
||||||
{
|
// Predict where the unit is when the missile lands.
|
||||||
// Hit the original target
|
var predictedPosition = {"x": targetPosition.x + targetVelocity.x * timeToTarget,
|
||||||
realTargetUnit = target;
|
"z": targetPosition.z + targetVelocity.z * timeToTarget};
|
||||||
realTargetPosition = targetPosition;
|
|
||||||
}
|
// Compute the real target point (based on spread and target speed)
|
||||||
else
|
var randNorm = this.GetNormalDistribution();
|
||||||
{
|
var offsetX = randNorm[0] * spread * (1 + this.VectorLength(targetVelocity) / 20);
|
||||||
// Hit the ground
|
var offsetZ = randNorm[1] * spread * (1 + this.VectorLength(targetVelocity) / 20);
|
||||||
// TODO: ought to make sure Y is on the ground
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hurt the target after the appropriate time
|
|
||||||
if (realTargetUnit)
|
|
||||||
{
|
|
||||||
var realHorizDistance = Math.sqrt(Math.pow(realTargetPosition.x - selfPosition.x, 2) + Math.pow(realTargetPosition.z - selfPosition.z, 2));
|
|
||||||
var timeToTarget = realHorizDistance / horizSpeed;
|
|
||||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
|
||||||
cmpTimer.SetTimeout(this.entity, IID_Attack, "CauseDamage", timeToTarget*1000, {"type": type, "target": target});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
// Launch the graphical projectile
|
||||||
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||||
if (realTargetUnit)
|
var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
|
||||||
cmpProjectileManager.LaunchProjectileAtEntity(this.entity, realTargetUnit, horizSpeed, gravity);
|
|
||||||
else
|
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||||
cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
|
cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -465,6 +499,170 @@ Attack.prototype.TargetKilled = function(killerEntity, targetEntity)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Attack.prototype.InterpolatedLocation = function(ent, lateness)
|
||||||
|
{
|
||||||
|
var targetPositionCmp = Engine.QueryInterface(ent, IID_Position);
|
||||||
|
if (!targetPositionCmp) // TODO: handle dead target properly
|
||||||
|
return undefined;
|
||||||
|
var curPos = targetPositionCmp.GetPosition();
|
||||||
|
var prevPos = targetPositionCmp.GetPreviousPosition();
|
||||||
|
lateness /= 1000;
|
||||||
|
return {"x": (curPos.x * (this.turnLength - lateness) + prevPos.x * lateness) / this.turnLength,
|
||||||
|
"z": (curPos.z * (this.turnLength - lateness) + prevPos.z * lateness) / this.turnLength};
|
||||||
|
};
|
||||||
|
|
||||||
|
Attack.prototype.VectorDistance = function(p1, p2)
|
||||||
|
{
|
||||||
|
return Math.sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.z - p2.z)*(p1.z - p2.z));
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests whether it point is inside of ent's footprint
|
||||||
|
Attack.prototype.testCollision = function(ent, point, lateness)
|
||||||
|
{
|
||||||
|
var targetPosition = this.InterpolatedLocation(ent, lateness);
|
||||||
|
var targetShape = Engine.QueryInterface(ent, IID_Footprint).GetShape();
|
||||||
|
|
||||||
|
if (!targetShape || !targetPosition)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (targetShape.type === 'circle')
|
||||||
|
{
|
||||||
|
return (this.VectorDistance(point, targetPosition) < targetShape.radius);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Attack.prototype.MissileHit = function(data, lateness)
|
||||||
|
{
|
||||||
|
|
||||||
|
var targetPosition = this.InterpolatedLocation(data.target, lateness);
|
||||||
|
if (!targetPosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.template.Ranged.Splash) // splash damage, do this first in case the direct hit kills the target
|
||||||
|
{
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.testCollision(data.target, data.position, lateness))
|
||||||
|
{
|
||||||
|
// Hit the primary target
|
||||||
|
this.CauseDamage(data);
|
||||||
|
|
||||||
|
// Remove the projectile
|
||||||
|
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||||
|
cmpProjectileManager.RemoveProjectile(data.projectileId);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Remove the projectile
|
||||||
|
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
|
||||||
|
cmpProjectileManager.RemoveProjectile(data.projectileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
* Inflict damage on the target
|
||||||
*/
|
*/
|
||||||
@@ -472,12 +670,14 @@ Attack.prototype.CauseDamage = function(data)
|
|||||||
{
|
{
|
||||||
var strengths = this.GetAttackStrengths(data.type);
|
var strengths = this.GetAttackStrengths(data.type);
|
||||||
|
|
||||||
var attackBonus = this.GetAttackBonus(data.type, data.target);
|
var damageMultiplier = this.GetAttackBonus(data.type, data.target);
|
||||||
|
if (data.damageMultiplier !== undefined)
|
||||||
|
damageMultiplier *= data.damageMultiplier;
|
||||||
|
|
||||||
var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
|
var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
|
||||||
if (!cmpDamageReceiver)
|
if (!cmpDamageReceiver)
|
||||||
return;
|
return;
|
||||||
var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * attackBonus, strengths.pierce * attackBonus, strengths.crush * attackBonus);
|
var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier);
|
||||||
// if target killed pick up loot and credit experience
|
// if target killed pick up loot and credit experience
|
||||||
if (targetState.killed == true)
|
if (targetState.killed == true)
|
||||||
{
|
{
|
||||||
@@ -490,4 +690,9 @@ Attack.prototype.CauseDamage = function(data)
|
|||||||
PlaySound("attack_impact", this.entity);
|
PlaySound("attack_impact", this.entity);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Attack.prototype.OnUpdate = function(msg)
|
||||||
|
{
|
||||||
|
this.turnLength = msg.turnLength;
|
||||||
|
}
|
||||||
|
|
||||||
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
|
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||||
<PrepareTime>0</PrepareTime>
|
<PrepareTime>0</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Identity>
|
<Identity>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
-2
@@ -9,8 +9,7 @@
|
|||||||
<Classes datatype="tokens">Town Wall</Classes>
|
<Classes datatype="tokens">Town Wall</Classes>
|
||||||
<GenericName>City Wall</GenericName>
|
<GenericName>City Wall</GenericName>
|
||||||
<Tooltip>Wall off your town for a stout defense.</Tooltip>
|
<Tooltip>Wall off your town for a stout defense.</Tooltip>
|
||||||
<Icon>structures/wall.png</Icon>
|
<RequiredTechnology>phase_town</RequiredTechnology>
|
||||||
<RequiredTechnology>phase_town</RequiredTechnology>
|
|
||||||
</Identity>
|
</Identity>
|
||||||
<WallSet>
|
<WallSet>
|
||||||
<MaxTowerOverlap>0.85</MaxTowerOverlap>
|
<MaxTowerOverlap>0.85</MaxTowerOverlap>
|
||||||
|
|||||||
+3
-5
@@ -38,11 +38,6 @@
|
|||||||
<Obstruction>
|
<Obstruction>
|
||||||
<Static width="30.0" depth="26.0"/>
|
<Static width="30.0" depth="26.0"/>
|
||||||
</Obstruction>
|
</Obstruction>
|
||||||
<ProductionQueue>
|
|
||||||
<Technologies datatype="tokens">
|
|
||||||
armor_trade_convoys
|
|
||||||
</Technologies>
|
|
||||||
</ProductionQueue>
|
|
||||||
<Sound>
|
<Sound>
|
||||||
<SoundGroups>
|
<SoundGroups>
|
||||||
<select>interface/select/building/sel_market.xml</select>
|
<select>interface/select/building/sel_market.xml</select>
|
||||||
@@ -56,6 +51,9 @@
|
|||||||
<Weight>65536</Weight>
|
<Weight>65536</Weight>
|
||||||
</TerritoryInfluence>
|
</TerritoryInfluence>
|
||||||
<ProductionQueue>
|
<ProductionQueue>
|
||||||
|
<Technologies datatype="tokens">
|
||||||
|
armor_trade_convoys
|
||||||
|
</Technologies>
|
||||||
<Entities datatype="tokens">
|
<Entities datatype="tokens">
|
||||||
units/{civ}_support_trader
|
units/{civ}_support_trader
|
||||||
</Entities>
|
</Entities>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<RepeatTime>1500</RepeatTime>
|
<RepeatTime>1500</RepeatTime>
|
||||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Identity>
|
<Identity>
|
||||||
|
|||||||
+11
-10
@@ -15,16 +15,17 @@
|
|||||||
<ProjectileSpeed>50.0</ProjectileSpeed>
|
<ProjectileSpeed>50.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
<Bonuses>
|
<Spread>1.5</Spread>
|
||||||
<BonusPrimary>
|
<Bonuses>
|
||||||
<Classes>Infantry Bow</Classes>
|
<BonusPrimary>
|
||||||
<Multiplier>2.0</Multiplier>
|
<Classes>Infantry Bow</Classes>
|
||||||
</BonusPrimary>
|
<Multiplier>2.0</Multiplier>
|
||||||
<BonusSecondary>
|
</BonusPrimary>
|
||||||
<Classes>Cavalry Sword</Classes>
|
<BonusSecondary>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Cavalry Sword</Classes>
|
||||||
</BonusSecondary>
|
<Multiplier>1.5</Multiplier>
|
||||||
</Bonuses>
|
</BonusSecondary>
|
||||||
|
</Bonuses>
|
||||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
|
|||||||
+11
-10
@@ -15,16 +15,17 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
<Bonuses>
|
<Spread>1.5</Spread>
|
||||||
<BonusPrimary>
|
<Bonuses>
|
||||||
<Classes>Infantry Sword</Classes>
|
<BonusPrimary>
|
||||||
<Multiplier>2.0</Multiplier>
|
<Classes>Infantry Sword</Classes>
|
||||||
</BonusPrimary>
|
<Multiplier>2.0</Multiplier>
|
||||||
<BonusSecondary>
|
</BonusPrimary>
|
||||||
<Classes>Cavalry Spear</Classes>
|
<BonusSecondary>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Cavalry Spear</Classes>
|
||||||
</BonusSecondary>
|
<Multiplier>1.5</Multiplier>
|
||||||
</Bonuses>
|
</BonusSecondary>
|
||||||
|
</Bonuses>
|
||||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
|
|||||||
+19
-18
@@ -15,24 +15,25 @@
|
|||||||
<ProjectileSpeed>50.0</ProjectileSpeed>
|
<ProjectileSpeed>50.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
<Bonuses>
|
<Spread>1.5</Spread>
|
||||||
<BonusPrimary>
|
<Bonuses>
|
||||||
<Classes>Infantry Spear</Classes>
|
<BonusPrimary>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Infantry Spear</Classes>
|
||||||
</BonusPrimary>
|
<Multiplier>1.5</Multiplier>
|
||||||
<BonusSecondary>
|
</BonusPrimary>
|
||||||
<Classes>Cavalry Bow</Classes>
|
<BonusSecondary>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Cavalry Bow</Classes>
|
||||||
</BonusSecondary>
|
<Multiplier>1.5</Multiplier>
|
||||||
<BonusElephants>
|
</BonusSecondary>
|
||||||
<Classes>Elephant</Classes>
|
<BonusElephants>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Elephant</Classes>
|
||||||
</BonusElephants>
|
<Multiplier>1.5</Multiplier>
|
||||||
<BonusChariots>
|
</BonusElephants>
|
||||||
<Classes>Chariot</Classes>
|
<BonusChariots>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Chariot</Classes>
|
||||||
</BonusChariots>
|
<Multiplier>1.5</Multiplier>
|
||||||
</Bonuses>
|
</BonusChariots>
|
||||||
|
</Bonuses>
|
||||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
|
|||||||
+11
-10
@@ -15,16 +15,17 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
<Bonuses>
|
<Spread>1.5</Spread>
|
||||||
<BonusPrimary>
|
<Bonuses>
|
||||||
<Classes>Infantry Spear</Classes>
|
<BonusPrimary>
|
||||||
<Multiplier>2.0</Multiplier>
|
<Classes>Infantry Spear</Classes>
|
||||||
</BonusPrimary>
|
<Multiplier>2.0</Multiplier>
|
||||||
<BonusSecondary>
|
</BonusPrimary>
|
||||||
<Classes>Infantry Sword</Classes>
|
<BonusSecondary>
|
||||||
<Multiplier>1.5</Multiplier>
|
<Classes>Infantry Sword</Classes>
|
||||||
</BonusSecondary>
|
<Multiplier>1.5</Multiplier>
|
||||||
</Bonuses>
|
</BonusSecondary>
|
||||||
|
</Bonuses>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Cost>
|
<Cost>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<ProjectileSpeed>25.0</ProjectileSpeed>
|
<ProjectileSpeed>25.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Cost>
|
<Cost>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<ProjectileSpeed>28.0</ProjectileSpeed>
|
<ProjectileSpeed>28.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||||
<PrepareTime>2000</PrepareTime>
|
<PrepareTime>2000</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>40.0</ProjectileSpeed>
|
<ProjectileSpeed>40.0</ProjectileSpeed>
|
||||||
<PrepareTime>2000</PrepareTime>
|
<PrepareTime>2000</PrepareTime>
|
||||||
<RepeatTime>4000</RepeatTime>
|
<RepeatTime>4000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Cost>
|
<Cost>
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@
|
|||||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||||
<PrepareTime>1000</PrepareTime>
|
<PrepareTime>1000</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+17
-8
@@ -7,19 +7,28 @@
|
|||||||
<Attack>
|
<Attack>
|
||||||
<Ranged>
|
<Ranged>
|
||||||
<Hack>0.0</Hack>
|
<Hack>0.0</Hack>
|
||||||
<Pierce>50.0</Pierce>
|
<Pierce>20.0</Pierce>
|
||||||
<Crush>50.0</Crush>
|
<Crush>20.0</Crush>
|
||||||
<MaxRange>60</MaxRange>
|
<MaxRange>60</MaxRange>
|
||||||
<MinRange>8.0</MinRange>
|
<MinRange>8.0</MinRange>
|
||||||
|
<Spread>6.0</Spread>
|
||||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||||
<PrepareTime>5000</PrepareTime>
|
<PrepareTime>5000</PrepareTime>
|
||||||
<RepeatTime>5000</RepeatTime>
|
<RepeatTime>5000</RepeatTime>
|
||||||
<Bonuses>
|
<Bonuses>
|
||||||
<BonusOrganic>
|
<BonusOrganic>
|
||||||
<Classes>Organic</Classes>
|
<Classes>Organic</Classes>
|
||||||
<Multiplier>2.0</Multiplier>
|
<Multiplier>2.0</Multiplier>
|
||||||
</BonusOrganic>
|
</BonusOrganic>
|
||||||
</Bonuses>
|
</Bonuses>
|
||||||
|
<Splash>
|
||||||
|
<Shape>Linear</Shape>
|
||||||
|
<Range>12</Range>
|
||||||
|
<FriendlyFire>false</FriendlyFire>
|
||||||
|
<Hack>0.0</Hack>
|
||||||
|
<Pierce>30.0</Pierce>
|
||||||
|
<Crush>30.0</Crush>
|
||||||
|
</Splash>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Cost>
|
<Cost>
|
||||||
|
|||||||
+11
-2
@@ -6,20 +6,29 @@
|
|||||||
</Armour>
|
</Armour>
|
||||||
<Attack>
|
<Attack>
|
||||||
<Ranged>
|
<Ranged>
|
||||||
<Hack>50.0</Hack>
|
<Hack>40.0</Hack>
|
||||||
<Pierce>0.0</Pierce>
|
<Pierce>0.0</Pierce>
|
||||||
<Crush>50.0</Crush>
|
<Crush>40.0</Crush>
|
||||||
<MaxRange>68</MaxRange>
|
<MaxRange>68</MaxRange>
|
||||||
<MinRange>12.0</MinRange>
|
<MinRange>12.0</MinRange>
|
||||||
<ProjectileSpeed>30.0</ProjectileSpeed>
|
<ProjectileSpeed>30.0</ProjectileSpeed>
|
||||||
<PrepareTime>5000</PrepareTime>
|
<PrepareTime>5000</PrepareTime>
|
||||||
<RepeatTime>5000</RepeatTime>
|
<RepeatTime>5000</RepeatTime>
|
||||||
|
<Spread>6.0</Spread>
|
||||||
<Bonuses>
|
<Bonuses>
|
||||||
<BonusStruct>
|
<BonusStruct>
|
||||||
<Classes>Structure</Classes>
|
<Classes>Structure</Classes>
|
||||||
<Multiplier>2.0</Multiplier>
|
<Multiplier>2.0</Multiplier>
|
||||||
</BonusStruct>
|
</BonusStruct>
|
||||||
</Bonuses>
|
</Bonuses>
|
||||||
|
<Splash>
|
||||||
|
<Shape>Circular</Shape>
|
||||||
|
<Range>12</Range>
|
||||||
|
<FriendlyFire>true</FriendlyFire>
|
||||||
|
<Hack>12.0</Hack>
|
||||||
|
<Pierce>0.0</Pierce>
|
||||||
|
<Crush>12.0</Crush>
|
||||||
|
</Splash>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Cost>
|
<Cost>
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@
|
|||||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||||
<PrepareTime>1200</PrepareTime>
|
<PrepareTime>1200</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<BuildingAI>
|
<BuildingAI>
|
||||||
|
|||||||
+1
@@ -10,6 +10,7 @@
|
|||||||
<ProjectileSpeed>10.0</ProjectileSpeed>
|
<ProjectileSpeed>10.0</ProjectileSpeed>
|
||||||
<PrepareTime>2000</PrepareTime>
|
<PrepareTime>2000</PrepareTime>
|
||||||
<RepeatTime>2000</RepeatTime>
|
<RepeatTime>2000</RepeatTime>
|
||||||
|
<Spread>1.5</Spread>
|
||||||
</Ranged>
|
</Ranged>
|
||||||
</Attack>
|
</Attack>
|
||||||
<Footprint replace="">
|
<Footprint replace="">
|
||||||
|
|||||||
@@ -944,9 +944,18 @@ bool CCmpObstructionManager::FindMostImportantObstruction(const IObstructionTest
|
|||||||
|
|
||||||
// Then look for obstructions that cover the target point when expanded by r
|
// Then look for obstructions that cover the target point when expanded by r
|
||||||
// (i.e. if the target is not inside an object but closer than we can get to it)
|
// (i.e. if the target is not inside an object but closer than we can get to it)
|
||||||
|
|
||||||
// TODO: actually do that
|
GetObstructionsInRange(filter, x-r, z-r, x+r, z+r, squares);
|
||||||
// (This might matter when you tell a unit to walk too close to the edge of a building)
|
// Building squares are more important but returned last, so check backwards
|
||||||
|
for (std::vector<ObstructionSquare>::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it)
|
||||||
|
{
|
||||||
|
CFixedVector2D halfSize(it->hw + r, it->hh + r);
|
||||||
|
if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize))
|
||||||
|
{
|
||||||
|
square = *it;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ public:
|
|||||||
// Dynamic state:
|
// Dynamic state:
|
||||||
|
|
||||||
bool m_InWorld;
|
bool m_InWorld;
|
||||||
entity_pos_t m_X, m_Z, m_LastX, m_LastZ; // these values contain undefined junk if !InWorld
|
// m_LastX/Z contain the position from the start of the most recent turn
|
||||||
|
// m_PrevX/Z conatain the position from the turn before that
|
||||||
|
entity_pos_t m_X, m_Z, m_LastX, m_LastZ, m_PrevX, m_PrevZ; // these values contain undefined junk if !InWorld
|
||||||
entity_pos_t m_YOffset;
|
entity_pos_t m_YOffset;
|
||||||
bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height
|
bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height
|
||||||
|
|
||||||
@@ -201,8 +203,8 @@ public:
|
|||||||
if (!m_InWorld)
|
if (!m_InWorld)
|
||||||
{
|
{
|
||||||
m_InWorld = true;
|
m_InWorld = true;
|
||||||
m_LastX = m_X;
|
m_LastX = m_PrevX = m_X;
|
||||||
m_LastZ = m_Z;
|
m_LastZ = m_PrevZ = m_Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
AdvertisePositionChanges();
|
AdvertisePositionChanges();
|
||||||
@@ -210,8 +212,8 @@ public:
|
|||||||
|
|
||||||
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
|
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
|
||||||
{
|
{
|
||||||
m_LastX = m_X = x;
|
m_LastX = m_PrevX = m_X = x;
|
||||||
m_LastZ = m_Z = z;
|
m_LastZ = m_PrevZ = m_Z = z;
|
||||||
m_InWorld = true;
|
m_InWorld = true;
|
||||||
|
|
||||||
AdvertisePositionChanges();
|
AdvertisePositionChanges();
|
||||||
@@ -278,6 +280,43 @@ public:
|
|||||||
return CFixedVector2D(m_X, m_Z);
|
return CFixedVector2D(m_X, m_Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual CFixedVector3D GetPreviousPosition()
|
||||||
|
{
|
||||||
|
if (!m_InWorld)
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpPosition::GetPreviousPosition called on entity when IsInWorld is false");
|
||||||
|
return CFixedVector3D();
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_pos_t baseY;
|
||||||
|
if (m_RelativeToGround)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
|
||||||
|
if (cmpTerrain)
|
||||||
|
baseY = cmpTerrain->GetGroundLevel(m_PrevX, m_PrevZ);
|
||||||
|
|
||||||
|
if (m_Floating)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
|
||||||
|
if (cmpWaterMan)
|
||||||
|
baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_PrevX, m_PrevZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CFixedVector3D(m_PrevX, baseY + m_YOffset, m_PrevZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CFixedVector2D GetPreviousPosition2D()
|
||||||
|
{
|
||||||
|
if (!m_InWorld)
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpPosition::GetPreviousPosition2D called on entity when IsInWorld is false");
|
||||||
|
return CFixedVector2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CFixedVector2D(m_PrevX, m_PrevZ);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void TurnTo(entity_angle_t y)
|
virtual void TurnTo(entity_angle_t y)
|
||||||
{
|
{
|
||||||
m_RotY = y;
|
m_RotY = y;
|
||||||
@@ -408,6 +447,10 @@ public:
|
|||||||
}
|
}
|
||||||
case MT_TurnStart:
|
case MT_TurnStart:
|
||||||
{
|
{
|
||||||
|
// Store the positions from the turn before
|
||||||
|
m_PrevX = m_LastX;
|
||||||
|
m_PrevZ = m_LastZ;
|
||||||
|
|
||||||
m_LastX = m_X;
|
m_LastX = m_X;
|
||||||
m_LastZ = m_Z;
|
m_LastZ = m_Z;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
#include "simulation2/system/Component.h"
|
#include "simulation2/system/Component.h"
|
||||||
#include "ICmpProjectileManager.h"
|
#include "ICmpProjectileManager.h"
|
||||||
|
|
||||||
|
#include "ICmpObstruction.h"
|
||||||
|
#include "ICmpObstructionManager.h"
|
||||||
#include "ICmpPosition.h"
|
#include "ICmpPosition.h"
|
||||||
#include "ICmpRangeManager.h"
|
#include "ICmpRangeManager.h"
|
||||||
#include "ICmpTerrain.h"
|
#include "ICmpTerrain.h"
|
||||||
@@ -58,6 +60,7 @@ public:
|
|||||||
virtual void Init(const CParamNode& UNUSED(paramNode))
|
virtual void Init(const CParamNode& UNUSED(paramNode))
|
||||||
{
|
{
|
||||||
m_ActorSeed = 0;
|
m_ActorSeed = 0;
|
||||||
|
m_Id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Deinit()
|
virtual void Deinit()
|
||||||
@@ -86,7 +89,7 @@ public:
|
|||||||
case MT_Interpolate:
|
case MT_Interpolate:
|
||||||
{
|
{
|
||||||
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
|
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
|
||||||
Interpolate(msgData.frameTime, msgData.offset);
|
Interpolate(msgData.frameTime);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MT_RenderSubmit:
|
case MT_RenderSubmit:
|
||||||
@@ -98,15 +101,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity)
|
virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
|
||||||
{
|
{
|
||||||
LaunchProjectile(source, CFixedVector3D(), target, speed, gravity);
|
return LaunchProjectile(source, target, speed, gravity);
|
||||||
}
|
|
||||||
|
|
||||||
virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
|
|
||||||
{
|
|
||||||
LaunchProjectile(source, target, INVALID_ENTITY, speed, gravity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void RemoveProjectile(uint32_t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Projectile
|
struct Projectile
|
||||||
@@ -114,36 +114,38 @@ private:
|
|||||||
CUnit* unit;
|
CUnit* unit;
|
||||||
CVector3D pos;
|
CVector3D pos;
|
||||||
CVector3D target;
|
CVector3D target;
|
||||||
entity_id_t targetEnt; // INVALID_ENTITY if the target is just a point
|
|
||||||
float timeLeft;
|
float timeLeft;
|
||||||
float speedFactor;
|
float speedFactor;
|
||||||
float gravity;
|
float gravity;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
|
uint32_t id;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Projectile> m_Projectiles;
|
std::vector<Projectile> m_Projectiles;
|
||||||
|
|
||||||
uint32_t m_ActorSeed;
|
uint32_t m_ActorSeed;
|
||||||
|
|
||||||
|
uint32_t m_Id;
|
||||||
|
|
||||||
void LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity);
|
uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity);
|
||||||
|
|
||||||
void AdvanceProjectile(Projectile& projectile, float dt, float frameOffset);
|
void AdvanceProjectile(Projectile& projectile, float dt);
|
||||||
|
|
||||||
void Interpolate(float frameTime, float frameOffset);
|
void Interpolate(float frameTime);
|
||||||
|
|
||||||
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
|
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
|
||||||
};
|
};
|
||||||
|
|
||||||
REGISTER_COMPONENT_TYPE(ProjectileManager)
|
REGISTER_COMPONENT_TYPE(ProjectileManager)
|
||||||
|
|
||||||
void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity)
|
uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity)
|
||||||
{
|
{
|
||||||
if (!GetSimContext().HasUnitManager())
|
if (!GetSimContext().HasUnitManager())
|
||||||
return; // do nothing if graphics are disabled
|
return 0; // do nothing if graphics are disabled
|
||||||
|
|
||||||
CmpPtr<ICmpVisual> cmpSourceVisual(GetSimContext(), source);
|
CmpPtr<ICmpVisual> cmpSourceVisual(GetSimContext(), source);
|
||||||
if (!cmpSourceVisual)
|
if (!cmpSourceVisual)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
std::wstring name = cmpSourceVisual->GetProjectileActor();
|
std::wstring name = cmpSourceVisual->GetProjectileActor();
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
@@ -151,7 +153,7 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
|||||||
// If the actor was actually loaded, complain that it doesn't have a projectile
|
// If the actor was actually loaded, complain that it doesn't have a projectile
|
||||||
if (!cmpSourceVisual->GetActorShortName().empty())
|
if (!cmpSourceVisual->GetActorShortName().empty())
|
||||||
LOGERROR(L"Unit with actor '%ls' launched a projectile but has no actor on 'projectile' attachpoint", cmpSourceVisual->GetActorShortName().c_str());
|
LOGERROR(L"Unit with actor '%ls' launched a projectile but has no actor on 'projectile' attachpoint", cmpSourceVisual->GetActorShortName().c_str());
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CVector3D sourceVec(cmpSourceVisual->GetProjectileLaunchPoint());
|
CVector3D sourceVec(cmpSourceVisual->GetProjectileLaunchPoint());
|
||||||
@@ -161,7 +163,7 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
|||||||
|
|
||||||
CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
|
CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
|
||||||
if (!sourcePos)
|
if (!sourcePos)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
sourceVec = sourcePos->GetPosition();
|
sourceVec = sourcePos->GetPosition();
|
||||||
sourceVec.Y += 3.f;
|
sourceVec.Y += 3.f;
|
||||||
@@ -169,31 +171,20 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
|||||||
|
|
||||||
CVector3D targetVec;
|
CVector3D targetVec;
|
||||||
|
|
||||||
if (targetEnt == INVALID_ENTITY)
|
targetVec = CVector3D(targetPoint);
|
||||||
{
|
|
||||||
targetVec = CVector3D(targetPoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), targetEnt);
|
|
||||||
if (!cmpTargetPosition)
|
|
||||||
return;
|
|
||||||
|
|
||||||
targetVec = CVector3D(cmpTargetPosition->GetPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
Projectile projectile;
|
Projectile projectile;
|
||||||
std::set<CStr> selections;
|
std::set<CStr> selections;
|
||||||
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections);
|
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections);
|
||||||
|
projectile.id = m_Id++;
|
||||||
if (!projectile.unit)
|
if (!projectile.unit)
|
||||||
{
|
{
|
||||||
// The error will have already been logged
|
// The error will have already been logged
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
projectile.pos = sourceVec;
|
projectile.pos = sourceVec;
|
||||||
projectile.target = targetVec;
|
projectile.target = targetVec;
|
||||||
projectile.targetEnt = targetEnt;
|
|
||||||
|
|
||||||
CVector3D offset = projectile.target - projectile.pos;
|
CVector3D offset = projectile.target - projectile.pos;
|
||||||
float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);
|
float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);
|
||||||
@@ -205,9 +196,11 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
|||||||
projectile.gravity = gravity.ToFloat();
|
projectile.gravity = gravity.ToFloat();
|
||||||
|
|
||||||
m_Projectiles.push_back(projectile);
|
m_Projectiles.push_back(projectile);
|
||||||
|
|
||||||
|
return projectile.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt, float frameOffset)
|
void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt)
|
||||||
{
|
{
|
||||||
// Do special processing if we've already reached the target
|
// Do special processing if we've already reached the target
|
||||||
if (projectile.timeLeft <= 0)
|
if (projectile.timeLeft <= 0)
|
||||||
@@ -223,24 +216,6 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt,
|
|||||||
// apply a bit of drag to them
|
// apply a bit of drag to them
|
||||||
projectile.speedFactor *= powf(1.0f - 0.4f*projectile.speedFactor, dt);
|
projectile.speedFactor *= powf(1.0f - 0.4f*projectile.speedFactor, dt);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Projectile hasn't reached the target yet:
|
|
||||||
// Track the target entity (if there is one, and it's still alive)
|
|
||||||
if (projectile.targetEnt != INVALID_ENTITY)
|
|
||||||
{
|
|
||||||
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), projectile.targetEnt);
|
|
||||||
if (cmpTargetPosition && cmpTargetPosition->IsInWorld())
|
|
||||||
{
|
|
||||||
CMatrix3D t = cmpTargetPosition->GetInterpolatedTransform(frameOffset, false);
|
|
||||||
projectile.target = t.GetTranslation();
|
|
||||||
projectile.target.Y += 2.f; // TODO: ought to aim towards a random point in the solid body of the target
|
|
||||||
|
|
||||||
// TODO: if the unit is moving, we should probably aim a bit in front of it
|
|
||||||
// so we don't have to curve so much just before reaching it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CVector3D offset = (projectile.target - projectile.pos) * projectile.speedFactor;
|
CVector3D offset = (projectile.target - projectile.pos) * projectile.speedFactor;
|
||||||
|
|
||||||
@@ -296,11 +271,11 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt,
|
|||||||
projectile.unit->GetModel().SetTransform(transform);
|
projectile.unit->GetModel().SetTransform(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset)
|
void CCmpProjectileManager::Interpolate(float frameTime)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < m_Projectiles.size(); ++i)
|
for (size_t i = 0; i < m_Projectiles.size(); ++i)
|
||||||
{
|
{
|
||||||
AdvanceProjectile(m_Projectiles[i], frameTime, frameOffset);
|
AdvanceProjectile(m_Projectiles[i], frameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the ones that have reached their target
|
// Remove the ones that have reached their target
|
||||||
@@ -310,7 +285,7 @@ void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset)
|
|||||||
// Those hitting the ground stay for a while, because it looks pretty.
|
// Those hitting the ground stay for a while, because it looks pretty.
|
||||||
if (m_Projectiles[i].timeLeft <= 0.f)
|
if (m_Projectiles[i].timeLeft <= 0.f)
|
||||||
{
|
{
|
||||||
if (m_Projectiles[i].targetEnt == INVALID_ENTITY && m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME)
|
if (m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME)
|
||||||
{
|
{
|
||||||
// Keep the projectile until it exceeds the decay time
|
// Keep the projectile until it exceeds the decay time
|
||||||
}
|
}
|
||||||
@@ -328,6 +303,22 @@ void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CCmpProjectileManager::RemoveProjectile(uint32_t id)
|
||||||
|
{
|
||||||
|
// Scan through the projectile list looking for one with the correct id to remove
|
||||||
|
for (size_t i = 0; i < m_Projectiles.size(); i++)
|
||||||
|
{
|
||||||
|
if (m_Projectiles[i].id == id)
|
||||||
|
{
|
||||||
|
// Delete in-place by swapping with the last in the list
|
||||||
|
std::swap(m_Projectiles[i], m_Projectiles.back());
|
||||||
|
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
|
||||||
|
m_Projectiles.pop_back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
|
void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
|
||||||
{
|
{
|
||||||
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
|
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ DEFINE_INTERFACE_METHOD_1("SetHeightFixed", void, ICmpPosition, SetHeightFixed,
|
|||||||
DEFINE_INTERFACE_METHOD_0("IsFloating", bool, ICmpPosition, IsFloating)
|
DEFINE_INTERFACE_METHOD_0("IsFloating", bool, ICmpPosition, IsFloating)
|
||||||
DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector3D, ICmpPosition, GetPosition)
|
DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector3D, ICmpPosition, GetPosition)
|
||||||
DEFINE_INTERFACE_METHOD_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D)
|
DEFINE_INTERFACE_METHOD_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D)
|
||||||
|
DEFINE_INTERFACE_METHOD_0("GetPreviousPosition", CFixedVector3D, ICmpPosition, GetPreviousPosition)
|
||||||
|
DEFINE_INTERFACE_METHOD_0("GetPreviousPosition2D", CFixedVector2D, ICmpPosition, GetPreviousPosition2D)
|
||||||
DEFINE_INTERFACE_METHOD_1("TurnTo", void, ICmpPosition, TurnTo, entity_angle_t)
|
DEFINE_INTERFACE_METHOD_1("TurnTo", void, ICmpPosition, TurnTo, entity_angle_t)
|
||||||
DEFINE_INTERFACE_METHOD_1("SetYRotation", void, ICmpPosition, SetYRotation, entity_angle_t)
|
DEFINE_INTERFACE_METHOD_1("SetYRotation", void, ICmpPosition, SetYRotation, entity_angle_t)
|
||||||
DEFINE_INTERFACE_METHOD_2("SetXZRotation", void, ICmpPosition, SetXZRotation, entity_angle_t, entity_angle_t)
|
DEFINE_INTERFACE_METHOD_2("SetXZRotation", void, ICmpPosition, SetXZRotation, entity_angle_t, entity_angle_t)
|
||||||
|
|||||||
@@ -108,6 +108,19 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual CFixedVector2D GetPosition2D() = 0;
|
virtual CFixedVector2D GetPosition2D() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the previous turn's x,y,z position (no interpolation).
|
||||||
|
* Depends on the current terrain heightmap.
|
||||||
|
* Must not be called unless IsInWorld is true.
|
||||||
|
*/
|
||||||
|
virtual CFixedVector3D GetPreviousPosition() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the previous turn's x,z position (no interpolation).
|
||||||
|
* Must not be called unless IsInWorld is true.
|
||||||
|
*/
|
||||||
|
virtual CFixedVector2D GetPreviousPosition2D() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate smoothly to the given angle around the upwards axis.
|
* Rotate smoothly to the given angle around the upwards axis.
|
||||||
* @param y clockwise radians from the +Z axis.
|
* @param y clockwise radians from the +Z axis.
|
||||||
|
|||||||
@@ -22,6 +22,6 @@
|
|||||||
#include "simulation2/system/InterfaceScripted.h"
|
#include "simulation2/system/InterfaceScripted.h"
|
||||||
|
|
||||||
BEGIN_INTERFACE_WRAPPER(ProjectileManager)
|
BEGIN_INTERFACE_WRAPPER(ProjectileManager)
|
||||||
DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtEntity", void, ICmpProjectileManager, LaunchProjectileAtEntity, entity_id_t, entity_id_t, fixed, fixed)
|
DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed)
|
||||||
DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", void, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed)
|
DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t)
|
||||||
END_INTERFACE_WRAPPER(ProjectileManager)
|
END_INTERFACE_WRAPPER(ProjectileManager)
|
||||||
|
|||||||
@@ -31,14 +31,6 @@
|
|||||||
class ICmpProjectileManager : public IComponent
|
class ICmpProjectileManager : public IComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* Launch a projectile from entity @p source to entity @p target.
|
|
||||||
* @param source source entity; the projectile will determined from the "projectile" prop in its actor
|
|
||||||
* @param target target entity; the projectile will automatically track the target to ensure it always hits precisely
|
|
||||||
* @param speed horizontal speed in m/s
|
|
||||||
* @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve)
|
|
||||||
*/
|
|
||||||
virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity) = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a projectile from entity @p source to point @p target.
|
* Launch a projectile from entity @p source to point @p target.
|
||||||
@@ -46,8 +38,15 @@ public:
|
|||||||
* @param target target point
|
* @param target target point
|
||||||
* @param speed horizontal speed in m/s
|
* @param speed horizontal speed in m/s
|
||||||
* @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve)
|
* @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve)
|
||||||
|
* @return id of the created projectile
|
||||||
*/
|
*/
|
||||||
virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) = 0;
|
virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a projectile, used when the projectile has hit a target
|
||||||
|
* @param id of the projectile to remove
|
||||||
|
*/
|
||||||
|
virtual void RemoveProjectile(uint32_t id) = 0;
|
||||||
|
|
||||||
DECLARE_INTERFACE_TYPE(ProjectileManager)
|
DECLARE_INTERFACE_TYPE(ProjectileManager)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ public:
|
|||||||
virtual bool IsFloating() { return false; }
|
virtual bool IsFloating() { return false; }
|
||||||
virtual CFixedVector3D GetPosition() { return CFixedVector3D(); }
|
virtual CFixedVector3D GetPosition() { return CFixedVector3D(); }
|
||||||
virtual CFixedVector2D GetPosition2D() { return CFixedVector2D(); }
|
virtual CFixedVector2D GetPosition2D() { return CFixedVector2D(); }
|
||||||
|
virtual CFixedVector3D GetPreviousPosition() { return CFixedVector3D(); }
|
||||||
|
virtual CFixedVector2D GetPreviousPosition2D() { return CFixedVector2D(); }
|
||||||
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
|
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
|
||||||
virtual void SetYRotation(entity_angle_t UNUSED(y)) { }
|
virtual void SetYRotation(entity_angle_t UNUSED(y)) { }
|
||||||
virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }
|
virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }
|
||||||
|
|||||||
@@ -395,7 +395,8 @@ exposed (in template files etc) with the name "ExampleTwo", and implementing the
|
|||||||
The @c Init and @c Deinit functions are optional. Unlike C++, there are no @c Serialize/Deserialize functions -
|
The @c Init and @c Deinit functions are optional. Unlike C++, there are no @c Serialize/Deserialize functions -
|
||||||
each JS component instance is automatically serialized and restored.
|
each JS component instance is automatically serialized and restored.
|
||||||
(This automatic serialization restricts what you can store as properties in the object - e.g. you cannot store function closures,
|
(This automatic serialization restricts what you can store as properties in the object - e.g. you cannot store function closures,
|
||||||
because they're too hard to serialize. The details should be documented on some other page eventually.)
|
because they're too hard to serialize. This will serialize Strings, numbers, bools, null, undefined, arrays of serializable
|
||||||
|
values whose property names are purely numeric, objects whose properties are serializable values. Cyclic structures are allowed.)
|
||||||
|
|
||||||
Instead of @c ClassInit and @c HandleMessage, you simply add functions of the form <code>On<var>MessageType</var></code>.
|
Instead of @c ClassInit and @c HandleMessage, you simply add functions of the form <code>On<var>MessageType</var></code>.
|
||||||
(If you want the equivalent of SubscribeGloballyToMessageType, then use <code>OnGlobal<var>MessageType</var></code> instead.)
|
(If you want the equivalent of SubscribeGloballyToMessageType, then use <code>OnGlobal<var>MessageType</var></code> instead.)
|
||||||
|
|||||||
Reference in New Issue
Block a user