diff --git a/binaries/data/mods/public/simulation/components/Attack.js b/binaries/data/mods/public/simulation/components/Attack.js
index 9376d3a9ca..8850137ab4 100644
--- a/binaries/data/mods/public/simulation/components/Attack.js
+++ b/binaries/data/mods/public/simulation/components/Attack.js
@@ -70,6 +70,7 @@ Attack.prototype.Schema =
"800" +
"1600" +
"50.0" +
+ "2.5" +
"" +
"" +
"Cavalry" +
@@ -77,6 +78,14 @@ Attack.prototype.Schema =
"" +
"" +
"Champion" +
+ "" +
+ "Circular" +
+ "20" +
+ "false" +
+ "0.0" +
+ "10.0" +
+ "0.0" +
+ "" +
"" +
"" +
"10.0" +
@@ -119,9 +128,23 @@ Attack.prototype.Schema =
"" +
"" +
"" +
+ "" +
bonusesSchema +
preferredClassesSchema +
restrictedClassesSchema +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ bonusesSchema +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -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);
diff --git a/binaries/data/mods/public/simulation/templates/other/plane.xml b/binaries/data/mods/public/simulation/templates/other/plane.xml
index 5804ffe33e..beb0266137 100644
--- a/binaries/data/mods/public/simulation/templates/other/plane.xml
+++ b/binaries/data/mods/public/simulation/templates/other/plane.xml
@@ -10,6 +10,7 @@
60.0
0
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
index 6fdbbc3e85..328bbea350 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
index 117091aae4..2eebed85a0 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml
index 698b7d545e..1aab30451b 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
index a0a9ff2123..70faa84dfe 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
index 768ae53792..1b74475255 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml
index b81955617a..55b8a0d296 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml
@@ -9,8 +9,7 @@
Town Wall
City Wall
Wall off your town for a stout defense.
- structures/wall.png
- phase_town
+ phase_town
0.85
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
index ee01600549..fa118c71a2 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
@@ -38,11 +38,6 @@
-
-
- armor_trade_convoys
-
-
@@ -56,6 +51,9 @@
65536
+
+ armor_trade_convoys
+
units/{civ}_support_trader
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml b/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
index 4bf8898e37..c80e862e98 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
@@ -15,6 +15,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
index e8c69229c9..70c120f84f 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
@@ -12,6 +12,7 @@
1500
Organic
StoneWall
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
index 5617109d77..3195b2f97f 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
@@ -15,16 +15,17 @@
50.0
1200
2000
-
-
- Infantry Bow
- 2.0
-
-
- Cavalry Sword
- 1.5
-
-
+ 1.5
+
+
+ Infantry Bow
+ 2.0
+
+
+ Cavalry Sword
+ 1.5
+
+
StoneWall
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
index 1dbc01456b..f1c46dbd70 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
@@ -15,16 +15,17 @@
75.0
1200
2000
-
-
- Infantry Sword
- 2.0
-
-
- Cavalry Spear
- 1.5
-
-
+ 1.5
+
+
+ Infantry Sword
+ 2.0
+
+
+ Cavalry Spear
+ 1.5
+
+
Organic
StoneWall
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
index cefb140905..f5dc4cfa7e 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
@@ -15,24 +15,25 @@
50.0
1200
2000
-
-
- Infantry Spear
- 1.5
-
-
- Cavalry Bow
- 1.5
-
-
- Elephant
- 1.5
-
-
- Chariot
- 1.5
-
-
+ 1.5
+
+
+ Infantry Spear
+ 1.5
+
+
+ Cavalry Bow
+ 1.5
+
+
+ Elephant
+ 1.5
+
+
+ Chariot
+ 1.5
+
+
Organic
StoneWall
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
index db0d130d9d..05a0a2072a 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
@@ -15,16 +15,17 @@
75.0
1200
2000
-
-
- Infantry Spear
- 2.0
-
-
- Infantry Sword
- 1.5
-
-
+ 1.5
+
+
+ Infantry Spear
+ 2.0
+
+
+ Infantry Sword
+ 1.5
+
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
index 43081adfdf..6473439609 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
@@ -14,6 +14,7 @@
25.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
index 560b817358..fafc67d327 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
@@ -10,6 +10,7 @@
28.0
1200
2000
+ 1.5
Organic
StoneWall
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
index ac0e53f6d5..e162f725e1 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
@@ -15,6 +15,7 @@
60.0
2000
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
index 84b1c0552e..9edcf40ca2 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
@@ -15,6 +15,7 @@
40.0
2000
4000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
index 86c53455db..0ec2d706ce 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
@@ -15,6 +15,7 @@
60.0
1000
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml
index f3c643fb13..79b442aae0 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml
@@ -7,19 +7,28 @@
0.0
- 50.0
- 50.0
+ 20.0
+ 20.0
60
8.0
+ 6.0
60.0
5000
5000
-
-
- Organic
- 2.0
-
-
+
+
+ Organic
+ 2.0
+
+
+
+ Linear
+ 12
+ false
+ 0.0
+ 30.0
+ 30.0
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml
index bee696e698..2519059700 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml
@@ -6,20 +6,29 @@
- 50.0
+ 40.0
0.0
- 50.0
+ 40.0
68
12.0
30.0
5000
5000
+ 6.0
Structure
2.0
+
+ Circular
+ 12
+ true
+ 12.0
+ 0.0
+ 12.0
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml
index 5e6d2b9e11..72b8523c67 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml
@@ -14,6 +14,7 @@
75.0
1200
2000
+ 1.5
diff --git a/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml b/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
index 019372cfca..7d56409857 100644
--- a/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
+++ b/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
@@ -10,6 +10,7 @@
10.0
2000
2000
+ 1.5
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp
index 68089e8092..2f5eed0167 100644
--- a/source/simulation2/components/CCmpObstructionManager.cpp
+++ b/source/simulation2/components/CCmpObstructionManager.cpp
@@ -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::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;
}
diff --git a/source/simulation2/components/CCmpPosition.cpp b/source/simulation2/components/CCmpPosition.cpp
index 94e0385721..984e9c6b88 100644
--- a/source/simulation2/components/CCmpPosition.cpp
+++ b/source/simulation2/components/CCmpPosition.cpp
@@ -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 cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpTerrain)
+ baseY = cmpTerrain->GetGroundLevel(m_PrevX, m_PrevZ);
+
+ if (m_Floating)
+ {
+ CmpPtr 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;
diff --git a/source/simulation2/components/CCmpProjectileManager.cpp b/source/simulation2/components/CCmpProjectileManager.cpp
index 8444427c79..b61261c38e 100644
--- a/source/simulation2/components/CCmpProjectileManager.cpp
+++ b/source/simulation2/components/CCmpProjectileManager.cpp
@@ -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 (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 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 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 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 cmpTargetPosition(GetSimContext(), targetEnt);
- if (!cmpTargetPosition)
- return;
-
- targetVec = CVector3D(cmpTargetPosition->GetPosition());
- }
+ targetVec = CVector3D(targetPoint);
Projectile projectile;
std::set 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 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 cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
diff --git a/source/simulation2/components/ICmpPosition.cpp b/source/simulation2/components/ICmpPosition.cpp
index 1e3c8b0efb..9aaeecc801 100644
--- a/source/simulation2/components/ICmpPosition.cpp
+++ b/source/simulation2/components/ICmpPosition.cpp
@@ -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)
diff --git a/source/simulation2/components/ICmpPosition.h b/source/simulation2/components/ICmpPosition.h
index 4a4b274534..3e454ced41 100644
--- a/source/simulation2/components/ICmpPosition.h
+++ b/source/simulation2/components/ICmpPosition.h
@@ -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.
diff --git a/source/simulation2/components/ICmpProjectileManager.cpp b/source/simulation2/components/ICmpProjectileManager.cpp
index f0bc5a552d..c235a96b44 100644
--- a/source/simulation2/components/ICmpProjectileManager.cpp
+++ b/source/simulation2/components/ICmpProjectileManager.cpp
@@ -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)
diff --git a/source/simulation2/components/ICmpProjectileManager.h b/source/simulation2/components/ICmpProjectileManager.h
index 0b80b16466..399b27d5a6 100644
--- a/source/simulation2/components/ICmpProjectileManager.h
+++ b/source/simulation2/components/ICmpProjectileManager.h
@@ -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)
};
diff --git a/source/simulation2/components/tests/test_RangeManager.h b/source/simulation2/components/tests/test_RangeManager.h
index e965ff0091..cf2ee7134d 100644
--- a/source/simulation2/components/tests/test_RangeManager.h
+++ b/source/simulation2/components/tests/test_RangeManager.h
@@ -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)) { }
diff --git a/source/simulation2/docs/SimulationDocs.h b/source/simulation2/docs/SimulationDocs.h
index fbdfb7fd0e..3eebd59cde 100644
--- a/source/simulation2/docs/SimulationDocs.h
+++ b/source/simulation2/docs/SimulationDocs.h
@@ -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 OnMessageType.
(If you want the equivalent of SubscribeGloballyToMessageType, then use OnGlobalMessageType instead.)