Index: 0ad/source/simulation2/Simulation2.cpp
===================================================================
--- 0ad/source/simulation2/Simulation2.cpp	(revision 13543)
+++ 0ad/source/simulation2/Simulation2.cpp	(working copy)
@@ -127,6 +127,7 @@
 			LOAD_SCRIPTED_COMPONENT("PlayerManager");
 			LOAD_SCRIPTED_COMPONENT("TechnologyTemplateManager");
 			LOAD_SCRIPTED_COMPONENT("Timer");
+			LOAD_SCRIPTED_COMPONENT("Damage");
 
 #undef LOAD_SCRIPTED_COMPONENT
 
Index: 0ad/binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- 0ad/binaries/data/mods/public/simulation/components/Damage.js	(revision 0)
+++ 0ad/binaries/data/mods/public/simulation/components/Damage.js	(working copy)
@@ -0,0 +1,161 @@
+function Damage() {}
+
+Damage.prototype.Schema = "<a:component type='system'/><empty/>";
+
+Damage.prototype.Init = function()
+{
+	// Create dummy entity for EntitiesNearPoint
+	this.dummyTargetEntity = Engine.AddEntity('special/dummy');
+}
+
+/****************************************
+ * Damages units around a given origin.
+ * data.attacker = <entity id>
+ * data.origin = {'x':<int>, 'z':<int>}
+ * data.radius = <int>
+ * data.shape = <string>
+ * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
+ * ***Optional Variables***
+ * data.direction = <unit vector>
+ * data.playersToDamage = <array of player ids>
+ */
+Damage.prototype.CauseSplashDamage = function(data)
+{
+	warn("Splash mania! Pos: "+data.origin.x+"x, "+data.origin.z+"z + Radius: "+data.radius)
+	// Get nearby entities and define variables
+	var nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
+	var damageMultiplier = 1;
+	// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
+	for each (var entity in nearEnts)
+	{
+		var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition();
+		if(data.shape=='Circular') // circular effect with quadratic falloff in every direction
+		{
+			var squaredDistanceFromOrigin = this.VectorDistanceSquared(data.origin, entityPosition);
+			damageMultiplier == 1 - ((squaredDistanceFromOrigin) / (data.radius * data.radius));
+		}
+		else if(data.shape=='Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
+		{
+			// Get position of entity relative to splash origin.
+			var relativePos = {"x": entityPosition.x - data.origin.x, "z": entityPosition.z - data.origin.z};
+			
+			// The width of linear splash is one fifth of the normal splash radius.
+			var width = data.radius/5;
+			
+			// Effectivly rotate the axis to align with the missile direction.
+			var parallelDist = this.VectorDot(relativePos, data.direction);//x axis
+			var perpDist = Math.abs(this.VectorCross(relativePos, data.direction));//y axis
+			
+			// Check that the unit is within the distance at which it will get damaged.
+			if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
+				multiplier = (data.radius*data.radius - parallelDist*parallelDist) / (data.radius*data.radius)
+						             * (width*width - perpDist*perpDist) / (width*width);
+			else
+				multiplier = 0;
+		}
+		else // In case someone calls this function with an invalid shape.
+		{
+			warn("The "+data.shape+" splash damage shape is not implemented!");
+		}
+		// Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
+		this.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":"Splash"})
+	}
+};
+
+/****************************************
+ * Causes damage on a given unit
+ * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
+ * data.target = <entity id>
+ * data.attacker = <entity id>
+ * data.multiplier = <float between 1 and 0>
+ * data.type = <string>
+ */
+Damage.prototype.CauseDamage = function(data)
+{
+	// Check the target can be damaged otherwise don't do anything.
+	var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
+	if (!cmpDamageReceiver)
+		return;
+
+	// Damage the target
+	var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier);
+
+	// If the target was killed run some cleanup
+	if (targetState.killed == true)
+		this.TargetKilled(data.attacker, data.target);
+
+	// Post the network command (make it work in multiplayer)
+	Engine.PostMessage(data.target, MT_Attacked,{ "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change });
+
+	// Play attacking sounds
+	PlaySound("attack_impact", data.attacker);
+};
+
+/****************************************
+ * Gets entities near a give point for given players.
+ * origin = {'x':<int>, 'z':<int>}
+ * radius = <int>
+ * players = <array>
+ * If players is not included, entities from all players are used.
+ */
+Damage.prototype.EntitiesNearPoint = function(origin, radius, players)
+{
+	// If there is insufficient data return an empty array.
+	if(!origin || !radius)	
+		return [];
+
+	// Move the dummy entity to the origin of the query.
+	var cmpDummyPosition = Engine.QueryInterface(this.dummyTargetEntity, IID_Position);
+	cmpDummyPosition.JumpTo(origin.x, origin.z);
+	
+	// If the players parameter is not specified use all players.
+	if(!players)
+		players = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers();
+
+	// Call RangeManager with dummy entity and return the result.
+	var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var rangeQuery = rangeManager.ExecuteQuery(this.dummyTargetEntity, 0, radius, players, IID_DamageReceiver);
+	return rangeQuery;
+};
+
+/****************************************
+ * Called when some units kills something (another unit, building, animal etc)
+ * killerEntity = <entity id>
+ * targetEntity = <entity id>
+ */
+Damage.prototype.TargetKilled = function(killerEntity, targetEntity)
+{
+	// Add to killer statistics.
+	var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
+	if (cmpKillerPlayerStatisticsTracker)
+		cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
+	// Add to loser statistics.
+	var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
+	if (cmpTargetPlayerStatisticsTracker)
+		cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
+
+	// If killer can collect loot, lets try to collect it.
+	var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
+	if (cmpLooter)
+		cmpLooter.Collect(targetEntity);
+};
+
+// Gets the straight line distance between p1 and p2
+Damage.prototype.VectorDistanceSquared = function(p1, p2)
+{
+	return ((p1.x - p2.x)*(p1.x - p2.x) + (p1.z - p2.z)*(p1.z - p2.z));
+};
+
+// Gets the dot product of two vectors.
+Damage.prototype.VectorDot = function(p1, p2)
+{
+    return (p1.x * p2.x + p1.z * p2.z);
+};
+
+// Gets the 2D interpreted version of the cross product of two vectors.
+Damage.prototype.VectorCross = function(p1, p2)
+{
+    return (p1.x * p2.z - p1.z * p2.x);
+};
+
+Engine.RegisterComponentType(IID_Damage, "Damage", Damage);
Index: 0ad/binaries/data/mods/public/simulation/components/interfaces/Damage.js
===================================================================
--- 0ad/binaries/data/mods/public/simulation/components/interfaces/Damage.js	(revision 0)
+++ 0ad/binaries/data/mods/public/simulation/components/interfaces/Damage.js	(working copy)
@@ -0,0 +1 @@
+Engine.RegisterInterface("Damage");
Index: 0ad/binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- 0ad/binaries/data/mods/public/simulation/components/Attack.js	(revision 13543)
+++ 0ad/binaries/data/mods/public/simulation/components/Attack.js	(working copy)
@@ -488,29 +488,12 @@
 	else
 	{
 		// Melee attack - hurt the target immediately
-		this.CauseDamage({"type": type, "target": target});
+		var cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
+		cmpDamage.CauseDamage({"strengths":this.GetAttackStrengths(type), "target":target, "attacker":this.entity, "multiplier":this.GetAttackBonus(type, target), "type":type});
 	}
 	// TODO: charge attacks (need to design how they work)
 };
 
-/**
- * Called when some units kills something (another unit, building, animal etc)
- */
-Attack.prototype.TargetKilled = function(killerEntity, targetEntity)
-{
-	var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
-	if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
-	var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
-	if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
-
-	// if unit can collect loot, lets try to collect it
-	var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
-	if (cmpLooter)
-	{
-		cmpLooter.Collect(targetEntity);
-	}
-};
-
 Attack.prototype.InterpolatedLocation = function(ent, lateness)
 {
 	var cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
@@ -532,12 +515,7 @@
 {
 	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);
@@ -580,60 +558,22 @@
 	var targetPosition = this.InterpolatedLocation(data.target, lateness);
 	if (!targetPosition)
 		return;
-	
+	var cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
 	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);
-			}
-		}
+		cmpDamage.CauseSplashDamage({"attacker":this.entity, "origin":data.position, "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":undefined});
 	}
 	
 	if (this.testCollision(data.target, data.position, lateness))
 	{
+		data.attacker=this.entity
+		data.multiplier=this.GetAttackBonus(data.type, data.target)
+		data.strengths=this.GetAttackStrengths(data.type)
 		// Hit the primary target
-		this.CauseDamage(data);
+		cmpDamage.CauseDamage(data);
 		
 		// Remove the projectile
 		var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
@@ -642,15 +582,16 @@
 	else
 	{
 		// If we didn't hit the main target look for nearby units
-		var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2);
+		var ents = cmpDamage.EntitiesNearPoint(data.position, 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);
+				var newData = {"strengths":this.GetAttackStrengths(data.type), "target":ents[i], "attacker":this.entity, "multiplier":this.GetAttackBonus(data.type, ents[i]), "type":data.type};
 				
+				cmpDamage.CauseDamage(newData);
+				
 				// Remove the projectile
 				var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
 				cmpProjectileManager.RemoveProjectile(data.projectileId);
@@ -659,53 +600,6 @@
 	}
 };
 
-Attack.prototype.GetNearbyEntities = function(startEnt, range, friendlyFire)
-{
-	var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
-	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
-	var owner = cmpOwnership.GetOwner();
-	var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);
-	var numPlayers = cmpPlayerManager.GetNumPlayers();
-	var players = [];
-	
-	for (var i = 1; i < numPlayers; ++i)
-	{	
-		// Only target enemies unless friendly fire is on
-		if (cmpPlayer.IsEnemy(i) || friendlyFire)
-			players.push(i);
-	}
-	
-	var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
-	return rangeManager.ExecuteQuery(startEnt, 0, range, players, IID_DamageReceiver);
-}
-
-/**
- * Inflict damage on the target
- */
-Attack.prototype.CauseDamage = function(data)
-{
-	var strengths = this.GetAttackStrengths(data.type);
-	
-	var damageMultiplier = this.GetAttackBonus(data.type, data.target);
-	if (data.damageMultiplier !== undefined)
-		damageMultiplier *= data.damageMultiplier;
-	
-	var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
-	if (!cmpDamageReceiver)
-		return;
-	var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier);
-	// if target killed pick up loot and credit experience
-	if (targetState.killed == true)
-	{
-		this.TargetKilled(this.entity, data.target);
-	}
-
-	Engine.PostMessage(data.target, MT_Attacked,
-		{ "attacker": this.entity, "target": data.target, "type": data.type, "damage": -targetState.change });
-
-	PlaySound("attack_impact", this.entity);
-};
-
 Attack.prototype.OnUpdate = function(msg)
 {
 	this.turnLength = msg.turnLength;
Index: 0ad/binaries/data/mods/public/simulation/components/PlayerManager.js
===================================================================
--- 0ad/binaries/data/mods/public/simulation/components/PlayerManager.js	(revision 13543)
+++ 0ad/binaries/data/mods/public/simulation/components/PlayerManager.js	(working copy)
@@ -46,4 +46,8 @@
 	this.playerEntities = [];
 };
 
+PlayerManager.prototype.GetAllPlayers = function()
+{
+	return this.playerEntities;
+};
 Engine.RegisterComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);
Index: 0ad/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- 0ad/binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 13543)
+++ 0ad/binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -1760,8 +1760,17 @@
 	"SetObstructionDebugOverlay": 1,
 	"SetMotionDebugOverlay": 1,
 	"SetRangeDebugOverlay": 1,
+	"TestSD": 1,
 };
 
+GuiInterface.prototype.TestSD = function(pl, d)
+{
+	var dm = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
+	if (!dm) warn("IID_Damage is returning " + dm);
+	else dm.CauseSplashDamage({"origin":{"x":d.x, "z":d.z}, "shape":"circular", "radius":d.r, "strengths":{"hack":20.0, "pierce":20.0, "crush":20.0}, "attacker":SYSTEM_ENTITY});
+	return
+};
+
 GuiInterface.prototype.ScriptCall = function(player, name, args)
 {
 	if (exposedFunctions[name])
Index: 0ad/binaries/data/mods/public/simulation/templates/special/dummy.xml
===================================================================
--- 0ad/binaries/data/mods/public/simulation/templates/special/dummy.xml	(revision 0)
+++ 0ad/binaries/data/mods/public/simulation/templates/special/dummy.xml	(working copy)
@@ -0,0 +1,8 @@
+<Entity>
+	<Position>
+		<Altitude>0</Altitude>
+		<Anchor>upright</Anchor>
+		<Floating>false</Floating>
+		<TurnRate>6.0</TurnRate>
+	</Position>
+</Entity>
