Index: source/simulation2/docs/SimulationDocs.h
===================================================================
--- source/simulation2/docs/SimulationDocs.h	(revision 11349)
+++ source/simulation2/docs/SimulationDocs.h	(working copy)
@@ -395,7 +395,8 @@
 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.)
Index: source/simulation2/components/tests/test_RangeManager.h
===================================================================
--- source/simulation2/components/tests/test_RangeManager.h	(revision 11349)
+++ source/simulation2/components/tests/test_RangeManager.h	(working copy)
@@ -50,6 +50,8 @@
 	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)) { }
Index: source/simulation2/components/ICmpProjectileManager.h
===================================================================
--- source/simulation2/components/ICmpProjectileManager.h	(revision 11349)
+++ source/simulation2/components/ICmpProjectileManager.h	(working copy)
@@ -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 @@
 	 * @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)
 };
Index: source/simulation2/components/ICmpProjectileManager.cpp
===================================================================
--- source/simulation2/components/ICmpProjectileManager.cpp	(revision 11349)
+++ source/simulation2/components/ICmpProjectileManager.cpp	(working copy)
@@ -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)
Index: source/simulation2/components/ICmpPosition.h
===================================================================
--- source/simulation2/components/ICmpPosition.h	(revision 11349)
+++ source/simulation2/components/ICmpPosition.h	(working copy)
@@ -108,6 +108,19 @@
 	 */
 	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.
Index: source/simulation2/components/ICmpPosition.cpp
===================================================================
--- source/simulation2/components/ICmpPosition.cpp	(revision 11349)
+++ source/simulation2/components/ICmpPosition.cpp	(working copy)
@@ -32,6 +32,8 @@
 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)
Index: source/simulation2/components/CCmpProjectileManager.cpp
===================================================================
--- source/simulation2/components/CCmpProjectileManager.cpp	(revision 11349)
+++ source/simulation2/components/CCmpProjectileManager.cpp	(working copy)
@@ -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 @@
 	virtual void Init(const CParamNode& UNUSED(paramNode))
 	{
 		m_ActorSeed = 0;
+		m_Id = 1;
 	}
 
 	virtual void Deinit()
@@ -86,7 +89,7 @@
 		case MT_Interpolate:
 		{
 			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
-			Interpolate(msgData.frameTime, msgData.offset);
+			Interpolate(msgData.frameTime);
 			break;
 		}
 		case MT_RenderSubmit:
@@ -98,52 +101,51 @@
 		}
 	}
 
-	virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity)
+	virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
 	{
-		LaunchProjectile(source, CFixedVector3D(), target, speed, gravity);
+		return LaunchProjectile(source, target, speed, gravity);
 	}
+	
+	virtual void RemoveProjectile(uint32_t);
 
-	virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
-	{
-		LaunchProjectile(source, target, INVALID_ENTITY, speed, gravity);
-	}
-
 private:
 	struct Projectile
 	{
 		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 @@
 		// 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 @@
 
 		CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
 		if (!sourcePos)
-			return;
+			return 0;
 
 		sourceVec = sourcePos->GetPosition();
 		sourceVec.Y += 3.f;
@@ -169,31 +171,20 @@
 
 	CVector3D targetVec;
 
-	if (targetEnt == INVALID_ENTITY)
-	{
-		targetVec = CVector3D(targetPoint);
-	}
-	else
-	{
-		CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), targetEnt);
-		if (!cmpTargetPosition)
-			return;
+    targetVec = CVector3D(targetPoint);
 
-		targetVec = CVector3D(cmpTargetPosition->GetPosition());
-	}
-
 	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 @@
 	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,25 +216,7 @@
 		// 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;
 
 	// Compute the vertical velocity that's needed so we travel in a ballistic curve and
@@ -296,11 +271,11 @@
 	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 @@
 		// 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::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);
Index: source/simulation2/components/CCmpPosition.cpp
===================================================================
--- source/simulation2/components/CCmpPosition.cpp	(revision 11349)
+++ source/simulation2/components/CCmpPosition.cpp	(working copy)
@@ -66,7 +66,9 @@
 	// 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 @@
 		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 @@
 
 	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 @@
 		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 @@
 		}
 		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;
 
Index: source/simulation2/components/CCmpObstructionManager.cpp
===================================================================
--- source/simulation2/components/CCmpObstructionManager.cpp	(revision 11349)
+++ source/simulation2/components/CCmpObstructionManager.cpp	(working copy)
@@ -927,10 +927,19 @@
 
 	// 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)
+	
+	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;
+		}
+	}
 
-	// TODO: actually do that
-	// (This might matter when you tell a unit to walk too close to the edge of a building)
-
 	return false;
 }
 
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js	(revision 11349)
+++ binaries/data/mods/public/simulation/components/Attack.js	(working copy)
@@ -1,5 +1,6 @@
 function Attack() {}
 
+
 var bonusesSchema = 
 	"<optional>" +
 		"<element name='Bonuses'>" +
@@ -48,12 +49,21 @@
 			"<PrepareTime>800</PrepareTime>" +
 			"<RepeatTime>1600</RepeatTime>" +
 			"<ProjectileSpeed>50.0</ProjectileSpeed>" +
+			"<Spread>2.5</Spread>" +
 			"<Bonuses>" +
 				"<Bonus1>" +
 					"<Classes>Cavalry</Classes>" +
 					"<Multiplier>2</Multiplier>" +
 				"</Bonus1>" +
 			"</Bonuses>" +
+			"<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>" +
@@ -94,7 +104,21 @@
 				"<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>" +
+				"<optional><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></optional>" +
 				bonusesSchema +
+				"<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>" +
@@ -143,11 +167,14 @@
 
 Attack.prototype.GetAttackStrengths = function(type)
 {
+	var template = this.template[type];
+	if (!template)
+		template = this.template[type.split(".")[0]].Splash;
 	// Convert attack values to numbers, default 0 if unspecified
 	return {
-		hack: +(this.template[type].Hack || 0),
-		pierce: +(this.template[type].Pierce || 0),
-		crush: +(this.template[type].Crush || 0)
+		hack: +(template.Hack || 0),
+		pierce: +(template.Pierce || 0),
+		crush: +(template.Crush || 0)
 	};
 };
 
@@ -162,16 +189,20 @@
 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){
@@ -188,6 +219,20 @@
 	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.GetNormalizedDistribution = 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
@@ -198,76 +243,59 @@
 	// 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 = 1.5; //
+		if (this.template.Ranged.Spread !== undefined) // explicit test here because the value might be 0
+			spread = this.template.Ranged.Spread;
+		
+		//horizSpeed /= 2; gravity /= 2; // slow it down for testing
+		
 		var selfPosition = Engine.QueryInterface(this.entity, IID_Position).GetPosition();
 		var targetPosition = Engine.QueryInterface(target, IID_Position).GetPosition();
-		var horizDistance = Math.sqrt(Math.pow(targetPosition.x - selfPosition.x, 2) + Math.pow(targetPosition.z - selfPosition.z, 2));
+		var relativePosition = {"x": targetPosition.x - selfPosition.x, "z": targetPosition.z - selfPosition.z}
+		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.GetNormalizedDistribution();
+		var offsetX = randNorm[0] * spread * (1 + this.VectorLength(targetVelocity) / 20);
+		var offsetZ = randNorm[1] * spread * (1 + this.VectorLength(targetVelocity) / 20);
 
-		// 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 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};
+		
 		// 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
 	{
@@ -295,6 +323,170 @@
 	}
 };
 
+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
  */
@@ -302,12 +494,14 @@
 {
 	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)
 	{
@@ -320,4 +514,9 @@
 	PlaySound("attack_impact", this.entity);
 };
 
+Attack.prototype.OnUpdate = function(msg)
+{
+	this.turnLength = msg.turnLength;
+}
+
 Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
