Index: binaries/data/mods/public/simulation/components/BuildingAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildingAI.js	(revision 13457)
+++ binaries/data/mods/public/simulation/components/BuildingAI.js	(working copy)
@@ -99,7 +99,7 @@
 	if (cmpAttack)
 	{
 		var range = cmpAttack.GetRange("Ranged");
-		this.enemyUnitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
+		this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
 		cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
 	}
 };
@@ -133,7 +133,7 @@
 		var range = cmpAttack.GetRange("Ranged");
 
 		// This query is only interested in Gaia entities that can attack.
-		this.gaiaUnitsQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
+		this.gaiaUnitsQuery = rangeMan.CreateActiveParabolicQuery(this.entity, range.min, range.max, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
 		rangeMan.EnableActiveQuery(this.gaiaUnitsQuery);
 	}
 };
@@ -239,12 +243,33 @@
 			//Fire N arrows, 0 <= N <= Number of arrows left
 			arrowsToFire = Math.floor(Math.random() * this.arrowsLeft);
 		}
+
 		if (this.targetUnits.length > 0)
 		{
+			var clonedTargets = this.targetUnits.slice();
 			for (var i = 0;i < arrowsToFire;i++)
 			{
-				cmpAttack.PerformAttack("Ranged", this.targetUnits[Math.floor(Math.random() * this.targetUnits.length)]);
-				PlaySound("arrowfly", this.entity);
+				var target = clonedTargets[Math.floor(Math.random() * this.targetUnits.length)];
+				if (
+					target && 
+					this.CheckTargetVisible(target) 
+				   ) 
+				{
+					cmpAttack.PerformAttack("Ranged", target);
+					PlaySound("arrowfly", this.entity);
+
+				}
+				else 
+				{
+					clonedTargets.splice(clonedTargets.indexOf(target),1);
+					i--; // one extra arrow left to fire
+					if(clonedTargets.length < 1) 
+					{
+						this.arrowsLeft += arrowsToFire;
+						// no targets found in this round, save arrows and go to next round
+						break;
+					}
+				}
 			}
 			this.arrowsLeft -= arrowsToFire;
 		}
@@ -252,4 +277,22 @@
 	}
 };
 
+/**
+ * Returns true if the target entity is visible through the FoW/SoD.
+ */
+BuildingAI.prototype.CheckTargetVisible = function(target)
+{
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (!cmpOwnership)
+		return false;
+
+	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+
+	if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
+		return false;
+
+	// Either visible directly, or visible in fog
+	return true;
+};
+
 Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI);
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 13457)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -367,7 +367,7 @@
 		this.attackType = type;
 
 		// If we are already at the target, try attacking it from here
-		if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
+		if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.attackType))
 		{
 			this.StopMoving();
 			// For packable units within attack range:
@@ -427,8 +427,8 @@
 			return;
 		}
 
-		// Try to move within attack range
-		if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
+		// Try to move within attack range 
+		if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0.5))
 		{
 			// We've started walking to the given point
 			if (this.IsAnimal())
@@ -1314,11 +1314,27 @@
 				},
 
 				"MoveCompleted": function() {
-					// If the unit needs to unpack, do so
-					if (this.CanUnpack())
-						this.SetNextState("UNPACKING");
-					else
-						this.SetNextState("ATTACKING");
+
+					if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack , this.attackType)) 
+					{
+						// If the unit needs to unpack, do so
+						if (this.CanUnpack())
+							this.SetNextState("UNPACKING");
+						else
+							this.SetNextState("ATTACKING");
+					} 
+					else 
+					{
+						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0))
+						{
+							this.SetNextState("APPROACHING");
+						}
+						else
+						{
+							// Give up
+							this.FinishOrder();
+						}
+					}
 				},
 
 				"Attacked": function(msg) {
@@ -1334,9 +1350,9 @@
 			"UNPACKING": {
 				"enter": function() {
 					// If we're not in range yet (maybe we stopped moving), move to target again
-					if (!this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
+					if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.attackType))
 					{
-						if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
+						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0))
 							this.SetNextState("APPROACHING");
 						else
 						{
@@ -1402,7 +1418,7 @@
 					if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null))
 					{
 						// Check we can still reach the target
-						if (this.CheckTargetRange(target, IID_Attack, this.attackType))
+						if (this.CheckTargetAttackRange(target, IID_Attack, this.attackType))
 						{
 							var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
 							this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
@@ -3287,6 +3303,54 @@
 	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
 };
 
+/**
+ * Move unit so we hope the target is in the attack range
+ * for melee attacks, this goes straight to the default range checks
+ * for ranged attacks, the parabolic range is used, so we can't know exactly at what horizontal range the target can be reached
+ * That's why a guess is needed
+ * a guess of 1 will take the maximum of the possible ranges, and stay far away
+ * a guess of 0 will take the minimum of the possible ranges and, in most cases, will have the target in range.
+ * every guess inbetween is a linear interpollation
+ */
+UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type,guess)
+{
+
+	if(type!= "Ranged") {
+		return this.MoveToTargetRange(target, iid, type);
+	}
+	if (!this.CheckTargetVisible(target)) {
+		return false;
+	}
+	var cmpRanged = Engine.QueryInterface(this.entity, iid);
+	var range = cmpRanged.GetRange(type);
+
+	var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	var s = thisCmpPosition.GetPosition();
+
+	var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
+	if(!targetCmpPosition.IsInWorld()) 
+		return false;   
+
+	var t = targetCmpPosition.GetPosition();
+	// h is positive when I'm higher than the target
+	var h = s.y-t.y;
+
+	// No negative roots please
+	if(h>-range.max/2) {
+		var parabolicMaxRange = Math.sqrt(range.max*range.max+2*range.max*h);
+	} else {
+		// return false? Or hope you come close enough?
+		var parabolicMaxRange = 0;
+		//return false;
+	}
+	// the parabole changes while walking, take something in the middle
+	var guessedMaxRange = Math.max(range.max, parabolicMaxRange)*guess+Math.min(range.max, parabolicMaxRange)*(1-guess) ;
+	
+	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+	var r = cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange);
+	return r;
+};
+
 UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
 {
 	if (!this.CheckTargetVisible(target))
@@ -3311,6 +3375,47 @@
 	return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
 };
 
+/**
+ * Check if the target is inside the attack range 
+ * For melee attacks, this goes straigt to the regular range calculation
+ * For ranged attacks, the parabolic formula is used to accout for bigger ranges
+ * when the target is lower, and smaller ranges when the target is higher
+ */ 
+UnitAI.prototype.CheckTargetAttackRange = function(target, iid, type)
+{
+
+	if (type != "Ranged") 
+	{
+		return this.CheckTargetRange(target,iid,type);
+	}
+	
+	var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
+	if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) 
+	{
+		return false; 
+	}
+
+	var cmpRanged = Engine.QueryInterface(this.entity, iid);
+	var range = cmpRanged.GetRange(type);
+
+	var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	var s = thisCmpPosition.GetPosition();
+
+	var t = targetCmpPosition.GetPosition();
+
+	var h = s.y-t.y;
+	var maxRangeSq = 2*range.max*(h + range.max/2);
+	if (maxRangeSq<0)
+	{ 
+		// certainly outside the range, the target is even too high to reach.
+		return false;
+	}
+
+	// use native range checking function to take account of target shape 
+	// in case of big targets
+	return this.CheckTargetRangeExplicit(target,range.min,Math.sqrt(maxRangeSq));
+};
+
 UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max)
 {
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
Index: source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- source/simulation2/components/CCmpRangeManager.cpp	(revision 13457)
+++ source/simulation2/components/CCmpRangeManager.cpp	(working copy)
@@ -20,10 +20,12 @@
 #include "simulation2/system/Component.h"
 #include "ICmpRangeManager.h"
 
+#include "ICmpTerrain.h"
 #include "simulation2/MessageTypes.h"
 #include "simulation2/components/ICmpPosition.h"
 #include "simulation2/components/ICmpTerritoryManager.h"
 #include "simulation2/components/ICmpVision.h"
+#include "simulation2/components/ICmpWaterManager.h"
 #include "simulation2/helpers/Render.h"
 #include "simulation2/helpers/Spatial.h"
 
@@ -44,6 +46,7 @@
 struct Query
 {
 	bool enabled;
+	bool parabolic;
 	entity_id_t source;
 	entity_pos_t minRange;
 	entity_pos_t maxRange;
@@ -88,6 +91,44 @@
 }
 
 /**
+ * Checks whether v is in a parabolic range of (0,0,0)
+ * The highest point of the paraboloid is (0,range/2,0)
+ * and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
+ * 
+ * Avoids sqrting and overflowing.
+ */
+static bool InParabolicRange(CFixedVector3D v, fixed range) 
+{
+	i64 x = (i64)v.X.GetInternalValue(); // abs(x) <= 2^31
+	i64 z = (i64)v.Z.GetInternalValue();
+	i64 xx = (x * x); // xx <= 2^62
+	i64 zz = (z * z);
+	i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
+	
+	i64 y = (i64)v.Y.GetInternalValue();
+
+	i64 c = (i64)range.GetInternalValue();
+	i64 c_2 = c >> 1; 
+
+	i64 c2 = (c_2-y)*c;
+
+	if (d2 <= c2)
+		return true;
+
+	return false;
+}
+
+struct EntityParabolicRangeOutline
+{
+	entity_id_t source;
+	CFixedVector3D position;
+	entity_pos_t range;
+	std::vector<float> outline;
+};
+
+static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
+
+/**
  * Representation of an entity, with the data needed for queries.
  */
 struct EntityData
@@ -113,6 +154,7 @@
 	void operator()(S& serialize, const char* UNUSED(name), Query& value)
 	{
 		serialize.Bool("enabled", value.enabled);
+		serialize.Bool("parabolic",value.parabolic);
 		serialize.NumberU32_Unbounded("source", value.source);
 		serialize.NumberFixed_Unbounded("min range", value.minRange);
 		serialize.NumberFixed_Unbounded("max range", value.maxRange);
@@ -589,6 +631,16 @@
 		return id;
 	}
 
+	virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
+		entity_pos_t minRange, entity_pos_t maxRange,
+		std::vector<int> owners, int requiredInterface, u8 flags)
+	{
+		tag_t id = m_QueryNext++;
+		m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, owners, requiredInterface, flags);
+
+		return id;
+	}
+
 	virtual void DestroyActiveQuery(tag_t tag)
 	{
 		if (m_Queries.find(tag) == m_Queries.end())
@@ -812,8 +864,47 @@
 				r.push_back(it->first);
 			}
 		}
-		else
+		// Not the entire world, so check a parabolic range, or a regular range
+		else if (q.parabolic) 
 		{
+			CFixedVector3D pos3d = cmpSourcePosition->GetPosition();
+			// Get a quick list of entities that are potentially in range, with a cutoff of 3*maxRange
+			std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange*2);
+
+			for (size_t i = 0; i < ents.size(); ++i)
+			{
+				std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.find(ents[i]);
+				ENSURE(it != m_EntityData.end());
+
+				if (!TestEntityQuery(q, it->first, it->second))
+					continue;
+				
+				CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
+				if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
+					continue;
+				CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
+
+				// Restrict based on precise distance
+				if (!InParabolicRange(
+						CFixedVector3D(it->second.x, secondPosition.Y, it->second.z) 
+							- pos3d, 
+						q.maxRange))
+					continue;
+
+				if (!q.minRange.IsZero())
+				{
+					int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
+					if (distVsMin < 0)
+						continue;
+				}
+
+				r.push_back(it->first);
+
+			}
+		}
+		// check a regular range (i.e. not the entire world, and not parabolic)
+		else 
+		{
 			// Get a quick list of entities that are potentially in range
 			std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange);
 
@@ -838,10 +929,86 @@
 				}
 
 				r.push_back(it->first);
+
 			}
 		}
 	}
 
+	virtual std::vector<float> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff) 
+	{
+
+		entity_pos_t minAngle = entity_pos_t::FromFloat(2.0f*3.15f/80.0f);
+	
+		entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
+
+		std::vector<float> r;
+		
+
+		CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+		CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
+		entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
+
+		if (cmpTerrain) 
+		{
+			for (entity_pos_t angle = entity_pos_t::Zero(); angle < entity_pos_t::FromFloat(2.0f*3.14); angle += minAngle)
+			{
+				entity_pos_t sin;
+				entity_pos_t cos;
+				entity_pos_t minDistance = entity_pos_t::Zero();
+				entity_pos_t maxDistance = cutoff;
+				sincos_approx(angle,sin,cos);
+
+				CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(),entity_pos_t::Zero());
+				CFixedVector2D maxVector = CFixedVector2D(cos,sin).Multiply(cutoff);
+				entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X,pos.Z+maxVector.Y);
+				// use water level to display range on water
+				targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
+
+				if (InParabolicRange(CFixedVector3D(maxVector.X,targetHeight-pos.Y,maxVector.Y),maxRange))
+				{
+					r.push_back((pos.X+maxVector.X).ToFloat());
+					r.push_back((pos.Z+maxVector.Y).ToFloat());
+					continue;
+				}
+				
+				// Loop until vectors come close enough
+				while ((maxVector - minVector).CompareLength(precision) > 0)
+				{
+					// difference still bigger than precision, bisect to get smaller difference
+					entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
+
+					CFixedVector2D newVector = CFixedVector2D(cos,sin).Multiply(newDistance);
+
+					// get the height of the ground
+					targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X,pos.Z+newVector.Y);
+					targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
+
+					if (InParabolicRange(CFixedVector3D(newVector.X,targetHeight-pos.Y,newVector.Y),maxRange))
+					{
+						// new vector is in parabolic range, so this is a new minVector
+						minVector = newVector;
+						minDistance = newDistance;
+					}
+					else 
+					{
+						// new vector is out parabolic range, so this is a new maxVector
+						maxVector = newVector;
+						maxDistance = newDistance;
+					}
+					
+				}
+				r.push_back((pos.X+maxVector.X).ToFloat());
+				r.push_back((pos.Z+maxVector.Y).ToFloat());
+				
+			}
+			r.push_back(r[0]);
+			r.push_back(r[1]); 
+
+		}
+		return r;
+
+	}
+	
 	Query ConstructQuery(entity_id_t source,
 		entity_pos_t minRange, entity_pos_t maxRange,
 		const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
@@ -856,6 +1023,7 @@
 
 		Query q;
 		q.enabled = false;
+		q.parabolic = false;
 		q.source = source;
 		q.minRange = minRange;
 		q.maxRange = maxRange;
@@ -870,11 +1038,20 @@
 		return q;
 	}
 
+	Query ConstructParabolicQuery(entity_id_t source,
+		entity_pos_t minRange, entity_pos_t maxRange,
+		const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
+	{
+		Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
+		q.parabolic = true;
+		return q;
+	}
+
+
 	void RenderSubmit(SceneCollector& collector)
 	{
 		if (!m_DebugOverlayEnabled)
 			return;
-
 		CColor enabledRingColour(0, 1, 0, 1);
 		CColor disabledRingColour(1, 0, 0, 1);
 		CColor rayColour(1, 1, 0, 0.2f);
@@ -893,9 +1070,66 @@
 				CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
 
 				// Draw the max range circle
-				m_DebugOverlayLines.push_back(SOverlayLine());
-				m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
-				SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
+				if (!q.parabolic)
+				{
+					m_DebugOverlayLines.push_back(SOverlayLine());
+					m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
+					SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
+				}
+				else 
+				{
+					CFixedVector3D pos = cmpSourcePosition->GetPosition();
+					std::vector<float> coords;
+					
+					// Get the outline from cache if possible
+					if (ParabolicRangesOutlines.find(q.source) != ParabolicRangesOutlines.end())
+					{
+						EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source];
+						if (e.position == pos && e.range == q.maxRange) 
+						{
+							// outline is cached correctly, use it
+							coords = e.outline;	
+						}
+						else
+						{
+							// outline was cached, but important parameters changed 
+							// (position, elevation, range)
+							// update it
+							coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2);
+							e.outline = coords;
+							e.range = q.maxRange;
+							e.position = pos;
+							ParabolicRangesOutlines[q.source] = e;
+						}
+					}
+					else 
+					{
+						// outline wasn't cached (first time you enable the range overlay 
+						// or you created a new entiy)
+						// cache a new outline
+						coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2);
+						EntityParabolicRangeOutline e;
+						e.source = q.source;
+						e.range = q.maxRange;
+						e.position = pos;
+						e.outline = coords;
+						ParabolicRangesOutlines[q.source] = e;
+					}
+					
+					CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
+					
+					// draw the outline (piece by piece)	
+					for (size_t i = 3; i < coords.size(); i += 2)
+					{
+						std::vector<float> c;
+						c.push_back(coords[i-3]);
+						c.push_back(coords[i-2]);
+						c.push_back(coords[i-1]);
+						c.push_back(coords[i]);
+						m_DebugOverlayLines.push_back(SOverlayLine());
+						m_DebugOverlayLines.back().m_Color = thiscolor;
+						SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
+					}
+				}
 
 				// Draw the min range circle
 				if (!q.minRange.IsZero())
Index: source/simulation2/components/ICmpRangeManager.cpp
===================================================================
--- source/simulation2/components/ICmpRangeManager.cpp	(revision 13457)
+++ source/simulation2/components/ICmpRangeManager.cpp	(working copy)
@@ -36,6 +36,7 @@
 BEGIN_INTERFACE_WRAPPER(RangeManager)
 DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
 DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
+DEFINE_INTERFACE_METHOD_6("CreateActiveParabolicQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveParabolicQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
 DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
 DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
 DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
Index: source/simulation2/components/ICmpRangeManager.h
===================================================================
--- source/simulation2/components/ICmpRangeManager.h	(revision 13457)
+++ source/simulation2/components/ICmpRangeManager.h	(working copy)
@@ -102,6 +102,23 @@
 	virtual tag_t CreateActiveQuery(entity_id_t source,
 		entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
 
+    /**
+	 * Construct an active query of a paraboloic form around the unit. 
+	 * The query will be disabled by default.
+	 * @param source the entity around which the range will be computed.
+	 * @param minRange non-negative minimum horizontal distance in metres (inclusive). MinRange doesn't do parabolic checks.
+	 * @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation; 
+	 *      or -1.0 to ignore distance. 
+	 *      For units on a different elevation, a physical correct paraboloid with height=maxRange/2 above the unit is used to query them
+	 * @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
+	 * @param requiredInterface if non-zero, an interface ID that matching entities must implement.
+	 * @param flags if a entity in range has one of the flags set it will show up.
+	 * @return unique non-zero identifier of query.
+	 */
+	virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
+		entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
+
+
 	/**
 	 * Destroy a query and clean up resources. This must be called when an entity no longer needs its
 	 * query (e.g. when the entity is destroyed).
