diff -uwrN sourceold/scripts/DBIKScripts.cpp source/scripts/DBIKScripts.cpp
--- sourceold/scripts/DBIKScripts.cpp	2017-11-23 05:41:45.146800000 +1000
+++ source/scripts/DBIKScripts.cpp	2018-11-11 08:30:08.519600000 +1000
@@ -10,6 +10,7 @@
 #include "PurchaseSettingsDefClass.h"
 #include "GameObjManager.h"
 #include "DB_General.h"
+#include "PurchaseSettingsDefClass.h"
 #ifdef DRAGONADE
 #include "engine_da.h"
 #endif // DRAGONADE
@@ -1015,7 +1016,7 @@
 		if (Get_Float_Parameter("Cost") && Commands->Get_Money(obj) < Get_Float_Parameter("Cost"))
 		{
 			char costMessage[220];
-			sprintf(costMessage,"You need $%d to place this.",Get_Float_Parameter("Cost"));
+			sprintf(costMessage,"You need $%i to place this.",(int)Get_Float_Parameter("Cost"));
 			Send_Message_Player(obj,255,255,255,costMessage);
 			return;
 		}
@@ -1779,3 +1780,59 @@
 ScriptRegistrant<DB_Damage_Vehicle_Occupants_Area_Timer> DB_Damage_Vehicle_Occupants_Area_Timer_Registrant("DB_Damage_Vehicle_Occupants_Area_Timer","Occupants_Damage_Amount=1000:float,Occupants_Damage_Warhead=warhead:string,Damage_Distance=1000:float,ShooterID=0:int");
 ScriptRegistrant<DB_Damage_Vehicle_Occupants_Area_Killed> DB_Damage_Vehicle_Occupants_Area_Killed_Registrant("DB_Damage_Vehicle_Occupants_Area_Killed","Area_Damaging_Explosion=explosion:string,Occupants_Damage_Warhead=warhead:string,Occupants_Damage_Amount=1000:float,Damage_Distance=1000:float");
 
+void DB_Set_PT_Slot::Created(GameObject *obj)
+{
+	Commands->Start_Timer(obj,this,0.0f,12321);
+	int Type=Get_Int_Parameter("Type");
+		int Team=Get_Int_Parameter("Team");
+		int Slot=Get_Int_Parameter("Slot");
+		int Alt=Get_Int_Parameter("Alt");
+		StringClass Preset=Get_Parameter("Preset");
+		if(stristr(Preset,"mirage"))
+		{
+			Team=0;
+			Type=1;
+			Slot=9;
+			Alt=0;
+		}
+		PurchaseSettingsDefClass *PT = PurchaseSettingsDefClass::Find_Definition((PurchaseSettingsDefClass::TYPE)Type,(PurchaseSettingsDefClass::TEAM)Team);
+		if(PT)
+		{
+			int PresetID = Get_Definition_ID(Preset);
+			if(PresetID)
+			{
+				if(Alt==0 || Alt==1 || Alt==2)
+				{
+					PT->Set_Alt_Definition(Slot,Alt,PresetID);
+				}
+				else
+				{
+					PT->Set_Definition(Slot,PresetID);
+				}
+			}
+			else
+			{
+				if(Alt==0 || Alt==1 || Alt==2)
+				{
+					PT->Set_Alt_Definition(Slot,Alt,0);
+				}
+				else
+				{
+					PT->Set_Definition(Slot,0);
+				}
+			}
+		}
+
+}
+
+void DB_Set_PT_Slot::Timer_Expired(GameObject *obj,int number)
+{
+#ifdef DRAGONADE
+	if(number==12321)
+	{
+		Console_Input("reload");
+	}
+#endif // DRAGONADE
+}
+
+ScriptRegistrant<DB_Set_PT_Slot> DB_Set_PT_Slot_Registrant("DB_Set_PT_Slot","Team=0:int,Type=0:int,Slot=0:int,Alt=-1:int,Preset=CnC_Nod_Light_Tank:String");
\ No newline at end of file
diff -uwrN sourceold/scripts/DBIKScripts.h source/scripts/DBIKScripts.h
--- sourceold/scripts/DBIKScripts.h	2017-11-23 05:41:45.147800000 +1000
+++ source/scripts/DBIKScripts.h	2018-11-11 08:30:08.520600000 +1000
@@ -260,3 +260,8 @@
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj, int number);
 };
\ No newline at end of file
+
+class DB_Set_PT_Slot : public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
\ No newline at end of file
diff -uwrN sourceold/scripts/DBScripts.cpp source/scripts/DBScripts.cpp
--- sourceold/scripts/DBScripts.cpp	2017-11-23 05:41:45.148800000 +1000
+++ source/scripts/DBScripts.cpp	2018-11-11 08:30:08.521600000 +1000
@@ -564,26 +564,36 @@
 
 class DB_capturable_Helipad_Terminal : public ScriptImpClass {
 	bool Do_Poke_Stuff;
+	ReferencerClass building;
 void  DB_capturable_Helipad_Terminal::Created(GameObject *obj)
 {
 	Do_Poke_Stuff = true; 
 	Commands->Enable_HUD_Pokable_Indicator(obj, true);
+	Commands->Set_Animation_Frame(obj,Get_Model(obj),0);
+	Commands->Start_Timer(obj,this,0.5f,1);
+	building = Commands->Find_Object(Get_Int_Parameter("BuildingID"));
 }
 void  DB_capturable_Helipad_Terminal::Poked(GameObject *obj,GameObject *poker)
 {
 	if (Do_Poke_Stuff)
 	{
 		Do_Poke_Stuff = false; Commands->Start_Timer(obj,this,5.0f,12345);
-		if (Get_Object_Type(Commands->Find_Object(Get_Int_Parameter("BuildingID")))==Get_Object_Type(poker))
+		if (Get_Object_Type(building)==Get_Object_Type(poker))
 		{
 			const char *preset;
-			int cost;
+			float cost;
 			int spawnlocation;
 			Vector3 location;
-			cost=Get_Int_Parameter("Cost");
+			bool powered=true;
+			cost=Get_Float_Parameter("Cost");
 			preset = Get_Parameter("Preset");
 			spawnlocation = Get_Int_Parameter("SpawnLocation");
 			location = Commands->Get_Position(Commands->Find_Object(spawnlocation));
+			if(!Is_Base_Powered(Get_Object_Type(poker)))
+			{
+				powered=false;
+				cost=cost*1.5f;
+			}
 			if (cost <= Commands->Get_Money(poker))
 			{
 				cost = -cost;
@@ -595,20 +605,24 @@
 				{
 					Set_Object_Type(Purchase,Get_Object_Type(poker));
 					Purchase->As_VehicleGameObj()->Lock_Vehicle(poker,45.0f);
-					//if(!Purchase->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass()->Can_Teleport(Purchase->As_PhysicalGameObj()->Get_Transform()))
-						//Fix_Stuck_Object(Purchase->As_VehicleGameObj(),15);
 				}
-				Send_Message_Player(poker,255,255,255,"Purchase request granted!");
+				Send_Message_Team(Get_Object_Type(poker),160,160,255,StringClass::getFormattedString("Building: %s - %s",Get_Translated_Definition_Name(preset),Get_Player_Name(poker)));
+				Send_Message_Player(poker,178,178,178,"Purchase request granted!");
+				Stop_Timer2(obj,this,1);
+				DB_capturable_Helipad_Terminal::Timer_Expired(obj, 1);
 			}
 			else
 			{
 				PlayInsufficientFunds(poker);
+				if(!powered)
+					Send_Message_Player(poker,255,255,255,"Access Denied! Insufficient Funds! (Power Down - Cost x 1.5)");
+				else
 				Send_Message_Player(poker,255,255,255,"Access Denied! Insufficient Funds!");
 			}
 		}
 		else
 		{
-			const char *Building = Get_Translated_Preset_Name(Commands->Find_Object(Get_Int_Parameter("BuildingID")));
+			const char *Building = Get_Translated_Preset_Name(building);
 			Send_Message_Player(poker,255,255,255,StringClass::getFormattedString("Sorry, the %s is not captured. Purchase denied.",Building));
 
 		}
@@ -617,11 +631,74 @@
 }
 void  DB_capturable_Helipad_Terminal::Timer_Expired(GameObject *obj, int number)
 {
-	if (number == 12345)
+	if(number == 1)
+	{
+		int team = Get_Object_Type(building);
+		int frame = 0;
+		if(team == 1)
+		{
+			if(Do_Poke_Stuff)
+			{
+				if(Is_Base_Powered(1))
+					frame=7;
+				else
+					frame=5;
+			}
+			else
+			{
+				if(Is_Base_Powered(1))
+					frame=8;
+				else
+					frame=6;
+			}
+			if(Get_Object_Type(obj) != -2)
+			{
+				Set_Object_Type(obj,1);
+			}
+		}
+		else if (team == 0)
+		{
+			if(Do_Poke_Stuff)
+			{
+				if(Is_Base_Powered(0))
+					frame=3;
+				else
+					frame=1;
+			}
+			else
+			{
+				if(Is_Base_Powered(0))
+					frame=4;
+				else
+					frame=2;
+			}
+			if(Get_Object_Type(obj) != 0)
+			{
+				Set_Object_Type(obj,0);
+			}
+		}
+		else
+		{
+			frame=0;
+			if(Get_Object_Type(obj) != -2)
+			{
+				Set_Object_Type(obj,-2);
+			}
+		}
+		if(Get_Animation_Frame(obj) != frame)
+		{
+			Commands->Set_Animation_Frame(obj,Get_Model(obj),frame);
+		}
+		Commands->Start_Timer(obj,this,0.5f,1);
+	}
+
+	else if (number == 12345)
 	{
 		Do_Poke_Stuff = true;
 		Commands->Enable_HUD_Pokable_Indicator(obj, true);
 	}
+
+
 }
 };
 
diff -uwrN sourceold/scripts/MoveablePhysDefClass.h source/scripts/MoveablePhysDefClass.h
--- sourceold/scripts/MoveablePhysDefClass.h	2016-10-31 07:17:12.366013500 +1000
+++ source/scripts/MoveablePhysDefClass.h	2018-11-11 08:30:08.522600000 +1000
@@ -25,8 +25,10 @@
 	virtual bool								Load(ChunkLoadClass &cload);
 	float											Get_Mass(void)					{ return Mass; }
 	float											Get_Grav_Scale(void)			{ return GravScale; }
+	float											Get_Elasticity(void) { return Elasticity; }
 	void											Set_Mass(float new_mass)	{ Mass = new_mass; }
 	void											Set_Grav_Scale(float new_g){ GravScale = new_g; }
+	void											Set_Elasticity(float new_e) { Elasticity = new_e; }
 #ifdef DDBEDIT
 	virtual void                        Dump (FileClass &file);
 #endif
diff -uwrN sourceold/scripts/PhysicsSceneClass.h source/scripts/PhysicsSceneClass.h
--- sourceold/scripts/PhysicsSceneClass.h	2016-10-31 07:17:12.379013500 +1000
+++ source/scripts/PhysicsSceneClass.h	2018-11-11 08:30:08.523600000 +1000
@@ -250,6 +250,7 @@
 	void Post_Load_Level_Dynamic_Data(void);
 	void Set_Polygon_Budgets(int static_count,int dynamic_count);
 	void Get_Polygon_Budgets(int * static_count,int * dynamic_count);
+	void Refresh_Polygon_Budgets();
 	void Set_Update_Only_Visible_Objects(bool b) { UpdateOnlyVisibleObjects=b; }
 	bool Get_Update_Only_Visible_Objects() { return UpdateOnlyVisibleObjects; }
 	virtual void Add_Render_Object(RenderObjClass * obj);
@@ -390,6 +391,8 @@
 	static bool AllowCollisionFlags[NUM_COLLISION_FLAGS];
 	int DynamicPolyBudget;
 	int StaticPolyBudget;
+	float mapStaticPolyBudget;
+	float mapDynamicPolyBudget;
 	RefMultiListClass<PhysClass> ObjList;
 	RefMultiListClass<PhysClass> StaticObjList;
 	RefMultiListClass<PhysClass> StaticLightList;
diff -uwrN sourceold/scripts/PurchaseSettingsDefClass.h source/scripts/PurchaseSettingsDefClass.h
--- sourceold/scripts/PurchaseSettingsDefClass.h	2016-10-31 07:17:12.386013500 +1000
+++ source/scripts/PurchaseSettingsDefClass.h	2018-11-11 08:30:08.524600000 +1000
@@ -184,6 +184,15 @@
 		PageBusy = b;
 		Set_Object_Dirty_Bit(BIT_OCCASIONAL,true);
 	}
+	void Set_Definition(int index,int preset) {
+		presetids[index] = preset;
+	}
+	void Set_Alt_Definition(int index,int index2,int preset) {
+		altpresetids[index][index2] = preset;
+	}
+	void Set_Cost(int index,int cost) {
+		costs[index] = cost;
+	}
 #endif
 private:
 	TEAM team;
diff -uwrN sourceold/scripts/SmartGameObj.h source/scripts/SmartGameObj.h
--- sourceold/scripts/SmartGameObj.h	2016-10-31 07:17:12.405013500 +1000
+++ source/scripts/SmartGameObj.h	2018-11-11 08:30:08.525600000 +1000
@@ -77,6 +77,7 @@
 	virtual	void	Import_Creation( BitStreamClass & packet );
 	bool Is_Control_Data_Dirty(cPacket & packet);
 	bool	SCRIPTS_API Is_Obj_Visible( PhysicalGameObj *obj );
+	bool	SCRIPTS_API Is_Splash_Possible( PhysicalGameObj *obj );
 	void	Set_Enemy_Seen_Enabled( bool enabled )	{ IsEnemySeenEnabled = enabled; }
 	bool	Is_Enemy_Seen_Enabled( void )				{ return IsEnemySeenEnabled; }
 	virtual Matrix3D Get_Look_Transform(void) { return Get_Transform(); }
diff -uwrN sourceold/scripts/Vector3.h source/scripts/Vector3.h
--- sourceold/scripts/Vector3.h	2016-10-31 07:17:12.427013500 +1000
+++ source/scripts/Vector3.h	2018-11-11 08:30:08.526600000 +1000
@@ -78,6 +78,12 @@
 		Y *= oolen;
 		Z *= oolen;
 	}
+	Vector3 Normalized() const
+    {
+        Vector3 v = *this;
+        v.Normalize();
+        return v;
+    }
 	TT_INLINE float Length() const
 	{
 		return WWMath::Sqrt(Length2());
diff -uwrN sourceold/scripts/dp88_customAI.cpp source/scripts/dp88_customAI.cpp
--- sourceold/scripts/dp88_customAI.cpp	2017-05-08 20:26:23.656009000 +1000
+++ source/scripts/dp88_customAI.cpp	2018-11-11 08:30:08.527600000 +1000
@@ -34,7 +34,7 @@
 void dp88_customAI::Timer_Expired(GameObject *obj, int number)
 {
   if (TIMER_AI_THINK == number)
-    Commands->Start_Timer(obj, this, 1.0, TIMER_AI_THINK);
+    Commands->Start_Timer(obj, this, 0.10f, TIMER_AI_THINK);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -107,10 +107,10 @@
   Commands->Enable_Hibernation(obj, false);
   Commands->Innate_Enable(obj);
   Commands->Enable_Enemy_Seen(obj, true);
-  Commands->Enable_Vehicle_Transitions(obj, false);
+ // Commands->Enable_Vehicle_Transitions(obj, false);
 
   // Start timer which runs for the lifetime of this object
-  Commands->Start_Timer ( obj, this, 1.0, TIMER_AI_THINK );
+  Commands->Start_Timer ( obj, this, 0.10f, TIMER_AI_THINK );
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -195,7 +195,8 @@
 
   // Priority is 0 if the unit is dead or not on teams 0 or 1
   if ( ( Commands->Get_Health ( target ) + Commands->Get_Shield_Strength ( target ) ) == 0
-    || ( Commands->Get_Player_Type ( target ) != 0 && Commands->Get_Player_Type ( target ) != 1 ) )
+	  || ( Commands->Get_Player_Type ( target ) == -2) )
+    //|| ( Commands->Get_Player_Type ( target ) != 0 && Commands->Get_Player_Type ( target ) != 1 ) )
   {
     if ( debug ) fprintf ( debugFile, "Target %d is dead or unteamed, ignoring\n", Commands->Get_ID(target));
     return 0.0;
@@ -730,14 +731,30 @@
 		if ( splashInfantry && enemy->As_SoldierGameObj() )
 		{
 			if ( debug ) fprintf ( debugFile, "Using splash damage against this target\n" );
+			if(obj->As_SmartGameObj()->Is_Splash_Possible(enemy->As_PhysicalGameObj()))
+			{
 			attackLocation(obj,Commands->Get_Position(enemy),m_bTargetPrimaryFire);
 		}
 		else
+			{
+				attackLocation ( obj, enemy->As_SoldierGameObj()->Get_Bullseye_Position(), m_bTargetPrimaryFire );
+			}
+		}
+		else
 			attackTarget(obj,enemy,m_bTargetPrimaryFire);
 	}
 	else if ( debug ) fprintf ( debugFile, "Current target has a higher priority than new enemy, continuing to attack current target\n" );
 }
 
+void dp88_AI_Turret::Damaged(GameObject *obj, GameObject *damager, float amount)
+{
+	if (!damager || amount <= 0 || !checkTeam(obj,damager) || Get_Object_Type(obj)==-2 || obj->As_SmartGameObj()->Is_Enemy_Seen_Enabled()==false)
+	{
+		return;
+	}
+	Enemy_Seen(obj,damager);
+}
+
 // -------------------------------------------------------------------------------------------------
 
 void dp88_AI_Turret::Timer_Expired( GameObject *obj, int number )
@@ -765,8 +782,17 @@
 			// Otherwise we are OK to continue attacking... if this is an infantry unit
 			// and splash targetting is enabled then update targetting position
 			else if ( splashInfantry && target->As_SoldierGameObj() )
+			{
+				if(obj->As_SmartGameObj()->Is_Splash_Possible(target->As_PhysicalGameObj()))
+				{
 				attackLocation ( obj, Commands->Get_Position(target), m_bTargetPrimaryFire );
 		}
+				else
+				{
+					attackLocation ( obj, target->As_SoldierGameObj()->Get_Bullseye_Position(), m_bTargetPrimaryFire );
+				}
+			}
+		}
 	}
 
   dp88_customAI::Timer_Expired(obj, number);
diff -uwrN sourceold/scripts/dp88_customAI.h source/scripts/dp88_customAI.h
--- sourceold/scripts/dp88_customAI.h	2016-10-31 07:17:12.462013500 +1000
+++ source/scripts/dp88_customAI.h	2018-11-11 08:30:08.529600000 +1000
@@ -334,6 +334,7 @@
   // Events
   virtual void Enemy_Seen ( GameObject *obj, GameObject *enemy );
   virtual void Timer_Expired( GameObject *obj, int number );
+  virtual void Damaged(GameObject *obj, GameObject *damager, float amount);
 
   // Custom AI initialisation script overrides
   virtual void Init( GameObject *obj );
diff -uwrN sourceold/scripts/engine_obj2.cpp source/scripts/engine_obj2.cpp
--- sourceold/scripts/engine_obj2.cpp	2016-10-31 07:17:12.478013500 +1000
+++ source/scripts/engine_obj2.cpp	2018-11-11 08:30:08.530600000 +1000
@@ -586,6 +586,26 @@
 		collisionTest.CollidedPhysObj == object->Peek_Physical_Object();
 }
 
+SCRIPTS_API bool SmartGameObj::Is_Splash_Possible(PhysicalGameObj* object)
+{
+	Vector3 bullseyePosition = object->Get_Position();
+	const Matrix3D lookTransform = Get_Look_Transform();
+	const Vector3 lookPosition = lookTransform.Get_Translation();
+	
+
+	
+	CastResultStruct castResult;
+	PhysRayCollisionTestClass collisionTest(LineSegClass(bullseyePosition,lookPosition), &castResult, BULLET_COLLISION_GROUP);
+	
+	object->Peek_Physical_Object()->Inc_Ignore_Counter();
+	PhysicsSceneClass::Get_Instance()->Cast_Ray(collisionTest, false);
+	object->Peek_Physical_Object()->Dec_Ignore_Counter();
+	
+	return
+		castResult.Fraction == 1.f ||
+		collisionTest.CollidedPhysObj == Peek_Physical_Object();
+}
+
 bool SCRIPTS_API ActionClass::Is_Acting( void )
 {
 	return ( ActionCode != NULL );
diff -uwrN sourceold/scripts/jmgBearHunter.cpp source/scripts/jmgBearHunter.cpp
--- sourceold/scripts/jmgBearHunter.cpp	2017-12-29 17:04:51.884600100 +1000
+++ source/scripts/jmgBearHunter.cpp	2018-11-11 08:30:08.532600000 +1000
@@ -7537,6 +7537,34 @@
 	*facing = node->facing;
 	return true;
 }
+bool JMG_Utility_Custom_Teleport_To_Random_Wander_Point::Get_A_Defense_Point(Vector3 *position,float *facing)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (wanderPointGroup != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(wanderPointGroup);
+	if (!node)
+	{
+		if (!retryOnFailure)
+			Console_Input("msg JMG_Utility_Custom_Teleport_To_Random_Wander_Point ERROR: No wander points could be found for that group!");
+		return false;
+	}
+	*position = node->position;
+	*facing = node->facing;
+	return true;
+}
+bool JMG_Utility_AI_Skittish_Herd_Animal::Get_A_Wander_Point(Vector3 *position,int wanderPointGroup)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (wanderPointGroup != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetNearest(*position);
+	if (!node)
+	{
+		Console_Input("msg JMG_Utility_AI_Skittish_Herd_Animal ERROR: No wander points could be found for that group!");
+		return false;
+	}
+	*position = node->position;
+	return true;
+}
 bool JMG_Utility_Zone_Teleport_To_Random_Wander_Point_Attach::Get_A_Defense_Point(Vector3 *position,float *facing)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
@@ -7565,11 +7593,11 @@
 bool JMG_Utility_AI_Goto_Player::GetRandomPosition(Vector3 *position)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1)
-		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(Get_Int_Parameter("WanderingAIGroupID"));
+	if (wanderingAiGroupId != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(wanderingAiGroupId);
 	if (!node)
 	{
-		Console_Input("msg JMG_Utility_AI_Vehicle ERROR: No wander points could be found for that group!");
+		Console_Input("msg JMG_Utility_AI_Goto_Player ERROR: No wander points could be found for that group!");
 		*position = Vector3();
 		return false;
 	}
@@ -7579,8 +7607,8 @@
 bool JMG_Utility_AI_Goto_Enemy::GetRandomPosition(Vector3 *position)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1)
-		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(Get_Int_Parameter("WanderingAIGroupID"));
+	if (wanderingAiGroupId != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(wanderingAiGroupId);
 	if (!node)
 	{
 		Console_Input("msg JMG_Utility_AI_Goto_Enemy ERROR: No wander points could be found for that group!");
@@ -7593,8 +7621,8 @@
 bool JMG_Utility_AI_Goto_Enemy_Not_Star::GetRandomPosition(Vector3 *position)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1)
-		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(Get_Int_Parameter("WanderingAIGroupID"));
+	if (wanderingAiGroupId != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(wanderingAiGroupId);
 	if (!node)
 	{
 		Console_Input("msg JMG_Utility_AI_Goto_Enemy_Not_Star ERROR: No wander points could be found for that group!");
diff -uwrN sourceold/scripts/jmgUtility.cpp source/scripts/jmgUtility.cpp
--- sourceold/scripts/jmgUtility.cpp	2017-12-29 17:04:51.889600100 +1000
+++ source/scripts/jmgUtility.cpp	2018-11-11 08:30:08.535600000 +1000
@@ -106,6 +106,9 @@
 }
 void JMG_Utility_Change_Model_On_Timer::Created(GameObject *obj)
 {
+	if (Get_Float_Parameter("Time") == 0)
+		Timer_Expired(obj,1);
+	else
 	Commands->Start_Timer(obj,this,Get_Float_Parameter("Time"),1);
 }
 void JMG_Utility_Change_Model_On_Timer::Timer_Expired(GameObject *obj,int number)
@@ -892,7 +895,7 @@
 			lastWanderPointSpot = Commands->Get_Position(obj);
 			Commands->Start_Timer(obj,this,0.1f,9);
 		}
-		else if (!(Get_Float_Parameter("MinRandWander") == Get_Float_Parameter("MaxRandWander") == 0))
+		else if (Get_Float_Parameter("MinRandWander") != 0.0f && Get_Float_Parameter("MaxRandWander") != 0.0f)
 			Commands->Start_Timer(obj,this,Get_Float_Parameter("MinRandWander") == Get_Float_Parameter("MaxRandWander") ? Get_Float_Parameter("MaxRandWander") : Commands->Get_Random(Get_Float_Parameter("MinRandWander"),Get_Float_Parameter("MaxRandWander")),2);
 	Commands->Start_Timer(obj,this,5.0,3);
 	Commands->Start_Timer(obj,this,0.1f,8);
@@ -995,7 +998,7 @@
 	if (number == 2)
 	{
 		Vector3 wanderPos = Vector3();
-		if (GetRandomPosition(&wanderPos) && (!Get_Int_Parameter("FollowTarget") || !currentAction.targetId))
+		if (GetRandomPosition(&wanderPos) && (!currentAction.targetId || (currentAction.targetId && !Get_Int_Parameter("FollowTarget"))))
 		{
 			moving = true;
 			lastWanderPointSpot = wanderPos;
@@ -1063,7 +1066,7 @@
 		if (attacking || currentAction.targetId)
 		{
 			GameObject *target = Commands->Find_Object(currentAction.targetId);
-			if ((attacking && !target) || (target && !Commands->Get_Health(target)))
+			if ((attacking && !target) || (target && !Commands->Get_Health(target)) || (target && Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)))
 			{
 				lastSeenCount = 0;
 				attacking = false;
@@ -1086,7 +1089,7 @@
 	}
 	if (number == 9)
 	{
-		if (!Get_Int_Parameter("FollowTarget") || !currentAction.targetId)
+		if (!currentAction.targetId || (currentAction.targetId && !Get_Int_Parameter("FollowTarget")))
 		{
 			if (JmgUtility::SimpleDistance(Commands->Get_Position(obj),lastWanderPointSpot) <= grabNextPointDistance)
 			{
@@ -2236,7 +2239,7 @@
 			{
 				underwater = true;
 				isUnderwater[playerId] = true;
-				DestroySoundEmitter(&pantSoundId);
+				HideSoundEmitter(&pantSoundId);
 				if (startedFadeRed)
 				{
 					Set_Screen_Fade_Color_Player(obj,1.0f,0.0f,0.0f,JmgUtility::MathClamp(Get_Float_Parameter("DrownTime")-drownTime,0,Get_Float_Parameter("StarDrownSequence")));
@@ -2265,7 +2268,7 @@
 				isUnderwater[playerId] = false;
 				JMG_Utility_Set_Screen_Color_Fade_Controller::Update_Player(obj,0.1f);
 				Set_Fog_Range_Player(obj,JMG_Utility_Swimming_Zone::fogMinDistance,JMG_Utility_Swimming_Zone::fogMaxDistance,0.1f);
-				DestroySoundEmitter(&heartBeatSoundId);
+				HideSoundEmitter(&heartBeatSoundId);
 				if (drownTime > 1.0f)
 					CreateSoundEmitter(obj,Get_Parameter("PantingSoundEmitterModel"),&pantSoundId);
 				if (drownTime > Get_Float_Parameter("DrownTime"))
@@ -2279,7 +2282,7 @@
 				if (drownTime <= 0)
 				{
 					drownTime = 0.0f;
-					DestroySoundEmitter(&pantSoundId);
+					HideSoundEmitter(&pantSoundId);
 				}
 			}
 		}
@@ -2374,6 +2377,12 @@
 	}
 	Commands->Set_Model(soundEmitter,model);
 }
+void JMG_Utility_Swimming_Infantry::HideSoundEmitter(int *soundId)
+{
+	GameObject *soundEmitter = Commands->Find_Object(*soundId);
+	if (soundEmitter)
+		Commands->Set_Model(soundEmitter,"null");
+}
 void JMG_Utility_Swimming_Infantry::DestroySoundEmitter(int *soundId)
 {
 	GameObject *soundEmitter = Commands->Find_Object(*soundId);
@@ -2477,7 +2486,7 @@
 		if (EnemyID)
 		{
 			GameObject *Target = Commands->Find_Object(EnemyID);
-			if (!Target || !Commands->Get_Health(Target) || Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj))
+			if (!Target || !Commands->Get_Health(Target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj)))
 			{
 				EnemyID = 0;
 				Goto_Location(obj);
@@ -2510,7 +2519,7 @@
 }
 void JMG_Utility_AI_Guardian_Aircraft::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
-	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj))
+	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj)))
 		return;
 	EnemyID = Commands->Get_ID(damager);
 	EnemyTimeOutTime = 2;
@@ -2662,6 +2671,18 @@
 	huntingStarId = 0;
 	stuckTime = 0;
 	reverseTime = 0;
+	wanderingAiGroupId = Get_Int_Parameter("WanderingAIGroupID");
+	wanderSpeed = Get_Float_Parameter("WanderSpeed");
+	huntSpeed = Get_Float_Parameter("HuntSpeed");
+	attackSpeed = Get_Float_Parameter("AttackSpeed");
+	returnHomeSpeed = Get_Float_Parameter("ReturnHomeSpeed");
+	changeWanderGroupCustom = Get_Int_Parameter("ChangeWanderGroupCustom");
+	changeWanderSpeedCustom = Get_Int_Parameter("ChangeWanderSpeedCustom");
+	changeHuntDistanceCustom = Get_Int_Parameter("ChangeHuntDistanceCustom");
+	changeHuntSpeedCustom = Get_Int_Parameter("ChangeHuntSpeedCustom");
+	changeReturnHomeSpeedCustom = Get_Int_Parameter("ChangeReturnHomeSpeedCustom");
+	changeMaxSightFromHomeLocationCustom = Get_Int_Parameter("ChangeMaxSightFromHomeLocationCustom");
+	changeAttackSpeedCustom = Get_Int_Parameter("ChangeAttackSpeedCustom");
 	maxSightFromHomeLocation = Get_Float_Parameter("MaxSightRangeFromHome");
 	maxSightFromHomeLocation *= maxSightFromHomeLocation;
 	huntStealth = Get_Int_Parameter("HuntStealth") ? true : false;
@@ -2676,6 +2697,8 @@
 	}
 	attackArriveDistance = Get_Float_Parameter("AttackDistance") >= 0 ? Get_Float_Parameter("AttackDistance") : weaponEffectiveRange;
 	huntArriveDistance = Get_Float_Parameter("HuntArriveDistance");
+	wanderDistanceOverride = Get_Float_Parameter("WanderDistanceOverride");
+	wanderDistanceOverride *= wanderDistanceOverride;
 	Commands->Enable_Enemy_Seen(obj,true);
 	Commands->Start_Timer(obj,this,1.0f,1);
 	Commands->Start_Timer(obj,this,1.0f,2);
@@ -2709,7 +2732,7 @@
 		targetId = Commands->Get_ID(seen);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,seen,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,seen,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 	if (targetId == Commands->Get_ID(seen))
 		lastSeenTime = Commands->Get_Random_Int(30,60);
@@ -2724,25 +2747,26 @@
 			lastSeenTime--;
 			if (!lastSeenTime)
 			{
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (targetId)
 		{
 			GameObject *target = Commands->Find_Object(targetId);
 			targetPos = Commands->Get_Position(target);
-			if (!target || !Commands->Get_Health(target) || Commands->Get_Player_Type(target) == -2 || (Commands->Get_Player_Type(obj) != -1 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)))
+			if (!target || !Commands->Get_Health(target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)) || Is_Script_Attached(target,"JMG_Utility_AI_Goto_Player_Ignore_Object"))
 			{
+				lastSeenTime = 0;
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (state == HUNTING_STAR)
 		{
 			GameObject *star = Get_GameObj(huntingStarId);
-			if (!star || !Commands->Get_Health(star))
-				Return_Home(obj);
+			if (!star || !Commands->Get_Health(star) || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance) || Is_Script_Attached(star,"JMG_Utility_AI_Goto_Player_Ignore_Object"))
+				Return_Home(obj,false);
 		}
 		if (state == IDLE || state == RETURNING_HOME || state == WANDERING_GROUP)
 		{
@@ -2750,27 +2774,21 @@
 			if (star)
 			{
 				state = HUNTING_STAR;
-				Attack_Move(obj,star,Vector3(),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,false,false);
+				Attack_Move(obj,star,Vector3(),huntSpeed,huntArriveDistance,false,false);
 			}
 		}
-		if (state == HUNTING_STAR)
-		{
-			GameObject *star = Get_GameObj(huntingStarId);
-			if (!star || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance))
-				Return_Home(obj);
-		}
-		if (state == IDLE && Get_Int_Parameter("WanderingAIGroupID") != -1)
+		if (state == IDLE && wanderingAiGroupId != -1)
 		{
 			Vector3 wanderPos = Vector3();
 			if (GetRandomPosition(&wanderPos))
 			{
 				state = WANDERING_GROUP;
 				homeLocation = wanderPos;
-				Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+				Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 			}
 		}
 		if (state == RETURNING_HOME || state == WANDERING_GROUP)
-			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (obj->As_VehicleGameObj() ? 25.0f : 1.0f))
+			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (wanderDistanceOverride != 0 ? wanderDistanceOverride : (obj->As_VehicleGameObj() ? 25.0f : 1.0f)))
 				state = IDLE;
 		if (state == ACTION_BADPATH)
 			Cant_Get_To_target(obj);
@@ -2798,6 +2816,57 @@
 		huntArriveDistance += Commands->Get_Random(Get_Float_Parameter("RandomHuntArriveDistance"),Get_Float_Parameter("RandomHuntArriveDistance"));
 	}
 }
+void JMG_Utility_AI_Goto_Player::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (changeWanderGroupCustom != 0 && changeWanderGroupCustom == message)
+	{
+		wanderingAiGroupId = param;
+		if (state == WANDERING_GROUP)
+		{
+			Vector3 wanderPos = Vector3();
+			if (GetRandomPosition(&wanderPos))
+			{
+				homeLocation = wanderPos;
+				Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,lastAction.speed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+			}
+		}
+	}
+	if (changeWanderSpeedCustom != 0 && changeWanderSpeedCustom == message)
+	{
+		wanderSpeed = param/100.0f;
+		if (state == WANDERING_GROUP)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,wanderSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeHuntDistanceCustom != 0 && changeHuntDistanceCustom == message)
+	{
+		huntSearchDistance = param/100.0f;
+		if (huntSearchDistance > 0)
+			huntSearchDistance *= huntSearchDistance;
+	}
+	if (changeHuntSpeedCustom != 0 && changeHuntSpeedCustom == message)
+	{
+		huntSpeed = param/100.0f;
+		if (state == HUNTING_STAR)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,huntSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeReturnHomeSpeedCustom != 0 && changeReturnHomeSpeedCustom == message)
+	{
+		returnHomeSpeed = param/100.0f;
+		if (state == RETURNING_HOME)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,returnHomeSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeMaxSightFromHomeLocationCustom != 0 && changeMaxSightFromHomeLocationCustom == message)
+	{
+		maxSightFromHomeLocation = param/100.0f;
+		maxSightFromHomeLocation *= maxSightFromHomeLocation;
+	}
+	if (changeAttackSpeedCustom != 0 && changeAttackSpeedCustom == message)
+	{
+		attackSpeed = param/100.0f;
+		if (state == ATTACKING_TARGET)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,attackSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+}
 void JMG_Utility_AI_Goto_Player::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason)
 {
 	if (reason == ACTION_COMPLETE_PATH_BAD_DEST)
@@ -2824,7 +2893,7 @@
 		targetId = Commands->Get_ID(damager);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,damager,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,damager,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 }
 void JMG_Utility_AI_Goto_Player::Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation)
@@ -2861,7 +2930,7 @@
 {
 	GameObject *nearest = NULL;
 	float nearDist = -1.0f;
-	Vector3 pos = maxSightFromHomeLocation > 0 ? homeLocation : Commands->Get_Position(obj);
+	Vector3 pos = Commands->Get_Position(obj);
 	for (int x = 1;x < 128;x++)
 	{
 		GameObject *player = Get_GameObj(x);
@@ -2877,6 +2946,8 @@
 			else if (player->As_SmartGameObj() && player->As_SmartGameObj()->Is_Stealthed())
 				continue;
 		}
+		if (maxSightFromHomeLocation > 0 && JmgUtility::SimpleDistance(homeLocation,Commands->Get_Position(player)) > maxSightFromHomeLocation)
+			continue;
 		float tempDist = JmgUtility::SimpleDistance(pos,Commands->Get_Position(player));
 		if (huntSearchDistance >= 0.0f && tempDist > huntSearchDistance)
 			continue;
@@ -2889,18 +2960,23 @@
 	}
 	return nearest;
 }
-void JMG_Utility_AI_Goto_Player::Return_Home(GameObject *obj)
+void JMG_Utility_AI_Goto_Player::Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint)
 {
-	Vector3 wanderPos = Vector3();
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	Vector3 wanderPos;
+	if (goNearLastWanderPoint.valid && Commands->Get_Random(0.0f,1.0f) < Get_Float_Parameter("ChanceToInvestigateLastSeenLocation"))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,goNearLastWanderPoint.location,wanderSpeed,1.0f,false,false);
+	}
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
+	{
+		state = WANDERING_GROUP;
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 	}
 	else if (Get_Int_Parameter("ReturnHome"))
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,NULL,homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,false,false);
 	}
 	else
 	{
@@ -2954,17 +3030,17 @@
 	if (star)
 	{
 		state = HUNTING_STAR;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,true,true);
+		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),huntSpeed,huntArriveDistance,true,true);
 	}
-	else if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,true,true);
 	}
 	else
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,true,true);
 	}
 }
 bool JMG_Utility_AI_Goto_Player::Choose_Target(GameObject *obj,GameObject *target)
@@ -2977,6 +3053,17 @@
 		return true;
 	return false;
 }
+JMG_Utility_AI_Goto_Player::ValidLastLocation::ValidLastLocation(int enemyId)
+{
+	GameObject *target = Commands->Find_Object(enemyId);
+	if (!target || !Commands->Get_Health(target))
+	{
+		this->valid = false;
+		return;
+	}
+	this->location = Commands->Get_Position(target);
+	this->valid = true;
+}
 AggressiveAttackSpotSystem *aggressiveAttackSpotSystemControl = NULL;
 void JMG_Utility_AI_Aggressive_Attack_Spot_Control::Created(GameObject *obj)
 {
@@ -3113,7 +3200,7 @@
 		{
 			GameObject *target = Commands->Find_Object(targetId);
 			targetPos = Commands->Get_Position(target);
-			if (!target || !Commands->Get_Health(target) || Commands->Get_Player_Type(target) == -2 || Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj))
+			if (!target || !Commands->Get_Health(target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)))
 			{
 				targetId = 0;
 				IdleChoice(obj,true);
@@ -3377,7 +3464,8 @@
 {
 	if (message == Get_Int_Parameter("TriggerCustom"))
 	{
-		Send_Custom_Event_To_Object(obj,Get_Parameter("Preset"),Get_Int_Parameter("Custom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+		int Param = Get_Int_Parameter("Param");
+		Send_Custom_Event_To_Object(obj,Get_Parameter("Preset"),Get_Int_Parameter("Custom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
 	}
 }
 void JMG_Utility_Basic_Spawner_Attach_Script::Created(GameObject *obj)
@@ -3959,7 +4047,7 @@
 		if (EnemyID)
 		{
 			GameObject *Target = Commands->Find_Object(EnemyID);
-			if (!Target || !Commands->Get_Health(Target) || Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj))
+			if (!Target || !Commands->Get_Health(Target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj)))
 			{
 				EnemyID = 0;
 				Goto_Location(obj);
@@ -3992,7 +4080,7 @@
 }
 void JMG_Utility_AI_Guardian_Infantry::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
-	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj))
+	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj)))
 		return;
 	EnemyID = Commands->Get_ID(damager);
 	EnemyTimeOutTime = Commands->Get_Random_Int(2,5);
@@ -4420,7 +4508,7 @@
 }
 void JMG_Utility_Zone_Teleport_To_Random_Wander_Point::Entered(GameObject *obj,GameObject *enter)
 {
-	if (CheckPlayerType(enter,playerType) || Commands->Get_Player_Type(enter) == -4)
+	if (CheckPlayerType(enter,playerType))
 		return;
 	Grab_Teleport_Spot(enter,5);
 }
@@ -4428,11 +4516,9 @@
 {
 	if (The_Game()->Get_Game_Duration_S() < 1.0f)
 	{
-		{
 			char params[220];
 			sprintf(params,"%d,%.2f",wanderPointGroup,safeTeleportDistance);
 			Commands->Attach_Script(enter,"JMG_Utility_Zone_Teleport_To_Random_Wander_Point_Attach",params);
-		}
 		return false;
 	}
 	if (!attempts)
@@ -4486,7 +4572,10 @@
 		Vector3 targetPos;
 		float facing;
 		if (!Get_A_Defense_Point(&targetPos,&facing))
+		{
+			Commands->Start_Timer(obj,this,0.25f,1);
 			return;
+		}
 		MoveablePhysClass *mphys = obj->As_PhysicalGameObj() ? obj->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
 		if (mphys && mphys->Find_Teleport_Location(targetPos,safeTeleportDistance,&targetPos))
 		{
@@ -5003,7 +5092,10 @@
 		int id = Get_Int_Parameter("ID");
 		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
 		if (object)
-			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+		{
+			int Param = Get_Int_Parameter("Param");
+			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
+		}
 	}
 }
 void JMG_Utility_Custom_Destroy_Self::Created(GameObject *obj)
@@ -5115,6 +5207,18 @@
 	huntStealth = Get_Int_Parameter("HuntStealth") ? true : false;
 	removeIgnoreTime = 0;
 	ignoreEnemyId = 0;
+	wanderingAiGroupId = Get_Int_Parameter("WanderingAIGroupID");
+	wanderSpeed = Get_Float_Parameter("WanderSpeed");
+	huntSpeed = Get_Float_Parameter("HuntSpeed");
+	attackSpeed = Get_Float_Parameter("AttackSpeed");
+	returnHomeSpeed = Get_Float_Parameter("ReturnHomeSpeed");
+	changeWanderGroupCustom = Get_Int_Parameter("ChangeWanderGroupCustom");
+	changeWanderSpeedCustom = Get_Int_Parameter("ChangeWanderSpeedCustom");
+	changeHuntDistanceCustom = Get_Int_Parameter("ChangeHuntDistanceCustom");
+	changeHuntSpeedCustom = Get_Int_Parameter("ChangeHuntSpeedCustom");
+	changeReturnHomeSpeedCustom = Get_Int_Parameter("ChangeReturnHomeSpeedCustom");
+	changeMaxSightFromHomeLocationCustom = Get_Int_Parameter("ChangeMaxSightFromHomeLocationCustom");
+	changeAttackSpeedCustom = Get_Int_Parameter("ChangeAttackSpeedCustom");
 	maxSightFromHomeLocation = Get_Float_Parameter("MaxSightRangeFromHome");
 	maxSightFromHomeLocation *= maxSightFromHomeLocation;
 	huntSearchDistance = Get_Float_Parameter("HuntSearchDistance") < 0.0f ? Get_Float_Parameter("HuntSearchDistance") : Get_Float_Parameter("HuntSearchDistance")*Get_Float_Parameter("HuntSearchDistance");
@@ -5126,6 +5230,8 @@
 	}
 	attackArriveDistance = Get_Float_Parameter("AttackDistance") >= 0 ? Get_Float_Parameter("AttackDistance") : weaponEffectiveRange;
 	huntArriveDistance = Get_Float_Parameter("HuntArriveDistance");
+	wanderDistanceOverride = Get_Float_Parameter("WanderDistanceOverride");
+	wanderDistanceOverride *= wanderDistanceOverride;
 	Commands->Enable_Enemy_Seen(obj,true);
 	Commands->Start_Timer(obj,this,1.0f,1);
 	Commands->Start_Timer(obj,this,1.0f,2);
@@ -5159,11 +5265,62 @@
 		targetId = Commands->Get_ID(seen);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,seen,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,seen,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 	if (targetId == Commands->Get_ID(seen))
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 }
+void JMG_Utility_AI_Goto_Enemy::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (changeWanderGroupCustom != 0 && changeWanderGroupCustom == message)
+	{
+		wanderingAiGroupId = param;
+		if (state == WANDERING_GROUP)
+		{
+			Vector3 wanderPos = Vector3();
+			if (GetRandomPosition(&wanderPos))
+			{
+				homeLocation = wanderPos;
+				Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,lastAction.speed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+			}
+		}
+	}
+	if (changeWanderSpeedCustom != 0 && changeWanderSpeedCustom == message)
+	{
+		wanderSpeed = param/100.0f;
+		if (state == WANDERING_GROUP)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,wanderSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeHuntDistanceCustom != 0 && changeHuntDistanceCustom == message)
+	{
+		huntSearchDistance = param/100.0f;
+		if (huntSearchDistance > 0)
+			huntSearchDistance *= huntSearchDistance;
+	}
+	if (changeHuntSpeedCustom != 0 && changeHuntSpeedCustom == message)
+	{
+		huntSpeed = param/100.0f;
+		if (state == HUNTING_STAR)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,huntSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeReturnHomeSpeedCustom != 0 && changeReturnHomeSpeedCustom == message)
+	{
+		returnHomeSpeed = param/100.0f;
+		if (state == RETURNING_HOME)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,returnHomeSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeMaxSightFromHomeLocationCustom != 0 && changeMaxSightFromHomeLocationCustom == message)
+	{
+		maxSightFromHomeLocation = param/100.0f;
+		maxSightFromHomeLocation *= maxSightFromHomeLocation;
+	}
+	if (changeAttackSpeedCustom != 0 && changeAttackSpeedCustom == message)
+	{
+		attackSpeed = param/100.0f;
+		if (state == ATTACKING_TARGET)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,attackSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+}
 void JMG_Utility_AI_Goto_Enemy::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -5174,25 +5331,26 @@
 			lastSeenTime--;
 			if (!lastSeenTime)
 			{
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (targetId)
 		{
 			GameObject *target = Commands->Find_Object(targetId);
 			targetPos = Commands->Get_Position(target);
-			if (!target || !Commands->Get_Health(target) || Commands->Get_Player_Type(target) == -2 || (Commands->Get_Player_Type(obj) != -1 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)))
+			if (!target || !Commands->Get_Health(target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)) || Is_Script_Attached(target,"JMG_Utility_AI_Goto_Enemy_Ignore_Object"))
 			{
+				lastSeenTime = 0;
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (state == HUNTING_STAR)
 		{
 			GameObject *star = Commands->Find_Object(huntingEnemyId);
-			if (!star || !Commands->Get_Health(star))
-				Return_Home(obj);
+			if (!star || !Commands->Get_Health(star) || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance) || Is_Script_Attached(star,"JMG_Utility_AI_Goto_Enemy_Ignore_Object"))
+				Return_Home(obj,false);
 		}
 		if (state == IDLE || state == RETURNING_HOME || state == WANDERING_GROUP)
 		{
@@ -5200,27 +5358,21 @@
 			if (star)
 			{
 				state = HUNTING_STAR;
-				Attack_Move(obj,star,Vector3(),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,false,false);
+				Attack_Move(obj,star,Vector3(),huntSpeed,huntArriveDistance,false,false);
 			}
 		}
-		if (state == HUNTING_STAR)
-		{
-			GameObject *star = Commands->Find_Object(huntingEnemyId);
-			if (!star || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance))
-				Return_Home(obj);
-		}
-		if (state == IDLE && Get_Int_Parameter("WanderingAIGroupID") != -1)
+		if (state == IDLE && wanderingAiGroupId != -1)
 		{
 			Vector3 wanderPos = Vector3();
 			if (GetRandomPosition(&wanderPos))
 			{
 				state = WANDERING_GROUP;
 				homeLocation = wanderPos;
-				Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+				Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 			}
 		}
 		if (state == RETURNING_HOME || state == WANDERING_GROUP)
-			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (obj->As_VehicleGameObj() ? 25.0f : 1.0f))
+			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (wanderDistanceOverride != 0 ? wanderDistanceOverride : (obj->As_VehicleGameObj() ? 25.0f : 1.0f)))
 				state = IDLE;
 		if (state == ACTION_BADPATH)
 			Cant_Get_To_target(obj);
@@ -5276,7 +5428,7 @@
 		targetId = Commands->Get_ID(damager);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,damager,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,damager,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 }
 void JMG_Utility_AI_Goto_Enemy::Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation)
@@ -5315,7 +5467,7 @@
 		return NULL;
 	GameObject *nearest = NULL;
 	float nearDist = -1.0f;
-	Vector3 pos = maxSightFromHomeLocation > 0 ? homeLocation : Commands->Get_Position(obj);
+	Vector3 pos = Commands->Get_Position(obj);
 	int myPlayerType = Commands->Get_Player_Type(obj);
 	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
 	{
@@ -5338,6 +5490,8 @@
 			else if (o->As_SmartGameObj() && o->As_SmartGameObj()->Is_Stealthed())
 				continue;
 		}
+		if (maxSightFromHomeLocation > 0 && JmgUtility::SimpleDistance(homeLocation,Commands->Get_Position(o)) > maxSightFromHomeLocation)
+			continue;
 		float tempDist = JmgUtility::SimpleDistance(pos,Commands->Get_Position(o));
 		if (huntSearchDistance >= 0.0f && tempDist > huntSearchDistance)
 			continue;
@@ -5350,18 +5504,23 @@
 	}
 	return nearest;
 }
-void JMG_Utility_AI_Goto_Enemy::Return_Home(GameObject *obj)
+void JMG_Utility_AI_Goto_Enemy::Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint)
 {
-	Vector3 wanderPos = Vector3();
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	Vector3 wanderPos;
+	if (goNearLastWanderPoint.valid && Commands->Get_Random(0.0f,1.0f) < Get_Float_Parameter("ChanceToInvestigateLastSeenLocation"))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,goNearLastWanderPoint.location,wanderSpeed,1.0f,false,false);
+	}
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
+	{
+		state = WANDERING_GROUP;
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 	}
 	else if (Get_Int_Parameter("ReturnHome"))
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,NULL,homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,false,false);
 	}
 	else
 	{
@@ -5415,17 +5574,17 @@
 	if (star && Commands->Get_ID(star) != ignoreEnemyId)
 	{
 		state = HUNTING_STAR;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,true,true);
+		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),huntSpeed,huntArriveDistance,true,true);
 	}
-	else if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,true,true);
 	}
 	else
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,true,true);
 	}
 }
 bool JMG_Utility_AI_Goto_Enemy::Choose_Target(GameObject *obj,GameObject *target)
@@ -5438,6 +5597,17 @@
 		return true;
 	return false;
 }
+JMG_Utility_AI_Goto_Enemy::ValidLastLocation::ValidLastLocation(int enemyId)
+{
+	GameObject *target = Commands->Find_Object(enemyId);
+	if (!target || !Commands->Get_Health(target))
+	{
+		this->valid = false;
+		return;
+	}
+	this->location = Commands->Get_Position(target);
+	this->valid = true;
+}
 void JMG_Utility_AI_Goto_Enemy_Not_Star::Created(GameObject *obj)
 {
 	state = IDLE;
@@ -5451,6 +5621,18 @@
 	huntStealth = Get_Int_Parameter("HuntStealth") ? true : false;
 	removeIgnoreTime = 0;
 	ignoreEnemyId = 0;
+	wanderingAiGroupId = Get_Int_Parameter("WanderingAIGroupID");
+	wanderSpeed = Get_Float_Parameter("WanderSpeed");
+	huntSpeed = Get_Float_Parameter("HuntSpeed");
+	attackSpeed = Get_Float_Parameter("AttackSpeed");
+	returnHomeSpeed = Get_Float_Parameter("ReturnHomeSpeed");
+	changeWanderGroupCustom = Get_Int_Parameter("ChangeWanderGroupCustom");
+	changeWanderSpeedCustom = Get_Int_Parameter("ChangeWanderSpeedCustom");
+	changeHuntDistanceCustom = Get_Int_Parameter("ChangeHuntDistanceCustom");
+	changeHuntSpeedCustom = Get_Int_Parameter("ChangeHuntSpeedCustom");
+	changeReturnHomeSpeedCustom = Get_Int_Parameter("ChangeReturnHomeSpeedCustom");
+	changeMaxSightFromHomeLocationCustom = Get_Int_Parameter("ChangeMaxSightFromHomeLocationCustom");
+	changeAttackSpeedCustom = Get_Int_Parameter("ChangeAttackSpeedCustom");
 	maxSightFromHomeLocation = Get_Float_Parameter("MaxSightRangeFromHome");
 	maxSightFromHomeLocation *= maxSightFromHomeLocation;
 	huntSearchDistance = Get_Float_Parameter("HuntSearchDistance") < 0.0f ? Get_Float_Parameter("HuntSearchDistance") : Get_Float_Parameter("HuntSearchDistance")*Get_Float_Parameter("HuntSearchDistance");
@@ -5462,6 +5644,8 @@
 	}
 	attackArriveDistance = Get_Float_Parameter("AttackDistance") >= 0 ? Get_Float_Parameter("AttackDistance") : weaponEffectiveRange;
 	huntArriveDistance = Get_Float_Parameter("HuntArriveDistance");
+	wanderDistanceOverride = Get_Float_Parameter("WanderDistanceOverride");
+	wanderDistanceOverride *= wanderDistanceOverride;
 	Commands->Enable_Enemy_Seen(obj,true);
 	Commands->Start_Timer(obj,this,1.0f,1);
 	Commands->Start_Timer(obj,this,1.0f,2);
@@ -5495,7 +5679,7 @@
 		targetId = Commands->Get_ID(seen);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,seen,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,seen,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 	if (targetId == Commands->Get_ID(seen))
 		lastSeenTime = Commands->Get_Random_Int(30,60);
@@ -5510,25 +5694,26 @@
 			lastSeenTime--;
 			if (!lastSeenTime)
 			{
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (targetId)
 		{
 			GameObject *target = Commands->Find_Object(targetId);
 			targetPos = Commands->Get_Position(target);
-			if (!target || !Commands->Get_Health(target) || Commands->Get_Player_Type(target) == -2 || (Commands->Get_Player_Type(obj) != -1 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)))
+			if (!target || !Commands->Get_Health(target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(target) == Commands->Get_Player_Type(obj)) || Is_Script_Attached(target,"JMG_Utility_AI_Goto_Enemy_Not_Star_Ignore_Object"))
 			{
+				lastSeenTime = 0;
+				Return_Home(obj,ValidLastLocation(targetId));
 				targetId = 0;
-				Return_Home(obj);
 			}
 		}
 		if (state == HUNTING_STAR)
 		{
 			GameObject *star = Commands->Find_Object(huntingEnemyId);
-			if (!star || !Commands->Get_Health(star))
-				Return_Home(obj);
+			if (!star || !Commands->Get_Health(star) || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance) || Is_Script_Attached(star,"JMG_Utility_AI_Goto_Enemy_Not_Star_Ignore_Object"))
+				Return_Home(obj,false);
 		}
 		if (state == IDLE || state == RETURNING_HOME || state == WANDERING_GROUP)
 		{
@@ -5536,27 +5721,21 @@
 			if (star)
 			{
 				state = HUNTING_STAR;
-				Attack_Move(obj,star,Vector3(),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,false,false);
+				Attack_Move(obj,star,Vector3(),huntSpeed,huntArriveDistance,false,false);
 			}
 		}
-		if (state == HUNTING_STAR)
-		{
-			GameObject *star = Commands->Find_Object(huntingEnemyId);
-			if (!star || (huntSearchDistance >= 0.0f && JmgUtility::SimpleDistance(Commands->Get_Position(star),Commands->Get_Position(obj)) > huntSearchDistance))
-				Return_Home(obj);
-		}
-		if (state == IDLE && Get_Int_Parameter("WanderingAIGroupID") != -1)
+		if (state == IDLE && wanderingAiGroupId != -1)
 		{
 			Vector3 wanderPos = Vector3();
 			if (GetRandomPosition(&wanderPos))
 			{
 				state = WANDERING_GROUP;
 				homeLocation = wanderPos;
-				Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+				Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 			}
 		}
 		if (state == RETURNING_HOME || state == WANDERING_GROUP)
-			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (obj->As_VehicleGameObj() ? 25.0f : 1.0f))
+			if (JmgUtility::SimpleDistance(lastAction.location,Commands->Get_Position(obj)) <= (wanderDistanceOverride != 0 ? wanderDistanceOverride : (obj->As_VehicleGameObj() ? 25.0f : 1.0f)))
 				state = IDLE;
 		if (state == ACTION_BADPATH)
 			Cant_Get_To_target(obj);
@@ -5585,6 +5764,57 @@
 		huntArriveDistance += Commands->Get_Random(Get_Float_Parameter("RandomHuntArriveDistance"),Get_Float_Parameter("RandomHuntArriveDistance"));
 	}
 }
+void JMG_Utility_AI_Goto_Enemy_Not_Star::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (changeWanderGroupCustom != 0 && changeWanderGroupCustom == message)
+	{
+		wanderingAiGroupId = param;
+		if (state == WANDERING_GROUP)
+		{
+			Vector3 wanderPos = Vector3();
+			if (GetRandomPosition(&wanderPos))
+			{
+				homeLocation = wanderPos;
+				Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,lastAction.speed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+			}
+		}
+	}
+	if (changeWanderSpeedCustom != 0 && changeWanderSpeedCustom == message)
+	{
+		wanderSpeed = param/100.0f;
+		if (state == WANDERING_GROUP)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,wanderSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeHuntDistanceCustom != 0 && changeHuntDistanceCustom == message)
+	{
+		huntSearchDistance = param/100.0f;
+		if (huntSearchDistance > 0)
+			huntSearchDistance *= huntSearchDistance;
+	}
+	if (changeHuntSpeedCustom != 0 && changeHuntSpeedCustom == message)
+	{
+		huntSpeed = param/100.0f;
+		if (state == HUNTING_STAR)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,huntSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeReturnHomeSpeedCustom != 0 && changeReturnHomeSpeedCustom == message)
+	{
+		returnHomeSpeed = param/100.0f;
+		if (state == RETURNING_HOME)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,returnHomeSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+	if (changeMaxSightFromHomeLocationCustom != 0 && changeMaxSightFromHomeLocationCustom == message)
+	{
+		maxSightFromHomeLocation = param/100.0f;
+		maxSightFromHomeLocation *= maxSightFromHomeLocation;
+	}
+	if (changeAttackSpeedCustom != 0 && changeAttackSpeedCustom == message)
+	{
+		attackSpeed = param/100.0f;
+		if (state == ATTACKING_TARGET)
+			Attack_Move(obj,Commands->Find_Object(lastAction.targetId),lastAction.location,attackSpeed,lastAction.distance,lastAction.attack,lastAction.overrideLocation);
+	}
+}
 void JMG_Utility_AI_Goto_Enemy_Not_Star::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason)
 {
 	if (reason == ACTION_COMPLETE_PATH_BAD_DEST)
@@ -5612,7 +5842,7 @@
 		targetId = Commands->Get_ID(damager);
 		lastSeenTime = Commands->Get_Random_Int(30,60);
 		state = ATTACKING_TARGET;
-		Attack_Move(obj,damager,Vector3(),Get_Float_Parameter("AttackSpeed"),attackArriveDistance,true,false);
+		Attack_Move(obj,damager,Vector3(),attackSpeed,attackArriveDistance,true,false);
 	}
 }
 void JMG_Utility_AI_Goto_Enemy_Not_Star::Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation)
@@ -5651,7 +5881,7 @@
 		return NULL;
 	GameObject *nearest = NULL;
 	float nearDist = -1.0f;
-	Vector3 pos = maxSightFromHomeLocation > 0 ? homeLocation : Commands->Get_Position(obj);
+	Vector3 pos = Commands->Get_Position(obj);
 	int myPlayerType = Commands->Get_Player_Type(obj);
 	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
 	{
@@ -5674,6 +5904,8 @@
 			else if (o->As_SmartGameObj() && o->As_SmartGameObj()->Is_Stealthed())
 				continue;
 		}
+		if (maxSightFromHomeLocation > 0 && JmgUtility::SimpleDistance(homeLocation,Commands->Get_Position(o)) > maxSightFromHomeLocation)
+			continue;
 		float tempDist = JmgUtility::SimpleDistance(pos,Commands->Get_Position(o));
 		if (huntSearchDistance >= 0.0f && tempDist > huntSearchDistance)
 			continue;
@@ -5686,18 +5918,23 @@
 	}
 	return nearest;
 }
-void JMG_Utility_AI_Goto_Enemy_Not_Star::Return_Home(GameObject *obj)
+void JMG_Utility_AI_Goto_Enemy_Not_Star::Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint)
 {
-	Vector3 wanderPos = Vector3();
-	if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	Vector3 wanderPos;
+	if (goNearLastWanderPoint.valid && Commands->Get_Random(0.0f,1.0f) < Get_Float_Parameter("ChanceToInvestigateLastSeenLocation"))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,NULL,wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,goNearLastWanderPoint.location,wanderSpeed,1.0f,false,false);
+	}
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
+	{
+		state = WANDERING_GROUP;
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,false,false);
 	}
 	else if (Get_Int_Parameter("ReturnHome"))
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,NULL,homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,false,false);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,false,false);
 	}
 	else
 	{
@@ -5751,17 +5988,17 @@
 	if (star && Commands->Get_ID(star) != ignoreEnemyId)
 	{
 		state = HUNTING_STAR;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),Get_Float_Parameter("HuntSpeed"),huntArriveDistance,true,true);
+		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),Commands->Get_Position(star),huntSpeed,huntArriveDistance,true,true);
 	}
-	else if (Get_Int_Parameter("WanderingAIGroupID") != -1 && GetRandomPosition(&wanderPos))
+	else if (wanderingAiGroupId != -1 && GetRandomPosition(&wanderPos))
 	{
 		state = WANDERING_GROUP;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),wanderPos,Get_Float_Parameter("WanderSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,wanderPos,wanderSpeed,1.0f,true,true);
 	}
 	else
 	{
 		state = RETURNING_HOME;
-		Attack_Move(obj,Commands->Find_Object(lastAction.targetId),homeLocation,Get_Float_Parameter("ReturnHomeSpeed"),1.0f,true,true);
+		Attack_Move(obj,NULL,homeLocation,returnHomeSpeed,1.0f,true,true);
 	}
 }
 bool JMG_Utility_AI_Goto_Enemy_Not_Star::Choose_Target(GameObject *obj,GameObject *target)
@@ -5774,6 +6011,17 @@
 		return true;
 	return false;
 }
+JMG_Utility_AI_Goto_Enemy_Not_Star::ValidLastLocation::ValidLastLocation(int enemyId)
+{
+	GameObject *target = Commands->Find_Object(enemyId);
+	if (!target || !Commands->Get_Health(target))
+	{
+		this->valid = false;
+		return;
+	}
+	this->location = Commands->Get_Position(target);
+	this->valid = true;
+}
 void JMG_Utility_Grant_Key_On_Create::Created(GameObject *obj)
 {
 	Commands->Grant_Key(obj,Get_Int_Parameter("Key"),Get_Int_Parameter("Grant") ? true : false);
@@ -5781,14 +6029,21 @@
 void JMG_Utility_Custom_Send_Custom::Created(GameObject *obj)
 {
 	recieveMessage = Get_Int_Parameter("Custom");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	randomDelay = Get_Float_Parameter("RandomDelay");
+	randomChance = Get_Float_Parameter("RandomChance");
 }
 void JMG_Utility_Custom_Send_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
 {
 	if (message == recieveMessage)
 	{
-		int id = Get_Int_Parameter("ID");
+		if (randomChance && randomChance < Commands->Get_Random(0.0f,1.0f))
+			return;
 		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
-		Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+ 		Commands->Send_Custom_Event(obj,object,custom,Param == -1 ? param : Param,delay+(randomDelay > 0 ? Commands->Get_Random(0.0f,randomDelay) : 0.0f));
 	}
 }
 void JMG_Utility_Damage_Unoccupied_Vehicle::Created(GameObject *obj)
@@ -5887,7 +6142,7 @@
 		if (EnemyID)
 		{
 			GameObject *Target = Commands->Find_Object(EnemyID);
-			if (!Target || !Commands->Get_Health(Target) || Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj))
+			if (!Target || !Commands->Get_Health(Target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj)))
 			{
 				EnemyID = 0;
 				Goto_Location(obj);
@@ -5948,7 +6203,7 @@
 }
 void JMG_Utility_AI_Guardian_Vehicle::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
-	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj))
+	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj)))
 		return;
 	EnemyID = Commands->Get_ID(damager);
 	EnemyTimeOutTime = Commands->Get_Random_Int(2,5);
@@ -7195,7 +7450,7 @@
 				if (dist > warnDistance)
 				{
 					screenEffectOn[x] = true;
-					float TempDistCalc = 1-(dist-maxSurviveDistance)/(warnDistance-maxSurviveDistance);
+					float TempDistCalc = min(1-(dist-maxSurviveDistance)/(warnDistance-maxSurviveDistance),1.0f);
 					float inverted = 1-TempDistCalc;
 					Set_Screen_Fade_Color_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].X*inverted+screenFadeColor.X,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Y*inverted+screenFadeColor.Y,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Z*inverted+screenFadeColor.Z,0.1f);
 					Set_Screen_Fade_Opacity_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::opacity[x]*inverted+TempDistCalc,0.1f);
@@ -7221,7 +7476,7 @@
 					if (dist > aircraftWarnDistance)
 					{
 						screenEffectOn[x] = true;
-						float TempDistCalc = 1-(dist-maxSurviveDistance)/(aircraftWarnDistance-maxSurviveDistance);
+						float TempDistCalc = min(1-(dist-maxSurviveDistance)/(aircraftWarnDistance-maxSurviveDistance),1.0f);
 						float inverted = 1-TempDistCalc;
 						Set_Screen_Fade_Color_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].X*inverted+screenFadeColor.X,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Y*inverted+screenFadeColor.Y,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Z*inverted+screenFadeColor.Z,0.1f);
 						Set_Screen_Fade_Opacity_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::opacity[x]*inverted+TempDistCalc,0.1f);
@@ -7243,7 +7498,7 @@
 					if (dist > vehicleWarnDistance)
 					{
 						screenEffectOn[x] = true;
-						float TempDistCalc = 1-(dist-maxSurviveDistance)/(vehicleWarnDistance-maxSurviveDistance);
+						float TempDistCalc = min(1-(dist-maxSurviveDistance)/(vehicleWarnDistance-maxSurviveDistance),1.0f);
 						float inverted = 1-TempDistCalc;
 						Set_Screen_Fade_Color_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].X*inverted+screenFadeColor.X,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Y*inverted+screenFadeColor.Y,JMG_Utility_Set_Screen_Color_Fade_Controller::color[x].Z*inverted+screenFadeColor.Z,0.1f);
 						Set_Screen_Fade_Opacity_Player(player,JMG_Utility_Set_Screen_Color_Fade_Controller::opacity[x]*inverted+TempDistCalc,0.1f);
@@ -7470,7 +7725,10 @@
 		int id = Get_Int_Parameter("ID");
 		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
 		if (object)
-			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+		{
+			int Param = Get_Int_Parameter("Param");
+			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
+		}
 		this->Destroy_Script();
 	}
 }
@@ -7531,7 +7789,10 @@
 		int id = Get_Int_Parameter("ID");
 		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
 		if (object)
-			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+		{
+			int Param = Get_Int_Parameter("Param");
+			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
+		}
 		this->Destroy_Script();
 	}
 }
@@ -7704,10 +7965,13 @@
 {
 	if (message == custom)
 	{
+		if (BasicObjectiveSystem.Get_Objective_Status(Get_Int_Parameter("ObjectiveID")) != NewObjectiveSystem::Accomplished)
+		{
 		BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("ObjectiveID"),NewObjectiveSystem::Primary,NewObjectiveSystem::Hidden,0,"",0);
 		BasicObjectiveSystem.Set_Objective_Status(Get_Int_Parameter("ObjectiveID"),NewObjectiveSystem::Failed);
 	}
 }
+}
 void JMG_Utility_Force_Player_Team_At_Gameover::Created(GameObject *obj)
 {
 	team = Get_Int_Parameter("Team");
@@ -7759,7 +8023,7 @@
 		if (EnemyID)
 		{
 			GameObject *Target = Commands->Find_Object(EnemyID);
-			if (!Target || !Commands->Get_Health(Target) || Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj))
+			if (!Target || !Commands->Get_Health(Target) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(Target) == Commands->Get_Player_Type(obj)))
 			{
 				EnemyID = 0;
 				Goto_Location(obj);
@@ -7793,7 +8057,7 @@
 }
 void JMG_Utility_AI_Guardian_Generic::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
-	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj))
+	if (damage <= 0 || (EnemyID && EnemyTimeOutTime) || (Commands->Get_Player_Type(obj) != -2 && Commands->Get_Player_Type(damager) == Commands->Get_Player_Type(obj)))
 		return;
 	EnemyID = Commands->Get_ID(damager);
 	EnemyTimeOutTime = Commands->Get_Random_Int(2,5);
@@ -8302,7 +8566,7 @@
 	rate = Get_Float_Parameter("Rate");
 	repeat = Get_Int_Parameter("Repeat") ? true : false;
 	id = Get_Int_Parameter("ID");
-	message = Get_Int_Parameter("Message");
+	custom = Get_Int_Parameter("Message");
 	param = Get_Int_Parameter("Param");
 	enabled = Get_Int_Parameter("StartsEnabled") ? true : false;
 	enableCustom = Get_Int_Parameter("EnableCustom");
@@ -8330,7 +8594,7 @@
 			{
 				GameObject *object = !id ? obj : Commands->Find_Object(id);
 				if (object)
-					Commands->Send_Custom_Event(obj,object,message,param,0);
+					Commands->Send_Custom_Event(obj,object,custom,param,0);
 				if (!repeat)
 				{
 					Destroy_Script();
@@ -8348,7 +8612,7 @@
 	rate = Get_Float_Parameter("Rate");
 	repeat = Get_Int_Parameter("Repeat") ? true : false;
 	id = Get_Int_Parameter("ID");
-	message = Get_Int_Parameter("Message");
+	custom = Get_Int_Parameter("Message");
 	param = Get_Int_Parameter("Param");
 	enabled = Get_Int_Parameter("StartsEnabled") ? true : false;
 	enableCustom = Get_Int_Parameter("EnableCustom");
@@ -8376,7 +8640,7 @@
 			{
 				GameObject *object = !id ? obj : Commands->Find_Object(id);
 				if (object)
-					Commands->Send_Custom_Event(obj,object,message,param,0);
+					Commands->Send_Custom_Event(obj,object,custom,param,0);
 				if (!repeat)
 				{
 					Destroy_Script();
@@ -8401,7 +8665,7 @@
 	rate = Get_Float_Parameter("Rate");
 	repeat = Get_Int_Parameter("Repeat") ? true : false;
 	id = Get_Int_Parameter("ID");
-	message = Get_Int_Parameter("Message");
+	custom = Get_Int_Parameter("Message");
 	param = Get_Int_Parameter("Param");
 	enabled = Get_Int_Parameter("StartsEnabled") ? true : false;
 	enableCustom = Get_Int_Parameter("EnableCustom");
@@ -8458,7 +8722,7 @@
 {
 	GameObject *object = !id ? obj : Commands->Find_Object(id);
 	if (object)
-		Commands->Send_Custom_Event(obj,object,message,param == -1 ? paramOverride : param,0);
+		Commands->Send_Custom_Event(obj,object,custom,param == -1 ? paramOverride : param,0);
 	if (!repeat)
 		Destroy_Script();
 }
@@ -8609,7 +8873,7 @@
 		lastTriggerTime = currentTime;
 		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
 		if (object)
-			Commands->Send_Custom_Event(obj,object,sendCustom,params,delay);
+			Commands->Send_Custom_Event(obj,object,sendCustom,params == -1 ? param : params,delay);
 	}
 	if (message == enableCustom)
 	{
@@ -9041,6 +9305,1075 @@
 	Commands->Set_Facing(newObject,Commands->Get_Facing(obj));
 	Commands->Destroy_Object(obj);
 }
+SList<JMG_Utility_Basic_Spawner_In_Radius::SpawnObjectNode> JMG_Utility_Basic_Spawner_In_Radius::spawnObjectNodeList;
+void JMG_Utility_Basic_Spawner_In_Radius::Created(GameObject *obj)
+{
+	scriptId = Get_Int_Parameter("Script_ID");
+	if (scriptId == -1)
+		scriptId = this->Get_ID();
+	spawnedObjects = 0;
+	spawnLocation = Get_Vector3_Parameter("Spawn_Location");
+	if (spawnLocation.X == -1.0f && spawnLocation.Y == -1.0f)
+	{
+		Vector3 mypos = Commands->Get_Position(obj);
+		spawnLocation.X = mypos.X;
+		spawnLocation.Y = mypos.Y;
+		if (spawnLocation.Z == -1.0f)
+			spawnLocation.Z = mypos.Z;
+	}
+	spawnAtATime = Get_Int_Parameter("Spawn_At_A_Time");
+	spawnLimit = Get_Int_Parameter("Spawn_Limit");
+	minRadius = Get_Float_Parameter("Min_Spawn_Radius");
+	maxRadius = Get_Float_Parameter("Max_Spawn_Radius");
+	initialSpawnHeight = Get_Float_Parameter("Initial_Spawn_Height");
+	xMultiplier = Get_Float_Parameter("X_Multiplier");
+	yMultiplier = Get_Float_Parameter("Y_Multiplier");
+	rate = Get_Float_Parameter("Spawn_Rate");
+	randomRate = Get_Float_Parameter("Random_Spawn_Rate");
+	collisionCheck = Get_Int_Parameter("Collision_Check")?true:false;
+	retryAttempts = Get_Int_Parameter("Collision_Retry_Attempts");
+	addHeight = Get_Float_Parameter("Collision_Add_Height");
+	changeSpawnCapCustom = Get_Int_Parameter("Change_Spawn_Cap_Custom");
+	sprintf(preset,Get_Parameter("Spawn_Preset"));
+	spawnCount = 0;
+	int initialSpawn = Get_Int_Parameter("Initial_Spawn");
+	if (initialSpawn == -1)
+		initialSpawn = spawnAtATime;
+	pointMustBeInPathfind = Get_Int_Parameter("Point_Must_Be_In_Pathfind")?true:false;
+	manualFacing = Get_Int_Parameter("Manual_Facing")?true:false;
+	faceLocation = Get_Vector3_Parameter("Face_Location");
+	faceDirection = Get_Float_Parameter("Face_Direction");
+	ignoreRayCastFailure = Get_Int_Parameter("Ignore_Ray_Cast_Failure")?true:false;
+	minDistanceBetweenObjects = Get_Float_Parameter("Min_Distance_Between_Objects");
+	minDistanceBetweenObjects *= minDistanceBetweenObjects;
+	spawnGroupId = Get_Int_Parameter("Spawn_Group_ID");
+	for (int x = 0;x < initialSpawn;x++)
+		for (int y = 0;y < 5;y++)
+		{
+			JMG_Utility_Basic_Spawner_In_Radius::SpawnFailureTypes spawnResult = AttemptSpawn(obj);
+			if (spawnResult == SpawnFailureTypes::SUCCESS)
+				break;
+			if (spawnResult == SpawnFailureTypes::LIMIT_REACHED)
+				x = initialSpawn+1;
+		}
+	Commands->Start_Timer(obj,this,rate+(randomRate ? Commands->Get_Random(-randomRate,randomRate) : 0.0f),1);
+}
+void JMG_Utility_Basic_Spawner_In_Radius::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1 && !The_Game()->Is_Game_Over())
+	{
+		JMG_Utility_Basic_Spawner_In_Radius::SpawnFailureTypes spawnResult = AttemptSpawn(obj);
+		if (spawnResult == SpawnFailureTypes::SPAWN_BLOCKED)
+		{
+			Commands->Start_Timer(obj,this,0.1f,1);
+			return;
+		}
+		if (spawnResult == SpawnFailureTypes::SUCCESS)
+			Commands->Start_Timer(obj,this,rate+(randomRate ? Commands->Get_Random(-randomRate,randomRate) : 0.0f),1);
+	}
+}
+void JMG_Utility_Basic_Spawner_In_Radius::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (changeSpawnCapCustom == -1 && message == changeSpawnCapCustom)
+	{
+		spawnAtATime = param;
+	}
+	if (message == 6873523 && param == scriptId)
+	{
+		spawnedObjects++;
+	}
+	if (message == 6873524 && param == scriptId)
+	{
+		spawnedObjects--;
+		Commands->Send_Custom_Event(obj,obj,6873525,scriptId,rate+(randomRate ? Commands->Get_Random(-randomRate,randomRate) : 0.0f));
+	}
+	if (message == 6873525 && param == scriptId)
+	{
+		if (AttemptSpawn(obj) == SpawnFailureTypes::SPAWN_BLOCKED)
+			Commands->Send_Custom_Event(obj,obj,6873525,scriptId,0.1f);
+	}
+}
+JMG_Utility_Basic_Spawner_In_Radius::SpawnFailureTypes JMG_Utility_Basic_Spawner_In_Radius::AttemptSpawn(GameObject *obj)
+{
+	if (!(spawnedObjects < spawnAtATime))
+		return SpawnFailureTypes::LIMIT_REACHED;
+	if (!((spawnLimit >= 0 && spawnCount < spawnLimit) || spawnLimit < 0))
+		return SpawnFailureTypes::LIMIT_REACHED;
+	float distance = (minRadius < maxRadius ? Commands->Get_Random(minRadius,maxRadius) : minRadius);
+	float facing = Commands->Get_Random(-180.0f,180.0f);
+	Vector3 topRay = spawnLocation,bottomRay;
+	topRay.X = spawnLocation.X+cos(facing)*(distance*xMultiplier);
+	topRay.Y = spawnLocation.Y+sin(facing)*(distance*yMultiplier);
+	bottomRay = topRay;
+	topRay.Z = spawnLocation.Z;
+	Vector3 bottom,top;
+	PhysicsSceneClass::Get_Instance()->Get_Level_Extents(bottom,top);
+	if (spawnLocation.Z < bottom.Z)
+		bottomRay.Z = top.Z+1.0f;
+	else
+		bottomRay.Z = bottom.Z-1.0f;
+	CastResultStruct res;
+	res.ComputeContactPoint = true;
+	LineSegClass ray(topRay,bottomRay);
+	PhysRayCollisionTestClass coltest(ray, &res, TERRAIN_ONLY_COLLISION_GROUP);
+	PhysicsSceneClass::Get_Instance()->Cast_Ray(coltest,false);
+	if (coltest.CollidedRenderObj && Vector3::Dot_Product(res.Normal,ray.Get_Dir()) <= 0)
+	{
+		bottomRay.Z = res.ContactPoint.Z+initialSpawnHeight;
+		Vector3 unneeded;
+		if (pointMustBeInPathfind && !Get_Random_Pathfind_Spot(bottomRay,0.0f,&unneeded))
+			return SpawnFailureTypes::SPAWN_BLOCKED;
+		if (CheckIfObjectIsNearAnotherObject(bottomRay))
+			return SpawnFailureTypes::SPAWN_BLOCKED;
+		GameObject *spawned = Commands->Create_Object(preset,bottomRay);
+		if (!spawned)
+		{
+			char errorMsg[220];
+			sprintf(errorMsg,"msg JMG_Utility_Basic_Spawner_In_Radius_Attached ERROR: Preset %s not found!",preset);
+			Console_Input(errorMsg);
+			return SpawnFailureTypes::SPAWN_CODE_ERROR;
+		}
+		if (manualFacing)
+		{
+			if (JmgUtility::SimpleDistance(Vector3(0.0f,0.0f,0.0f),faceLocation) > 0.0f)
+			{
+				Vector3 facePos = faceLocation;
+				facePos.X -= bottomRay.X;
+				facePos.Y -= bottomRay.Y;
+				SetFacing(spawned,atan2(facePos.Y,facePos.X)*(180.0f/PI));
+			}
+			else
+				SetFacing(spawned,faceDirection);
+		}
+		else
+			SetFacing(spawned,Commands->Get_Random(-180.0f,180.0f));
+		if (collisionCheck)
+		{
+			for (int x = 0;x <= retryAttempts;x++)
+			{
+				MoveablePhysClass *mphys = spawned->As_PhysicalGameObj() ? spawned->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : nullptr;
+				if (!mphys)
+				{
+					Console_Input("msg JMG_Utility_Basic_Spawner_In_Radius ERROR: Collision check is turned on but the created object isn't a PhysicalGameObj!");
+					Destroy_Script();
+					return SpawnFailureTypes::SPAWN_CODE_ERROR;
+				}
+				if (!mphys->Can_Teleport(Matrix3D(bottomRay)) || (pointMustBeInPathfind && !Get_Random_Pathfind_Spot(bottomRay,0.0f,&unneeded)))
+				{
+					if (x >= retryAttempts)
+					{
+						Commands->Destroy_Object(spawned);
+						return SpawnFailureTypes::SPAWN_BLOCKED;
+					}
+					bottomRay.Z += addHeight;
+				}
+			}
+			Commands->Set_Position(spawned,bottomRay);
+		}
+		spawnCount++;
+		char params[32];
+		sprintf(params,"%d,%d,%d",Commands->Get_ID(obj),scriptId,spawnGroupId);
+		Commands->Attach_Script(spawned,"JMG_Utility_Basic_Spawner_In_Radius_Attached",params);
+		Commands->Send_Custom_Event(spawned,obj,6873522,6873522,0.0f);
+		return SpawnFailureTypes::SUCCESS;
+	}
+	if (!ignoreRayCastFailure)
+	{
+		Console_Input("msg JMG_Utility_Basic_Spawner_In_Radius_Attached ERROR: Failed to cast Ray!");
+		return SpawnFailureTypes::SPAWN_CODE_ERROR;
+	}
+	return SpawnFailureTypes::SPAWN_BLOCKED;
+}
+bool JMG_Utility_Basic_Spawner_In_Radius::CheckIfObjectIsNearAnotherObject(Vector3 pos)
+{
+	if (minDistanceBetweenObjects <= 0)
+		return false;
+	for (SLNode<SpawnObjectNode> *node = spawnObjectNodeList.Head();node != NULL;node = node->Next())
+	{
+		if (SpawnObjectNode *data = node->Data())
+		{
+			if (!data->obj || (spawnGroupId > 0 && spawnGroupId != data->groupId))
+				continue;
+			if (JmgUtility::SimpleDistance(Commands->Get_Position(data->obj),pos) <= minDistanceBetweenObjects)
+				return true;
+		}
+	}
+	return false;
+}
+void JMG_Utility_Basic_Spawner_In_Radius::SetFacing(GameObject *obj,float facing)
+{
+	if (obj->As_SoldierGameObj())
+	{
+		char params[220];
+		sprintf(params,"0.1,%.2f",facing);
+		Commands->Attach_Script(obj,"JMG_Utility_Delay_Then_Rotate_Camera",params);
+	}
+	else
+		Commands->Set_Facing(obj,facing);
+}
+void JMG_Utility_Basic_Spawner_In_Radius_Attached::Created(GameObject *obj)
+{
+	GameObject *controller = Commands->Find_Object(Get_Int_Parameter("Controller_ID"));
+	if (controller)
+		Commands->Send_Custom_Event(obj,controller,6873523,Get_Int_Parameter("Script_ID"),0.0f);
+	AddSpawnedObjectToGroup(obj);
+}
+void JMG_Utility_Basic_Spawner_In_Radius_Attached::Destroyed(GameObject *obj)
+{
+	GameObject *controller = Commands->Find_Object(Get_Int_Parameter("Controller_ID"));
+	if (controller)
+		Commands->Send_Custom_Event(obj,controller,6873524,Get_Int_Parameter("Script_ID"),0.0f);
+}
+void JMG_Utility_Basic_Spawner_In_Radius_Attached::AddSpawnedObjectToGroup(GameObject *spawned)
+{
+	int spawnGroupId = Get_Int_Parameter("Spawn_Group_ID");
+	if (spawnGroupId == 0)
+		return;
+	for (SLNode<JMG_Utility_Basic_Spawner_In_Radius::SpawnObjectNode> *node = JMG_Utility_Basic_Spawner_In_Radius::spawnObjectNodeList.Head();node != NULL;node = node->Next())
+	{
+		if (JMG_Utility_Basic_Spawner_In_Radius::SpawnObjectNode *data = node->Data())
+		{
+			if (data->obj)
+				continue;
+			data->obj = spawned;
+			data->groupId = spawnGroupId;
+			return;
+		}
+	}
+	JMG_Utility_Basic_Spawner_In_Radius::SpawnObjectNode *node = new JMG_Utility_Basic_Spawner_In_Radius::SpawnObjectNode(spawned,spawnGroupId);
+	JMG_Utility_Basic_Spawner_In_Radius::spawnObjectNodeList.Add_Tail(node);
+}
+void JMG_Utility_Flying_Vehicle_Crash_Apply_Damage::Created(GameObject *obj)
+{
+	subtractMinSpeed = Get_Int_Parameter("Subtract_Min_Speed")?true:false; 
+	minCollisionSpeed = Get_Float_Parameter("Min_Collision_Speed");
+	maxCollisionSpeed = Get_Float_Parameter("Max_Collision_Speed");
+	sprintf(collisionSound,Get_Parameter("Collision_Sound"));
+	sprintf(explosionPreset,Get_Parameter("Explosion_Preset"));
+	allowCrash = false;
+	if (!obj->As_VehicleGameObj())
+	{
+		Console_Input("msg JMG_Utility_Flying_Vehicle_Crash_Apply_Damage ERROR: Must be attached to a vehicle");
+		Destroy_Script();
+		return;
+	}
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Flying_Vehicle_Crash_Apply_Damage::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (!obj->As_PhysicalGameObj()->Is_Attached_To_An_Object())
+		{
+			Vector3 tempSpeed,futurePos;
+			obj->As_VehicleGameObj()->Get_Velocity(tempSpeed);
+			float speed = tempSpeed.Length2()-(subtractMinSpeed?minCollisionSpeed:0.0f);
+			futurePos = Commands->Get_Position(obj);
+			if ((subtractMinSpeed && speed > 0.0f) || speed > minCollisionSpeed)
+			{
+				float facing = Commands->Get_Facing(obj);
+				float positionOffset = (speed/202.336f)*15.0f;
+				futurePos.X += cos(facing*PI180)*positionOffset;
+				futurePos.Y += sin(facing*PI180)*positionOffset;
+				MoveablePhysClass *mphys = obj->As_PhysicalGameObj() ? obj->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+				if (!mphys || mphys->Can_Teleport(Matrix3D(futurePos)))
+				{
+					if (allowCrash)
+					{
+						allowCrash = false;
+						Commands->Apply_Damage(obj,(speed/maxCollisionSpeed*(Commands->Get_Max_Health(obj)+Commands->Get_Max_Shield_Strength(obj))*0.5f),"None",obj);
+						Commands->Create_Sound(collisionSound,Commands->Get_Position(obj),obj);
+						if (!Commands->Get_Health(obj))
+							Commands->Create_Explosion(explosionPreset,Commands->Get_Position(obj),obj);
+					}
+				}
+				else
+					allowCrash = true;
+			}
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Enemy_Seen_Send_Custom::Created(GameObject *obj)
+{
+	lastEnemyId = 0;
+	seenTime = 0;
+	enemyPresetId = Get_Int_Parameter("Enemy_Preset_ID");
+	id = Get_Int_Parameter("ID");
+	visibleMessage = Get_Int_Parameter("Visible_Message");
+	notVisibleMessage = Get_Int_Parameter("Not_Visible_Message");
+	visibleParam = Get_Int_Parameter("Visible_Param");
+	notVisibleParam = Get_Int_Parameter("Not_Visible_Param");
+	maxNotSeenTime = (int)(Get_Float_Parameter("Max_Lost_Sight_Time")*10);
+	Commands->Enable_Enemy_Seen(obj,true);
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Enemy_Seen_Send_Custom::Enemy_Seen(GameObject *obj,GameObject *seen)
+{
+	if (!JmgUtility::CanSeeStealth(0,obj,seen))
+		return;
+	if (enemyPresetId && enemyPresetId != Commands->Get_Preset_ID(seen))
+		return;
+	if (Is_Script_Attached(seen,"JMG_Utility_Enemy_Seen_Send_Custom_Ignore"))
+		return;
+	int seenId = Commands->Get_ID(seen);
+	if (!seenTime)
+	{
+		GameObject *object = !id ? obj : (id == -1 ? seen : Commands->Find_Object(id));
+		if (object)
+			Commands->Send_Custom_Event(obj,object,visibleMessage,visibleParam,0);
+		lastEnemyId = seenId;
+	}
+	if (lastEnemyId == seenId)
+		seenTime = maxNotSeenTime;
+
+}
+void JMG_Utility_Enemy_Seen_Send_Custom::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (seenTime)
+		{
+			seenTime--;
+			if (!seenTime)
+			{
+				GameObject *object = !id ? obj : (id == -1 ? Commands->Find_Object(lastEnemyId) : Commands->Find_Object(id));
+				if (object)
+					Commands->Send_Custom_Event(obj,object,notVisibleMessage,notVisibleParam,0);
+			}
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Custom_Send_Custom_If_Script_Attached::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	sprintf(script,"%s",Get_Parameter("Script"));
+}
+void JMG_Utility_Custom_Send_Custom_If_Script_Attached::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		if (!Is_Script_Attached(obj,script))
+			return;
+		int id = Get_Int_Parameter("ID");
+		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+		int Param = Get_Int_Parameter("Param");
+		Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
+	}
+}
+void JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	sprintf(script,"%s",Get_Parameter("Script"));
+}
+void JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		if (Is_Script_Attached(obj,script))
+			return;
+		int id = Get_Int_Parameter("ID");
+		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+		int Param = Get_Int_Parameter("Param");
+		Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("Delay"));
+	}
+}
+void JMG_Utility_Basic_Spawner_In_Radius_Controller::Destroyed(GameObject *obj)
+{
+	JMG_Utility_Basic_Spawner_In_Radius::spawnObjectNodeList.Remove_All();
+}
+void JMG_Utility_Custom_Set_Engine::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	enable = Get_Int_Parameter("Enable");
+}
+void JMG_Utility_Custom_Set_Engine::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Commands->Enable_Engine(obj,(enable == -1 ? param : enable) != 0 ? true : false);
+	}
+}
+void JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count::Created(GameObject *obj)
+{
+	presetId = Get_Int_Parameter("Preset_ID");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("Message");
+	param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	rate = Get_Float_Parameter("Rate");
+	minPlayers = Get_Int_Parameter("Min_Player_Count");
+	maxPlayers = Get_Int_Parameter("Max_Player_Count");
+	repeat = Get_Int_Parameter("Repeat") == 0 ? false : true;
+	Commands->Start_Timer(obj,this,rate,1);
+}
+void JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		int playerCount = Get_Player_Count();
+		if ((minPlayers == -1 || playerCount >= minPlayers) && (maxPlayers == -1 || playerCount <= maxPlayers))
+		{
+			int count = 0;
+			for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+			{
+				GameObject* o = current->Data()->As_ScriptableGameObj();
+				if (Commands->Get_Preset_ID(o) == presetId)
+					count++;
+			}
+			if (count == playerCount)
+			{
+				GameObject *object = !id ? obj : Commands->Find_Object(id);
+				if (object)
+					Commands->Send_Custom_Event(obj,object,custom,param,delay);
+				if (!repeat)
+				{
+					Destroy_Script();
+					return;
+				}
+			}
+		}
+		Commands->Start_Timer(obj,this,rate,1);
+	}
+}
+void JMG_Utility_Custom_Set_Position::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	position = Get_Vector3_Parameter("Position");
+}
+void JMG_Utility_Custom_Set_Position::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Commands->Set_Position(obj,position);
+		if (obj->As_ScriptZoneGameObj())
+		{
+			OBBoxClass *zone = Get_Zone_Box(obj);
+			zone->Center = position;
+			JmgUtility::RotateZoneBox(Commands->Get_Facing(obj),zone->Basis);
+			Set_Zone_Box(obj,*zone);
+		}
+	}
+}
+void JMG_Utility_Custom_Delay_Send_Custom::Created(GameObject *obj)
+{
+	watchMessage = Get_Int_Parameter("Custom");
+	id = Get_Int_Parameter("ID");
+	sendMessage = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	randomDelay = Get_Float_Parameter("RandomDelay");
+	cancelCustom = Get_Int_Parameter("CancelCustom");
+	lastSender = NULL;
+}
+void JMG_Utility_Custom_Delay_Send_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == watchMessage)
+	{
+		Stop_Timer2(obj,this,1);
+		lastSender = sender;
+		lastParam = param;
+		Commands->Start_Timer(obj,this,delay+(randomDelay ? Commands->Get_Random(0.0f,randomDelay) : 0.0f),1);
+	}
+	if (message == cancelCustom)
+	{
+		Stop_Timer2(obj,this,1);
+	}
+}
+void JMG_Utility_Custom_Delay_Send_Custom::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		GameObject *object = id ? (id == -1 ? lastSender : Commands->Find_Object(id)) : obj;
+		Commands->Send_Custom_Event(obj,object,sendMessage,Param == -1 ? lastParam : Param,0);
+	}
+}
+void JMG_Utility_Scale_HP_By_Player_Count::Created(GameObject *obj)
+{
+	maxPlayerCount = Get_Int_Parameter("Max_Player_Count");
+	lastPlayerCount = 1;
+	originalHealth = Commands->Get_Max_Health(obj);
+	originalArmor = Commands->Get_Max_Shield_Strength(obj);;
+	healthMultiplier = Get_Float_Parameter("Health_Multiplier");
+	armorMultiplier = Get_Float_Parameter("Armor_Multiplier");
+	repeat = Get_Int_Parameter("Repeat") == 0 ? false : true;
+	updateScaleCustom = Get_Int_Parameter("UpdateScaleCustom");
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Scale_HP_By_Player_Count::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		RescaleHP(obj);
+		if (!repeat)
+		{
+			Destroy_Script();
+			return;
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Scale_HP_By_Player_Count::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (updateScaleCustom && message == updateScaleCustom)
+	{
+		RescaleHP(obj);
+	}
+}
+void JMG_Utility_Scale_HP_By_Player_Count::RescaleHP(GameObject *obj)
+{
+	int playerCount = maxPlayerCount == -1 ? Get_Player_Count() : min(maxPlayerCount,Get_Player_Count());
+	if (playerCount && lastPlayerCount != playerCount)
+	{
+		float healthRatio = Commands->Get_Health(obj)/Commands->Get_Max_Health(obj);
+		float armorRatio = Commands->Get_Shield_Strength(obj)/Commands->Get_Max_Shield_Strength(obj);
+		Set_Max_Health(obj,healthMultiplier*playerCount*originalHealth);
+		Set_Max_Shield_Strength(obj,armorMultiplier*playerCount*originalArmor);
+		Commands->Set_Health(obj,Commands->Get_Max_Health(obj)*healthRatio);
+		Commands->Set_Shield_Strength(obj,Commands->Get_Max_Shield_Strength(obj)*armorRatio);
+		lastPlayerCount = playerCount;
+	}
+}
+void JMG_Utility_Custom_Send_Custom_To_All_Objects::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	team = Get_Int_Parameter("Team");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+}
+void JMG_Utility_Custom_Send_Custom_To_All_Objects::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (o && (team == 2 || CheckPlayerType(o,team)))
+ 				Commands->Send_Custom_Event(obj,o,custom,Param == -1 ? param : Param,delay);
+		}
+	}
+}
+void JMG_Utility_Enemy_Seen_Send_Custom_Ignore::Created(GameObject *obj)
+{
+}
+void JMG_Utility_In_Line_Of_Sight_Send_Custom::Created(GameObject *obj)
+{
+	lastEnemyId = 0;
+	enemyPresetId = Get_Int_Parameter("Enemy_Preset_ID");
+	id = Get_Int_Parameter("ID");
+	visibleMessage = Get_Int_Parameter("Visible_Message");
+	notVisibleMessage = Get_Int_Parameter("Not_Visible_Message");
+	visibleParam = Get_Int_Parameter("Visible_Param");
+	notVisibleParam = Get_Int_Parameter("Not_Visible_Param");
+	rate = Get_Float_Parameter("Scan_Rate");
+	enemyOnly = Get_Int_Parameter("Enemy_Only") == 0 ? false : true;
+	Commands->Start_Timer(obj,this,rate,1);
+}
+void JMG_Utility_In_Line_Of_Sight_Send_Custom::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		bool objectSeen = false;
+		for (SLNode<SmartGameObj> *node = GameObjManager::SmartGameObjList.Head();node;node = node->Next())
+		{
+			SmartGameObj* object = node->Data();
+			if (object == obj)
+				continue;
+			if ((!enemyOnly || obj->As_SmartGameObj()->Is_Enemy(object)) && object->Is_Visible() && obj->As_SmartGameObj()->Is_Obj_Visible(object))
+			{
+				if (object->Peek_Physical_Object())
+				{
+					RenderObjClass* model = object->Peek_Physical_Object()->Peek_Model();
+					if (model && model->Is_Hidden())
+						continue;
+				}
+				if (Test_Line_Of_Sight(obj,object))
+					objectSeen = true;
+			}
+		}
+		if (!objectSeen && lastEnemyId)
+		{
+			GameObject *object = !id ? obj : (id == -1 ? Commands->Find_Object(lastEnemyId) : Commands->Find_Object(id));
+			if (object)
+				Commands->Send_Custom_Event(obj,object,notVisibleMessage,notVisibleParam,0);
+			lastEnemyId = 0;
+		}
+		Commands->Start_Timer(obj,this,rate,1);
+	}
+}
+bool JMG_Utility_In_Line_Of_Sight_Send_Custom::Test_Line_Of_Sight(GameObject *obj,GameObject *seen)
+{
+	if (!JmgUtility::CanSeeStealth(0,obj,seen))
+		return false;
+	if (enemyPresetId && enemyPresetId != Commands->Get_Preset_ID(seen))
+		return false;
+	if (Is_Script_Attached(seen,"JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore"))
+		return false;
+	int seenId = Commands->Get_ID(seen);
+	if (!lastEnemyId)
+	{
+		GameObject *object = !id ? obj : (id == -1 ? seen : Commands->Find_Object(id));
+		if (object)
+			Commands->Send_Custom_Event(obj,object,visibleMessage,visibleParam,0);
+		lastEnemyId = seenId;
+		return true;
+	}
+	if (lastEnemyId == seenId)
+		return true;
+	return false;
+}
+void JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore::Created(GameObject *obj)
+{
+}
+void JMG_Utility_Timer_Trigger_Enemy_Seen::Created(GameObject *obj)
+{
+	rate = Get_Float_Parameter("Scan_Rate");
+	Commands->Start_Timer(obj,this,rate,1);
+}
+void JMG_Utility_Timer_Trigger_Enemy_Seen::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		for (SLNode<SmartGameObj> *node = GameObjManager::SmartGameObjList.Head();node;node = node->Next())
+		{
+			SmartGameObj* object = node->Data();
+			if (object == obj)
+				continue;
+			if (obj->As_SmartGameObj()->Is_Enemy(object) && object->Is_Visible() && obj->As_SmartGameObj()->Is_Obj_Visible(object))
+			{
+				if (object->Peek_Physical_Object())
+				{
+					RenderObjClass* model = object->Peek_Physical_Object()->Peek_Model();
+					if (model && model->Is_Hidden())
+						continue;
+				}
+				const SimpleDynVecClass<GameObjObserverClass *> & observer_list = obj->Get_Observers();
+				for(int index = 0;index < observer_list.Count();index++)
+					observer_list[index]->Enemy_Seen(obj,object);
+			}
+		}
+		Commands->Start_Timer(obj,this,rate,1);
+	}
+}
+void JMG_Utility_Custom_Teleport_To_Random_Wander_Point::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	retryOnFailure = Get_Int_Parameter("RetryOnFailure") ? true : false;
+	safeTeleportDistance = Get_Float_Parameter("SafeTeleportDistance");
+	wanderPointGroup = Get_Int_Parameter("WanderingAIGroupID");
+}
+void JMG_Utility_Custom_Teleport_To_Random_Wander_Point::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Grab_Teleport_Spot(obj,5);
+	}
+}
+bool JMG_Utility_Custom_Teleport_To_Random_Wander_Point::Grab_Teleport_Spot(GameObject *enter,int attempts)
+{
+	if (The_Game()->Get_Game_Duration_S() < 1.0f)
+	{
+		char params[220];
+		sprintf(params,"%d,%.2f",wanderPointGroup,safeTeleportDistance);
+		Commands->Attach_Script(enter,"JMG_Utility_Zone_Teleport_To_Random_Wander_Point_Attach",params);
+		return false;
+	}
+	if (!attempts)
+	{
+		if (retryOnFailure)
+		{
+			char params[220];
+			sprintf(params,"%d,%.2f",wanderPointGroup,safeTeleportDistance);
+			Commands->Attach_Script(enter,"JMG_Utility_Zone_Teleport_To_Random_Wander_Point_Attach",params);
+		}
+		return false;
+	}
+	attempts--;
+	Vector3 targetPos;
+	float facing;
+	if (!Get_A_Defense_Point(&targetPos,&facing))
+	{
+		char errormsg[220];
+		sprintf(errormsg,"msg JMG_Utility_Custom_Teleport_To_Random_Wander_Point ERROR: No wander points found for group %d!",wanderPointGroup);
+		Console_Input(errormsg);
+		return false;
+	}
+	MoveablePhysClass *mphys = enter->As_PhysicalGameObj() ? enter->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+	if (mphys && mphys->Find_Teleport_Location(targetPos,safeTeleportDistance,&targetPos))
+	{
+		Toggle_Fly_Mode(enter);
+		Commands->Set_Position(enter,targetPos);
+		Force_Position_Update(enter);
+		Toggle_Fly_Mode(enter);
+		char params[220];
+		sprintf(params,"0.1,%.2f",facing);
+		Commands->Attach_Script(enter,"JMG_Utility_Delay_Then_Rotate_Camera",params);
+		return true;
+	}
+	else
+	{
+		Commands->Set_Position(enter,targetPos);
+		return Grab_Teleport_Spot(enter,attempts);
+	}
+}
+void JMG_Utility_Send_Custom_If_Not_Moving_Enough::Created(GameObject *obj)
+{
+	resetTime = time = (int)(Get_Float_Parameter("Time")*10);
+	distance = Get_Float_Parameter("Distance");
+	distance *= distance;
+	lastPos = Commands->Get_Position(obj);
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Send_Custom_If_Not_Moving_Enough::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (JmgUtility::SimpleDistance(Commands->Get_Position(obj),lastPos) < distance)
+		{
+			time--;
+			if (!time)
+			{
+				GameObject *object = id ? Commands->Find_Object(id) : obj;
+ 				Commands->Send_Custom_Event(obj,object,custom,Param,delay);
+			}
+			if (time < 0)
+				time = resetTime;
+		}
+		else
+		{
+			lastPos = Commands->Get_Position(obj);
+			time = resetTime;
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionSystem JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionControl[128] = {HerdAnimalPositionSystem()};
+void JMG_Utility_AI_Skittish_Herd_Animal::Created(GameObject *obj)
+{
+	weaponRange = 3.75;
+	sprintf(defaultWeapon,"%s",Get_Current_Weapon(obj));
+	int minHerdId = Get_Int_Parameter("MinHerdID");
+	int maxHerdId = Get_Int_Parameter("MaxHerdID");
+	wanderPointGroup = Get_Int_Parameter("WanderGroupID");
+	if (minHerdId > 128 || maxHerdId > 128)
+	{
+		Console_Input("msg JMG_Utility_AI_Skittish_Herd_Animal ERROR: Herd ID must not exceed 128!");
+		Destroy_Script();
+		return;
+	}
+	minRetreatRange = Get_Float_Parameter("MinRetreatRange");
+	maxRetreatRange = Get_Float_Parameter("MaxRetreatRange");
+	wanderRadiusAroundHerdCenter = Get_Float_Parameter("WanderRadiusAroundHerdCenter");
+	wanderRadiusAroundHerdCenterSq = wanderRadiusAroundHerdCenter+10.0f;
+	wanderRadiusAroundHerdCenterSq *= wanderRadiusAroundHerdCenterSq;
+	minWanderFrequency = Get_Float_Parameter("MinWanderFrequency");
+	maxWanderFrequency = Get_Float_Parameter("MaxWanderFrequency");
+	minRetreatTime = Get_Float_Parameter("MinRetreatTime")*1000.0f;
+	maxRetreatTime = Get_Float_Parameter("MaxRetreatTime")*1000.0f;
+	minUpdateHerdCenter = Get_Float_Parameter("MinUpdateHerdCenter")*1000.0f;
+	maxUpdateHerdCenter = Get_Float_Parameter("MaxUpdateHerdCenter")*1000.0f;
+	runTowardThreatChance = Get_Float_Parameter("RunTowardThreatChance");
+	herdId = minHerdId == maxHerdId ? maxHerdId : Commands->Get_Random_Int(minHerdId,maxHerdId);
+	HerdPositionNode = HerdAnimalPositionControl[herdId].AddNode(obj);
+	Commands->Enable_Enemy_Seen(obj,true);
+	Commands->Start_Timer(obj,this,1.1f,1);
+	Commands->Start_Timer(obj,this,1.1f,2);
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::Enemy_Seen(GameObject *obj,GameObject *seen)	
+{
+	if (((HerdAnimalPositionControl[herdId].checkRetreatCloseEnough() || difftime(clock(),HerdAnimalPositionControl[herdId].herdRetreatStart) > HerdAnimalPositionControl[herdId].herdRetreatTime)))
+		setRetreatLocation(obj,seen);
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1 && Commands->Get_Health(obj))
+	{
+		if (HerdPositionNode)
+			HerdPositionNode->pos = Commands->Get_Position(obj);
+		if (difftime(clock(),HerdAnimalPositionControl[herdId].herdRetreatStart) < HerdAnimalPositionControl[herdId].herdRetreatTime)
+		{
+			Vector3 pos = Commands->Get_Position(obj);
+			if (JmgUtility::SimpleDistance(pos,HerdAnimalPositionControl[herdId].herdRetreatLocation) < wanderRadiusAroundHerdCenterSq)
+			{
+				pos = HerdAnimalPositionControl[herdId].herdRetreatLocation;
+				float randomRot = Commands->Get_Random(-180.0f,180.0f);
+				float dist = Commands->Get_Random(0.0f,wanderRadiusAroundHerdCenter);
+				pos.X += cos(randomRot)*dist;
+				pos.Y += sin(randomRot)*dist;
+				Vector3 finalPos;
+				if (Get_Random_Pathfind_Spot(pos,wanderRadiusAroundHerdCenter,&finalPos))
+					pos = finalPos;
+			}
+			else
+				pos = HerdAnimalPositionControl[herdId].herdRetreatLocation;
+			GotoLocation(obj,pos,NULL,1.25f);
+			if (HerdAnimalPositionControl[herdId].checkRetreatSuccessful())
+				HerdAnimalPositionControl[herdId].herdRetreatTime = 0;
+		}
+		Commands->Start_Timer(obj,this,1.0f,1);
+	}
+	if (number == 2)
+	{
+		if (difftime(clock(),HerdAnimalPositionControl[herdId].herdRetreatStart) > HerdAnimalPositionControl[herdId].herdRetreatTime)
+		{
+			const char *weap = Get_Current_Weapon(obj);
+			if (weap && !_stricmp(weap,defaultWeapon))
+			{
+				Commands->Select_Weapon(obj,"");
+				Commands->Select_Weapon(obj,defaultWeapon);
+			}
+		}
+		Vector3 pos = HerdAnimalPositionControl[herdId].getCenterLocation(true,wanderPointGroup,minUpdateHerdCenter,maxUpdateHerdCenter);
+		float randomRot = Commands->Get_Random(-180.0f,180.0f);
+		float dist = Commands->Get_Random(0.0f,wanderRadiusAroundHerdCenter);
+		pos.X += cos(randomRot)*dist;
+		pos.Y += sin(randomRot)*dist;
+		Vector3 finalPos;
+		if (Get_Random_Pathfind_Spot(pos,wanderRadiusAroundHerdCenter,&finalPos))
+			GotoLocation(obj,finalPos,NULL);
+		else
+			GotoLocation(obj,pos,NULL);
+		Commands->Start_Timer(obj,this,Commands->Get_Random(minWanderFrequency,maxWanderFrequency),2);
+	}
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::Damaged(GameObject *obj,GameObject *damager,float damage)
+{
+	if (damager && damage > 0 && (herdId && (HerdAnimalPositionControl[herdId].checkRetreatCloseEnough() || difftime(clock(),HerdAnimalPositionControl[herdId].herdRetreatStart) > HerdAnimalPositionControl[herdId].herdRetreatTime)))
+		setRetreatLocation(obj,damager);
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::Destroyed(GameObject *obj)
+{
+	HerdAnimalPositionControl[herdId] -= obj;
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::GotoLocation(GameObject *obj,const Vector3 &pos,GameObject *Enemy,float speed)
+{
+	ActionParamsStruct params;
+	Commands->Get_Action_Params(obj,params);
+	params.Set_Basic(this,100,1);
+	params.Set_Movement(pos,speed ? speed : (Enemy ? 1.0f : Commands->Get_Random(0.25f,1.0f)),Enemy ? weaponRange*Commands->Get_Random(0.01f,1.0f) : 1.0f,false);
+	params.MovePathfind = true;
+	Vector3 mypos = Commands->Get_Position(obj);
+	if (!speed && JmgUtility::SimpleDistance(mypos,pos) < 625 && Commands->Get_Random(0.0f,1.0f) < 0.25f)
+	{
+		params.MoveCrouched = true;
+		params.AttackCrouched = true;
+	}
+	Commands->Action_Goto(obj,params);
+}
+void JMG_Utility_AI_Skittish_Herd_Animal::setRetreatLocation(GameObject *obj,GameObject *enemy)
+{
+	Vector3 pos = Commands->Get_Position(obj);
+	Vector3 mypos = Commands->Get_Position(enemy);
+	pos.X -= mypos.X;
+	pos.Y -= mypos.Y;
+	float TempRotation = atan2(pos.Y,pos.X);
+	float dist = Commands->Get_Random(minRetreatRange,maxRetreatRange);
+	if (Commands->Get_Random(0.0f,1.0f) < runTowardThreatChance)
+		dist *= -1.0f;
+	pos.X += cos(TempRotation)*dist;
+	pos.Y += sin(TempRotation)*dist;
+	if (Get_A_Wander_Point(&pos,wanderPointGroup))
+	{
+		HerdAnimalPositionControl[herdId].herdRetreatLocation = pos;
+		HerdAnimalPositionControl[herdId].herdRetreatTime = Commands->Get_Random(minRetreatTime,maxRetreatTime);
+		HerdAnimalPositionControl[herdId].herdRetreatStart = clock();
+	}
+	GotoLocation(obj,pos,NULL,1.25f);
+}
+Vector3 JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionSystem::getCenterLocation(bool allowWander,int wanderPointGroup,float minUpdateHerdCenter,float maxUpdateHerdCenter)
+{
+	if (difftime(clock(),herdRetreatStart) < herdRetreatTime)
+		return herdRetreatLocation;
+	if (difftime(clock(),lastPosCalcTime) < minUpdateDelay && hascalculatedPos)
+		return centerPos;
+	minUpdateDelay = Commands->Get_Random(minUpdateHerdCenter,maxUpdateHerdCenter);
+	hascalculatedPos = true;
+	lastPosCalcTime = clock();
+	if (allowWander && Commands->Get_Random(0.00,1.00) < 0.1f && JMG_Utility_AI_Skittish_Herd_Animal::Get_A_Wander_Point(&centerPos,wanderPointGroup))
+		return centerPos;
+	HerdPositionNode *Current = HerdPositionNodeList;
+	Vector3 activePos;
+	int activeCount = 0;
+	while (Current)
+	{
+		if (Current->alive)
+		{
+			activeCount++;
+			activePos += Current->pos;
+		}
+		Current = Current->next;			
+	}
+	if (!activeCount)
+	{
+		JMG_Utility_AI_Skittish_Herd_Animal::Get_A_Wander_Point(&centerPos,wanderPointGroup);
+		return centerPos;
+	}
+	centerPos.X = activePos.X/activeCount;
+	centerPos.Y = activePos.Y/activeCount;
+	centerPos.Z = activePos.Z/activeCount;
+	return centerPos;
+}
+void JMG_Utility_AI_Skittish_Herd_Animal_Ignore::Created(GameObject *obj)
+{
+}
+void JMG_Utility_AI_Skittish_Herd_Animal_Controller::Destroyed(GameObject *obj)
+{
+	for (int x = 0;x < 128;x++)
+		JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionControl[x].Empty_List();
+}
+void JMG_Utility_Custom_Display_Dialog_Box::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	textMessage = newstr(Get_Parameter("Message"));
+	char delim = Get_Parameter("Delim")[0];
+	unsigned int length = strlen(textMessage);
+	for (unsigned int x = 0;x < length;x++)
+		if (textMessage[x] == delim)
+			textMessage[x] = ',';
+}
+void JMG_Utility_Custom_Display_Dialog_Box::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		char dialog[1000];
+		sprintf(dialog,"pamsg %d %s",JmgUtility::JMG_Get_Player_ID(obj),textMessage);
+		Console_Input(dialog);
+	}
+}
+void JMG_Utility_Custom_Send_Custom_On_Preset_Count::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	presetId = Get_Int_Parameter("PresetID");
+	minCount = Get_Int_Parameter("MinCount");
+	maxCount = Get_Int_Parameter("MaxCount");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+}
+void JMG_Utility_Custom_Send_Custom_On_Preset_Count::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		int count = 0;
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (Commands->Get_Preset_ID(o) == presetId)
+				count++;
+		}
+		if (count >= minCount && (maxCount == -1 || count <= maxCount))
+		{
+			GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+ 			Commands->Send_Custom_Event(obj,object,custom,Param == -1 ? param : Param,delay);
+		}
+	}
+}
+void JMG_Utility_Custom_Send_Custom_On_Script_Count::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	sprintf(script,"%s",Get_Parameter("Script"));
+	minCount = Get_Int_Parameter("MinCount");
+	maxCount = Get_Int_Parameter("MaxCount");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+}
+void JMG_Utility_Custom_Send_Custom_On_Script_Count::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		int count = 0;
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (Is_Script_Attached(o,script))
+				count++;
+		}
+		if (count >= minCount && (maxCount == -1 || count <= maxCount))
+		{
+			GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+ 			Commands->Send_Custom_Event(obj,object,custom,Param == -1 ? param : Param,delay);
+		}
+	}
+}
+
+void JMG_Utility_Poke_Send_Custom_From_Poker::Created(GameObject *obj)
+{
+	Commands->Enable_HUD_Pokable_Indicator(obj,true);
+}
+void JMG_Utility_Poke_Send_Custom_From_Poker::Poked(GameObject *obj, GameObject *poker)
+{
+	if (CheckPlayerType(poker,Get_Int_Parameter("PlayerType")) || Commands->Get_Player_Type(poker) == -4)
+		return;
+	int id = Get_Int_Parameter("ID");
+	GameObject *object = id ? (id == -1 ? poker : Commands->Find_Object(id)) : obj;
+	if (object)
+		Commands->Send_Custom_Event(poker,object,Get_Int_Parameter("Custom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+	if (Get_Int_Parameter("TriggerOnce"))
+	{
+		Commands->Enable_HUD_Pokable_Indicator(obj,false);
+		Destroy_Script();
+	}
+}
+void JMG_Utility_Custom_Grant_Scaled_Score::Created(GameObject *obj)
+{
+	maxPlayerCount = Get_Int_Parameter("MaxPlayerCount");
+	lastPlayerCount = 1;
+	theScore = score = Get_Float_Parameter("Score");
+	scoreMultiplier = Get_Float_Parameter("ScoreMultiplier");
+	repeat = Get_Int_Parameter("Repeat") == 0 ? false : true;
+	grantCustom = Get_Int_Parameter("GrantCustom");
+	updateScaleCustom = Get_Int_Parameter("UpdateScaleCustom");
+	stopUpdateCustom = Get_Int_Parameter("StopUpdateCustom");
+	grantToSender = Get_Int_Parameter("GrantToSender") == 0 ? false : true;
+	entireTeam = Get_Int_Parameter("EntireTeam") == 0 ? false : true;
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Custom_Grant_Scaled_Score::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		RescaleScore(obj);
+		if (!repeat)
+		{
+			Destroy_Script();
+			return;
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Custom_Grant_Scaled_Score::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (grantCustom && message == grantCustom)
+	{
+		Commands->Give_Points(grantToSender ? sender : obj,theScore,entireTeam);
+	}
+	if (updateScaleCustom && message == updateScaleCustom)
+	{
+		RescaleScore(obj);
+	}
+	if (stopUpdateCustom && message == stopUpdateCustom)
+	{
+		Stop_Timer2(obj,this,1);
+	}
+}
+void JMG_Utility_Custom_Grant_Scaled_Score::RescaleScore(GameObject *obj)
+{
+	int playerCount = maxPlayerCount == -1 ? Get_Player_Count() : min(maxPlayerCount,Get_Player_Count());
+	if (playerCount && lastPlayerCount != playerCount)
+	{
+		theScore = score*scoreMultiplier*playerCount;
+		lastPlayerCount = playerCount;
+	}
+}
 ScriptRegistrant<JMG_Utility_Check_If_Script_Is_In_Library> JMG_Utility_Check_If_Script_Is_In_Library_Registrant("JMG_Utility_Check_If_Script_Is_In_Library","ScriptName:string,CppName:string");
 ScriptRegistrant<JMG_Send_Custom_When_Custom_Sequence_Matched> JMG_Send_Custom_When_Custom_Sequence_Matched_Registrant("JMG_Send_Custom_When_Custom_Sequence_Matched","Success_Custom=0:int,Correct_Step_Custom=0:int,Partial_Failure_Custom=0:int,Failure_Custom=0:int,Send_To_ID=0:int,Custom_0=0:int,Custom_1=0:int,Custom_2=0:int,Custom_3=0:int,Custom_4=0:int,Custom_5=0:int,Custom_6=0:int,Custom_7=0:int,Custom_8=0:int,Custom_9=0:int,Disable_On_Success=1:int,Disable_On_Failure=0:int,Starts_Enabled=1:int,Enable_Custom=0:int,Correct_Step_Saftey=0:int,Failure_Saftey=1:int,Max_Failures=1:int");
 ScriptRegistrant<JMG_Utility_Change_Model_On_Timer> JMG_Utility_Change_Model_On_Timer_Registrant("JMG_Utility_Change_Model_On_Timer","Model=null:string,Time=0:float");
@@ -9089,7 +10422,7 @@
 ScriptRegistrant<JMG_Utility_Send_Custom_When_Near_Building> JMG_Utility_Send_Custom_When_Near_Building_Registrant("JMG_Utility_Send_Custom_When_Near_Building","SendCustomObjectID=0:int,NearToBuildingCustom:int,FarFromBuildingCustom:int,CloseToBuildingDistance=1.0:float,BuildingPlayerType=2:int,CheckDeadBuildings=1:int,CheckRate=0.25:float");
 ScriptRegistrant<JMG_Utility_AI_Engineer_Repair_Target> JMG_Utility_AI_Engineer_Repair_Target_Registrant("JMG_Utility_AI_Engineer_Repair_Target","");
 ScriptRegistrant<JMG_Utility_Reset_Screen_Fade_And_Fog_On_Destroy> JMG_Utility_Reset_Screen_Fade_And_Fog_On_Destroy_Registrant("JMG_Utility_Reset_Screen_Fade_And_Fog_On_Destroy","");
-ScriptRegistrant<JMG_Utility_AI_Goto_Player> JMG_Utility_AI_Goto_Player_Registrant("JMG_Utility_AI_Goto_Player","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float");
+ScriptRegistrant<JMG_Utility_AI_Goto_Player> JMG_Utility_AI_Goto_Player_Registrant("JMG_Utility_AI_Goto_Player","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float,WanderDistanceOverride=0.0:float,ChangeWanderGroupCustom=0:int,ChangeWanderSpeedCustom=0:int,ChangeHuntDistanceCustom=0:int,ChangeHuntSpeedCustom=0:int,ChangeReturnHomeSpeedCustom=0:int,ChangeMaxSightFromHomeLocationCustom=0:int,ChangeAttackSpeedCustom=0:int,ChanceToInvestigateLastSeenLocation=0.66:float");
 ScriptRegistrant<JMG_Utility_AI_Aggressive_Attack_Spot_Control> JMG_Utility_AI_Aggressive_Attack_Spot_Control_Registrant("JMG_Utility_AI_Aggressive_Attack_Spot_Control","");
 ScriptRegistrant<JMG_Utility_AI_Aggressive_Attack_Spot_Point> JMG_Utility_AI_Aggressive_Attack_Spot_Point_Registrant("JMG_Utility_AI_Aggressive_Attack_Spot_Point","GroupId=0:int,AttackOffset=0.0 0.0 0.0:Vector3");
 ScriptRegistrant<JMG_Utility_AI_Aggressive_Attack_Spot_Point2> JMG_Utility_AI_Aggressive_Attack_Spot_Point2_Registrant("JMG_Utility_AI_Aggressive_Attack_Spot_Point2","GroupId=0:int,AbsolutePosition=0.0 0.0 0.0:Vector3,ObjectID=0:int");
@@ -9165,10 +10498,10 @@
 ScriptRegistrant<JMG_Utility_Set_Collision_Group> JMG_Utility_Set_Collision_Group_Registrant("JMG_Utility_Set_Collision_Group","CollisionGroupID:int");
 ScriptRegistrant<JMG_Utility_Cap_Credits> JMG_Utility_Cap_Credits_Registrant("JMG_Utility_Cap_Credits","Credits:float,Team=2:int,Custom=0:int");
 ScriptRegistrant<JMG_Utility_Custom_Apply_Damage> JMG_Utility_Custom_Apply_Damage_Registrant("JMG_Utility_Custom_Apply_Damage","Custom:int,ID=0:int,Damage:float,Warhead=None:string,DamagerID=0:int");
-ScriptRegistrant<JMG_Utility_AI_Goto_Enemy> JMG_Utility_AI_Goto_Enemy_Registrant("JMG_Utility_AI_Goto_Enemy","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float");
-ScriptRegistrant<JMG_Utility_AI_Goto_Enemy_Not_Star> JMG_Utility_AI_Goto_Enemy_Not_Star_Registrant("JMG_Utility_AI_Goto_Enemy_Not_Star","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float");
+ScriptRegistrant<JMG_Utility_AI_Goto_Enemy> JMG_Utility_AI_Goto_Enemy_Registrant("JMG_Utility_AI_Goto_Enemy","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float,WanderDistanceOverride=0.0:float,ChangeWanderGroupCustom=0:int,ChangeWanderSpeedCustom=0:int,ChangeHuntDistanceCustom=0:int,ChangeHuntSpeedCustom=0:int,ChangeReturnHomeSpeedCustom=0:int,ChangeMaxSightFromHomeLocationCustom=0:int,ChangeAttackSpeedCustom=0:int,ChanceToInvestigateLastSeenLocation=0.66:float");
+ScriptRegistrant<JMG_Utility_AI_Goto_Enemy_Not_Star> JMG_Utility_AI_Goto_Enemy_Not_Star_Registrant("JMG_Utility_AI_Goto_Enemy_Not_Star","HuntSearchDistance=-1.0:float,HuntSpeed=1.0:float,HuntArriveDistance=1.0:float,RandomHuntArriveDistance=0.0:float,HuntStealth=0:int,AttackSpeed=1.0:float,AttackDistance=-1.0:float,RandomAttackDistance=0.0:float,ReturnHome=1:int,ReturnHomeSpeed=1.0:float,WanderingAIGroupID=-1:int,WanderSpeed=1.0:float,CanSeeStealth=1:int,ShutdownEngineOnArrival=0:int,AttackCheckBlocked=1:int,MaxSightRangeFromHome=0.0:float,WanderDistanceOverride=0.0:float,ChangeWanderGroupCustom=0:int,ChangeWanderSpeedCustom=0:int,ChangeHuntDistanceCustom=0:int,ChangeHuntSpeedCustom=0:int,ChangeReturnHomeSpeedCustom=0:int,ChangeMaxSightFromHomeLocationCustom=0:int,ChangeAttackSpeedCustom=0:int,ChanceToInvestigateLastSeenLocation=0.66:float");
 ScriptRegistrant<JMG_Utility_Grant_Key_On_Create> JMG_Utility_Grant_Key_On_Create_Registrant("JMG_Utility_Grant_Key_On_Create","Key:int,Grant=1:int");
-ScriptRegistrant<JMG_Utility_Custom_Send_Custom> JMG_Utility_Custom_Send_Custom_Registrant("JMG_Utility_Custom_Send_Custom","Custom:int,ID=0:int,SendCustom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom> JMG_Utility_Custom_Send_Custom_Registrant("JMG_Utility_Custom_Send_Custom","Custom:int,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float,RandomDelay=0.0:float,RandomChance=0.0:float");
 ScriptRegistrant<JMG_Utility_Damage_Unoccupied_Vehicle> JMG_Utility_Damage_Unoccupied_Vehicle_Registrant("JMG_Utility_Damage_Unoccupied_Vehicle","Rate=0.1:float,Delay=60.0:float,DecreaseTick=0.1:float,IncreaseTick=0.1:float,Damage=1.0:float,Warhead=None:string");
 ScriptRegistrant<JMG_Utility_Custom_Damage_All_Soldiers_On_Team> JMG_Utility_Custom_Damage_All_Soldiers_On_Team_Registrant("JMG_Utility_Custom_Damage_All_Soldiers_On_Team","Custom:int,Team:int,Damage:float,Warhead=None:string");
 ScriptRegistrant<JMG_Utility_AI_Guardian_Vehicle> JMG_Utility_AI_Guardian_Vehicle_Registrant("JMG_Utility_AI_Guardian_Vehicle","WanderingAIGroupID:int,WanderSpeed=1.0:float,FireRange=100.0:float,FaceTarget=1:int,CheckBlocked=1:int,AimAtFeet=1:int,TurnOffEngineOnArrival=1:int,StealthModeOverride=0:int");
@@ -9268,7 +10601,34 @@
 ScriptRegistrant<JMG_Utility_Send_Custom_On_Deaths_Reporter_Zone> JMG_Utility_Send_Custom_On_Deaths_Reporter_Zone_Registrant("JMG_Utility_Send_Custom_On_Deaths_Reporter_Zone","");
 ScriptRegistrant<JMG_Utility_Killed_Give_Money> JMG_Utility_Killed_Give_Money_Registrant("JMG_Utility_Killed_Give_Money","Money:float");
 ScriptRegistrant<JMG_Utility_Created_Set_Damage_And_Death_Points> JMG_Utility_Created_Set_Damage_And_Death_Points_Registrant("JMG_Utility_Created_Set_Damage_And_Death_Points","Damage_Points=-1.0:float,DeathPoints=-1.0:float");
-ScriptRegistrant<JMG_Utility_Detect_AFK_Controller> JMG_Utility_Detect_AFK_Controller_Registrant("JMG_Utility_Detect_AFK_Controller","AFKThreshold");
+ScriptRegistrant<JMG_Utility_Detect_AFK_Controller> JMG_Utility_Detect_AFK_Controller_Registrant("JMG_Utility_Detect_AFK_Controller","AFKThreshold:int");
 ScriptRegistrant<JMG_Utility_Credit_Trickle_When_Not_AFK> JMG_Utility_Credit_Trickle_When_Not_AFK_Registrant("JMG_Utility_Credit_Trickle_When_Not_AFK","Credits=1.0:float,Rate=1.0:float,TrickleCap=0.0:float,Team=2:int,Custom=0:int");
 ScriptRegistrant<JMG_Utility_Killed_Create_Object> JMG_Utility_Killed_Create_Object_Registrant("JMG_Utility_Killed_Create_Object","Preset:string");
 ScriptRegistrant<JMG_Utility_Damaged_Create_Object_When_Shield_Zero> JMG_Utility_Damaged_Create_Object_When_Shield_Zero_Registrant("JMG_Utility_Damaged_Create_Object_When_Shield_Zero","Preset:string");
+ScriptRegistrant<JMG_Utility_Basic_Spawner_In_Radius> JMG_Utility_Basic_Spawner_In_Radius_Registrant("JMG_Utility_Basic_Spawner_In_Radius","Spawn_Preset:string,Spawn_Rate:float,Random_Spawn_Rate:float,Spawn_At_A_Time:int,Min_Spawn_Radius:float,Max_Spawn_Radius:float,Initial_Spawn_Height=0.0:float,Spawn_Limit=-1:int,Spawn_Location=-1.0 -1.0 -1.0:Vector3,X_Multiplier=1.0:float,Y_Multiplier=1.0:float,Collision_Check=1:int,Collision_Retry_Attempts=3:int,Collision_Add_Height=1.0:float,Change_Spawn_Cap_Custom=-1:int,Initial_Spawn=-1:int,Script_ID=-1:int,Point_Must_Be_In_Pathfind=1:int,Manual_Facing=0:int,Face_Location=0.0 0.0 0.0:vector3,Face_Direction=0.0:float,Ignore_Ray_Cast_Failure=0:int,Min_Distance_Between_Objects=0.0:float,Spawn_Group_ID=0:int");
+ScriptRegistrant<JMG_Utility_Basic_Spawner_In_Radius_Attached> JMG_Utility_Basic_Spawner_In_Radius_Attached_Registrant("JMG_Utility_Basic_Spawner_In_Radius_Attached","Controller_ID:int,Script_ID:int,Spawn_Group_ID:int");
+ScriptRegistrant<JMG_Utility_Flying_Vehicle_Crash_Apply_Damage> JMG_Utility_Flying_Vehicle_Crash_Apply_Damage_Registrant("JMG_Utility_Flying_Vehicle_Crash_Apply_Damage","Min_Collision_Speed=33.528:float,Max_Collision_Speed=42.4688:float,Collision_Sound=vehicle_collide_01:string,Explosion_Preset=Plane_Air_Collision:string,Subtract_Min_Speed=0:int");
+ScriptRegistrant<JMG_Utility_Enemy_Seen_Send_Custom> JMG_Utility_Enemy_Seen_Send_Custom_Registrant("JMG_Utility_Enemy_Seen_Send_Custom","Enemy_Preset_ID:int,ID:int,Visible_Message:int,Visible_Param:int,Not_Visible_Message:int,Not_Visible_Param:int,Max_Lost_Sight_Time=2.5:float");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_If_Script_Attached> JMG_Utility_Custom_Send_Custom_If_Script_Attached_Registrant("JMG_Utility_Custom_Send_Custom_If_Script_Attached","Custom:int,Script:string,ID=0:int,SendCustom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached> JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached_Registrant("JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached","Custom:int,Script:string,ID=0:int,SendCustom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Basic_Spawner_In_Radius_Controller> JMG_Utility_Basic_Spawner_In_Radius_Controller_Registrant("JMG_Utility_Basic_Spawner_In_Radius_Controller","");
+ScriptRegistrant<JMG_Utility_Custom_Set_Engine> JMG_Utility_Custom_Set_Engine_Registrant("JMG_Utility_Custom_Set_Engine","Custom:int,Enable:int");
+ScriptRegistrant<JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count> JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count_Registrant("JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count","Preset_ID:int,ID:int,Message:int,Param:int,Delay:float,Min_Player_Count=1:int,Max_Player_Count=-1:int,Rate=0.1:float,Repeat=0:int");
+ScriptRegistrant<JMG_Utility_Custom_Set_Position> JMG_Utility_Custom_Set_Position_Registrant("JMG_Utility_Custom_Set_Position","Custom:int,Position:Vector3");
+ScriptRegistrant<JMG_Utility_Custom_Delay_Send_Custom> JMG_Utility_Custom_Delay_Send_Custom_Registrant("JMG_Utility_Custom_Delay_Send_Custom","Custom:int,ID=0:int,SendCustom:int,Param:int,Delay:float,RandomDelay:float,CancelCustom=0:int");
+ScriptRegistrant<JMG_Utility_Scale_HP_By_Player_Count> JMG_Utility_Scale_HP_By_Player_Count_Registrant("JMG_Utility_Scale_HP_By_Player_Count","Health_Multiplier=1.0:float,Armor_Multiplier=1.0:float,Max_Player_Count=-1:int,Repeat=1:int,UpdateScaleCustom=0:int");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_To_All_Objects> JMG_Utility_Custom_Send_Custom_To_All_Objects_Registrant("JMG_Utility_Custom_Send_Custom_To_All_Objects","Custom:int,SendCustom:int,Param:int,Team=2:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Enemy_Seen_Send_Custom_Ignore> JMG_Utility_Enemy_Seen_Send_Custom_Ignore_Registrant("JMG_Utility_Enemy_Seen_Send_Custom_Ignore","");
+ScriptRegistrant<JMG_Utility_In_Line_Of_Sight_Send_Custom> JMG_Utility_In_Line_Of_Sight_Send_Custom_Registrant("JMG_Utility_In_Line_Of_Sight_Send_Custom","Enemy_Preset_ID:int,ID:int,Visible_Message:int,Visible_Param:int,Not_Visible_Message:int,Not_Visible_Param:int,Scan_Rate=0.1:float,Enemy_Only=1:int");
+ScriptRegistrant<JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore> JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore_Registrant("JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore","");
+ScriptRegistrant<JMG_Utility_Timer_Trigger_Enemy_Seen> JMG_Utility_Timer_Trigger_Enemy_Seen_Registrant("JMG_Utility_Timer_Trigger_Enemy_Seen","Scan_Rate=0.1:float");
+ScriptRegistrant<JMG_Utility_Custom_Teleport_To_Random_Wander_Point> JMG_Utility_Custom_Teleport_To_Random_Wander_Point_Registrant("JMG_Utility_Custom_Teleport_To_Random_Wander_Point","Custom:int,WanderingAIGroupID=-1:int,SafeTeleportDistance=1.5:float,RetryOnFailure=0:int");
+ScriptRegistrant<JMG_Utility_Send_Custom_If_Not_Moving_Enough> JMG_Utility_Send_Custom_If_Not_Moving_Enough_Registrant("JMG_Utility_Send_Custom_If_Not_Moving_Enough","Time:float,Distance:float,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_AI_Skittish_Herd_Animal> JMG_Utility_AI_Skittish_Herd_Animal_Registrant("JMG_Utility_AI_Skittish_Herd_Animal","MinHerdID=0:int,MaxHerdID=0:int,WanderGroupID=0:int,WanderRadiusAroundHerdCenter=25.0:float,MinWanderFrequency=5.0:float,MaxWanderFrequency=30.0:float,MinRetreatRange=50.0:float,MaxRetreatRange=200.0:float,MinRetreatTime=6.0:float,MaxRetreatTime=24.0:float,MinUpdateHerdCenter=10.0:float,MaxUpdateHerdCenter=25.0:float,RunTowardThreatChance=0.0:float");
+ScriptRegistrant<JMG_Utility_AI_Skittish_Herd_Animal_Ignore> JMG_Utility_AI_Skittish_Herd_Animal_Ignore_Registrant("JMG_Utility_AI_Skittish_Herd_Animal_Ignore","");
+ScriptRegistrant<JMG_Utility_AI_Skittish_Herd_Animal_Controller> JMG_Utility_AI_Skittish_Herd_Animal_Controller_Registrant("JMG_Utility_AI_Skittish_Herd_Animal_Controller","");
+ScriptRegistrant<JMG_Utility_Custom_Display_Dialog_Box> JMG_Utility_Custom_Display_Dialog_Box_Registrant("JMG_Utility_Custom_Display_Dialog_Box","Custom:int,Message:string,Delim=@:string");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_On_Preset_Count> JMG_Utility_Custom_Send_Custom_On_Preset_Count_Registrant("JMG_Utility_Custom_Send_Custom_On_Preset_Count","Custom:int,PresetID:int,MinCount=1,MaxCount=-1,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_On_Script_Count> JMG_Utility_Custom_Send_Custom_On_Script_Count_Registrant("JMG_Utility_Custom_Send_Custom_On_Script_Count","Custom:int,Script:string,MinCount=1,MaxCount=-1,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Poke_Send_Custom_From_Poker> JMG_Utility_Poke_Send_Custom_From_Poker_Registrant("JMG_Utility_Poke_Send_Custom_From_Poker","ID=0:int,Custom=0:int,Param=0:int,Delay=0.0:float,TriggerOnce=1:int,PlayerType=2:int");
+ScriptRegistrant<JMG_Utility_Custom_Grant_Scaled_Score> JMG_Utility_Custom_Grant_Scaled_Score_Registrant("JMG_Utility_Custom_Grant_Scaled_Score","GrantCustom:int,Score:float,ScoreMultiplier:float,EntireTeam=0:int,MaxPlayerCount=-1:int,Repeat=1:int,GrantToSender=0:int,UpdateScaleCustom=0:int,StopUpdateCustom=0:int");
diff -uwrN sourceold/scripts/jmgUtility.h source/scripts/jmgUtility.h
--- sourceold/scripts/jmgUtility.h	2017-11-23 09:14:58.449800000 +1000
+++ source/scripts/jmgUtility.h	2018-11-11 08:30:08.537600000 +1000
@@ -265,7 +265,7 @@
 	}
 	static char *JMG_Get_Compass_Directions(float compassDegree)
 	{
-		static char name[4];
+		static char name[6];
 		sprintf(name,"%s",compassDegree <= -168.75 ? "W":
 			compassDegree <= -146.25 ? "WSW":
 			compassDegree <= -123.75 ? "SW":
@@ -2079,6 +2079,7 @@
 	void Detach(GameObject *obj);
 	void CreateSoundEmitter(GameObject *obj,const char *model,int *soundId);
 	void DestroySoundEmitter(int *soundId);
+	void HideSoundEmitter(int *soundId);
 public:
 	static bool isUnderwater[128];
 	static bool isInWater[128];
@@ -2226,6 +2227,15 @@
 * \ShutdownEngineOnArrival - Used for vehicles, turn on if you have issues with your vehicle rolling away from its move positions after it arrives
 * \AttackCheckBlocked - Defines whether they should check if they can actually hit the player before shooting
 * \MaxSightRangeFromHome - Maximum range the AI can see from its nearest wander point/home location, useful to keep them from wandering off after a trail of enemies, 0 to not use
+* \WanderDistanceOverride - Overrides the default wander arrive distance (1 meter for infantry and 5 for vehicles)
+* \ChangeWanderGroupCustom - If this custom is received the wander group will be set to the param, -1 param to disable the wander group code
+* \ChangeWanderSpeedCustom - If this custom is received the wander speed will be updated to the param/100
+* \ChangeHuntDistanceCustom - If this custom is received the hunt range will be updated to the param/100 (-1 means infinite range)
+* \ChangeHuntSpeedCustom - If this custom is received the hunt speed will be updated to the param/100
+* \ChangeReturnHomeSpeedCustom - If this custom is received the return home speed will be updated to the param/100
+* \ChangeMaxSightFromHomeLocationCustom - If this custom is received the AI won't be able to see targets past the specified range of the param/100, 0 means don't use
+* \ChangeAttackSpeedCustom - If this custom is received the attack speed will be updated to the param/100
+* \ChanceToInvestigateLastSeenLocation - The percent chance (0.0-1.0) of checking out the last spot an enemy/target was seen
 * \author jgray
 * \ingroup JmgUtility
 */
@@ -2252,6 +2262,16 @@
 			this->overrideLocation = overrideLocation;
 		}
 	};
+	struct ValidLastLocation
+	{
+		bool valid;
+		Vector3 location;
+		ValidLastLocation(bool valid)
+		{
+			this->valid = valid;
+		}
+		ValidLastLocation(int enemyId);
+	};
 	float maxSightFromHomeLocation;
 	LastAction lastAction;
 	aiState state;
@@ -2270,14 +2290,28 @@
 	int reverseTime;
 	Vector3 lastPosition;
 	bool moveBackward;
+	float wanderDistanceOverride;
+	int wanderingAiGroupId;
+	float wanderSpeed;
+	float huntSpeed;
+	float attackSpeed;
+	float returnHomeSpeed;
+	int changeWanderGroupCustom;
+	int changeWanderSpeedCustom;
+	int changeHuntDistanceCustom;
+	int changeReturnHomeSpeedCustom;
+	int changeHuntSpeedCustom;
+	int changeMaxSightFromHomeLocationCustom;
+	int changeAttackSpeedCustom;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *seen);
 	void Timer_Expired(GameObject *obj,int number);
 	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
 	void Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation);
 	GameObject *findClosestStar(GameObject *obj);
-	void Return_Home(GameObject *obj);
+	void Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint);
 	void Stuck_Check(GameObject *obj,Vector3 targetPos);
 	void Cant_Get_To_target(GameObject *obj);
 	bool GetRandomPosition(Vector3 *position);
@@ -2569,7 +2603,7 @@
 * \TriggerCustom - Custom to trigger the script on
 * \Preset - Preset group to send the message too
 * \Custom - Custom to send to the presets
-* \Param - Param to send to the presets
+* \Param - Param to send to the presets, -1 uses the param of the trigger custom
 * \Delay - Delay to add to the message before sending
 * \author jgray
 * \ingroup JmgUtility
@@ -3636,7 +3670,7 @@
 * \Count - how many customs needed
 * \ID - ID to send to, 0 sends to self, -1 sends to sender
 * \SendCustom - custom to send
-* \Param - param to send
+* \Param - param to send (-1 sends the param that was received)
 * \Delay - delay to add
 * \ResetCustom - custom to reset the count
 * \author jgray
@@ -3751,6 +3785,15 @@
 * \ShutdownEngineOnArrival - Used for vehicles, turn on if you have issues with your vehicle rolling away from its move positions after it arrives
 * \AttackCheckBlocked - Defines whether they should check if they can actually hit the player before shooting
 * \MaxSightRangeFromHome - Maximum range the AI can see from its nearest wander point/home location, useful to keep them from wandering off after a trail of enemies, 0 to not use
+* \WanderDistanceOverride - Overrides the default wander arrive distance (1 meter for infantry and 5 for vehicles)
+* \ChangeWanderGroupCustom - If this custom is received the wander group will be set to the param, -1 param to disable the wander group code
+* \ChangeWanderSpeedCustom - If this custom is received the wander speed will be updated to the param/100
+* \ChangeHuntDistanceCustom - If this custom is received the hunt range will be updated to the param/100 (-1 means infinite range)
+* \ChangeHuntSpeedCustom - If this custom is received the hunt speed will be updated to the param/100
+* \ChangeReturnHomeSpeedCustom - If this custom is received the return home speed will be updated to the param/100
+* \ChangeMaxSightFromHomeLocationCustom - If this custom is received the AI won't be able to see targets past the specified range of the param/100, 0 means don't use
+* \ChangeAttackSpeedCustom - If this custom is received the attack speed will be updated to the param/100
+* \ChanceToInvestigateLastSeenLocation - The percent chance (0.0-1.0) of checking out the last spot an enemy/target was seen
 * \author jgray
 * \ingroup JmgUtility
 */
@@ -3781,6 +3824,16 @@
 			return (targetId == l.targetId && JmgUtility::SimpleDistance(location,l.location) <= 0.0f && speed == l.speed && distance == l.distance && attack == l.attack && overrideLocation == l.overrideLocation);
 		}
 	};
+	struct ValidLastLocation
+	{
+		bool valid;
+		Vector3 location;
+		ValidLastLocation(bool valid)
+		{
+			this->valid = valid;
+		}
+		ValidLastLocation(int enemyId);
+	};
 	LastAction lastAction;
 	aiState state;
 	Vector3 homeLocation;
@@ -3800,14 +3853,28 @@
 	int reverseTime;
 	Vector3 lastPosition;
 	bool moveBackward;
+	float wanderDistanceOverride;
+	int wanderingAiGroupId;
+	float wanderSpeed;
+	float huntSpeed;
+	float attackSpeed;
+	float returnHomeSpeed;
+	int changeWanderGroupCustom;
+	int changeWanderSpeedCustom;
+	int changeHuntDistanceCustom;
+	int changeReturnHomeSpeedCustom;
+	int changeHuntSpeedCustom;
+	int changeMaxSightFromHomeLocationCustom;
+	int changeAttackSpeedCustom;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *seen);
 	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason);
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
 	void Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation);
 	GameObject *findClosestStar(GameObject *obj);
-	void Return_Home(GameObject *obj);
+	void Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint);
 	void Stuck_Check(GameObject *obj,Vector3 targetPos);
 	void Cant_Get_To_target(GameObject *obj);
 	bool GetRandomPosition(Vector3 *position);
@@ -3832,6 +3899,15 @@
 * \ShutdownEngineOnArrival - Used for vehicles, turn on if you have issues with your vehicle rolling away from its move positions after it arrives
 * \AttackCheckBlocked - Defines whether they should check if they can actually hit the player before shooting
 * \MaxSightRangeFromHome - Maximum range the AI can see from its nearest wander point/home location, useful to keep them from wandering off after a trail of enemies, 0 to not use
+* \WanderDistanceOverride - Overrides the default wander arrive distance (1 meter for infantry and 5 for vehicles)
+* \ChangeWanderGroupCustom - If this custom is received the wander group will be set to the param, -1 param to disable the wander group code
+* \ChangeWanderSpeedCustom - If this custom is received the wander speed will be updated to the param/100
+* \ChangeHuntDistanceCustom - If this custom is received the hunt range will be updated to the param/100 (-1 means infinite range)
+* \ChangeHuntSpeedCustom - If this custom is received the hunt speed will be updated to the param/100
+* \ChangeReturnHomeSpeedCustom - If this custom is received the return home speed will be updated to the param/100
+* \ChangeMaxSightFromHomeLocationCustom - If this custom is received the AI won't be able to see targets past the specified range of the param/100, 0 means don't use
+* \ChangeAttackSpeedCustom - If this custom is received the attack speed will be updated to the param/100
+* \ChanceToInvestigateLastSeenLocation - The percent chance (0.0-1.0) of checking out the last spot an enemy/target was seen
 * \author jgray
 * \ingroup JmgUtility
 */
@@ -3858,6 +3934,16 @@
 			this->overrideLocation = overrideLocation;
 		}
 	};
+	struct ValidLastLocation
+	{
+		bool valid;
+		Vector3 location;
+		ValidLastLocation(bool valid)
+		{
+			this->valid = valid;
+		}
+		ValidLastLocation(int enemyId);
+	};
 	float maxSightFromHomeLocation;
 	LastAction lastAction;
 	aiState state;
@@ -3877,14 +3963,28 @@
 	int reverseTime;
 	Vector3 lastPosition;
 	bool moveBackward;
+	float wanderDistanceOverride;
+	int wanderingAiGroupId;
+	float wanderSpeed;
+	float huntSpeed;
+	float attackSpeed;
+	float returnHomeSpeed;
+	int changeWanderGroupCustom;
+	int changeWanderSpeedCustom;
+	int changeHuntDistanceCustom;
+	int changeReturnHomeSpeedCustom;
+	int changeHuntSpeedCustom;
+	int changeMaxSightFromHomeLocationCustom;
+	int changeAttackSpeedCustom;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *seen);
 	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason);
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
 	void Attack_Move(GameObject *obj,GameObject *target,Vector3 location,float speed,float distance,bool attack,bool overrideLocation);
 	GameObject *findClosestStar(GameObject *obj);
-	void Return_Home(GameObject *obj);
+	void Return_Home(GameObject *obj,ValidLastLocation goNearLastWanderPoint);
 	void Stuck_Check(GameObject *obj,Vector3 targetPos);
 	void Cant_Get_To_target(GameObject *obj);
 	bool GetRandomPosition(Vector3 *position);
@@ -3906,17 +4006,24 @@
 
 /*!
 * \brief Sends a custom on a custom
-* \Custom - Custom to count
+* \Custom - Custom to watch for
 * \ID - ID to send to, 0 sends to self, -1 sends to sender
 * \SendCustom - custom to send
-* \Param - param to send
+* \Param - param to send (-1 sends the param that was received)
 * \Delay - delay to add
-* \ResetCustom - custom to reset the count
+* \RandomDelay - Max amount of random delay that can be added to the delay
+* \RandomChance - If non-zero this will be the chance that the custom can send 0.0-1.0, 1 will always send
 * \author jgray
 * \ingroup JmgUtility
 */
 class JMG_Utility_Custom_Send_Custom : public ScriptImpClass {
 	int recieveMessage;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	float randomDelay;
+	float randomChance;
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 };
@@ -4835,7 +4942,7 @@
 * \PlayerCount - added to the base count, multiplied by the number of players in game
 * \ID - ID to send to, 0 sends to self, -1 sends to sender
 * \SendCustom - custom to send
-* \Param - param to send
+* \Param - param to send (-1 sends the param that was received)
 * \Delay - delay to add
 * \ResetCustom - custom to reset the count
 * \author jgray
@@ -4912,7 +5019,7 @@
 * \PlayerCount - added to the base count, multiplied by the number of players in game
 * \ID - ID to send to, 0 sends to self, -1 sends to sender
 * \SendCustom - custom to send
-* \Param - param to send
+* \Param - param to send (-1 sends the param that was received)
 * \Delay - delay to add
 * \ResetCustom - custom to reset the count
 * \HudStringId - stringId for the string to be parsed and displayed on the HUD, formatting goes %d/%d (current count out of calculated total)
@@ -5453,7 +5560,7 @@
 class JMG_Utility_Send_Custom_When_Speed_Exceeds_Amount : public ScriptImpClass {
 	float speed;
 	int id;
-	int message;
+	int custom;
 	int param;
 	bool repeat;
 	float rate;
@@ -5480,7 +5587,7 @@
 class JMG_Utility_Send_Custom_When_Speed_Below_Amount : public ScriptImpClass {
 	float speed;
 	int id;
-	int message;
+	int custom;
 	int param;
 	bool repeat;
 	float rate;
@@ -5513,7 +5620,7 @@
 	Vector3 velocityFBL;
 	Vector3 velocityRUD;
 	int id;
-	int message;
+	int custom;
 	int param;
 	bool repeat;
 	float rate;
@@ -5640,7 +5747,7 @@
 * \Custom - Custom to trigger on
 * \ID - ID to send the custom to, 0 sends to self, -1 sends to sender
 * \SendCustom - Custom message to send
-* \Param - Param to send
+* \Param - Param to send (-1 sends the param that was received)
 * \Delay - Delay to add before sending custom
 * \IgnoreTime - Time during which customs can't be sent again
 * \StartsEnabled - Does the script start enabled
@@ -5968,3 +6075,743 @@
 class JMG_Utility_Damaged_Create_Object_When_Shield_Zero : public ScriptImpClass {
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
 };
\ No newline at end of file
+
+/*!
+* \brief Spawns objects randomly in a circle using ray casts to find the ground around the location specified can have scripts attached to the spawned object with JMG_Utility_Basic_Spawner_Attach_Script
+* \Spawn_Preset - Preset to create (can be a twiddler).
+* \Spawn_Rate - Rate at which to spawn more objects (also used to respawn when an object is killed).
+* \Random_Spawn_Rate - Random amount to add or subtract from the spawn time.
+* \Spawn_At_A_Time - Number of instances of the spawned object can exist at once, for example in a traditional spawner only 1 can exist at a time.
+* \Min_Spawn_Radius - The min size of the radius to spawn in, non-zero makes a torus basically
+* \Max_Spawn_Radius - The max size of the radius to spawn in.
+* \Initial_Spawn_Height - Adds this much to the Z axis on object creation.
+* \Spawn_Limit - How many times can objects be spawned before being disabled, -1 means no limit.
+* \Spawn_Location - Location in which to spawn the objects, -1 -1 ? (Last value is the height of which to start casting the ray from, -1 makes it the current position)
+* \X_Multiplier - Multiply how the circle is shaped on the X coordinates, default is 1.0.
+* \Y_Multiplier - Multiply how the circle is shaped on the Y coordinates, default is 1.0.
+* \Collision_Check - Check if the spawn location is clear to create the object, 0 means it'll place even if blocked.
+* \Collision_Retry_Attempts - How many times will the script "bump" the object if the collision is blocked.
+* \Collision_Add_Height - Amount of height to add each time if blocked.
+* \Initial_Spawn - Setting the value to -1 will spawn the Spawn_At_A_Time on create, any 0 or above will make the objects slowly spawn in at the spawn rate.
+* \Script_ID - Set the id for this script, -1 uses default (only use this if you're going to need to manually add spawned objects to this spawner).
+* \Point_Must_Be_In_Pathfind - Requires the created point to be in the pathfind field, if its outside of the field the point will not be created.
+* \Manual_Facing - If true random facing will not be used, first it will attempt to use Face_Location and if that's all 0's Face_Direction
+* \Face_Location - If anything besides 0,0,0 all objects created will attempt to face the point
+* \Face_Direction - If Face_Location was 0,0,0 then Face_Direction will be used
+* \Ignore_Ray_Cast_Failure - If a ray fails to cast the script will stop by default (this usually means that it cast beyond the terrain) turn this setting on to continue casting
+* \Min_Distance_Between_Objects - If non-zero the script will place objects at least the specified distance away from other objects in the group
+* \Spawn_Group_ID - The spawn group id which these objects belong to, if -1 or 0 the distance will be checked against all spawn groups (-1 adds the objects to a group, 0 does not)
+* \ if the value is above 0 the objects will only be checked against other objects with the same value.
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Basic_Spawner_In_Radius : public ScriptImpClass {
+public:
+	struct SpawnObjectNode
+	{
+		int groupId;
+		ReferencerClass obj;
+		SpawnObjectNode(GameObject *obj,int spawnGroupId)
+		{
+			this->obj = obj;
+			this->groupId = spawnGroupId;
+		}
+	};
+	static SList<SpawnObjectNode> spawnObjectNodeList;
+private:
+	float minDistanceBetweenObjects;
+	int spawnGroupId;
+	int scriptId;
+	int spawnedObjects;
+	enum SpawnFailureTypes{SPAWN_BLOCKED, SUCCESS, LIMIT_REACHED, SPAWN_CODE_ERROR};
+	Vector3 spawnLocation;
+	int spawnAtATime;
+	int spawnLimit;
+	float minRadius;
+	float maxRadius;
+	float xMultiplier;
+	float yMultiplier;
+	float addHeight;
+	float rate;
+	float randomRate;
+	char preset[128];
+	int spawnCount;
+	bool collisionCheck;
+	int retryAttempts;
+	float initialSpawnHeight;
+	int changeSpawnCapCustom;
+	bool pointMustBeInPathfind;
+	bool manualFacing;
+	bool ignoreRayCastFailure;
+	Vector3 faceLocation;
+	float faceDirection;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	SpawnFailureTypes AttemptSpawn(GameObject *obj);
+	bool CheckIfObjectIsNearAnotherObject(Vector3 pos);
+	void SetFacing(GameObject *obj,float facing);
+};
+
+/*!
+* \brief Tells an object that it belongs to a group of the JMG_Utility_Basic_Spawner_In_Radius (normally attached by JMG_Utility_Basic_Spawner_In_Radius)
+* \ A case where manually using this would make sense would be in a drop cinematic, you might have to define on the vehicle object it belongs to the spawn controller.
+* \Controller_ID - ID of the object with JMG_Utility_Basic_Spawner_In_Radius attached.
+* \Script_ID - Manually or automatically set Script_ID on JMG_Utility_Basic_Spawner_In_Radius.
+* \Spawn_Group_ID - Manually or automatically set Spawn_Group_ID on JMG_Utility_Basic_Spawner_In_Radius.
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Basic_Spawner_In_Radius_Attached: public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Destroyed(GameObject *obj);
+	void AddSpawnedObjectToGroup(GameObject *spawned);
+};
+
+/*!
+* \brief Applies damage based on speed when a flying vehicle collides with terrain or another object
+* \Min_Collision_Speed - Min speed in M/S to detect collisions
+* \Max_Collision_Speed - Max speed a collision can happen and still survive (assuming full health)
+* \Collision_Sound - Sound to play when colliding
+* \Explosion_Preset - Explosion to create when the vehicle dies from a collision
+* \Subtract_Min_Speed - Use this to subtract the min speed from the actual, thus reducing damage done and increasing the amount of speed needed to die
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Flying_Vehicle_Crash_Apply_Damage : public ScriptImpClass {
+	bool subtractMinSpeed;
+	char explosionPreset[128];
+	char collisionSound[128];
+	bool allowCrash;
+	float minCollisionSpeed;
+	float maxCollisionSpeed;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Sends a custom when an enemy is seen, sends a custom again after no enemy in the criteria is visible
+* \Enemy_Preset_ID - Preset ID required to be seen to remove the script, if 0 any enemy will trigger the script
+* \ID - Id of the object to send the custom to, 0 sends to itself, -1 sends to the seen object
+* \Visible_Message - Custom to send when there is a target that is visible
+* \Visible_Param - Param to send when there is a target that is visible
+* \Not_Visible_Message - Custom sent when no preset is visible anymore
+* \Not_Visible_Param - Param sent when no preset is visible anymore
+* \Max_Lost_Sight_Time - This is the max amount of time an enmey can go without being seen before being lost, 
+* \  game scans at least once a second so I reccomend at least 2.5 seconds if not longer
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Enemy_Seen_Send_Custom : public ScriptImpClass {
+	int lastEnemyId;
+	int seenTime;
+	int enemyPresetId;
+	int id;
+	int visibleMessage;
+	int notVisibleMessage;
+	int visibleParam;
+	int notVisibleParam;
+	int maxNotSeenTime;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Enemy_Seen(GameObject *obj,GameObject *seen);
+};
+
+/*!
+* \brief Sends a custom on a custom if the specified script is attached
+* \Custom - Custom to count
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \Script - script that must be attached to this object in order to send the custom
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_If_Script_Attached : public ScriptImpClass {
+	char script[128];
+	int recieveMessage;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom on a custom if the specified script is not attached
+* \Custom - Custom to count
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \Script - script that must not be attached to this object in order to send the custom
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_If_Script_Not_Attached : public ScriptImpClass {
+	char script[128];
+	int recieveMessage;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Place this script on an object that exists the duration of the map, all it does is deletes the spawn group nodes if you use them on game end.
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Basic_Spawner_In_Radius_Controller: public ScriptImpClass {
+	void Destroyed(GameObject *obj);
+};
+
+/*!
+* \brief Enables or disables the attached vehicles engine on custom
+* \Custom - Custom to trigger on
+* \Enable - Whether to enable or disable the engine, -1 uses the param instead
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Set_Engine : public ScriptImpClass {
+	int custom;
+	int enable;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom when the count of presets on the map matches the player count
+* \Preset_ID - Preset ID required to match the count of players on the map
+* \ID - Id of the object to send the custom to, 0 sends to itself
+* \Message - Custom to send when when the count matches
+* \Param - Param to send when when the count matches
+* \Delay - Time to wait before sending the message
+* \Min_Player_Count - Min required players to trigger script, -1 any
+* \Max_Player_Count - Max players allowed to trigger script, -1 any
+* \Rate - Rate to scan for count changes (0.1 - 10 times a second by default).
+* \Repeat - Can the script fire more than once.
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Send_Custom_Player_Count_Matches_Preset_Count : public ScriptImpClass {
+	int presetId;
+	int id;
+	int custom;
+	int param;
+	float delay;
+	float rate;
+	int minPlayers;
+	int maxPlayers;
+	bool repeat;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+
+/*!
+* \brief Moves an object when a custom is received, works on zones
+* \Custom - Custom to trigger on
+* \Position - Spot to place object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Set_Position : public ScriptImpClass {
+	int custom;
+	Vector3 position;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom after a delay, if the custom being watched for is received again the delay is reset before attempting to send again
+* \Custom - Custom to watch for
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to wait before sending
+* \RandomDelay - Random amount of delay that can be added to the delay time
+* \CancelCustom - If this custom is caught the pending custom will be canceled 
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Delay_Send_Custom : public ScriptImpClass {
+	int watchMessage;
+	int id;
+	int Param;
+	ReferencerClass lastSender;
+	int sendMessage;
+	int lastParam;
+	float delay;
+	float randomDelay;
+	int cancelCustom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Scales the HP and armor of the object as players join/leave the match (if no players are in game the HP/Armor is not updated from the last player count)
+* \Health_Multiplier - Amount to multiply the health by per player (keep in mind the max value transmitted across the network is 10k).
+* \Armor_Multiplier - Amount to multiply the armor by per player (keep in mind the max value transmitted across the network is 10k).
+* \Max_Player_Count - Max player count allows you to specify a maximum amount of players the script should account for, any above it will be ignored.
+* \Repeat - Should the script check multiple times for player count changes 1 - yes, 0 - no.
+* \UpdateScaleCustom - If recieved the health will be updated
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Scale_HP_By_Player_Count : public ScriptImpClass {
+	int lastPlayerCount;
+	int maxPlayerCount;
+	float originalHealth;
+	float originalArmor;
+	float healthMultiplier;
+	float armorMultiplier;
+	int updateScaleCustom;
+	bool repeat;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void RescaleHP(GameObject *obj);
+};
+
+/*!
+* \brief Sends a to all objects on the map
+* \Custom - Custom to watch for
+* \Team - Required team to send to, 2 for any
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_To_All_Objects : public ScriptImpClass {
+	int recieveMessage;
+	int team;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Prevents JMG_Utility_Enemy_Seen_Send_Custom from seening the attached object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Enemy_Seen_Send_Custom_Ignore : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Sends a custom when an enemy is in the line of sight, sends a custom again after no enemy in the criteria is visible
+* \Enemy_Preset_ID - Preset ID required to be seen to remove the script, if 0 any enemy will trigger the script
+* \ID - Id of the object to send the custom to, 0 sends to itself, -1 sends to the seen object
+* \Visible_Message - Custom to send when there is a target that is visible
+* \Visible_Param - Param to send when there is a target that is visible
+* \Not_Visible_Message - Custom sent when no preset is visible anymore
+* \Not_Visible_Param - Param sent when no preset is visible anymore
+* \Scan_Rate - This is how often to scan for enemies
+* \Enemy_Only - Only detect enemies
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_In_Line_Of_Sight_Send_Custom : public ScriptImpClass {
+	int lastEnemyId;
+	int enemyPresetId;
+	int id;
+	int visibleMessage;
+	int notVisibleMessage;
+	int visibleParam;
+	int notVisibleParam;
+	bool enemyOnly;
+	float rate;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	bool Test_Line_Of_Sight(GameObject *obj,GameObject *seen);
+};
+
+/*!
+* \brief Prevents JMG_Utility_In_Line_Of_Sight_Send_Custom from seening the attached object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_In_Line_Of_Sight_Send_Custom_Ignore : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Forces the attached object to scan for enemies more often (server's default rate has a random of 0.5-1.0 seconds added to the last scan)
+* \Scan_Rate - This is how often to scan for enemies
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Timer_Trigger_Enemy_Seen : public ScriptImpClass {
+	float rate;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Teleports the object that enters the zone to a random wander point
+* \Custom - Custom to trigger the teleport
+* \WanderingAIGroupID - Group of points to teleport to
+* \SafeTeleportDistance - How far can infantry be moved if the spot is blocked
+* \RetryOnFailure - If this is true a script will be attached that will continue to try to teleport the player until successful (Warning: Turning this on hides error messages)
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Teleport_To_Random_Wander_Point : public ScriptImpClass {
+	bool retryOnFailure;
+	int custom;
+	float safeTeleportDistance;
+	int wanderPointGroup;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	bool Get_A_Defense_Point(Vector3 *position,float *facing);
+	bool Grab_Teleport_Spot(GameObject *enter,int attempts);
+};
+
+/*!
+* \brief Sends a custom if the unit doesn't manage to move more than the specified distance in the specified time
+* \Time - amount of time the unit has to move
+* \Distance - the distance the unit has to move
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Send_Custom_If_Not_Moving_Enough : public ScriptImpClass {
+	int time;
+	int resetTime;
+	float distance;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	Vector3 lastPos;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+
+/*!
+* \brief The AI attached to this script will flee enemies seen and combat (Like in Deer Hunter mode for ECW)
+* \MinHerdID - Min Herd ID that this object will belong to (max of 128)
+* \MaxHerdID - Max Herd ID that this object will belong to (max of 128)
+* \WanderGroupID - The wander group network the herd will use to run from threats
+* \WanderRadiusAroundHerdCenter - How far can the objects wander around the center of the herd
+* \MinWanderFrequency - Min amount of time a member of the herd can hold still before moving
+* \MaxWanderFrequency - Max amount of time a member of the herd can hold still before moving
+* \MinRetreatTime - Min amount of time that the AI can flee
+* \MaxRetreatTime - Max amount of time that the AI can flee
+* \MinUpdateHerdCenter - Min amount of time required for the center point of the herd to reposition
+* \MaxUpdateHerdCenter - Max amount of time required for the center point of the herd to reposition
+* \RunTowardThreatChance - Chance that it'll flee on purpose toward the threat (0.0 - 1.0)
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_AI_Skittish_Herd_Animal : public ScriptImpClass {
+public:
+	class HerdAnimalPositionSystem
+	{
+	public:
+		int animalCount;
+		struct HerdPositionNode
+		{
+			int id;
+			Vector3 pos;
+			bool alive;
+			struct HerdPositionNode *next;
+			HerdPositionNode(GameObject *obj)
+			{
+				this->id = Commands->Get_ID(obj);
+				this->pos = Commands->Get_Position(obj);
+				this->alive = true;
+				next = NULL;
+			}
+		};
+		Vector3 herdRetreatLocation;
+		float herdRetreatTime;
+		time_t herdRetreatStart;
+	private:
+		Vector3 centerPos;
+		time_t lastPosCalcTime;
+		float minUpdateDelay;
+		bool hascalculatedPos;
+		HerdPositionNode *HerdPositionNodeList;
+	public:
+		HerdAnimalPositionSystem()
+		{
+			herdRetreatLocation = Vector3(0.0f,0.0f,0.0f);
+			herdRetreatTime = 0.0f;
+			herdRetreatStart = clock();
+			minUpdateDelay = 10000.0f;
+			hascalculatedPos = false;
+			animalCount = 0;
+			centerPos = Vector3(0.0f,0.0f,0.0f);
+			lastPosCalcTime = clock();
+			HerdPositionNodeList = NULL;
+		}
+		HerdPositionNode *AddNode(GameObject *obj)
+		{
+			int id = Commands->Get_ID(obj);
+			HerdPositionNode *Current = HerdPositionNodeList;
+			while (Current)
+			{
+				if (Current->id == id)
+				{
+					animalCount--;
+					return Current;
+				}
+				Current = Current->next;
+			}
+			animalCount++;
+			if (!HerdPositionNodeList)
+				return (HerdPositionNodeList = new HerdPositionNode(obj));
+			Current = HerdPositionNodeList;
+			while (Current)
+			{
+				if (!Current->alive)
+				{
+					Current->id = Commands->Get_ID(obj);
+					Current->pos = Commands->Get_Position(obj);
+					Current->alive = true;
+					return Current;
+				}
+				if (!Current->next)
+				{
+					Current->next = new HerdPositionNode(obj);
+					return Current->next;
+				}
+				Current = Current->next;
+			}
+			return NULL;
+		}
+		HerdAnimalPositionSystem &operator -= (GameObject *obj)
+		{
+			int id = Commands->Get_ID(obj);
+			HerdPositionNode *Current = HerdPositionNodeList;
+			while (Current)
+			{
+				if (Current->id == id)
+				{
+					animalCount--;
+					Current->alive = false;
+					break;
+				}
+				Current = Current->next;
+			}
+			return *this;
+		}
+		Vector3 getCenterLocation(bool allowWander,int wanderPointGroup,float minUpdateHerdCenter,float maxUpdateHerdCenter);
+		bool checkRetreatSuccessful()
+		{
+			HerdPositionNode *Current = HerdPositionNodeList;
+			int retreatedCount = 0;
+			while (Current)
+			{
+				if (Current->alive && JmgUtility::SimpleDistance(Current->pos,herdRetreatLocation) < 2500)
+					retreatedCount++;
+				Current = Current->next;			
+			}
+			if (animalCount == retreatedCount)
+				return true;
+			return false;
+		}
+		bool checkRetreatCloseEnough()
+		{
+			HerdPositionNode *Current = HerdPositionNodeList;
+			int retreatedCount = 0;
+			while (Current)
+			{
+				if (Current->alive && JmgUtility::SimpleDistance(Current->pos,herdRetreatLocation) < 2500)
+					retreatedCount++;
+				Current = Current->next;			
+			}
+			if (animalCount*0.75 <= retreatedCount)
+				return true;
+			return false;
+		}
+		void Empty_List()
+		{
+			HerdPositionNode *temp = HerdPositionNodeList,*die;
+			while (temp)
+			{
+				die = temp;
+				temp = temp->next;
+				delete die;
+			}
+			HerdPositionNodeList = NULL;
+		}
+	};
+	static HerdAnimalPositionSystem HerdAnimalPositionControl[128];
+private:
+	int herdId;
+	HerdAnimalPositionSystem::HerdPositionNode *HerdPositionNode;
+	int wanderPointGroup;
+	char defaultWeapon[128];
+	float weaponRange;
+	float minRetreatRange;
+	float maxRetreatRange;
+	float minRetreatTime;
+	float maxRetreatTime;
+	float minUpdateHerdCenter;
+	float maxUpdateHerdCenter;
+	float wanderRadiusAroundHerdCenter;
+	float wanderRadiusAroundHerdCenterSq;
+	float minWanderFrequency;
+	float maxWanderFrequency;
+	float runTowardThreatChance;
+	void Created(GameObject *obj);
+	void Enemy_Seen(GameObject *obj,GameObject *seen);
+	void Timer_Expired(GameObject *obj,int number);
+	void Damaged(GameObject *obj,GameObject *damager,float damage);
+	void Destroyed(GameObject *obj);
+	void GotoLocation(GameObject *obj,const Vector3 &pos,GameObject *Enemy,float speed = 0);
+	void setRetreatLocation(GameObject *obj,GameObject *enemy);
+public:
+	JMG_Utility_AI_Skittish_Herd_Animal()
+	{
+		herdId = 0;
+		HerdPositionNode = NULL;
+	}
+	static bool Get_A_Wander_Point(Vector3 *position,int wanderPointGroup);
+};
+/*!
+* \brief Prevents JMG_Utility_AI_Skittish_Herd_Animal from seening the attached object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_AI_Skittish_Herd_Animal_Ignore : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Just used to clean up JMG_Utility_AI_Skittish_Herd_Animal at game end, not required but nice to have
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_AI_Skittish_Herd_Animal_Controller : public ScriptImpClass {
+public:
+	JMG_Utility_AI_Skittish_Herd_Animal_Controller()
+	{
+		for (int x = 0;x < 128;x++)
+			JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionControl[x] = JMG_Utility_AI_Skittish_Herd_Animal::HerdAnimalPositionSystem();
+	}
+	void Destroyed(GameObject *obj);
+};
+
+/*!
+* \brief Displays a popup dialog to the attached player object
+* \Custom - Custom to trigger the teleport
+* \Message - Text message to display
+* \Delim - Character to use to replace with ,'s
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Display_Dialog_Box : public ScriptImpClass {
+	int custom;
+	char *textMessage;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom if the specified preset ID is on the map
+* \Custom - Custom to watch for
+* \PresetID - Preset ID to check for
+* \MinCount - Min number of presets that must exist
+* \MaxCount - Max number of presets that can exist (-1 no limit)
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_On_Preset_Count : public ScriptImpClass {
+	int recieveMessage;
+	int presetId;
+	int minCount;
+	int maxCount;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom if the specified script is on a preset on the map
+* \Custom - Custom to watch for
+* \Script - Script to check for
+* \MinCount - Min number of presets that must exist
+* \MaxCount - Max number of presets that can exist (-1 no limit)
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_On_Script_Count : public ScriptImpClass {
+	int recieveMessage;
+	char script[128];
+	int minCount;
+	int maxCount;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+
+/*!
+* \brief Sends a custom message on poke, also enables the pokable indicator icon, just like JMG_Utility_Poke_Send_Custom but sends from the poker
+* \ID - ID to send the custom to, 0 sends to self, -1 sends to poker
+* \Custom - Custom message to send
+* \Param - Param to send
+* \Delay - Delay to add before sending custom
+* \TriggerOnce - Allows the script only to trigger the first time all customs are received
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Poke_Send_Custom_From_Poker : public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Poked(GameObject *obj, GameObject *poker);
+};
+
+/*!
+* \brief Scales the given score as players join/leave the match (if no players are in game the score is not updated from the last player count)
+* \GrantCustom - Custom to trigger granting the score
+* \Score - Score to give on custom
+* \ScoreMultiplier - Amount to multiply the score by per player 
+* \EntireTeam - Give the score to the whole team 0 no 1 yes
+* \MaxPlayerCount - Max player count allows you to specify a maximum amount of players the script should account for, any above it will be ignored.
+* \Repeat - Should the script check multiple times for player count changes 1 - yes, 0 - no.
+* \GrantToSender - The object to grant to, 0 is itself 1 is the sender
+* \UpdateScaleCustom - If recieved the health will be updated
+* \StopUpdateCustom - Custom to stop updating the score value
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Grant_Scaled_Score : public ScriptImpClass {
+	int lastPlayerCount;
+	int maxPlayerCount;
+	int grantCustom;
+	float score;
+	float scoreMultiplier;
+	int updateScaleCustom;
+	bool repeat;
+	bool grantToSender;
+	float theScore;
+	bool entireTeam;
+	int stopUpdateCustom;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void RescaleScore(GameObject *obj);
+};
\ No newline at end of file
diff -uwrN sourceold/scripts/weaponmgr.h source/scripts/weaponmgr.h
--- sourceold/scripts/weaponmgr.h	2016-12-10 07:57:58.701001000 +1000
+++ source/scripts/weaponmgr.h	2018-11-11 08:30:08.538600000 +1000
@@ -96,7 +96,9 @@
 	safe_float		TargetingRange;//new
 	int             C4Preset;
 	bool			DisableHitscan;
-	bool			TrackingIgnoreTarget;
+	bool			TrackingIgnoreVehicle;
+	bool			TrackingIgnoreSoldier;
+	bool			TrackingIgnoreVTOL;
 
 #if (PARAM_EDITING_ON) || (DDBEDIT) || (W3DVIEWER)
     Vector3 ProjectileExtent;
