Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 14310)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -378,7 +378,7 @@
 		// We use the distance between the entities to account for ranged attacks
 		var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance);
 		var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-		if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1))
+		if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1, 0))
 		{
 			// We've started fleeing from the given target
 			if (this.IsAnimal())
@@ -485,7 +485,7 @@
 		}
 
 		// Try to move within attack range
-		if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+		if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 		{
 			// We've started walking to the given point
 			if (this.IsAnimal())
@@ -1587,7 +1587,7 @@
 					} 
 					else 
 					{
-						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+						if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 						{
 							this.SetNextState("APPROACHING");
 						}
@@ -1614,7 +1614,7 @@
 					// If we're not in range yet (maybe we stopped moving), move to target again
 					if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
 					{
-						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+						if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 							this.SetNextState("APPROACHING");
 						else
 						{
@@ -1715,7 +1715,7 @@
 						// Can't reach it - try to chase after it
 						if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
 						{
-							if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
+							if (this.MoveToTargetAttackRange(target, this.order.data.attackType))
 							{
 								this.SetNextState("COMBAT.CHASING");
 								return;
@@ -3843,10 +3843,10 @@
 		return false;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
+	return cmpUnitMotion.MoveToTargetRange(target, 0, 0, 0);
 };
 
-UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
+UnitAI.prototype.MoveToTargetRange = function(target, iid, type, time)
 {
 	if (!this.CheckTargetVisible(target))
 		return false;
@@ -3855,9 +3855,11 @@
 	if (!cmpRanged)
 		return false;
 	var range = cmpRanged.GetRange(type);
+	if (!time) 
+		var time = 0;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, time);
 };
 
 /**
@@ -3865,17 +3867,19 @@
  * for melee attacks, this goes straight to the default range checks
  * for ranged attacks, the parabolic range is used
  */
-UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type)
+UnitAI.prototype.MoveToTargetAttackRange = function(target, type)
 {
+	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+	var timers = cmpAttack.GetTimers(type);
+	var time = timers.prepare; 
 
 	if(type!= "Ranged") 
-		return this.MoveToTargetRange(target, iid, type);
+		return this.MoveToTargetRange(target, IID_Attack, type, time);
 	
 	if (!this.CheckTargetVisible(target)) 
 		return false;
 	
-	var cmpRanged = Engine.QueryInterface(this.entity, iid);
-	var range = cmpRanged.GetRange(type);
+	var range = cmpAttack.GetRange(type);
 
 	var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
 	var s = thisCmpPosition.GetPosition();
@@ -3900,20 +3904,23 @@
 	var guessedMaxRange = (range.max + parabolicMaxRange)/2;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange))
+	if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange, time))
 		return true;
 
 	// if that failed, try closer
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange));
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange), time);
 };
 
-UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
+UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max, time)
 {
 	if (!this.CheckTargetVisible(target))
 		return false;
 
+	if (!time)
+		var time = 0;
+
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, min, max);
+	return cmpUnitMotion.MoveToTargetRange(target, min, max, time);
 };
 
 UnitAI.prototype.MoveToGarrisonRange = function(target)
@@ -3927,7 +3934,7 @@
 	var range = cmpGarrisonHolder.GetLoadingRange();
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, 0);
 };
 
 UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max)
Index: source/simulation2/components/CCmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/CCmpUnitMotion.cpp	(revision 14310)
+++ source/simulation2/components/CCmpUnitMotion.cpp	(working copy)
@@ -132,6 +132,7 @@
 	bool m_FormationController;
 	fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second
 	fixed m_RunSpeed, m_OriginalRunSpeed;
+	fixed m_TimeToStayInRange, m_TurnLength;
 	ICmpPathfinder::pass_class_t m_PassClass;
 	ICmpPathfinder::cost_class_t m_CostClass;
 
@@ -297,6 +298,7 @@
 		m_WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
 		m_Speed = m_WalkSpeed;
 		m_CurSpeed = fixed::Zero();
+		m_TimeToStayInRange = fixed::Zero();
 
 		if (paramNode.GetChild("Run").IsOk())
 			m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
@@ -350,6 +352,9 @@
 
 		serialize.NumberFixed_Unbounded("speed", m_Speed);
 
+		serialize.NumberFixed_Unbounded("timeToStayInRange", m_TimeToStayInRange);
+		serialize.NumberFixed_Unbounded("turnLength", m_TurnLength);
+
 		serialize.Bool("moving", m_Moving);
 		serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
 
@@ -379,8 +384,8 @@
 		{
 			if (m_FormationController)
 			{
-				fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;
-				Move(dt);
+				m_TurnLength = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;
+				Move(m_TurnLength);
 			}
 			break;
 		}
@@ -388,8 +393,8 @@
 		{
 			if (!m_FormationController)
 			{
-				fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength;
-				Move(dt);
+				m_TurnLength = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;
+				Move(m_TurnLength);
 			}
 			break;
 		}
@@ -470,7 +475,7 @@
 
 	virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
 	virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange);
 	virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
 	virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
 
@@ -548,7 +553,7 @@
 		GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
 	}
 
-	bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target);
+	bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target, entity_pos_t timeToStayInRange);
 
 	/**
 	 * Handle the result of an asynchronous path query.
@@ -995,11 +1000,19 @@
 						}
 						else
 						{
+							// check if target was reached in case of a moving target
+							CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
+							if 
+							(
+								cmpUnitMotion && 
+								cmpUnitMotion->IsMoving() &&
+								MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange, m_TimeToStayInRange)
+							)
+								return;
+
 							// Not in formation, so just finish moving
-
 							StopMoving();
 
-
 							if (m_FacePointAfterMove)
 								FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z);
 							// TODO: if the goal was a square building, we ought to point towards the
@@ -1300,10 +1313,10 @@
 
 bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
 {
-	return MoveToPointRange(x, z, minRange, maxRange, INVALID_ENTITY);
+	return MoveToPointRange(x, z, minRange, maxRange, INVALID_ENTITY, fixed::Zero());
 }
 
-bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target)
+bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target, entity_pos_t timeToStayInRange)
 {
 	PROFILE("MoveToPointRange");
 
@@ -1313,6 +1326,26 @@
 
 	CFixedVector2D pos = cmpPosition->GetPosition2D();
 
+	entity_pos_t overShoot = fixed::Zero();
+	entity_pos_t margin = fixed::Zero();
+	// if the target is also moving, take an overshoot into account
+	CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), target);
+	if (cmpUnitMotion)
+	{
+		margin = cmpUnitMotion->GetCurrentSpeed().Multiply(m_TurnLength);
+		if (timeToStayInRange > m_TurnLength)
+			overShoot = cmpUnitMotion->GetCurrentSpeed().Multiply(timeToStayInRange - m_TurnLength);
+	}
+
+	entity_pos_t origMaxRange = maxRange + margin;
+	entity_pos_t origMinRange = minRange;
+	maxRange -= overShoot;
+	minRange += overShoot;
+	if (maxRange <= origMinRange && origMaxRange >= origMinRange)
+		maxRange = origMinRange;
+	if (maxRange > fixed::Zero() && minRange > maxRange)
+		minRange = maxRange;	
+
 	ICmpPathfinder::Goal goal;
 
 	if (minRange.IsZero() && maxRange.IsZero())
@@ -1381,8 +1414,9 @@
 	m_State = STATE_INDIVIDUAL_PATH;
 	m_TargetEntity = target;
 	m_TargetOffset = CFixedVector2D();
-	m_TargetMinRange = minRange;
-	m_TargetMaxRange = maxRange;
+	m_TargetMinRange = origMinRange;
+	m_TargetMaxRange = origMaxRange;
+	m_TimeToStayInRange = timeToStayInRange;
 	m_FinalGoal = goal;
 
 	BeginPathing(pos, goal);
@@ -1454,10 +1488,29 @@
 	return (errCircle < errSquare);
 }
 
-bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
 {
 	PROFILE("MoveToTargetRange");
+	entity_pos_t overShoot = fixed::Zero();
+	entity_pos_t margin = fixed::Zero();
+	// if the target is also moving, take an overshoot into account
+	CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), target);
+	if (cmpUnitMotion)
+	{
+		margin = cmpUnitMotion->GetCurrentSpeed().Multiply(m_TurnLength);
+		if (timeToStayInRange > m_TurnLength)
+			overShoot = cmpUnitMotion->GetCurrentSpeed().Multiply(timeToStayInRange - m_TurnLength);
+	}
 
+	entity_pos_t origMaxRange = maxRange + margin;
+	entity_pos_t origMinRange = minRange;
+	maxRange -= overShoot;
+	minRange += overShoot;
+	if (maxRange <= origMinRange && origMaxRange >= origMinRange)
+		maxRange = origMinRange;
+	if (maxRange > fixed::Zero() && minRange > maxRange)
+		minRange = maxRange;	
+
 	CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
 	if (!cmpPosition || !cmpPosition->IsInWorld())
 		return false;
@@ -1578,8 +1631,9 @@
 		m_State = STATE_INDIVIDUAL_PATH;
 		m_TargetEntity = target;
 		m_TargetOffset = CFixedVector2D();
-		m_TargetMinRange = minRange;
-		m_TargetMaxRange = maxRange;
+		m_TargetMinRange = origMinRange;
+		m_TargetMaxRange = origMaxRange;
+		m_TimeToStayInRange = timeToStayInRange;
 		m_FinalGoal = goal;
 
 		BeginPathing(pos, goal);
@@ -1596,7 +1650,7 @@
 
 		CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
 
-		return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange, target);
+		return MoveToPointRange(targetPos.X, targetPos.Y, origMinRange, origMaxRange, target, timeToStayInRange);
 	}
 }
 
@@ -1609,6 +1663,11 @@
 	if (!cmpPosition || !cmpPosition->IsInWorld())
 		return false;
 
+	// if the target is also moving, take an overshoot into account
+	CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), target);
+	if (cmpUnitMotion)
+		maxRange += cmpUnitMotion->GetCurrentSpeed().Multiply(m_TurnLength);
+
 	CFixedVector2D pos = cmpPosition->GetPosition2D();
 
 	CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
Index: source/simulation2/components/ICmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/ICmpUnitMotion.cpp	(revision 14310)
+++ source/simulation2/components/ICmpUnitMotion.cpp	(working copy)
@@ -26,7 +26,7 @@
 DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
-DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
+DEFINE_INTERFACE_METHOD_4("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
@@ -60,9 +60,9 @@
 		return m_Script.Call<bool>("IsInTargetRange", target, minRange, maxRange);
 	}
 
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
 	{
-		return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange);
+		return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange, timeToStayInRange);
 	}
 
 	virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
Index: source/simulation2/components/ICmpUnitMotion.h
===================================================================
--- source/simulation2/components/ICmpUnitMotion.h	(revision 14310)
+++ source/simulation2/components/ICmpUnitMotion.h	(working copy)
@@ -66,7 +66,7 @@
 	 * and sends another MotionChanged after finishing moving.
 	 * If maxRange is negative, then the maximum range is treated as infinity.
 	 */
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange) = 0;
 
 	/**
 	 * Join a formation, and move towards a given offset relative to the formation controller entity.
