forked from mirrors/0ad
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>" +
|
||||
"<RepeatTime>1600</RepeatTime>" +
|
||||
"<ProjectileSpeed>50.0</ProjectileSpeed>" +
|
||||
"<Spread>2.5</Spread>" +
|
||||
"<Bonuses>" +
|
||||
"<Bonus1>" +
|
||||
"<Classes>Cavalry</Classes>" +
|
||||
@@ -77,6 +78,14 @@ Attack.prototype.Schema =
|
||||
"</Bonus1>" +
|
||||
"</Bonuses>" +
|
||||
"<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>" +
|
||||
"<Charge>" +
|
||||
"<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'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</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 +
|
||||
preferredClassesSchema +
|
||||
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>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
@@ -287,6 +310,14 @@ 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)
|
||||
{
|
||||
template = this.template[type.split(".")[0]].Splash;
|
||||
splash = "/Splash";
|
||||
}
|
||||
|
||||
var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager);
|
||||
var applyTechs = function(damageType)
|
||||
{
|
||||
@@ -294,8 +325,8 @@ Attack.prototype.GetAttackStrengths = function(type)
|
||||
if (cmpTechMan)
|
||||
{
|
||||
// All causes caching problems so disable it for now.
|
||||
//var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + "/All", strength, self.entity) - self.template[type][damageType];
|
||||
strength = cmpTechMan.ApplyModifications("Attack/" + type + "/" + damageType, strength, self.entity);
|
||||
//var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/All", strength, self.entity) - self.template[type][damageType];
|
||||
strength = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/" + damageType, strength, self.entity);
|
||||
}
|
||||
return strength;
|
||||
};
|
||||
@@ -326,16 +357,20 @@ Attack.prototype.GetRange = function(type)
|
||||
Attack.prototype.GetAttackBonus = function(type, target)
|
||||
{
|
||||
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);
|
||||
if (!cmpIdentity)
|
||||
return 1;
|
||||
|
||||
// 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;
|
||||
if (bonus.Classes){
|
||||
@@ -352,6 +387,20 @@ Attack.prototype.GetAttackBonus = function(type, target)
|
||||
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,
|
||||
* 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 (type == "Ranged")
|
||||
{
|
||||
// To implement (in)accuracy, for arrows and javelins, we want to do the following:
|
||||
// * 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.)
|
||||
// In the future this could be extended:
|
||||
// * Obstacles like trees could reduce the probability of the target being hit
|
||||
// * 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
|
||||
var horizSpeed = +this.template[type].ProjectileSpeed;
|
||||
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
|
||||
|
||||
//horizSpeed /= 8; gravity /= 8; // slow it down for testing
|
||||
|
||||
// Find the distance to the target
|
||||
|
||||
var spread = this.template.Ranged.Spread;
|
||||
|
||||
//horizSpeed /= 2; gravity /= 2; // slow it down for testing
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||
return;
|
||||
@@ -396,48 +431,47 @@ Attack.prototype.PerformAttack = function(type, target)
|
||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
|
||||
return;
|
||||
var targetPosition = cmpTargetPosition.GetPosition();
|
||||
var horizDistance = Math.sqrt(Math.pow(targetPosition.x - selfPosition.x, 2) + Math.pow(targetPosition.z - selfPosition.z, 2));
|
||||
|
||||
// Compute the real target point (based on accuracy)
|
||||
var angle = Math.random() * 2*Math.PI;
|
||||
var r = 1 - Math.sqrt(Math.random()); // triangular distribution [0,1] (cluster around the center)
|
||||
var offset = r * accuracy; // TODO: should be affected by range
|
||||
var offsetX = offset * Math.sin(angle);
|
||||
var offsetZ = offset * Math.cos(angle);
|
||||
|
||||
var realTargetPosition = { "x": targetPosition.x + offsetX, "y": targetPosition.y, "z": targetPosition.z + offsetZ };
|
||||
|
||||
// TODO: what we should really do here is select the unit whose footprint is closest to the realTargetPosition
|
||||
// (and harmlessly hit the ground if there's none), but as a simplification let's just randomly decide whether to
|
||||
// hit the original target or not.
|
||||
var realTargetUnit = undefined;
|
||||
if (Math.random() < 0.5) // TODO: this is yucky and hardcoded
|
||||
{
|
||||
// Hit the original target
|
||||
realTargetUnit = target;
|
||||
realTargetPosition = targetPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit the ground
|
||||
// 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 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);
|
||||
if (realTargetUnit)
|
||||
cmpProjectileManager.LaunchProjectileAtEntity(this.entity, realTargetUnit, horizSpeed, gravity);
|
||||
else
|
||||
cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
|
||||
var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
|
||||
|
||||
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});
|
||||
}
|
||||
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
|
||||
*/
|
||||
@@ -472,12 +670,14 @@ Attack.prototype.CauseDamage = function(data)
|
||||
{
|
||||
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);
|
||||
if (!cmpDamageReceiver)
|
||||
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 (targetState.killed == true)
|
||||
{
|
||||
@@ -490,4 +690,9 @@ Attack.prototype.CauseDamage = function(data)
|
||||
PlaySound("attack_impact", this.entity);
|
||||
};
|
||||
|
||||
Attack.prototype.OnUpdate = function(msg)
|
||||
{
|
||||
this.turnLength = msg.turnLength;
|
||||
}
|
||||
|
||||
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||
<PrepareTime>0</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Identity>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@
|
||||
<Classes datatype="tokens">Town Wall</Classes>
|
||||
<GenericName>City Wall</GenericName>
|
||||
<Tooltip>Wall off your town for a stout defense.</Tooltip>
|
||||
<Icon>structures/wall.png</Icon>
|
||||
<RequiredTechnology>phase_town</RequiredTechnology>
|
||||
<RequiredTechnology>phase_town</RequiredTechnology>
|
||||
</Identity>
|
||||
<WallSet>
|
||||
<MaxTowerOverlap>0.85</MaxTowerOverlap>
|
||||
|
||||
+3
-5
@@ -38,11 +38,6 @@
|
||||
<Obstruction>
|
||||
<Static width="30.0" depth="26.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Technologies datatype="tokens">
|
||||
armor_trade_convoys
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_market.xml</select>
|
||||
@@ -56,6 +51,9 @@
|
||||
<Weight>65536</Weight>
|
||||
</TerritoryInfluence>
|
||||
<ProductionQueue>
|
||||
<Technologies datatype="tokens">
|
||||
armor_trade_convoys
|
||||
</Technologies>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}_support_trader
|
||||
</Entities>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<RepeatTime>1500</RepeatTime>
|
||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Identity>
|
||||
|
||||
+11
-10
@@ -15,16 +15,17 @@
|
||||
<ProjectileSpeed>50.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Bow</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Sword</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
<Spread>1.5</Spread>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Bow</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Sword</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
|
||||
+11
-10
@@ -15,16 +15,17 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Sword</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Spear</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
<Spread>1.5</Spread>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Sword</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Spear</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||
</Ranged>
|
||||
|
||||
+19
-18
@@ -15,24 +15,25 @@
|
||||
<ProjectileSpeed>50.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Spear</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Bow</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
<BonusElephants>
|
||||
<Classes>Elephant</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusElephants>
|
||||
<BonusChariots>
|
||||
<Classes>Chariot</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusChariots>
|
||||
</Bonuses>
|
||||
<Spread>1.5</Spread>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Spear</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Cavalry Bow</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
<BonusElephants>
|
||||
<Classes>Elephant</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusElephants>
|
||||
<BonusChariots>
|
||||
<Classes>Chariot</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusChariots>
|
||||
</Bonuses>
|
||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||
</Ranged>
|
||||
|
||||
+11
-10
@@ -15,16 +15,17 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Spear</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Infantry Sword</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
<Spread>1.5</Spread>
|
||||
<Bonuses>
|
||||
<BonusPrimary>
|
||||
<Classes>Infantry Spear</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusPrimary>
|
||||
<BonusSecondary>
|
||||
<Classes>Infantry Sword</Classes>
|
||||
<Multiplier>1.5</Multiplier>
|
||||
</BonusSecondary>
|
||||
</Bonuses>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Cost>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ProjectileSpeed>25.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Cost>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<ProjectileSpeed>28.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
<PreferredClasses datatype="tokens">Organic</PreferredClasses>
|
||||
<RestrictedClasses datatype="tokens">StoneWall</RestrictedClasses>
|
||||
</Ranged>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||
<PrepareTime>2000</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>40.0</ProjectileSpeed>
|
||||
<PrepareTime>2000</PrepareTime>
|
||||
<RepeatTime>4000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Cost>
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||
<PrepareTime>1000</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+17
-8
@@ -7,19 +7,28 @@
|
||||
<Attack>
|
||||
<Ranged>
|
||||
<Hack>0.0</Hack>
|
||||
<Pierce>50.0</Pierce>
|
||||
<Crush>50.0</Crush>
|
||||
<Pierce>20.0</Pierce>
|
||||
<Crush>20.0</Crush>
|
||||
<MaxRange>60</MaxRange>
|
||||
<MinRange>8.0</MinRange>
|
||||
<Spread>6.0</Spread>
|
||||
<ProjectileSpeed>60.0</ProjectileSpeed>
|
||||
<PrepareTime>5000</PrepareTime>
|
||||
<RepeatTime>5000</RepeatTime>
|
||||
<Bonuses>
|
||||
<BonusOrganic>
|
||||
<Classes>Organic</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusOrganic>
|
||||
</Bonuses>
|
||||
<Bonuses>
|
||||
<BonusOrganic>
|
||||
<Classes>Organic</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusOrganic>
|
||||
</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>
|
||||
</Attack>
|
||||
<Cost>
|
||||
|
||||
+11
-2
@@ -6,20 +6,29 @@
|
||||
</Armour>
|
||||
<Attack>
|
||||
<Ranged>
|
||||
<Hack>50.0</Hack>
|
||||
<Hack>40.0</Hack>
|
||||
<Pierce>0.0</Pierce>
|
||||
<Crush>50.0</Crush>
|
||||
<Crush>40.0</Crush>
|
||||
<MaxRange>68</MaxRange>
|
||||
<MinRange>12.0</MinRange>
|
||||
<ProjectileSpeed>30.0</ProjectileSpeed>
|
||||
<PrepareTime>5000</PrepareTime>
|
||||
<RepeatTime>5000</RepeatTime>
|
||||
<Spread>6.0</Spread>
|
||||
<Bonuses>
|
||||
<BonusStruct>
|
||||
<Classes>Structure</Classes>
|
||||
<Multiplier>2.0</Multiplier>
|
||||
</BonusStruct>
|
||||
</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>
|
||||
</Attack>
|
||||
<Cost>
|
||||
|
||||
+1
@@ -14,6 +14,7 @@
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
|
||||
+1
@@ -10,6 +10,7 @@
|
||||
<ProjectileSpeed>10.0</ProjectileSpeed>
|
||||
<PrepareTime>2000</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Footprint replace="">
|
||||
|
||||
@@ -944,9 +944,18 @@ bool CCmpObstructionManager::FindMostImportantObstruction(const IObstructionTest
|
||||
|
||||
// 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)
|
||||
|
||||
// TODO: actually do that
|
||||
// (This might matter when you tell a unit to walk too close to the edge of a building)
|
||||
|
||||
GetObstructionsInRange(filter, x-r, z-r, x+r, z+r, squares);
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,9 @@ public:
|
||||
// Dynamic state:
|
||||
|
||||
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;
|
||||
bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height
|
||||
|
||||
@@ -201,8 +203,8 @@ public:
|
||||
if (!m_InWorld)
|
||||
{
|
||||
m_InWorld = true;
|
||||
m_LastX = m_X;
|
||||
m_LastZ = m_Z;
|
||||
m_LastX = m_PrevX = m_X;
|
||||
m_LastZ = m_PrevZ = m_Z;
|
||||
}
|
||||
|
||||
AdvertisePositionChanges();
|
||||
@@ -210,8 +212,8 @@ public:
|
||||
|
||||
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
|
||||
{
|
||||
m_LastX = m_X = x;
|
||||
m_LastZ = m_Z = z;
|
||||
m_LastX = m_PrevX = m_X = x;
|
||||
m_LastZ = m_PrevZ = m_Z = z;
|
||||
m_InWorld = true;
|
||||
|
||||
AdvertisePositionChanges();
|
||||
@@ -278,6 +280,43 @@ public:
|
||||
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)
|
||||
{
|
||||
m_RotY = y;
|
||||
@@ -408,6 +447,10 @@ public:
|
||||
}
|
||||
case MT_TurnStart:
|
||||
{
|
||||
// Store the positions from the turn before
|
||||
m_PrevX = m_LastX;
|
||||
m_PrevZ = m_LastZ;
|
||||
|
||||
m_LastX = m_X;
|
||||
m_LastZ = m_Z;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpProjectileManager.h"
|
||||
|
||||
#include "ICmpObstruction.h"
|
||||
#include "ICmpObstructionManager.h"
|
||||
#include "ICmpPosition.h"
|
||||
#include "ICmpRangeManager.h"
|
||||
#include "ICmpTerrain.h"
|
||||
@@ -58,6 +60,7 @@ public:
|
||||
virtual void Init(const CParamNode& UNUSED(paramNode))
|
||||
{
|
||||
m_ActorSeed = 0;
|
||||
m_Id = 1;
|
||||
}
|
||||
|
||||
virtual void Deinit()
|
||||
@@ -86,7 +89,7 @@ public:
|
||||
case MT_Interpolate:
|
||||
{
|
||||
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
|
||||
Interpolate(msgData.frameTime, msgData.offset);
|
||||
Interpolate(msgData.frameTime);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
|
||||
{
|
||||
LaunchProjectile(source, target, INVALID_ENTITY, speed, gravity);
|
||||
return LaunchProjectile(source, target, speed, gravity);
|
||||
}
|
||||
|
||||
virtual void RemoveProjectile(uint32_t);
|
||||
|
||||
private:
|
||||
struct Projectile
|
||||
@@ -114,36 +114,38 @@ private:
|
||||
CUnit* unit;
|
||||
CVector3D pos;
|
||||
CVector3D target;
|
||||
entity_id_t targetEnt; // INVALID_ENTITY if the target is just a point
|
||||
float timeLeft;
|
||||
float speedFactor;
|
||||
float gravity;
|
||||
bool stopped;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
std::vector<Projectile> m_Projectiles;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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())
|
||||
return; // do nothing if graphics are disabled
|
||||
return 0; // do nothing if graphics are disabled
|
||||
|
||||
CmpPtr<ICmpVisual> cmpSourceVisual(GetSimContext(), source);
|
||||
if (!cmpSourceVisual)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
std::wstring name = cmpSourceVisual->GetProjectileActor();
|
||||
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 (!cmpSourceVisual->GetActorShortName().empty())
|
||||
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());
|
||||
@@ -161,7 +163,7 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
||||
|
||||
CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
|
||||
if (!sourcePos)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
sourceVec = sourcePos->GetPosition();
|
||||
sourceVec.Y += 3.f;
|
||||
@@ -169,31 +171,20 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
||||
|
||||
CVector3D targetVec;
|
||||
|
||||
if (targetEnt == INVALID_ENTITY)
|
||||
{
|
||||
targetVec = CVector3D(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), targetEnt);
|
||||
if (!cmpTargetPosition)
|
||||
return;
|
||||
|
||||
targetVec = CVector3D(cmpTargetPosition->GetPosition());
|
||||
}
|
||||
targetVec = CVector3D(targetPoint);
|
||||
|
||||
Projectile projectile;
|
||||
std::set<CStr> selections;
|
||||
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections);
|
||||
projectile.id = m_Id++;
|
||||
if (!projectile.unit)
|
||||
{
|
||||
// The error will have already been logged
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
projectile.pos = sourceVec;
|
||||
projectile.target = targetVec;
|
||||
projectile.targetEnt = targetEnt;
|
||||
|
||||
CVector3D offset = projectile.target - projectile.pos;
|
||||
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();
|
||||
|
||||
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
|
||||
if (projectile.timeLeft <= 0)
|
||||
@@ -223,24 +216,6 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt,
|
||||
// apply a bit of drag to them
|
||||
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;
|
||||
|
||||
@@ -296,11 +271,11 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt,
|
||||
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)
|
||||
{
|
||||
AdvanceProjectile(m_Projectiles[i], frameTime, frameOffset);
|
||||
AdvanceProjectile(m_Projectiles[i], frameTime);
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
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("GetPosition", CFixedVector3D, ICmpPosition, GetPosition)
|
||||
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("SetYRotation", void, ICmpPosition, SetYRotation, 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param y clockwise radians from the +Z axis.
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
#include "simulation2/system/InterfaceScripted.h"
|
||||
|
||||
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", void, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed)
|
||||
DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed)
|
||||
DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t)
|
||||
END_INTERFACE_WRAPPER(ProjectileManager)
|
||||
|
||||
@@ -31,14 +31,6 @@
|
||||
class ICmpProjectileManager : public IComponent
|
||||
{
|
||||
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.
|
||||
@@ -46,8 +38,15 @@ public:
|
||||
* @param target target point
|
||||
* @param speed horizontal speed in m/s
|
||||
* @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)
|
||||
};
|
||||
|
||||
@@ -50,6 +50,8 @@ public:
|
||||
virtual bool IsFloating() { return false; }
|
||||
virtual CFixedVector3D GetPosition() { return CFixedVector3D(); }
|
||||
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 SetYRotation(entity_angle_t UNUSED(y)) { }
|
||||
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 -
|
||||
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,
|
||||
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>.
|
||||
(If you want the equivalent of SubscribeGloballyToMessageType, then use <code>OnGlobal<var>MessageType</var></code> instead.)
|
||||
|
||||
Reference in New Issue
Block a user