DIFF

ActionTargets.c

//! objects in vicinity - extended with secondary object which is parent of that Object
class VicinityObjects
{
	private ref map<Object, Object> m_VicinityObjects;
	
	void VicinityObjects()
	{
		m_VicinityObjects = new map<Object, Object>;
	}

	//! stores VicinityObject to Hashmap - for storing of parent/child relationship
	void StoreVicinityObject(Object object, Object parent = null)
	{
		//! completely remove items that are being placed or are holograms
		ItemBase ib = ItemBase.Cast(object);
		if (ib && (ib.IsBeingPlaced() || ib.IsHologram()))
			return;

		//! ignores plain objects
		/*if(object && object.IsPlainObject())
		{
			Print("ERROR: VicinityObjects | StoreVicinityObject | IsPlainObject check fail");
			return;
		}*/
		
		if ( !m_VicinityObjects.Contains(object) )
		{
			//! init of VicinityObjects - object, parent(if exists)
			m_VicinityObjects.Set(object, parent);
		}
	}

	//! transform simple array of Objects to VicinityObjects hashmap
	void TransformToVicinityObjects(array<Object> objects)
	{
		foreach (Object obj : objects)
		{
			if (IsValidVicinityObject(obj))
			{
				StoreVicinityObject(obj);
			}
		}
	}
	
	protected bool IsValidVicinityObject(notnull Object obj)
	{
		return obj.CanBeActionTarget();
	}
	
	void ClearVicinityObjects()
	{
		m_VicinityObjects.Clear();
	}

	//! return simple array of Objects in Vicinity
	array<Object> GetVicinityObjects()
	{
		array<Object> vicinityObjects = new array<Object>();
		for (int i = 0; i < m_VicinityObjects.Count(); i++)
		{
			//! filters out non-takeable items (won't be shown in vicinity)
			ItemBase ib = ItemBase.Cast(GetObject(i));
			if (ib && !ib.IsTakeable())
				continue;

			vicinityObjects.Insert(GetObject(i));
		}
		
		return vicinityObjects;
	}
	
	//! return simple array of Objects in Vicinity
	array<Object> GetRawVicinityObjects()
	{
		array<Object> vicinityObjects = new array<Object>();
		for (int i = 0; i < m_VicinityObjects.Count(); i++)
		{
			vicinityObjects.Insert(GetObject(i));
		}
		
		return vicinityObjects;
	}

	//! returns VicinityObjects Key
	Object GetObject(int i)
	{
		return m_VicinityObjects.GetKey(i);
	}

	//! returns VicinityObjects Element
	Object GetParent(int i)
	{
		return m_VicinityObjects.GetElement(i);
	}
	
	int Count()
	{
		return m_VicinityObjects.Count();
	}
	
	void Remove(Object object)
	{
		m_VicinityObjects.Remove(object);
	}
	
	void Remove(array<Object> objects)
	{
		for (int i = 0; i < objects.Count(); i++)
		{
			m_VicinityObjects.Remove(objects[i]);
		}
	}
}

class ActionTarget
{
	void ActionTarget(Object object, Object parent, int componentIndex, vector cursorHitPos, float utility)
	{
		m_Object = object;
		m_Parent = parent;
		m_ComponentIndex = componentIndex;
		m_CursorHitPos = cursorHitPos;
		m_Utility = utility;
	} 

	Object GetObject()
		{ return m_Object; }

	Object GetParent()
		{ return m_Parent; }

	bool IsProxy()
	{
		if (m_Parent)
		 	return true;
		return false;
	}
	
	int GetComponentIndex()	
		{ return m_ComponentIndex; }
	
	float GetUtility()
		{ return m_Utility; }
	
	vector GetCursorHitPos()
		{ return m_CursorHitPos; }
	
	void SetCursorHitPos(vector cursor_position)
	{
		m_CursorHitPos = cursor_position;
	}
	
	void DbgPrintTargetDump()
	{
		Print(DumpToString());
	}
	
	string DumpToString()
	{
		string res = "ActionTarget dump = {";
		res = res + "m_Object: " + Object.GetDebugName(m_Object);
		res = res + "; m_Parent: " + Object.GetDebugName(m_Parent);
		res = res + "; m_ComponentIndex: " + m_ComponentIndex.ToString();
		res = res + "; m_CursorHitPos: " + m_CursorHitPos.ToString();
		res = res + "; m_Utility: " + m_Utility.ToString();
		res = res + "}";
		return res;
	}
	
	private Object m_Object;		// object itself
	private Object m_Parent;		// null or parent of m_Object
	private int m_ComponentIndex;	// p3d Component ID or -1
	private vector m_CursorHitPos;
	private float m_Utility;
};

class ActionTargets
{
	private ref map<int, ref array<RaycastRVResult>> m_SortedResults = new map<int, ref array<RaycastRVResult>>();
	
	void ActionTargets(PlayerBase player)
	{
		m_Player = player;
		m_VicinityObjects = new VicinityObjects;
		m_Targets = new array<ref ActionTarget>;
		
		m_Debug = false;
	}
	
	static array<Object> GetVicinityObjects()
	{
		return m_VicinityObjects.GetVicinityObjects();
	}
	
	void Clear()
	{
		m_Targets.Clear();
		m_SortedResults.Clear();
		m_HitPos = vector.Zero;
	}
	
	void Update()
	{
		//! clear state
		m_VicinityObjects.ClearVicinityObjects();
		Clear();

		Object firstValidRaycastedObject = null;
		array<Object> vicinityObjects = new array<Object>;
		
		//! camera & ray properties
		vector playerPos = m_Player.GetPosition();
		vector headingDirection = MiscGameplayFunctions.GetHeadingVector(m_Player);

		m_RayStart = g_Game.GetCurrentCameraPosition();
		m_RayEnd = m_RayStart + g_Game.GetCurrentCameraDirection() * c_RayDistance;

		RaycastRVParams rayInput = new RaycastRVParams(m_RayStart, m_RayEnd, m_Player);
		rayInput.flags = CollisionFlags.ALLOBJECTS;
		//rayInput.sorted = true;
		array<ref RaycastRVResult> results = new array<ref RaycastRVResult>();
		array<RaycastRVResult> sortedValidRaycastResults = new array<RaycastRVResult>();
		map<Object, RaycastRVResult> resultMap = new map<Object, RaycastRVResult>();
		
		if (DayZPhysics.RaycastRVProxy(rayInput, results))
		{
			array<int> sortedDistanceHelper = SortResultsDistance(results);
			int sortedResultsCount = m_SortedResults.Count();
			if (sortedResultsCount > 0 )
			{
				foreach (int sortedDist : sortedDistanceHelper) //distances already sorted
				{
					array<RaycastRVResult> sameDistResults = m_SortedResults.Get(sortedDist);
					
					foreach (RaycastRVResult res : sameDistResults)
					{
						if (res.obj && !res.obj.CanBeActionTarget())
							continue;
						
						//! if the cursor target is a proxy
						if (res.hierLevel > 0)
						{
							//! ignores attachments on player and objects with invalid IDs
							if (res.parent.IsMan() || (res.obj.GetID() < 1 && !IsRaycastResultException(res)))
								continue;
						}
						
						sortedValidRaycastResults.Insert(res);
						resultMap.Insert(res.obj, res);
						if (!firstValidRaycastedObject)
							firstValidRaycastedObject = res.obj;
						
						if (m_HitPos == vector.Zero)
							m_HitPos = res.pos;
						
						break;
					}
				}
			}
		}
		else
		{
			firstValidRaycastedObject = null;
			m_HitPos = vector.Zero;
		}
		
		//! spacial search
		DayZPlayerCamera camera = m_Player.GetCurrentCamera();
		if (camera && camera.GetCurrentPitch() <= -45) // Spatial search is a contributor to very heavy searching, limit it to when we are at least looking down
			DayZPlayerUtils.GetEntitiesInCone(playerPos, headingDirection, c_ConeAngle, c_MaxTargetDistance, c_ConeHeightMin, c_ConeHeightMax, vicinityObjects);
		
		//! removes player from the vicinity
		vicinityObjects.RemoveItem(m_Player);
		
		FilterDuplicateVicinityObjects(vicinityObjects, sortedValidRaycastResults);

		//! transformation of array of Objects to hashmap (VicinityObjects)
		m_VicinityObjects.TransformToVicinityObjects(vicinityObjects);
		
		//! removes Vicinity objects that are not directly visible from player position
		FilterObstructedObjectsEx(firstValidRaycastedObject, vicinityObjects);
		
		//! select & sort targets based on utility function
		int vicinityObjCount = m_VicinityObjects.Count();
		for (int i = 0; i < vicinityObjCount; ++i)
		{
			Object object = m_VicinityObjects.GetObject(i);
			Object parent;
			RaycastRVResult result;
			int targetComponent;
			vector hitpos;
			//proper raycast results will use their own data
			if (resultMap.Find(object, result))
			{
				parent = result.parent;
				hitpos = result.pos;
				targetComponent = result.component;
			}
			else
			{
				parent = m_VicinityObjects.GetParent(i);
				hitpos = m_HitPos; //substitute raycast hitpos as reference for the utility check
				targetComponent = -1; //invalid component
			}
			
			float utility = ComputeUtility(object, m_RayStart, m_RayEnd, firstValidRaycastedObject, hitpos);
			if (utility > 0)
			{
				ActionTarget at = new ActionTarget(object, parent, targetComponent, m_HitPos, utility);
				StoreTarget(at);
			}
		}

		//! action target for surface actions (lowest utility)
		if (m_HitPos == vector.Zero)
		{
			vector 		contact_pos, contact_dir, hitNormal;
			int 		contactComponent;
			float 		hitFraction;
			Object 		hitObject;
			
			m_RayEnd = m_RayStart + g_Game.GetCurrentCameraDirection() * c_RayDistance * 3;
			
			PhxInteractionLayers collisionLayerMask = PhxInteractionLayers.ROADWAY|PhxInteractionLayers.TERRAIN|PhxInteractionLayers.WATERLAYER;
			DayZPhysics.RayCastBullet(m_RayStart,m_RayEnd,collisionLayerMask,null,hitObject,contact_pos,hitNormal,hitFraction);
			m_HitPos = contact_pos;
		}
		
		m_Targets.Insert(new ActionTarget(null, null, -1, m_HitPos, 0));

#ifdef DIAG_DEVELOPER
		if (DiagMenu.GetBool(DiagMenuIDs.MISC_ACTION_TARGETS_DEBUG))
		{
			ShowDebugActionTargets(true);
			DrawDebugActionTargets(true);
			DrawDebugCone(true);
			DrawDebugRay(true);
			DrawSelectionPos(DiagMenu.GetBool(DiagMenuIDs.MISC_ACTION_TARGETS_SELPOS_DEBUG));
		}
		else
		{
			ShowDebugActionTargets(false);
			DrawDebugActionTargets(false);
			DrawDebugCone(false);
			DrawDebugRay(false);
			DrawSelectionPos(false);
		}
#endif
	}
	
	//! Initial raycast filtering exceptions (can't ask the entity type, sadly)
	protected bool IsRaycastResultException(RaycastRVResult res)
	{
		if (TentBase.Cast(res.parent))
			return true;
		
		return false;
	}
	
	//distance sorting, returns sorted unique distance tiers as ints (float*1000)
	protected array<int> SortResultsDistance(array<ref RaycastRVResult> results)
	{
		array<int> distHelper = new array<int>();
		foreach (RaycastRVResult res : results)
		{
			int distance = (int)(vector.DistanceSq(res.pos, m_RayStart) * 1000);
			array<RaycastRVResult> arr = m_SortedResults.Get(distance);
			if (!arr)
				arr = new array<RaycastRVResult>();
			
			arr.Insert(res);
			m_SortedResults.Set(distance, arr);
			
			if (distHelper.Find(distance) == -1)
				distHelper.Insert(distance);
		}
		
		distHelper.Sort();
		return distHelper;
	}
	
	private bool IsObstructed(Object object)
	{
		IsObjectObstructedCache cache = new IsObjectObstructedCache(m_RayStart, 1);
		return IsObstructedEx(object, cache);
	}
	
	private bool IsObstructedEx(Object object, IsObjectObstructedCache cache)
	{
		return MiscGameplayFunctions.IsObjectObstructedEx(object, cache);
	}
 	
	//! returns count of founded targets
	int GetTargetsCount()
		{ return m_Targets.Count(); }
	
	//! returns action target at index
	ActionTarget GetTarget(int index)
		{ return m_Targets.Get(index); }

	//! inserts action into sorted array based on utility
	private void StoreTarget(ActionTarget pActionTarget)
	{
		int index = FindIndexForStoring(pActionTarget.GetUtility());
		m_Targets.InsertAt(pActionTarget, index);
		//Print("StoreTarget; object: " + pActionTarget.GetObject() + " | parent: " + pActionTarget.GetParent() + " | idx: " + index);
	}

	//! binary search algorithm	
	private int FindIndexForStoring(float value)
	{
		int left = 0;
		int right = m_Targets.Count() - 1;
		while ( left <= right )
		{
			int middle = (left + right) / 2;
			float middleValue = m_Targets.Get(middle).GetUtility();
			
			if ( middleValue == value )
				return middle;
            else if ( middleValue < value )
				right = middle - 1;
            else
				left = middle + 1;
		}
		
		return left;
	}
	
	//! computes utility of target
	private float ComputeUtility(Object pTarget, vector pRayStart, vector pRayEnd, Object cursorTarget, vector hitPos)
	{
		//! out of reach
		if (vector.DistanceSq(hitPos, m_Player.GetPosition()) > c_MaxTargetDistance * c_MaxTargetDistance)
			return -1;

		if (pTarget)
		{
			if ( pTarget == cursorTarget )
			{
				//! ground and static objects
				if ( pTarget.GetType() == string.Empty )
					return 0.01;

				if ( pTarget.IsBuilding() )
					return 0.25;

				if ( pTarget.IsTransport() )
					return 0.25;
				
				//!basebuilding objects
				if (pTarget.CanUseConstruction())
					return 0.85;

				if ( pTarget.IsWell() )
					return 0.9;

				vector playerPosXZ = m_Player.GetPosition();
				vector hitPosXZ = hitPos;
				playerPosXZ[1] = 0;
				hitPosXZ[1] = 0;
				if ( vector.DistanceSq(playerPosXZ, hitPosXZ) <= c_MaxTargetDistance * c_MaxTargetDistance )
					return c_UtilityMaxValue;
			}

			if ( PlayerBase.Cast(pTarget) && PlayerBase.Cast(pTarget).IsInVehicle() ) // utility in vehicle should be below base vehicle val
				return 0.20;
			
			float distSqr = DistSqrPoint2Line(pTarget.GetPosition(), pRayStart, pRayEnd);
			return (c_UtilityMaxDistFromRaySqr - distSqr) / c_UtilityMaxDistFromRaySqr;
		}

		return -1;
	}

	//! distance between point and line
	private float DistSqrPoint2Line(vector pPoint, vector pL1, vector pL2)
	{
		vector v = pL2 - pL1;
		vector w = pPoint - pL1;
	
		float c1 = vector.Dot(w,v);
		float c2 = vector.Dot(v,v);

		if ( c1 <= 0 || c2 == 0 )
			return vector.DistanceSq(pPoint, pL1);

		float b = c1 / c2;	
		vector nearestPoint = pL1 + (v * b);
		return vector.DistanceSq(pPoint, nearestPoint);		
	}
	
	private void FilterDuplicateVicinityObjects(inout array<Object> vicinityObjectsOutput, array<RaycastRVResult> sortedRaycastResults)
	{
		//remove duplicates
		array<Object> tmp = new array<Object>();
		tmp.Copy(vicinityObjectsOutput);
		vicinityObjectsOutput.Clear();
		foreach (Object obj : tmp)
		{
			if (obj && vicinityObjectsOutput.Find(obj) == -1)
				vicinityObjectsOutput.Insert(obj);
		}
		
		//next append the raycast result objects
		foreach (RaycastRVResult raycastResult : sortedRaycastResults)
		{
			if (raycastResult.obj && vicinityObjectsOutput.Find(raycastResult.obj) == -1)
				vicinityObjectsOutput.Insert(raycastResult.obj);
		}
	}
	
	private void FilterObstructedObjectsEx(Object cursor_target, array<Object> vicinityObjects)
	{
		#ifdef DIAG_DEVELOPER
		if (DiagMenu.GetBool(DiagMenuIDs.MISC_ACTION_TARGETS_DEBUG))
			CleanupDebugShapes(obstruction);
		#endif

		array<Object> obstructingObjects = new array<Object>;
		MiscGameplayFunctions.FilterObstructingObjects(vicinityObjects, obstructingObjects);
		
		if ( obstructingObjects.Count() > 0 )
		{
			PlayerBase player = PlayerBase.Cast(g_Game.GetPlayer());
					
			int numObstructed = 0;
			int mCount = m_VicinityObjects.Count();
			
			if (mCount > GROUPING_COUNT_THRESHOLD)
			{
				array<Object> filteredObjects = new array<Object>;
				MiscGameplayFunctions.FilterObstructedObjectsByGrouping(m_RayStart, c_MaxTargetDistance, c_DistanceDelta, m_VicinityObjects.GetRawVicinityObjects(), vicinityObjects, filteredObjects);
				m_VicinityObjects.ClearVicinityObjects();
				m_VicinityObjects.TransformToVicinityObjects(filteredObjects);
			}
			else
			{
				FilterObstructedObjects(cursor_target);
			}
		}
	}
	
	private void FilterObstructedObjects(Object cursor_target)
	{
		int numObstructed = 0;
		int mCount = m_VicinityObjects.Count();
		IsObjectObstructedCache cache = new IsObjectObstructedCache(m_RayStart, mCount);
		mCount--;
	
		//! check if targets are not obstructed (eg.: wall)
		for ( int i = mCount; i >= 0; --i )
		{
			Object object = m_VicinityObjects.GetObject(i);
			Object parent = m_VicinityObjects.GetParent(i);

			//! check for object obstruction(if the object is not a proxy - has no parent)
			if (object && !parent)
			{
				//! when the number of obstructed items is higher than OBSTRUCTED_COUNT_THRESHOLD
				//! remove do no run obstruction check and skip these items
				if (numObstructed > OBSTRUCTED_COUNT_THRESHOLD && object != cursor_target)
				{
					m_VicinityObjects.Remove(object);
					continue;
				}

				//! obstruction check
				if (object != cursor_target && IsObstructedEx(object, cache))
				{
					m_VicinityObjects.Remove(object);
					numObstructed++;
				}

				cache.ClearCache();
			}
		}
	}
	
#ifdef DIAG_DEVELOPER
	ref array<Shape> shapes = new array<Shape>();
	ref array<Shape> dbgConeShapes = new array<Shape>();
	ref array<Shape> rayShapes = new array<Shape>();
	ref array<Shape> obstruction = new array<Shape>();
	ref array<Shape> dbgPosShapes = new array<Shape>();
	
	void ShowDebugActionTargets(bool enabled)
	{
		int windowPosX = 0;
		int windowPosY = 50;

		Object obj;

		DbgUI.BeginCleanupScope();
		DbgUI.Begin("Action Targets", windowPosX, windowPosY);
		if ( enabled )
		{
			for ( int i = 0; i < GetTargetsCount(); i++ )
			{				
				obj = m_Targets.Get(i).GetObject();
				if ( obj )
				{
					float util = m_Targets.Get(i).GetUtility();
					int compIdx = m_Targets.Get(i).GetComponentIndex();
					string compName;
					array<string> compNames = new array<string>;
					compName = obj.GetActionComponentName(compIdx);
					obj.GetActionComponentNameList(compIdx, compNames);

					if ( compNames.Count() > 0 )
					{
						for ( int c = 0; c < compNames.Count(); c++ )
						{
							DbgUI.Text(obj.GetDisplayName() + " :: " + obj + " | util: " + util + " | compIdx: " + compIdx + " | compName: " + compNames[c] + "| wPos: " + obj.GetWorldPosition() );
						}
					}
					else
					{
						DbgUI.Text(obj.GetDisplayName() + " :: " + obj + " | util: " + util + " | compIdx: " + compIdx + " | compName: " + compName + "| wPos: " + obj.GetWorldPosition() );					
					}
				}
				else
					continue;
			}
		}
		DbgUI.End();
		DbgUI.EndCleanupScope();
	}

	void DrawDebugActionTargets(bool enabled)
	{
		int s_id;
		vector w_pos;
		vector w_pos_sphr;
		vector w_pos_lend;
		Object obj;

		if ( enabled )
		{
			CleanupDebugShapes(shapes);
			
			for ( int i = 0; i < GetTargetsCount(); i++ )
			{
				obj = m_Targets.Get(i).GetObject();
				if ( obj )
				{
					w_pos = obj.GetPosition();
					// sphere pos tweaks
					w_pos_sphr = w_pos;
					w_pos_sphr[1] = w_pos_sphr[1] + 0.5;
					// line pos tweaks
					w_pos_lend = w_pos;
					w_pos_lend[1] = w_pos_lend[1] + 0.5;
					
					if ( i == 0 )
					{
						shapes.Insert( Debug.DrawSphere(w_pos_sphr, 0.03, COLOR_RED) );
						shapes.Insert( Debug.DrawLine(w_pos, w_pos_lend, COLOR_RED) );
					}
					else
					{
						shapes.Insert( Debug.DrawSphere(w_pos_sphr, 0.03, COLOR_YELLOW) );
						shapes.Insert( Debug.DrawLine(w_pos, w_pos_lend, COLOR_YELLOW) );
					}
				}
			}
		}
		else
			CleanupDebugShapes(shapes);
	}
	
	private void DrawDebugCone(bool enabled)
	{
		// "cone" settings
		vector start, end, endL, endR;
		float playerAngle;
		float xL,xR,zL,zR;
		
		if (enabled)
		{
			CleanupDebugShapes(dbgConeShapes);

			start = m_Player.GetPosition();
			playerAngle = MiscGameplayFunctions.GetHeadingAngle(m_Player);
			
			// offset position of the shape in height
			start[1] = start[1] + 0.2;

			endL = start;
			endR = start;
			xL = c_MaxTargetDistance * Math.Cos(playerAngle + Math.PI_HALF + c_ConeAngle * Math.DEG2RAD); // x
			zL = c_MaxTargetDistance * Math.Sin(playerAngle + Math.PI_HALF + c_ConeAngle * Math.DEG2RAD); // z
			xR = c_MaxTargetDistance * Math.Cos(playerAngle + Math.PI_HALF - c_ConeAngle * Math.DEG2RAD); // x
			zR = c_MaxTargetDistance * Math.Sin(playerAngle + Math.PI_HALF - c_ConeAngle * Math.DEG2RAD); // z
			endL[0] = endL[0] + xL;
			endL[2] = endL[2] + zL;
			endR[0] = endR[0] + xR;
			endR[2] = endR[2] + zR;

			dbgConeShapes.Insert( Debug.DrawLine(start, endL, COLOR_BLUE) );
			dbgConeShapes.Insert( Debug.DrawLine(start, endR, COLOR_BLUE) ) ;
			dbgConeShapes.Insert( Debug.DrawLine(endL, endR, COLOR_BLUE) );
		}
		else
			CleanupDebugShapes(dbgConeShapes);		
	}
	
	private void DrawSelectionPos(bool enabled)
	{
		if (enabled)
		{
			CleanupDebugShapes(dbgPosShapes);
			if (GetTargetsCount() > 0 && GetTarget(0).GetUtility() > -1 )
			{
				ActionTarget at = GetTarget(0);
				if (at.GetObject())
				{
					string compName = at.GetObject().GetActionComponentName(at.GetComponentIndex());
					vector modelPos = at.GetObject().GetSelectionPositionMS(compName);
					vector worldPos = at.GetObject().ModelToWorld(modelPos);
					dbgPosShapes.Insert( Debug.DrawSphere(worldPos, 0.25, Colors.PURPLE, ShapeFlags.NOZBUFFER) );
				}
			}
		}
		else
			CleanupDebugShapes(dbgPosShapes);
	}

	private void DrawDebugRay(bool enabled)
	{
		if (enabled)
		{
			CleanupDebugShapes(rayShapes);
			rayShapes.Insert( Debug.DrawSphere(m_HitPos, Math.Sqrt(c_UtilityMaxDistFromRaySqr), COLOR_BLUE_A, ShapeFlags.TRANSP) );
			rayShapes.Insert( Debug.DrawLine(m_RayStart, m_RayEnd, COLOR_BLUE) );
		}
		else
			CleanupDebugShapes(rayShapes);
	}

	private void CleanupDebugShapes(array<Shape> shapesArr)
	{
		for ( int it = 0; it < shapesArr.Count(); ++it )
		{
			Shape shape = shapesArr[it];
			Debug.RemoveShape( shape );
		}
		
		shapesArr.Clear();
	}
#endif
	
	//--------------------------------------------------------
	// Members
	//--------------------------------------------------------
	//! player owner
	private PlayerBase m_Player;
	
	//! selected & sorted targets by utility function
	private ref array<ref ActionTarget> m_Targets;
	
	//! objects in vicinity
	static private ref VicinityObjects m_VicinityObjects

	private bool m_Debug
	
	private vector m_RayStart;
	private vector m_RayEnd;
	private vector m_HitPos; //!first valid hitpos! Used only as rough reference value for conecasted results.
	
	//--------------------------------------------------------
	// Constants
	//--------------------------------------------------------
	//! searching properties
	private const float c_RayDistance = 5.0;
	private const float c_MaxTargetDistance = 3.0;
	private const float c_MaxActionDistance = UAMaxDistances.DEFAULT;
	private const float c_ConeAngle = 30.0;
	private const float c_ConeHeightMin = -0.5;
	private const float c_ConeHeightMax = 2.0;
	private const float c_DistanceDelta = 0.3;
	
	//! utility constants
	private const float c_UtilityMaxValue = 10000;
	private const float c_UtilityMaxDistFromRaySqr = 0.8 * 0.8;

	//! p3d 
	private const string CE_CENTER 					= "ce_center";
	private const float HEIGHT_OFFSET 				= 0.2;
	
	//! misc
	private const int OBSTRUCTED_COUNT_THRESHOLD	= 3;
	private const int GROUPING_COUNT_THRESHOLD		= 10;
	
	//! DEPRECATED
	vector CalculateRayStart();
};

class ObjectGroup
{
	ref array<Object> Objects = new array<Object>;
}