diff -urN sourceold/AntiSpawnKill/AntiSpawnKill.vcxproj source/AntiSpawnKill/AntiSpawnKill.vcxproj
--- sourceold/AntiSpawnKill/AntiSpawnKill.vcxproj	2018-10-27 22:04:54.438400000 +1000
+++ source/AntiSpawnKill/AntiSpawnKill.vcxproj	2020-09-26 09:00:03.253003000 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/CTF/ctf.vcxproj source/CTF/ctf.vcxproj
--- sourceold/CTF/ctf.vcxproj	2018-10-27 22:04:54.445400000 +1000
+++ source/CTF/ctf.vcxproj	2020-09-26 09:00:03.254025700 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/CharacterRefund/CharacterRefund.vcxproj source/CharacterRefund/CharacterRefund.vcxproj
--- sourceold/CharacterRefund/CharacterRefund.vcxproj	2018-10-27 22:04:54.449400000 +1000
+++ source/CharacterRefund/CharacterRefund.vcxproj	2020-09-26 09:00:03.254025700 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/ExtraConsoleCommands/ExtraConsoleCommands.vcxproj source/ExtraConsoleCommands/ExtraConsoleCommands.vcxproj
--- sourceold/ExtraConsoleCommands/ExtraConsoleCommands.vcxproj	2018-10-27 22:04:54.455400000 +1000
+++ source/ExtraConsoleCommands/ExtraConsoleCommands.vcxproj	2020-09-26 09:00:03.255035400 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/FirstBlood/FirstBlood.vcxproj source/FirstBlood/FirstBlood.vcxproj
--- sourceold/FirstBlood/FirstBlood.vcxproj	2018-10-27 22:04:54.460400000 +1000
+++ source/FirstBlood/FirstBlood.vcxproj	2020-09-26 09:00:03.255035400 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/MemoryManager/MemoryManager.vcxproj source/MemoryManager/MemoryManager.vcxproj
--- sourceold/MemoryManager/MemoryManager.vcxproj	2018-10-27 22:04:54.620400000 +1000
+++ source/MemoryManager/MemoryManager.vcxproj	2020-09-26 09:00:03.260402700 +1000
@@ -28,7 +28,7 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -40,7 +40,7 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -134,6 +134,7 @@
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -186,6 +187,7 @@
       <SubSystem>Windows</SubSystem>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent />
   </ItemDefinitionGroup>
@@ -225,6 +227,7 @@
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <GenerateMapFile>true</GenerateMapFile>
       <MapFileName>$(TargetDir)$(TargetName).map</MapFileName>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -276,6 +279,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent />
   </ItemDefinitionGroup>
diff -urN sourceold/Mute/Mute.vcxproj source/Mute/Mute.vcxproj
--- sourceold/Mute/Mute.vcxproj	2018-10-27 22:04:54.636400000 +1000
+++ source/Mute/Mute.vcxproj	2020-09-26 09:00:03.260402700 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/NoPoints/NoPoints.vcxproj source/NoPoints/NoPoints.vcxproj
--- sourceold/NoPoints/NoPoints.vcxproj	2018-10-27 22:04:54.641400000 +1000
+++ source/NoPoints/NoPoints.vcxproj	2020-09-26 09:00:03.261400000 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/PointsDistribution/PointsDistribution.vcxproj source/PointsDistribution/PointsDistribution.vcxproj
--- sourceold/PointsDistribution/PointsDistribution.vcxproj	2018-10-27 22:04:54.652400000 +1000
+++ source/PointsDistribution/PointsDistribution.vcxproj	2020-09-26 09:00:03.262426400 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/Spectate/Spectate.vcxproj source/Spectate/Spectate.vcxproj
--- sourceold/Spectate/Spectate.vcxproj	2018-10-27 22:04:54.657400000 +1000
+++ source/Spectate/Spectate.vcxproj	2020-09-26 09:00:03.263394900 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/SuddenDeath/SuddenDeath.vcxproj source/SuddenDeath/SuddenDeath.vcxproj
--- sourceold/SuddenDeath/SuddenDeath.vcxproj	2018-10-27 22:04:54.663400000 +1000
+++ source/SuddenDeath/SuddenDeath.vcxproj	2020-09-26 09:00:03.263394900 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/bansystem/bansystem.vcxproj source/bansystem/bansystem.vcxproj
--- sourceold/bansystem/bansystem.vcxproj	2018-10-27 22:04:54.675400000 +1000
+++ source/bansystem/bansystem.vcxproj	2020-09-26 09:00:03.268381300 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/crates/crates.vcxproj source/crates/crates.vcxproj
--- sourceold/crates/crates.vcxproj	2018-10-27 22:04:54.685400000 +1000
+++ source/crates/crates.vcxproj	2020-09-26 09:00:03.269381100 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/example-plugin/example-plugin.vcxproj source/example-plugin/example-plugin.vcxproj
--- sourceold/example-plugin/example-plugin.vcxproj	2018-10-27 22:04:56.127400000 +1000
+++ source/example-plugin/example-plugin.vcxproj	2020-09-26 09:00:03.271374200 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -93,6 +93,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -159,6 +160,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/randomstartingcredits/RandomStartingCredits.vcxproj source/randomstartingcredits/RandomStartingCredits.vcxproj
--- sourceold/randomstartingcredits/RandomStartingCredits.vcxproj	2018-10-27 22:04:56.159400000 +1000
+++ source/randomstartingcredits/RandomStartingCredits.vcxproj	2020-09-26 09:00:03.274365300 +1000
@@ -20,14 +20,14 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -109,6 +109,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -186,6 +187,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/scripts/ArmedGameObj.h source/scripts/ArmedGameObj.h
--- sourceold/scripts/ArmedGameObj.h	2018-11-17 10:23:56.953600000 +1000
+++ source/scripts/ArmedGameObj.h	2020-09-10 09:08:27.944914000 +1000
@@ -39,6 +39,10 @@
 	virtual const Matrix3D &Get_Muzzle( int index = 0 );
 	void							Start_Recoil( int muzzle_index,float recoil_scale,float recoil_time );
 	float							Get_Weapon_Error( void )					{ return Get_Definition().WeaponError; }
+	float							Get_Weapon_Turn_Min(void)					{ return Get_Definition().WeaponTurnMin; }
+	float							Get_Weapon_Turn_Max(void)					{ return Get_Definition().WeaponTurnMax; }
+	float							Get_Weapon_Tilt_Min(void)					{ return Get_Definition().WeaponTiltMin; }
+	float							Get_Weapon_Tilt_Max(void)					{ return Get_Definition().WeaponTiltMax; }
 	Vector3						Get_Targeting_Pos( void )					{ return TargetingPos; }
 	virtual bool				Set_Targeting( const Vector3 & pos, bool do_tilt = true );
 	virtual	ArmedGameObj	*As_ArmedGameObj( void )					{ return this; }
diff -urN sourceold/scripts/ArmedGameObjDef.h source/scripts/ArmedGameObjDef.h
--- sourceold/scripts/ArmedGameObjDef.h	2018-11-17 10:23:56.954600000 +1000
+++ source/scripts/ArmedGameObjDef.h	2020-03-28 21:10:59.007874300 +1000
@@ -18,6 +18,8 @@
 	ArmedGameObjDef( void );
 	virtual bool								Save( ChunkSaveClass &csave );
 	virtual bool								Load( ChunkLoadClass &cload );
+	int											Get_Weapon_Def_ID() const { return WeaponDefID; }
+	int											Get_Secondary_Weapon_Def_ID() const { return SecondaryWeaponDefID; }
 #ifdef DDBEDIT
 	virtual void                        Dump (FileClass &file);
 #endif
diff -urN sourceold/scripts/AudibleSoundDefinitionClass.cpp source/scripts/AudibleSoundDefinitionClass.cpp
--- sourceold/scripts/AudibleSoundDefinitionClass.cpp	2018-11-17 10:23:56.956600000 +1000
+++ source/scripts/AudibleSoundDefinitionClass.cpp	2020-09-10 09:08:27.944914000 +1000
@@ -31,7 +31,7 @@
 AudibleSoundDefinitionClass::AudibleSoundDefinitionClass() : m_Priority(0.5), m_Volume(1), m_VolumeRandomizer(0), m_Pan(0.5), m_LoopCount(1), m_VirtualChannel(0), m_DropoffRadius(40), m_MaxVolRadius(20), m_Is3DSound(true), m_Type(1), m_StartOffset(0), m_PitchFactor(1), m_PitchFactorRandomizer(0), m_LogicalType(0), m_LogicalNotifDelay(2.0), m_LogicalDropoffRadius(-1.0), m_CreateLogicalSound(false), m_SphereColor(0,0.75,0.75)
 {
 #ifdef PARAM_EDITING_ON
-	NAMED_EDITABLE_PARAM(AudibleSoundDefinitionClass,ParameterClass::TYPE_FILENAME,m_Filename,"Filename");
+	NAMED_EDITABLE_PARAM(AudibleSoundDefinitionClass,ParameterClass::TYPE_SOUND_FILENAME,m_Filename,"Filename");
 	INT_EDITABLE_PARAM(AudibleSoundDefinitionClass,m_VirtualChannel,0,100);
 	NAMED_EDITABLE_PARAM(AudibleSoundDefinitionClass,ParameterClass::TYPE_FLOAT,m_DropoffRadius,"Drop-off Radius");
 	NAMED_EDITABLE_PARAM(AudibleSoundDefinitionClass,ParameterClass::TYPE_FLOAT,m_MaxVolRadius,"Max-Vol Radius");
diff -urN sourceold/scripts/BeaconGameObjDef.h source/scripts/BeaconGameObjDef.h
--- sourceold/scripts/BeaconGameObjDef.h	2018-11-17 10:23:56.961600000 +1000
+++ source/scripts/BeaconGameObjDef.h	2020-09-10 09:08:27.945919000 +1000
@@ -26,6 +26,7 @@
 	PersistClass* Create() const;
 	int Is_Nuke() const {return IsNuke;}
 	bool Requires_Silo() const {return RequiresSilo;}
+	int Get_Explosion_Obj_Def() const {return ExplosionObjDef;}
 #ifdef DDBEDIT
 	virtual void                        Dump (FileClass &file);
 #endif
diff -urN sourceold/scripts/BuildingGameObj.h source/scripts/BuildingGameObj.h
--- sourceold/scripts/BuildingGameObj.h	2018-11-17 10:23:56.965600000 +1000
+++ source/scripts/BuildingGameObj.h	2020-03-28 21:10:59.007874300 +1000
@@ -107,6 +107,7 @@
 #endif
 	SCRIPTS_API void											Find_Closest_Poly (const Vector3 &pos, float *distance2);
 	float Find_Closest_Poly(const Vector3 &pos) { float distance2; Find_Closest_Poly(pos,&distance2); return distance2; }
+	void Find_Closest_Poly(const Vector3& pos, float* distance2, Vector3* polyPos);
 	int											Building_In_Range(const Vector3 &point, float range);	//return 0 for no, 1 for yes and 2 for MCT in range
 	bool										Is_In_Range_Coarse(const Vector3& point, float range_sq);
 	bool Cast_Ray(RayCollisionTestClass& raytest);
@@ -145,7 +146,7 @@
 	void											Add_Aggregate(BuildingAggregateClass * aggregate);
 	void											Remove_Aggregate(BuildingAggregateClass * aggregate);
 	void											Add_Light(LightPhysClass * light);
-	void											Find_Closest_Poly_For_Model (RenderObjClass *model, const Vector3 &pos, float *distance2);
+	void											Find_Closest_Poly_For_Model (RenderObjClass *model, const Vector3 &pos, float *distance2, Vector3* polyPos = NULL);
 	void											Update_State(bool force_update = false);
 	void											Enable_Alternate_Materials(RefMultiListClass<StaticPhysClass> & models, bool onoff);
 	void											Enable_Alternate_Materials(RenderObjClass * model,bool onoff);
diff -urN sourceold/scripts/Defines.h source/scripts/Defines.h
--- sourceold/scripts/Defines.h	2018-11-17 10:23:56.993600000 +1000
+++ source/scripts/Defines.h	2020-09-26 09:00:03.275362600 +1000
@@ -30,6 +30,7 @@
 #pragma warning(disable:4251)
 
 #define WIN32_LEAN_AND_MEAN
+#define _WIN32_WINNT 0x0601
 #define _USE_MATH_DEFINES
 
 // breakpoint
diff -urN sourceold/scripts/Doxyfile source/scripts/Doxyfile
--- sourceold/scripts/Doxyfile	2018-11-17 10:23:57.001600000 +1000
+++ source/scripts/Doxyfile	2020-04-02 12:02:44.436401100 +1000
@@ -32,7 +32,7 @@
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 4.6
+PROJECT_NUMBER         = 4.7
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer
 # a quick idea about the purpose of the project. Keep the description short.
diff -urN sourceold/scripts/HumanStateClass.h source/scripts/HumanStateClass.h
--- sourceold/scripts/HumanStateClass.h	2018-11-17 10:23:57.015600000 +1000
+++ source/scripts/HumanStateClass.h	2020-09-10 09:08:27.945919000 +1000
@@ -160,6 +160,7 @@
 	void					Update_Recoil(WeaponClass * weapon);
 	void					Begin_Jump( void );
 	void					Complete_Jump( void );
+	void					GetLegAndHoldStyle (int *leg_style,int *hold_style);
 };
 
 
diff -urN sourceold/scripts/LineSegClass.cpp source/scripts/LineSegClass.cpp
--- sourceold/scripts/LineSegClass.cpp	2018-11-17 10:23:57.023600000 +1000
+++ source/scripts/LineSegClass.cpp	2020-09-10 09:08:27.946909600 +1000
@@ -34,6 +34,7 @@
     Matrix3D::Transform_Vector(transform, object.P1, &P1);
     DP = P1 - P0;
     Matrix3D::Rotate_Vector(transform, object.Dir, &Dir);
+	Length = object.Length;
 }
 
 bool LineSegClass::Find_Intersection(const LineSegClass &other_line, Vector3 *p1, float *fraction1, Vector3 *p2, float *fraction2) const
diff -urN sourceold/scripts/ObjectLibraryManager.cpp source/scripts/ObjectLibraryManager.cpp
--- sourceold/scripts/ObjectLibraryManager.cpp	1970-01-01 10:00:00.000000000 +1000
+++ source/scripts/ObjectLibraryManager.cpp	2020-03-28 21:10:59.056743700 +1000
@@ -0,0 +1,31 @@
+#include "general.h"
+
+#include "ObjectLibraryManager.h"
+#include "DefinitionMgrClass.h"
+#include "Definition.h"
+PhysicalGameObj* ObjectLibraryManager::Create_Object
+   (const char* cPresetName)
+{
+   // const DefinitionClass* oDefinition = DefinitionMgrClass::Find_Typed_Definition (cPresetName, 12288, true);
+   DefinitionClass* oDefinition = DefinitionMgrClass::Find_Named_Definition (cPresetName, true);
+
+   StringClass oError;
+   if (!oDefinition || !oDefinition->Is_Valid_Config (oError))
+      return 0;
+
+   return (PhysicalGameObj *)oDefinition->Create();
+}
+
+
+
+PhysicalGameObj* ObjectLibraryManager::Create_Object
+   (uint32 uDefinitionID)
+{
+   DefinitionClass* oDefinition = DefinitionMgrClass::Find_Definition (uDefinitionID, true);
+
+   StringClass oError;
+   if (!oDefinition || !oDefinition->Is_Valid_Config (oError))
+      return 0;
+
+   return (PhysicalGameObj *)oDefinition->Create();
+}
diff -urN sourceold/scripts/ObjectLibraryManager.h source/scripts/ObjectLibraryManager.h
--- sourceold/scripts/ObjectLibraryManager.h	1970-01-01 10:00:00.000000000 +1000
+++ source/scripts/ObjectLibraryManager.h	2020-03-28 21:10:59.056743700 +1000
@@ -0,0 +1,16 @@
+#ifndef TT_INCLUDE__OBJECTLIBRARYMANAGER_H
+#define TT_INCLUDE__OBJECTLIBRARYMANAGER_H
+#include "Persist.h"
+class PhysicalGameObj;
+class ObjectLibraryManager
+{
+
+public:
+
+   static PhysicalGameObj* Create_Object (const char* cPresetName);
+   static PhysicalGameObj* Create_Object (uint32 uDefinitionID);
+
+};
+
+
+#endif
diff -urN sourceold/scripts/PhysicsSceneClass.h source/scripts/PhysicsSceneClass.h
--- sourceold/scripts/PhysicsSceneClass.h	2019-06-22 07:43:44.222400000 +1000
+++ source/scripts/PhysicsSceneClass.h	2020-03-28 21:10:59.011863900 +1000
@@ -393,10 +393,6 @@
 	static bool AllowCollisionFlags[NUM_COLLISION_FLAGS];
 	int DynamicPolyBudget;
 	int StaticPolyBudget;
-#ifdef TT_EXPORTS
-	float mapStaticPolyBudget;
-	float mapDynamicPolyBudget;
-#endif
 	RefMultiListClass<PhysClass> ObjList;
 	RefMultiListClass<PhysClass> StaticObjList;
 	RefMultiListClass<PhysClass> StaticLightList;
@@ -417,5 +413,9 @@
 #endif
 	friend class WW3D;
 	friend class VisOptimizationContextClass;
+#ifdef TT_EXPORTS
+	float mapStaticPolyBudget;
+	float mapDynamicPolyBudget;
+#endif
 }; // 792, WIN: 700
 #endif
diff -urN sourceold/scripts/RefineryGameObj.h source/scripts/RefineryGameObj.h
--- sourceold/scripts/RefineryGameObj.h	2018-11-17 10:23:57.068600000 +1000
+++ source/scripts/RefineryGameObj.h	2020-09-10 09:08:27.946909600 +1000
@@ -61,6 +61,7 @@
 	{
 		AllowHarvesterSpawn = true;
 	}
+	static float HarvesterDumpRate;
 private:
 	void					Load_Variables (ChunkLoadClass &cload);	
 	HarvesterClass *	Harvester;
@@ -75,6 +76,8 @@
 	AudibleSoundClass * MoneyTrickleSound;
 	bool					AllowHarvesterSpawn;
 	ReferencerClass HarvesterVehicle;
+	float accumulatedDumpCredits;
+	float DumpRate;
 }; // 2232, RH7 2252
 
 
diff -urN sourceold/scripts/RefineryGameObjDef.h source/scripts/RefineryGameObjDef.h
--- sourceold/scripts/RefineryGameObjDef.h	2018-11-17 10:23:57.068600000 +1000
+++ source/scripts/RefineryGameObjDef.h	2020-03-28 21:10:59.012889300 +1000
@@ -22,6 +22,8 @@
 	~RefineryGameObjDef (void);
 	uint32								Get_Class_ID (void) const;
 	PersistClass *						Create (void) const;
+	float								Get_Funds_Gathered() const { return FundsGathered; }
+	float								Get_Funds_Distributed_Per_Sec() const { return FundsDistributedPerSec; }
 	bool									Save (ChunkSaveClass &csave);
 	bool									Load (ChunkLoadClass &cload);
 	const PersistFactoryClass &	Get_Factory (void) const;
diff -urN sourceold/scripts/SmartGameObjDef.h source/scripts/SmartGameObjDef.h
--- sourceold/scripts/SmartGameObjDef.h	2018-11-17 10:23:57.086600000 +1000
+++ source/scripts/SmartGameObjDef.h	2020-09-10 09:08:27.947906000 +1000
@@ -26,6 +26,12 @@
 	virtual bool								Save( ChunkSaveClass &csave );
 	virtual bool								Load( ChunkLoadClass &cload );
 	bool Is_Stealthed() const {return IsStealthUnit;}
+	/* Modifies the sight range of the object. SightRange is used by AI code only. */
+	void Set_Sight_Range (const float range) { SightRange = range; }
+	float Get_Sight_Range() const {return SightRange;}
+	/* Modifies the sight arc of the object. SightArc is used by AI code only. */
+	void Set_Sight_Arc (const float arc) { SightArc = arc; }
+	float Get_Sight_Arc() const {return SightArc;}
 #ifdef DDBEDIT
 	virtual void                        Dump (FileClass &file);
 #endif
diff -urN sourceold/scripts/SoldierGameObj.h source/scripts/SoldierGameObj.h
--- sourceold/scripts/SoldierGameObj.h	2019-06-22 07:31:28.791400000 +1000
+++ source/scripts/SoldierGameObj.h	2020-09-10 09:08:27.947906000 +1000
@@ -202,6 +202,9 @@
 	bool Get_Use_Stock_Ghost_Behavior(){return useStockGhostBehavior;}
 	void Set_Override_Muzzle_Direction(bool override){OverrideMuzzleDirection = override; Set_Object_Dirty_Bit(BIT_RARE, true);};
 	bool Get_Override_Muzzle_Direction(){return OverrideMuzzleDirection;};
+	void Set_Bot_Tag(const char* tag) { botTag = tag; Set_Object_Dirty_Bit(BIT_RARE, true); }
+	const WideStringClass& Get_Bot_Tag() { return botTag; }
+	bool Is_Bot() { return botTag.Get_Length() > 0; }
 	virtual int Get_Contact_Surface_Type();
 	virtual float		Get_Skeleton_Heigth( void );
 	virtual void 		Set_Skeleton_Height( float height );
@@ -288,6 +291,8 @@
 	float						lastSkeletonWidth;
 	int							overrideWeaponHoldStyleId;
 	bool						enableHumanAnimOverride;
+	WideStringClass				botTag;
+	int sight_bone;
 	TT_DEPRECATED("Do not use") int						Check(void);
 }; // size: 3404
 
diff -urN sourceold/scripts/SoundSceneObjClass.h source/scripts/SoundSceneObjClass.h
--- sourceold/scripts/SoundSceneObjClass.h	2018-10-27 22:04:56.326400000 +1000
+++ source/scripts/SoundSceneObjClass.h	2020-03-28 21:10:59.014855700 +1000
@@ -18,6 +18,9 @@
 #include "audiocallback.h"
 #include "Persist.h"
 #include "Matrix3D.h"
+#if !defined(W3DVIEWER) && !defined(TTLE_EXPORTS)
+#include "HashTemplateClass.h"
+#endif
 class SoundSceneClass;
 class SoundCullObjClass;
 class Sound3DClass;
@@ -28,6 +31,7 @@
 class RenderObjClass;
 const uint32	SOUND_OBJ_DEFAULT_ID	= 0;
 const uint32	SOUND_OBJ_START_ID	= 1000000000;
+
 class SoundSceneObjClass : public MultiListObjectClass, public PersistClass, public RefCountClass
 {
 public:
@@ -87,9 +91,23 @@
 	int								m_AttachedBone; // 0030  0048
 	uint32							m_UserData; // 0034  004C
 	RefCountClass *				m_UserObj; // 0038  0050
+
 	static uint32 m_NextAvailableID;
 	static DynamicVectorClass<SoundSceneObjClass *>	m_GlobalSoundList;
 	static CriticalSectionClass m_IDListMutex;
+
+	// Added by TT
+#if !defined(W3DVIEWER) && !defined(TTLE_EXPORTS)
+public:
+	static void Associate_Server_ID(SoundSceneObjClass* sound_obj, uint32 server_id);
+
+protected:
+	static HashTemplateClass<uint32, SoundSceneObjClass*> m_ServerGlobalSoundList;
+	static HashTemplateClass<uint32, uint32> m_ReversedServerGlobalSoundList;
+	static CriticalSectionClass m_ServerIDListMutex;
+
+	static bool Find_Server_Sound_Object(uint32 server_id_to_find, SoundSceneObjClass*& sound_obj);
+#endif
 }; // 003C  0054
 
 #endif
diff -urN sourceold/scripts/SysTimeClass.cpp source/scripts/SysTimeClass.cpp
--- sourceold/scripts/SysTimeClass.cpp	2018-11-17 10:23:57.099600000 +1000
+++ source/scripts/SysTimeClass.cpp	2020-09-10 09:08:27.948915200 +1000
@@ -32,7 +32,15 @@
       this->Reset();
    }
 
-   return timeGetTime() - this->uTimeInit;
+   unsigned int u = timeGetTime();
+   if (u <= uTimeInit)
+   {
+       return uTimeInitNeg + u;
+   }
+   else
+   {
+       return u - uTimeInit;
+   }
 }
 
 
diff -urN sourceold/scripts/TwiddlerClass.h source/scripts/TwiddlerClass.h
--- sourceold/scripts/TwiddlerClass.h	2018-11-17 10:23:57.112600000 +1000
+++ source/scripts/TwiddlerClass.h	2020-03-28 21:10:59.014855700 +1000
@@ -19,6 +19,7 @@
 	virtual DefinitionClass *Twiddle() const;
 	PersistClass *Create() const;
 	const PersistFactoryClass &Get_Factory() const;
+	const DynamicVectorClass<int>& Get_Definition_List() { return m_DefinitionList; }
 	bool Save(ChunkSaveClass &csave);
 	bool Load(ChunkLoadClass &cload);
 	bool Save_Variables(ChunkSaveClass &csave);
diff -urN sourceold/scripts/Vector3.h source/scripts/Vector3.h
--- sourceold/scripts/Vector3.h	2018-11-17 10:23:57.113600000 +1000
+++ source/scripts/Vector3.h	2020-09-10 09:08:27.948915200 +1000
@@ -327,9 +327,11 @@
 
 	TT_INLINE static float Distance(const Vector3 &p1, const Vector3 &p2)
 	{
-		Vector3	temp;
-		temp = p1 - p2;
-		return (temp.Length());
+		return (p1 - p2).Length();
+	}
+	TT_INLINE static float Distance_Squared(const Vector3 &p1, const Vector3 &p2)
+	{
+		return (p1 - p2).Length2();
 	}
 	TT_INLINE static void Lerp(const Vector3 &a, const Vector3 &b, float alpha,Vector3 *set_result)
 	{
diff -urN sourceold/scripts/VehicleGameObj.h source/scripts/VehicleGameObj.h
--- sourceold/scripts/VehicleGameObj.h	2019-06-22 07:31:28.793400000 +1000
+++ source/scripts/VehicleGameObj.h	2020-03-28 21:10:59.015853000 +1000
@@ -98,6 +98,7 @@
 	void	Update_Damage_Meshes( void );
 	float	Get_Squish_Velocity( void) { return Get_Definition().SquishVelocity; }
 	void Script_Enable_Transitions( bool enable )	{	TransitionsEnabled = enable; Create_And_Destroy_Transitions(); }
+	bool Get_Transitions_Enabled ( void ) { return TransitionsEnabled; }
 	static void Set_Precision(void);
 	static bool	Toggle_Target_Steering( void );
 	static void	Set_Target_Steering( bool onoff );
diff -urN sourceold/scripts/WWAudioClass.h source/scripts/WWAudioClass.h
--- sourceold/scripts/WWAudioClass.h	2018-10-27 22:04:56.358400000 +1000
+++ source/scripts/WWAudioClass.h	2020-03-28 21:10:59.016850200 +1000
@@ -198,6 +198,10 @@
 	AudibleSoundClass *	Create_Sound (int definition_id, RefCountClass *user_obj = NULL, uint32 user_data = 0, int classid_hint = CLASSID_3D);
 	AudibleSoundClass *	Create_Sound (const char *def_name, RefCountClass *user_obj = NULL, uint32 user_data = 0, int classid_hint = CLASSID_3D);
 	SoundSceneObjClass *	Find_Sound_Object (uint32 sound_obj_id);
+#if !defined(W3DVIEWER) && !defined(TTLE_EXPORTS)
+	SoundSceneObjClass* Find_Server_Sound_Object(uint32 server_sound_obj_id); // Added by TT
+	uint32 Get_New_Server_Sound_ID(); // Added by TT
+#endif
 	SoundSceneClass *	Get_Sound_Scene (void) const		{ return m_SoundScene; }
 	bool					Set_Cache_Size (int kbytes = DEF_CACHE_SIZE)	{ m_MaxCacheSize = (kbytes * 1024); }
 	int					Get_Cache_Size (void) const						{ return m_MaxCacheSize / 1024; }
diff -urN sourceold/scripts/anticheat.ini source/scripts/anticheat.ini
--- sourceold/scripts/anticheat.ini	2018-11-17 10:23:37.857600000 +1000
+++ source/scripts/anticheat.ini	2020-09-26 08:59:57.386159600 +1000
@@ -134,3 +134,63 @@
 185315cb0070e52ab9c89d95d06f05e5=C&C_Complex.ldd
 11fc8b1d6d70fbacbb18181618fc3964=c&c_field.mix
 b94a4486508afdc014c1ff3ea4f3d455=c&c_field.ldd
+b3b5a7faa502f747d3bc8ff5c63a99cb=M00_Tutorial.mix
+5da07b82073c698031b3bacdf286709c=M02.mix
+ceb5f647bec120fedffc9d9fc70f9ae6=M04.mix
+77e35ed03742c2a3916cae9baf6b119a=M07.mix
+72d92296beb9b14c1cd227e8ee592e19=M08.mix
+b3a1e43b578cd1f1899d920d9502b4e3=always.dat
+bbcd3fe6b90f81db7a55a1a214a75368=always.dbs
+f89d34c3bc56931a98aab08ad58d8ef1=c&c_city_flying.lsd
+7ddf27c53b4ff6e2a359f7e1b0fb9d2c=c&c_glacier_flying.ddb
+840a735015e5a5f97f9b595d27d6939e=c&c_glacier_flying.ldd
+9d5a1a6534e87623734592583ebd7514=c&c_glacier_flying.lsd
+908f10aa21b880481f00df979ee9ffc3=c&c_walls_flying.lsd
+e8a85eab76883e81ddf00c8a3832e62b=m09.dep
+d42dde72d9b33d70e27186d8576f7f28=m09.ldd
+3f313b1d45c289b4315b849613e24d5d=m09.lsd
+8a73e57ed8328458fc5b8e188431b58a=c&c_canyon.ldd
+3c2948416ae9ef87576244334dd96dcc=c&c_canyon.lsd
+fa8ec7d845fc98fa4e251c47ddda08ab=C&C_Canyon.mix
+b707c64d4ecbd8852a9203194ea870d6=c&c_city.ldd
+06cea509a508dbbe2a77a20837d8af1a=c&c_city.lsd
+66653f776fc48d86fab9bd4e7a79fa50=C&C_City.mix
+8980464e3794ee7814fa8e628a6bf03f=c&c_city_flying.ldd
+1d9c1184dcf889db9e89fa0f4cbbc2f3=c&c_city_flying.lsd
+3ae55a803f618419613d4f9449621311=C&C_City_Flying.mix
+e579475a79eb6064e850540dcdbc93db=c&c_complex.ldd
+1f65b69504fc5900583038f4878395b5=c&c_complex.lsd
+4874ba832f9244cec2fdb06298d84f28=C&C_Complex.mix
+1f384b687a76243972d041416e971dd7=c&c_field.ldd
+09c11621c186be319abc9c67bfb6c851=c&c_field.lsd
+48bff89bfedbe2dec0bed18d30419a5f=C&C_Field.mix
+3a15d5bee28c4d1efb49a80da9912971=c&c_glacier_flying.ddb
+ce8e22aafb5d126384ecde5d424c774c=c&c_glacier_flying.ldd
+10a0a9086ccb7885c6140f98f099084a=c&c_glacier_flying.lsd
+5fb74b0a88a3d17296ceb7eb01b48ecf=C&C_Glacier_Flying.mix
+2c827469432c744858076605522f3179=c&c_hourglass.ldd
+d165cb79cb5928e4235d325296affebd=c&c_hourglass.lsd
+df78d1b07d1a9e6982a2b059687a35be=C&C_Hourglass.mix
+22bb4baf876ba7dbd3500e7f7a6b06b4=c&c_islands.ldd
+0250e5ff5fc14d206b6047c8ce878fe9=c&c_islands.lsd
+4189d8309dd8af379cd6059c4161001b=C&C_Islands.mix
+d274bd131570e2c86ca9c1283740c316=c&c_mesa.ldd
+ae9f30a84b0addad158b9607d650d1a6=c&c_mesa.lsd
+1093a6bfa25597ae0b092e2ff0841e55=C&C_Mesa.mix
+b4b62db6b2b678e6280788c31be801a4=c&c_under.ldd
+d52da922114ee3815e3af8e14f8f3407=c&c_under.lsd
+5ff1b5861853e4dda9193f0de21c72ca=C&C_Under.mix
+681e7516692cb3b2573591f74b5a9d18=c&c_volcano.ldd
+b3fa999e7d40b378d57d7dacfba87f36=c&c_volcano.lsd
+0eb0f530b48926241cbea44a24ef6a6e=C&C_Volcano.mix
+c6e8f0e037a922caa4aeeed7d9e51400=C&C_Walls.ldd
+a44fe08c4400d45dfc7c26e545cc1c44=C&C_Walls.lsd
+3e9c2babf700a495f8403303ef6e1707=C&C_Walls.mix
+49cadbf6942f619922e2d9ef03b58e0e=C&C_Walls_Flying.ldd
+779dbe4cc96b4b75be0189699eeab597=C&C_Walls_Flying.lsd
+cedd5f9edf76b92a5f8af4099c1e2e0e=C&C_Walls_Flying.mix
+dbcbf16ad119e54b3f11d6fa44de2fcc=m09.dep
+504fa186ffab468a37a64fbd3ab26b37=m09.ldd
+9a1b58b2327389f8065f704dd6e4d8a0=m09.lsd
+9be4d74ee0deff8204e7091df81eeff4=M09.mix
+3cedfb2b7c127d456c37ff10c7cc65a8=always3.dat
diff -urN sourceold/scripts/dllmain.cpp source/scripts/dllmain.cpp
--- sourceold/scripts/dllmain.cpp	2019-06-22 07:43:44.225400000 +1000
+++ source/scripts/dllmain.cpp	2020-09-26 08:59:57.387156900 +1000
@@ -334,6 +334,9 @@
 	Retrieve_Waypaths = (rwpa)Address(tt,"Retrieve_Waypaths");
 	Retrieve_Waypoints = (rwpo)Address(tt,"Retrieve_Waypoints");
 	Get_Waypoint_Position = (gwp)Address(tt,"Get_Waypoint_Position");
+	Test_Raycast_Collision = (trc)Address(tt,"Test_Raycast_Collision");
+	Is_Inside_AABox = (iib)Address(tt,"Is_Inside_AABox");
+	Can_Generic_Soldier_Teleport = (cgst)Address(tt,"Can_Generic_Soldier_Teleport");
 	Create_Lightning = (cl)Address(tt,"Create_Lightning");
 	Set_Global_Stealth_Disable = (smie)Address(tt,"Set_Global_Stealth_Disable");
 	Get_Clouds = (gc) Address(tt, "Get_Clouds");
@@ -378,6 +381,39 @@
 	Cancel_Get_Pathfind_Distance = (cgpd)Address(tt,"Cancel_Get_Pathfind_Distance");
 	Get_Pathfind_Distance_Async = (gpda)Address(tt, "Get_Pathfind_Distance_Async");
 	Get_Pathfind_Distance_Blocking = (gpdb)Address(tt, "Get_Pathfind_Distance_Blocking");
+	Get_Multiplayer_Spawn_Location = (gmsl)Address(tt, "Get_Multiplayer_Spawn_Location");
+	Enable_Spawners_By_Name = (esbn)Address(tt, "Enable_Spawners_By_Name");
+	Is_Pathfind_Generated = (ipg)Address(tt, "Is_Pathfind_Generated");
+	Get_Closest_Pathfind_Spot = (gcps)Address(tt, "Get_Closest_Pathfind_Spot");
+	Get_Closest_Pathfind_Spot_Size = (gcpss)Address(tt, "Get_Closest_Pathfind_Spot_Size");
+	Get_Radio_Command_String = (grcs)Address(tt, "Get_Radio_Command_String");
+	Set_Emot_Icon = (sei)Address(tt, "Set_Emot_Icon");
+	Kill_Messages_Disabled = (kmd)Address(tt, "Kill_Messages_Disabled");
+	Is_Sidebar_Enabled = (ise)Address(tt, "Is_Sidebar_Enabled");
+	Is_Extras_Enabled = (iee)Address(tt, "Is_Extras_Enabled");
+	Can_Build_Ground = (cbg)Address(tt, "Can_Build_Ground");
+	Can_Build_Air = (cba)Address(tt, "Can_Build_Air");
+	Can_Build_Naval = (cbn)Address(tt, "Can_Build_Naval");
+	Is_Soldier_Busy = (isb)Address(tt, "Is_Soldier_Busy");
+	Is_On_Enemy_Pedestal = (ioep)Address(tt, "Is_On_Enemy_Pedestal");
+	Find_Closest_Poly_Position = (fcpp)Address(tt, "Find_Closest_Poly_Position");
+	Say_Dynamic_Dialogue = (sdd)Address(tt, "Say_Dynamic_Dialogue");
+	Say_Dynamic_Dialogue_Player = (sddp)Address(tt, "Say_Dynamic_Dialogue_Player");
+	Say_Dynamic_Dialogue_Team = (sddt)Address(tt, "Say_Dynamic_Dialogue_Team");
+	Enable_Letterbox_Player = (elp)Address(tt, "Enable_Letterbox_Player");
+	Create_Sound_Team = (cst)Address(tt, "Create_Sound_Team");
+	Create_2D_Sound_Team = (c2dst)Address(tt, "Create_2D_Sound_Team");
+	Create_2D_WAV_Sound_Team = (c2dwst)Address(tt, "Create_2D_WAV_Sound_Team");
+	Create_2D_WAV_Sound_Team_Dialog = (c2dwstd)Address(tt, "Create_2D_WAV_Sound_Team_Dialog");
+	Create_2D_WAV_Sound_Team_Cinematic = (c2dwstc)Address(tt, "Create_2D_WAV_Sound_Team_Cinematic");
+	Create_3D_WAV_Sound_At_Bone_Team = (c3dwsbt)Address(tt, "Create_3D_WAV_Sound_At_Bone_Team");
+	Create_3D_Sound_At_Bone_Team = (c3dsbt)Address(tt, "Create_3D_Sound_At_Bone_Team");
+	Stop_Sound_Player = (_Stop_Sound_Player)Address(tt, "Stop_Sound_Player");
+	Stop_Sound_Team = (_Stop_Sound_Team)Address(tt, "Stop_Sound_Team");
+	Set_Subobject_Animation = (ssa)Address(tt,"Set_Subobject_Animation");
+	Set_Time_Scale = (sts)Address(tt,"Set_Time_Scale");
+	Set_Subobject_Animation_Player = (ssap)Address(tt,"Set_Subobject_Animation_Player");
+	Write_File_Async = (wfa)Address(tt, "Write_File_Async");
 
 #ifdef SSGM
 	SSGMGameManager::Init();
diff -urN sourceold/scripts/dp88_ar.cpp source/scripts/dp88_ar.cpp
--- sourceold/scripts/dp88_ar.cpp	2018-11-17 10:23:57.137600000 +1000
+++ source/scripts/dp88_ar.cpp	2020-09-10 09:08:27.950898100 +1000
@@ -14,6 +14,7 @@
 #include "scripts.h"
 #include "engine.h"
 #include "dp88_ar.h"
+#include "ms.h"
 #include "Definition.h"
 #include "PurchaseSettingsDefClass.h"
 #include "SoldierGameObj.h"
@@ -1031,6 +1032,7 @@
 
     // Grant money to team and reset ore load level
     Commands->Give_Money ( obj, (float)m_oreValue, true );
+	MS_AccessHelper::Give_Bot_Credits(Get_Object_Type(obj), (float)m_oreValue);
     m_oreMined = 0;
     m_oreValue = 0;
 
@@ -1453,7 +1455,7 @@
 
       // OK, got a candidate zone, can we chronoshift here?
       Vector3 zonePos = Commands->Get_Position(zone);
-      if ( Commands->Get_Distance(zonePos,refineryPos) > maxDist || !CanChronoshiftToLocation(obj, zonePos) )
+      if (Vector3::Distance_Squared(zonePos, refineryPos) > maxDist * maxDist || !CanChronoshiftToLocation(obj, zonePos) )
         continue;
 
       // Is this zone in use for another chronoshift operation? If so then we cannot use it
@@ -3276,7 +3278,7 @@
 {
   // If we recieve a stop charging message from the tower we are currently
   // charging then we should stop all actions
-  if ( type == CUSTOM_PRISMTOWER_STOP_CHARGING && isAssistingTower && Commands->Get_ID(sender) == targetID )
+  if ( type == CUSTOM_PRISMTOWER_STOP_CHARGING && isAssistingTower && sender == m_target )
     StopAssisting(obj);
 
 
@@ -3286,12 +3288,12 @@
   else if ( type == CUSTOM_PRISMTOWER_REQUEST_CHARGING )
   {
     // Are we idle? If so then there's no reason not to simply start charging immediatly
-    if ( targetID == 0 )
+    if ( !m_target )
       StartAssisting(obj, sender, (float)param);
 
     // Is this request from the tower we are currently charging? If so then update the last seen
     // time and priority and forward the assistance request to any other adjacent towers
-    else if ( isAssistingTower && targetID == Commands->Get_ID(sender) )
+    else if ( isAssistingTower && m_target == sender )
     {
       targetLastSeen = (int)time(NULL);
       targetPriority = (float)param;
@@ -3319,7 +3321,7 @@
     *   Can this screw up the timings between charging towers and the attacking tower? Need to think
     *   this through sometime and possibly do some experimentation...
     */
-    if ( targetID )
+    if ( m_target )
       SendAssistanceRequests(obj);
 
     /* Refill a single unit of charge if depleted */
@@ -3327,7 +3329,7 @@
       Set_Current_Clip_Bullets(obj,1);
 
     /* If current bullets > 1 and no enemy seen recently then additional charge is lost */
-    if ( Get_Current_Bullets(obj) > 1 && targetID == 0 )
+    if ( Get_Current_Bullets(obj) > 1 && !m_target )
       Set_Current_Bullets(obj,1);
   }
 
@@ -3342,7 +3344,7 @@
 {
   // If the target is the tower we are currently charging then return
   // the priority of that charging sequence
-  if ( Commands->Get_ID(target) == targetID && isAssistingTower )
+  if ( m_target == target && isAssistingTower )
     return targetPriority;
 
   // Otherwise run the normal priority calculation
@@ -3354,7 +3356,7 @@
 bool dp88_AR_Prism_Tower::checkTeam( GameObject *obj, GameObject *target )
 {
   // Return true for the tower we are charging, even though it is on the same team as us
-  if ( Commands->Get_ID(target) == targetID && isAssistingTower )
+  if ( m_target == target && isAssistingTower )
     return true;
 
   // Otherwise run the normal check team function
@@ -3408,7 +3410,7 @@
 void dp88_AR_Prism_Tower::StartAssisting(GameObject* obj, GameObject* tower, float priority)
 {
   // Set our new target ID and priority
-  targetID = Commands->Get_ID(tower);
+  m_target = tower;
   targetPriority = priority;
   targetLastSeen = (int)time(NULL);
   isAssistingTower = true;
@@ -3427,7 +3429,7 @@
 {
   if ( isAssistingTower )
   {
-    targetID = NULL;
+    m_target = nullptr;
     targetPriority = 0;
     isAssistingTower = false;
     Commands->Action_Reset(obj, 101.0f);
@@ -3454,7 +3456,7 @@
   // Send out assistance requests to all adjacent towers except the one we are charging, if any
   for ( int i = 0; i < adjacentTowerCount; ++i )
   {
-    if ( adjacentTowers[i] != targetID && Commands->Find_Object(adjacentTowers[i]) != obj)
+    if ( adjacentTowers[i] != m_target->Get_ID() && Commands->Find_Object(adjacentTowers[i]) != obj)
       Commands->Send_Custom_Event(obj, Commands->Find_Object(adjacentTowers[i]), CUSTOM_PRISMTOWER_REQUEST_CHARGING, (int)targetPriority, 0.0f );
   }
 }
diff -urN sourceold/scripts/dp88_buildingScripts.cpp source/scripts/dp88_buildingScripts.cpp
--- sourceold/scripts/dp88_buildingScripts.cpp	2018-11-17 10:23:57.142600000 +1000
+++ source/scripts/dp88_buildingScripts.cpp	2020-03-28 21:10:59.018844900 +1000
@@ -15,6 +15,7 @@
 #include "engine.h"
 #include "dp88_custom_timer_defines.h"
 #include "dp88_buildingScripts.h"
+#include "ms.h"
 #include "definition.h"
 #include "BaseControllerClass.h"
 #include "WarFactoryGameObj.h"
@@ -1078,6 +1079,8 @@
 		int team = Get_Object_Type(obj);
 		if ( team == 0 || team == 1 )
 		{
+			MS_AccessHelper::Give_Bot_Credits(team, Get_Float_Parameter("creditsPerSec"));
+
 			// NB: Calling Give_Money with the building controller as a parameter doesn't seem
 			// to give anyone any money, so get the first player on the team and use them as
 			// the target for Give_Money (true as third param to give to whole team)
@@ -1102,6 +1105,8 @@
 {
 	if (team == 0 || team == 1)
 	{
+		MS_AccessHelper::Give_Bot_Credits(team, m_credits);
+
 		GameObject* pFirstPlayer = Find_First_Player(team);
 		if(pFirstPlayer)
 		{
diff -urN sourceold/scripts/dp88_customAI.cpp source/scripts/dp88_customAI.cpp
--- sourceold/scripts/dp88_customAI.cpp	2019-06-22 07:31:28.796400000 +1000
+++ source/scripts/dp88_customAI.cpp	2020-09-10 09:08:27.951896300 +1000
@@ -34,7 +34,7 @@
 void dp88_customAI::Timer_Expired(GameObject *obj, int number)
 {
   if (TIMER_AI_THINK == number)
-    Commands->Start_Timer(obj, this, 0.10f, TIMER_AI_THINK);
+    Commands->Start_Timer(obj, this, thinkTime, TIMER_AI_THINK);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -45,7 +45,8 @@
   {
     m_bAiEnabled = false;
     AIStateChanged(pObj, false);
-	targetID = 0;
+	m_target = nullptr;
+	hasTarget = false;
 	targetPriority = 0.0f;
 	targetLastSeen = 0;
   }
@@ -53,11 +54,17 @@
   else if ( message == CUSTOM_AI_ENABLEAI && !m_bAiEnabled )
   {
     m_bAiEnabled = true;
-	targetID = 0;
+	m_target = nullptr;
+	hasTarget = false;
 	targetPriority = 0.0f;
 	targetLastSeen = 0;
     AIStateChanged(pObj, true);
   }
+
+  else if (message == CUSTOM_AI_RESET_ACTIONS)
+  {
+    ResetAllActions(pObj);
+  }
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -85,7 +92,8 @@
 {
   if ( action_id == 2 )
   {
-    targetID = 0;
+    m_target = nullptr;
+	hasTarget = false;
     targetPriority = 0.0f;
     Commands->Action_Reset( obj, 100 );
   }
@@ -104,19 +112,21 @@
 
   // Set default values
   m_bAiEnabled    = true;
-  targetID        = 0;
+  m_target        = nullptr;
+  hasTarget       = false;
   targetLastSeen  = 0;
   targetPriority  = 0.0f;
   m_bTargetPrimaryFire  = true;
+  thinkTime       = 1.0f;
 
   // Set state
   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, 0.10f, TIMER_AI_THINK );
+  Commands->Start_Timer ( obj, this, 1.0, TIMER_AI_THINK );
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -176,17 +186,19 @@
 
 float dp88_customAI::getBasePriority(GameObject *target)
 {
-  if ( Get_Vehicle_Mode(target) == VEHICLE_TYPE_FLYING || (target->As_SoldierGameObj() && (Get_Fly_Mode(target)||Find_Script_On_Object(target,"dp88_AR_Paradrop")) ) )
-    return priority_VTOL;
-  if ( target->As_SoldierGameObj() )
-    return priority_infantry;
-  if ( target->As_VehicleGameObj() && Is_Script_Attached ( target, "dp88_AI_heavyVehicleMarker" ) )
-    return priority_heavyVehicle;
-  if ( target->As_VehicleGameObj() )
-    return priority_lightVehicle;
-  if ( target->As_BuildingGameObj() )
-    return priority_building;
-
+  switch (GetTargetType(target))
+  {
+    case SOLDIER:
+      return priority_infantry;
+    case LIGHT_VEHICLE:
+      return priority_lightVehicle;
+    case HEAVY_VEHICLE:
+      return priority_heavyVehicle;
+    case FLYING:
+      return priority_VTOL;
+    case BUILDING:
+      return priority_building;
+  }
   return 0;
 }
 
@@ -200,9 +212,8 @@
   if ( debug ) fprintf ( debugFile, "Calculating priority of %d (%s)\n", Commands->Get_ID(target), Commands->Get_Preset_Name(target) );
 
   // 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 ) == -2) )
-    //|| ( Commands->Get_Player_Type ( target ) != 0 && Commands->Get_Player_Type ( target ) != 1 ) )
+  if ( ( Commands->Get_Health ( target ) + Commands->Get_Shield_Strength ( target ) ) == 0
+    ||   Commands->Get_Player_Type ( target ) == SCRIPT_PLAYERTYPE_NEUTRAL || Commands->Get_Player_Type ( target ) == SCRIPT_PLAYERTYPE_SPECTATOR )
   {
     if ( debug ) fprintf ( debugFile, "Target %d is dead or unteamed, ignoring\n", Commands->Get_ID(target));
     return 0.0;
@@ -214,7 +225,7 @@
     return 0.0;
   }
 
-  if ( !m_bCanDetectStealth && target->As_SmartGameObj() && target->As_SmartGameObj()->Is_Stealthed() )
+  if ( !m_bCanDetectStealth && target->As_SmartGameObj() && target->As_SmartGameObj()->Is_Stealthed() && getDistance(obj, target) > target->As_SmartGameObj()->Get_Stealth_Fade_Distance() )
   {
     if ( debug ) fprintf ( debugFile, "Target %d is currently stealthed and stealth detection is disabled\n", Commands->Get_ID(target));
     return 0.0;
@@ -238,7 +249,7 @@
   // cost to purchase - not applicable to buildings)
   if ( !target->As_BuildingGameObj() )
   {
-    int cost = Get_Team_Cost( Commands->Get_Preset_Name ( target ), Commands->Get_Player_Type ( target ) );
+    int cost = Get_Team_Cost( Commands->Get_Preset_ID ( target ), Commands->Get_Player_Type ( target ) );
     priority += ((float)cost/10.0f) * modifier_target_value;
 
     if ( debug ) fprintf ( debugFile, "\tTarget Value Modifier: Adding %.4f priority ((Value %d /10) * modifier %.2f)\n", ((float)cost/10.0f)*modifier_target_value, cost, modifier_target_value );
@@ -287,14 +298,19 @@
 
 bool dp88_customAI::getPrimary ( GameObject *target )
 {
-  if ( target->As_SoldierGameObj() )
-    return primary_infantry;
-  else if ( Get_Vehicle_Mode ( target ) == VEHICLE_TYPE_FLYING )
-    return primary_VTOL;
-  else if ( target->As_VehicleGameObj() )
-    return (Is_Script_Attached(target,"dp88_AI_heavyVehicleMarker")) ? primary_heavyVehicle : primary_lightVehicle;
-  else if ( target->As_BuildingGameObj() )
-    return primary_building;
+  switch (GetTargetType(target))
+  {
+    case SOLDIER:
+      return primary_infantry;
+    case LIGHT_VEHICLE:
+      return primary_lightVehicle;
+    case HEAVY_VEHICLE:
+      return primary_heavyVehicle;
+    case FLYING:
+      return primary_VTOL;
+    case BUILDING:
+      return primary_building;
+  }
   return true;
 }
 
@@ -318,17 +334,65 @@
   return (vobj->Get_Action()->Is_Acting());
 }
 
+// -------------------------------------------------------------------------------------------------
+
+dp88_customAI::TargetType dp88_customAI::GetTargetType(GameObject* target)
+{
+  if (target->As_BuildingGameObj() || Is_Script_Attached(target, "dp88_AI_Marker_Building"))
+    return BUILDING;
+  if (Is_Script_Attached(target, "JFW_Deployable_Aircraft_Deployed"))
+    return LIGHT_VEHICLE;
+  if ((Get_Vehicle_Mode(target) == VEHICLE_TYPE_FLYING) || (target->As_SoldierGameObj() && (Get_Fly_Mode(target) || Find_Script_On_Object(target, "MS_Paradrop") || Find_Script_On_Object(target, "dp88_AR_Paradrop"))))
+    return FLYING;
+  if (target->As_SoldierGameObj())
+    return SOLDIER;
+  if (target->As_VehicleGameObj() && (Is_Script_Attached(target, "dp88_AI_Marker_HeavyVehicle") || Is_Script_Attached ( target, "dp88_AI_heavyVehicleMarker")))
+    return HEAVY_VEHICLE;
+  if (Is_Script_Attached(target, "dp88_AI_Marker_Repairable"))
+    return REPAIRABLE;
+  if (target->As_VehicleGameObj())
+    return LIGHT_VEHICLE;
+
+  return UNKNOWN;
+}
+
+
 
+/*------------------------
+Deprecated heavy vehicle marker script
+--------------------------*/
+void dp88_AI_heavyVehicleMarker::Created(GameObject* obj)
+{
+	Console_Output("[%d:%hs:%hs] This script is deprecated, use dp88_AI_Marker_HeavyVehicle instead\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+	Attach_Script_V(obj,"dp88_AI_Marker_HeavyVehicle","0");
+	Destroy_Script();
+}
 
+/*------------------------
+Repairable marker script
+--------------------------*/
 
+void dp88_AI_Marker_Repairable::Created(GameObject* obj)
+{
+    pathfindDistance = Get_Float_Parameter("Pathfind_Distance");
+}
 
+// -------------------------------------------------------------------------------------------------
 
+float dp88_AI_Marker_Repairable::Get_Distance_From_Pathfind()
+{
+    return pathfindDistance;
+}
 
 
 /*------------------------
 Unit AI base class
 --------------------------*/
 
+DynamicVectorClass<dp88_AI_Unit*> dp88_AI_Unit::ListOfAIUnits;
+
+// -------------------------------------------------------------------------------------------------
+
 void dp88_AI_Unit::Created(GameObject* obj)
 {
   // Base class should not be instantiated
@@ -345,6 +409,23 @@
   m_bMovingToTarget = false;
   m_bMovingToObjective = false;
   m_pCurrentObjective = NULL;
+  ListOfAIUnits.Add(this);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void dp88_AI_Unit::Detach(GameObject* obj)
+{
+    ListOfAIUnits.DeleteObj(this);
+    dp88_customAI::Detach(obj);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void dp88_AI_Unit::Force_Clear_Current_Objective()
+{
+    m_pCurrentObjective = nullptr;
+    m_bMovingToObjective = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -354,16 +435,14 @@
   // Check target is still alive, still an enemy (eg: stolen vehicles) and in range
   if (TIMER_AI_THINK == number)
   {
-    GameObject* target = (NULL != targetID) ? Commands->Find_Object(targetID) : NULL;
-
     // Check the current objective, if any, is still valid
     if (NULL != m_pCurrentObjective && !dp88_AI_Objective::IsValidObjective(m_pCurrentObjective))
     {
       m_pCurrentObjective = NULL;
       if (m_bMovingToObjective)
       {
-        if (NULL != target && IsValidTarget(obj, target))
-          AttackTarget(obj, target);
+        if (m_target && IsValidTarget(obj, m_target))
+          AttackTarget(obj, m_target);
         else
           ResetAllActions(obj);
       }
@@ -373,23 +452,24 @@
     if (NULL == m_pCurrentObjective)
     {
       m_pCurrentObjective = ChooseNewObjective(obj);
-      if (NULL != m_pCurrentObjective && (NULL == target || !ShouldPursueTarget(obj, target)))
+      if (NULL != m_pCurrentObjective && (!m_target || !ShouldPursueTarget(obj, m_target)))
         GoToObjective(obj);
     }
 
     // If we have a target check it is still valid
-    if (NULL != targetID)
+    if (m_target)
     {
-      if (NULL == target || !IsValidTarget(obj, target) || (int)time(NULL) - targetLastSeen > 3)
+      if (!IsValidTarget(obj, m_target) || (int)time(NULL) - targetLastSeen > 3)
       {
-        targetID = NULL;
+        m_target = nullptr;
+		hasTarget = false;
         ResetAllActions(obj);
       }
 
       // Check if we need to move closer to our target
-      else if (!m_bMovingToTarget && ShouldPursueTarget(obj, target) && getDistance(obj, target) > GetPreferredAttackRange(obj, target))
+      else if (!m_bMovingToTarget && ShouldPursueTarget(obj, m_target) && getDistance(obj, m_target) > GetPreferredAttackRange(obj, m_target))
       {
-        AttackTarget(obj, target);    // Will modify the action with movement parameters
+        AttackTarget(obj, m_target);    // Will modify the action with movement parameters
       }
     }
 
@@ -409,7 +489,10 @@
   ResetAllActions(obj);
 
   if (MOVEMENT_COMPLETE_ARRIVED == reason && action_id == ACTION_ID_ATTACK_TARGET)
-    AttackTarget(obj, Commands->Find_Object(targetID));
+  {
+    if (m_target)
+      AttackTarget(obj, m_target);
+  }
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -431,7 +514,7 @@
 
 // -------------------------------------------------------------------------------------------------
 
-void dp88_AI_Unit::GoToObjective(GameObject *obj)
+void dp88_AI_Unit::GoToObjective(GameObject *obj, float speed)
 {
   if (NULL == m_pCurrentObjective)
     return;
@@ -439,7 +522,7 @@
   ActionParamsStruct params;
   bool bModifyAction = false;
 
-  if (NULL == targetID || !obj->As_SmartGameObj()->Get_Action()->Is_Acting())
+  if (!m_target || !obj->As_SmartGameObj()->Get_Action()->Is_Acting())
   {
     ResetAllActions(obj);
     params.Set_Basic(this, 80, ACTION_ID_MOVE_TO_OBJECTIVE);
@@ -453,9 +536,10 @@
 
   // Setup the movement parameters
   int arrivalRange = m_pCurrentObjective->GetRange();
-  m_bMovingToObjective = arrivalRange < getDistance(obj, m_pCurrentObjective->GetGameObject());
+  // Fix arrival range by letting it 1 meter further than wanted, because soldiers keep calling action_complete when 1.58m away than 1.0 wanted.
+  m_bMovingToObjective = (arrivalRange + 1) < getDistance(obj, m_pCurrentObjective->GetGameObject());
   if (m_bMovingToObjective)
-    params.Set_Movement(m_pCurrentObjective->GetGameObject(), 1.0f, (float)arrivalRange);
+    params.Set_Movement(m_pCurrentObjective->GetGameObject(), speed, (float)arrivalRange);
   else
   {
     // Already in range of the objective
@@ -463,6 +547,9 @@
     return;
   }
 
+  if (GetTargetType(obj) == FLYING)
+    params.MovePathfind = false;
+
   // Commence the action
   if (bModifyAction)
     Commands->Modify_Action(obj, params.ActionID, params, true, false);
@@ -491,13 +578,14 @@
   }
 
   // Setup parameters to track state
-  targetID = Commands->Get_ID(target);
+  m_target = target;
+  hasTarget = true;
   m_bTargetPrimaryFire = getPrimary(target);
   targetPriority = getPriority(obj, target);
   targetLastSeen = (int)time(NULL);
 
   // Setup the attack parameters
-  params.Set_Attack(target, (float)((m_bTargetPrimaryFire)?primary_maxRange:secondary_maxRange), 0.0, true);
+  params.Set_Attack(target, (float)((m_bTargetPrimaryFire)?primary_maxRange:secondary_maxRange), 0.0, m_bTargetPrimaryFire);
   params.AttackCheckBlocked = false;
 
   // Setup the movement parameters if we want to persue the target
@@ -569,16 +657,20 @@
   //fprintf( debugFile, "Seen enemy %s\n", Commands->Get_Preset_Name(enemy) );
 
   // If this is our current target update it's last seen time and priority
-  if (Commands->Get_ID(enemy) == targetID)
+  if (enemy == m_target)
   {
     targetLastSeen = (int)time(NULL);
-    targetPriority = getPriority(obj, targetID);
+    targetPriority = getPriority(obj, m_target);
     return;
   }
 
+  // Update current target priority (needed due to linked hp objects)
+  if (m_target)
+    targetPriority = getPriority(obj, m_target);
+
   // Get priority for seen object and return if 0 or lower than current target
   float seenPriority = getPriority( obj, enemy );
-  if (seenPriority == 0.0 || (targetID!=0 && seenPriority <= targetPriority))
+  if (seenPriority == 0.0 || (m_target && seenPriority <= targetPriority))
     return;
 
   //fprintf( debugFile, "Higher priority than existing target... attacking!\n" );
@@ -588,6 +680,9 @@
     return;
 
   // All good, attack the enemy
+  m_target = enemy;
+  hasTarget = true;
+  targetLastSeen = (int)time(NULL);
   AttackTarget(obj, enemy);
 }
 
@@ -608,8 +703,8 @@
   int maxRange = attackPrimary ? primary_maxRange : secondary_maxRange;
 
   return 0.0f < Commands->Get_Health(target)
-        && 2 != Commands->Get_Player_Type(target)
-        && Commands->Get_Player_Type(target) != Commands->Get_Player_Type(obj)
+        && SCRIPT_PLAYERTYPE_NEUTRAL != Get_Object_Type(target)
+        && (Get_Object_Type(target) != Get_Object_Type(obj) || GetTargetType(target) == REPAIRABLE)
         && targetDistance >= minRange
         && (ShouldPursueTarget(obj, target) || targetDistance <= maxRange);
 }
@@ -618,9 +713,10 @@
 
 dp88_AI_Objective* dp88_AI_Tank_Offensive::ChooseNewObjective(GameObject* obj)
 {
-  dp88_AI_Objective* pObjective = dp88_AI_Objective::GetBestObjective(obj, dp88_AI_Objective::TYPE_OFFENSIVE, modifier_distance);
+  DynamicVectorClass<int> ignoredObjectives;
+  dp88_AI_Objective* pObjective = dp88_AI_Objective::GetBestObjective(obj, dp88_AI_Objective::TYPE_OFFENSIVE, modifier_distance, ignoredObjectives);
   if (NULL == pObjective)
-    pObjective = dp88_AI_Objective::GetBestObjective(obj, dp88_AI_Objective::TYPE_DEFENSIVE, modifier_distance);
+    pObjective = dp88_AI_Objective::GetBestObjective(obj, dp88_AI_Objective::TYPE_DEFENSIVE, modifier_distance, ignoredObjectives);
 
   return pObjective;
 }
@@ -688,7 +784,7 @@
   if ( debug ) fprintf( debugFile, "Seen an enemy: %d (%s)\n", Commands->Get_ID(enemy), Commands->Get_Preset_Name(enemy) );
 
 	// If this is our current target update last seen time and priority
-	if ( Commands->Get_ID ( enemy ) == targetID )
+	if ( enemy == m_target )
 	{
 		if ( debug ) fprintf( debugFile, "Enemy is current target, last seen time and priority updated\n" );
 		targetLastSeen = (int)time(NULL);
@@ -724,12 +820,13 @@
 
 
 	// If this is a higher priority than our current target attack it
-	if ( targetID == 0 || enemyPriority > targetPriority )
+	if ( !m_target || enemyPriority > targetPriority )
 	{
-		if ( debug && targetID == 0 ) fprintf ( debugFile, "No current target to compare with, attacking enemy!\n" );
+		if ( debug && !m_target ) fprintf ( debugFile, "No current target to compare with, attacking enemy!\n" );
 		else if ( debug ) fprintf ( debugFile, "New enemy has a higher priority than current target, attacking enemy\n" );
 
-		targetID = Commands->Get_ID ( enemy );
+		m_target = enemy;
+		hasTarget = true;
 		targetLastSeen = (int)time(NULL);
 		targetPriority = enemyPriority;
 		m_bTargetPrimaryFire = attackPrimary;
@@ -768,18 +865,18 @@
 	// Check target is still alive, still an enemy (eg: stolen vehicles) and in range
 	if ( number == TIMER_AI_THINK )
 	{
-		if ( targetID != 0 )
+		if ( hasTarget )
 		{
-			GameObject *target = Commands->Find_Object ( targetID );
-			if (!target || !checkPowerState(obj)
-				|| ( Commands->Get_Health ( target ) + Commands->Get_Shield_Strength ( target ) <= 0 )
-				|| !checkTeam ( obj, target )
-				|| !checkRange ( obj, target, m_bTargetPrimaryFire )
+			if (!m_target || !checkPowerState(obj)
+				|| ( Commands->Get_Health ( m_target ) + Commands->Get_Shield_Strength ( m_target ) <= 0 )
+				|| !checkTeam ( obj, m_target )
+				|| !checkRange ( obj, m_target, m_bTargetPrimaryFire )
 				|| (int)time(NULL) - targetLastSeen > 3
-				|| ( target->As_VehicleGameObj() && !IsVehicleAIEnabled(target->As_VehicleGameObj()) && IsVehicleEmpty(target->As_VehicleGameObj()) ))
+				|| ( m_target->As_VehicleGameObj() && !IsVehicleAIEnabled(m_target->As_VehicleGameObj()) && IsVehicleEmpty(m_target->As_VehicleGameObj()) ))
 			{
-				if ( debug ) fprintf ( debugFile, "Target %d no longer valid, ceasing attack.\n", targetID);
-				targetID = 0;
+				if ( debug ) fprintf ( debugFile, "Target %d no longer valid, ceasing attack.\n", m_target->Get_ID());
+				m_target = nullptr;
+				hasTarget = false;
 				targetPriority = 0.0f;
 				targetLastSeen = 0;
 				stopAttacking(obj);
@@ -787,15 +884,15 @@
 
 			// 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())
+			else if ( splashInfantry && m_target->As_SoldierGameObj())
 			{
-				if(obj->As_SmartGameObj()->Is_Splash_Possible(target->As_PhysicalGameObj()))
+				if(obj->As_SmartGameObj()->Is_Splash_Possible(m_target->As_PhysicalGameObj()))
 				{
-					attackLocation ( obj, Commands->Get_Position(target), m_bTargetPrimaryFire );
+					attackLocation ( obj, Commands->Get_Position(m_target), m_bTargetPrimaryFire );
 				}
 				else
 				{
-					attackLocation ( obj, target->As_SoldierGameObj()->Get_Bullseye_Position(), m_bTargetPrimaryFire );
+					attackLocation ( obj, m_target->As_SoldierGameObj()->Get_Bullseye_Position(), m_bTargetPrimaryFire );
 				}
 			}
 		}
@@ -969,11 +1066,11 @@
   if ( number == TIMER_AI_THINK && m_deploymentState == STATE_DEPLOYED )
   {
     // If we have no target and have reached the undeploy timeout then trigger undeployment
-    if ( targetID == 0 && time(NULL) > m_undeployTime )
+    if ( !m_target && time(NULL) > m_undeployTime )
       Undeploy(pSelf);
     
     // Else, if we have an active target, update the undeployment timeout time
-    else if ( targetID != 0 )
+    else if ( m_target )
       m_undeployTime = time(NULL)+Get_Int_Parameter("Deploy_Timeout");
   }
 
@@ -1008,7 +1105,8 @@
 
     // Reset targetting parameters, otherwise the next Enemy_Seen call will think we are already attacking
     // an enemy and not trigger the appropriate actions we are depending upon
-    targetID = 0;
+    m_target = nullptr;
+	hasTarget = false;
   }
   else if ( m_deploymentState == STATE_UNDEPLOYING )
     m_deploymentState = STATE_UNDEPLOYED;
@@ -1185,7 +1283,7 @@
       
       // todo: set bpreloading as appropriate
 
-      if (NULL != targetID)
+      if (m_target)
         StartCharging(pSelf);
       else
         ApplyIdleAnimation(pSelf);
@@ -1193,7 +1291,7 @@
 
     // Has our target already died or ran like a coward before we could empty our clip? If so then
     // reset and trigger a reload (so we don't have a partial charge next time round)
-    else if (NULL == targetID)
+    else if (!m_target)
     {
       m_bIsDischarging = false;
       Set_Current_Bullets(pSelf, 0);
@@ -1208,7 +1306,7 @@
   if (number == TIMER_AI_CHARGE_PRERELOAD_COMPLETE && m_bIsPreReloading)
   {
     m_bIsPreReloading = false;
-    if (NULL != targetID)
+    if (m_target)
       StartCharging(pSelf);
   }
 
@@ -1289,7 +1387,7 @@
 {
   // If we are not charging, not discharging, powered and have a target then we can start a new
   // charging cycle
-  if (!m_bIsPreReloading && !m_bIsCharging && !m_bIsDischarging && checkPowerState(pSelf) && targetID != NULL)
+  if (!m_bIsPreReloading && !m_bIsCharging && !m_bIsDischarging && checkPowerState(pSelf) && m_target)
   {
     m_bIsCharging = true;
 
@@ -1315,18 +1413,15 @@
     m_bIsCharging = false;
   
     // Got an enemy? Also double check power state whilst we are here, just to be sure
-    if (targetID != NULL && checkPowerState(pSelf))
+    if (m_target && checkPowerState(pSelf))
     {
-      if (GameObject* pTarget = Commands->Find_Object(targetID))
-      {
         m_bIsDischarging = true;
 
         // Call into the base class and let it do it's thing
-        if (splashInfantry && pTarget->As_SoldierGameObj())
-          dp88_AI_Turret::attackLocation(pSelf, Commands->Get_Position(pTarget), m_bTargetPrimaryFire);
+        if (splashInfantry && m_target->As_SoldierGameObj())
+          dp88_AI_Turret::attackLocation(pSelf, Commands->Get_Position(m_target), m_bTargetPrimaryFire);
         else
-          dp88_AI_Turret::attackTarget(pSelf, pTarget, m_bTargetPrimaryFire);
-      }
+          dp88_AI_Turret::attackTarget(pSelf, m_target, m_bTargetPrimaryFire);
     }
 
     // If we didn't find anything to shoot at then apply the idle animation again
@@ -1429,13 +1524,17 @@
 void dp88_AI_Objective::Created ( GameObject* obj )
 {
   // Register objective
-  m_objID = Commands->Get_ID(obj);
+  //m_objID = Commands->Get_ID(obj);
   Objectives.Add(this);
 
   // Store data
-  m_type = (unsigned char)Get_Int_Parameter("Type");
+  m_type = (unsigned int)Get_Int_Parameter("Type");
   m_range = Get_Int_Parameter("Range");
   m_team = Get_Int_Parameter("Team");
+  m_debugTag = Get_Parameter("Debug_Tag");
+  if (strlen(m_debugTag) == 0) {
+    m_debugTag = "No Tag";
+  }
 
   m_priority[UNITTYPE_SOLDIER] = Get_Int_Parameter("Priority_Soldier");
   m_priority[UNITTYPE_LVEHICLE] = Get_Int_Parameter("Priority_Light_Vehicle");
@@ -1451,13 +1550,24 @@
   // De-register objective
   if (Exe != EXE_LEVELEDIT)
     Objectives.DeleteObj(this);
+
+  // delete references to this objective from AI units so they don't try to access our memory after it's freed
+  for (int i = 0; i < dp88_AI_Unit::ListOfAIUnits.Count(); i++)
+  {
+    dp88_AI_Unit *unit = dp88_AI_Unit::ListOfAIUnits[i];
+    if (unit->Get_Current_Objective() == this)
+      unit->Force_Clear_Current_Objective();
+  }
 }
 
 // -------------------------------------------------------------------------------------------------
 
 GameObject* dp88_AI_Objective::GetGameObject()
 {
-  return Commands->Find_Object(m_objID);
+  // NOTE: We no longer need to store object IDs since the objective script itself is destroyed when its owner is destroyed anyways.
+  //       The objective is removed from all AI units when destroyed (see Detach()), so we don't need to do lookups at all.
+  return Owner();
+  // return Commands->Find_Object(m_objID);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -1472,7 +1582,7 @@
 
   if (obj->As_VehicleGameObj())
   {
-    if (Is_Script_Attached(obj, "dp88_AI_heavyVehicleMarker"))
+    if (Is_Script_Attached(obj, "dp88_AI_heavyVehicleMarker") || Is_Script_Attached(obj, "dp88_AI_Marker_HeavyVehicle"))
       return UNITTYPE_HVEHICLE;
     return UNITTYPE_LVEHICLE;
   }
@@ -1500,22 +1610,25 @@
 
 // -------------------------------------------------------------------------------------------------
 
-dp88_AI_Objective* dp88_AI_Objective::GetBestObjective(GameObject* obj, unsigned char objective_type, float distance_modifier)
+dp88_AI_Objective* dp88_AI_Objective::GetBestObjective(GameObject* obj, unsigned int objective_type, float distance_modifier, DynamicVectorClass<int> ignoredObjectives)
 {
   dp88_AI_Objective* result = NULL;
   float top_priority = 0.0f;
+  float best_distance = FLT_MAX;
 
   int team = Get_Object_Type(obj);
   for ( int i = 0; i < Objectives.Count(); ++i )
   {
     int objTeam = Objectives[i]->GetTeam();
-    if (objective_type != Objectives[i]->GetType() || (objTeam != 2 && team != objTeam))
+    if (objective_type != Objectives[i]->GetType() || (objTeam != 2 && team != objTeam) || (ignoredObjectives.Length() && ignoredObjectives.ID(Objectives[i]->GetGameObject()->Get_ID()) != -1))
       continue;
 
     float priority = Objectives[i]->GetPriority(obj, distance_modifier);
-    if( priority > top_priority )
+    float distance = Vector3::Distance_Squared(Commands->Get_Position(obj), Commands->Get_Position(Objectives[i]->GetGameObject()));
+    if( priority > top_priority || (priority > 0 && priority == top_priority && distance < best_distance) )
     {
       top_priority = priority;
+      best_distance = distance;
       result = Objectives[i];
     }
   }
@@ -1525,6 +1638,40 @@
 
 // -------------------------------------------------------------------------------------------------
 
+int dp88_AI_Objective::CountObjectives(int team, unsigned int objective_type)
+{
+  int count = 0;
+
+  for (int i = 0; i < Objectives.Count(); ++i)
+  {
+    int objTeam = Objectives[i]->GetTeam();
+    if (objective_type != Objectives[i]->GetType() || (objTeam != 2 && team != objTeam))
+      continue;
+    count++;
+  }
+
+  return count;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+int dp88_AI_Objective::CountUnitObjectives(int team, unsigned int objective_type, GameObject* obj, float distance_modifier)
+{
+  int count = 0;
+
+  for (int i = 0; i < Objectives.Count(); ++i)
+  {
+    int objTeam = Objectives[i]->GetTeam();
+    if (objective_type != Objectives[i]->GetType() || (objTeam != 2 && team != objTeam) || Objectives[i]->GetPriority(obj, distance_modifier) == 0)
+      continue;
+    count++;
+  }
+
+  return count;
+}
+
+// -------------------------------------------------------------------------------------------------
+
 bool dp88_AI_Objective::IsValidObjective(dp88_AI_Objective* pObjective)
 {
   for ( int i = 0; i < Objectives.Count(); ++i )
@@ -1546,7 +1693,8 @@
   "Priority_Soldier=1:int,"
   "Priority_Light_Vehicle=1:int,"
   "Priority_Heavy_Vehicle=1:int,"
-  "Priority_Aircraft=1:int");
+  "Priority_Aircraft=1:int,"
+  "Debug_Tag:string");
 
 
 
@@ -1557,9 +1705,18 @@
 //  Script Registrants
 // -------------------------------------------------------------------------------------------------
 
-// Heavy vehicle marker (dummy script)
+// Heavy vehicle marker (dummy script, deprecated	)
 ScriptRegistrant<dp88_AI_heavyVehicleMarker> dp88_AI_heavyVehicleMarker_Registrant("dp88_AI_heavyVehicleMarker","");
 
+// Building marker (dummy script)
+ScriptRegistrant<dp88_AI_Marker_Building> dp88_AI_Marker_Building_Registrant("dp88_AI_Marker_Building","");
+
+// Heavy vehicle marker (dummy script)
+ScriptRegistrant<dp88_AI_Marker_HeavyVehicle> dp88_AI_Marker_HeavyVehicle_Registrant("dp88_AI_Marker_HeavyVehicle","");
+
+// Repair target marker (dummy script)
+ScriptRegistrant<dp88_AI_Marker_Repairable> dp88_AI_Marker_Repairable_Registrant("dp88_AI_Marker_Repairable","Pathfind_Distance=5.0:float");
+
 ScriptRegistrant<dp88_AI_Turret> dp88_AI_Turret_Registrant("dp88_AI_Turret","Priority_Infantry=1.0:float,Weapon_Infantry=1:int,Splash_Infantry=0:int,Priority_Light_Vehicle=5.0:float,Weapon_Light_Vehicle=1:int,Priority_Heavy_Vehicle=7.0:float,Weapon_Heavy_Vehicle=1:int,Priority_VTOL=5.0:float,Weapon_VTOL=1:int,Min_Attack_Range=0:int,Max_Attack_Range=150:int,Min_Attack_Range_Secondary=0:int,Max_Attack_Range_Secondary=150:int,Modifier_Distance=0.25:float,Modifier_Target_Damage=0.1:float,Modifier_Target_Value=0.05:float,Requires_Power=0:int,Debug=0:int,Detects_Stealth=1:int");
 ScriptRegistrant<dp88_AI_PopupTurret> dp88_AI_PopupTurret_Registrant("dp88_AI_PopupTurret","Priority_Infantry=1.0:float,Weapon_Infantry=1:int,Splash_Infantry=0:int,Priority_Light_Vehicle=5.0:float,Weapon_Light_Vehicle=1:int,Priority_Heavy_Vehicle=7.0:float,Weapon_Heavy_Vehicle=1:int,Priority_VTOL=5.0:float,Weapon_VTOL=1:int,Min_Attack_Range=0:int,Max_Attack_Range=150:int,Min_Attack_Range_Secondary=0:int,Max_Attack_Range_Secondary=150:int,Deploy_Animation:string,Deploy_Animation_Frames:int,Deploy_Sound:string,Deploy_Timeout:int,Spotter_Preset:string,Modifier_Distance=0.25:float,Modifier_Target_Damage=0.1:float,Modifier_Target_Value=0.05:float,Requires_Power=0:int,Debug=0:int,Detects_Stealth=1:int");
 ScriptRegistrant<dp88_AI_PopupTurret_Spotter> dp88_AI_PopupTurret_Spotter_Registrant("dp88_AI_PopupTurret_Spotter","tId:int");
diff -urN sourceold/scripts/dp88_customAI.h source/scripts/dp88_customAI.h
--- sourceold/scripts/dp88_customAI.h	2018-11-17 10:23:57.146600000 +1000
+++ source/scripts/dp88_customAI.h	2020-09-10 09:08:27.952893600 +1000
@@ -13,8 +13,24 @@
 
 #include "LoopedAnimationController.h"
 #include "ObserverImpClass.h"
+#include "ReferencerClass.h"
+
+class ActionParamsStruct;
+
+class Pathfind_Help_Implementer
+{
+public:
+	virtual ~Pathfind_Help_Implementer() {};
+	virtual bool Should_Ignore_Blocked_Checking(GameObject *obj) = 0;
+	virtual void Prepare_Getting_Around(GameObject *obj) = 0;
+	virtual void Stop_Getting_Around(GameObject *obj) = 0;
+	virtual void On_Unable_To_Get_Unstuck(GameObject *obj) = 0;
+	// Modify action params during getting unstuck to include target shooting, if any exist
+	virtual ActionParamsStruct Get_Param_Attack_Modifications(ActionParamsStruct params) = 0;
+};
 
 // Forward declaration
+class VehicleGameObj;
 class dp88_AI_Objective;
 class dp88_AI_ChargedTurret_AnimationObserver;
 
@@ -72,11 +88,16 @@
   int secondary_minRange, secondary_maxRange;
 
   // Current target state
-  int targetID;
+  //int targetID;
+  ReferencerClass m_target;
+  bool hasTarget;
   int targetLastSeen;
   float targetPriority;
   bool m_bTargetPrimaryFire;
 
+  // Brain speed
+  float thinkTime;
+
   // Other settings
   bool m_bAiEnabled;
   bool m_bCanDetectStealth;
@@ -85,6 +106,16 @@
   bool debug;
   FILE* debugFile;
 
+  // Target types
+  enum TargetType {
+	  SOLDIER = 0,
+	  LIGHT_VEHICLE,
+	  HEAVY_VEHICLE,
+	  FLYING,
+	  BUILDING,
+	  REPAIRABLE,
+	  UNKNOWN
+  };
 
 
 
@@ -126,11 +157,40 @@
   /* Utility functions for both priority calculations and AI scripts to utilise */
   virtual bool IsVehicleEmpty( VehicleGameObj* vobj );
   virtual bool IsVehicleAIEnabled( VehicleGameObj* vobj );
+
+  // "Global" decider about what a unit type is
+  static TargetType GetTargetType(GameObject* obj);
+};
+
+// -------------------------------------------------------------------------------------------------
+
+/*!
+* \brief danpaul88's Custom AI Heavy Vehicle Marker
+* \author Daniel Paul (danpaul88@yahoo.co.uk)
+*
+* This is a dummy script with no functionality which can be attached to an object to indicate all of
+* my custom AI scripts should treat this as a heavy vehicle. Any vehicle without this script will be
+* considered to be a light vehicle.
+*/
+class dp88_AI_heavyVehicleMarker : public ScriptImpClass
+{
+	void Created(GameObject* obj);
 };
 
 // -------------------------------------------------------------------------------------------------
 
 /*!
+* \brief danpaul88's Custom AI Building Marker
+* \author Daniel Paul (danpaul88@yahoo.co.uk)
+*
+* This is a dummy script with no functionality which can be attached to an object to indicate all of
+* my custom AI scripts should treat this as a building.
+*/
+class dp88_AI_Marker_Building : public ScriptImpClass {};
+
+// -------------------------------------------------------------------------------------------------
+
+/*!
 * \brief danpaul88's Custom AI Heavy Vehicle Marker
 * \author Daniel Paul (danpaul88@yahoo.co.uk)
 *
@@ -138,7 +198,25 @@
 * my custom AI scripts should treat this as a heavy vehicle. Any vehicle without this script will be
 * considered to be a light vehicle.
 */
-class dp88_AI_heavyVehicleMarker : public ScriptImpClass{};
+class dp88_AI_Marker_HeavyVehicle : public ScriptImpClass {};
+
+// -------------------------------------------------------------------------------------------------
+
+/*!
+* \brief danpaul88's Custom AI Repair Target Marker
+* \author Daniel Paul (danpaul88@yahoo.co.uk)
+*
+* This is a dummy script with no functionality which can be attached to an object to indicate all of
+* my custom AI scripts should treat this as a repair target.
+*/
+class dp88_AI_Marker_Repairable : public ScriptImpClass
+{
+public:
+    void Created(GameObject* obj);
+    float Get_Distance_From_Pathfind();
+protected:
+    float pathfindDistance;
+};
 
 // -------------------------------------------------------------------------------------------------
 
@@ -154,6 +232,7 @@
 public:
   // Game Events
   void Created(GameObject *obj);
+  void Detach(GameObject *obj);
   void Timer_Expired(GameObject *obj, int number);
   void Action_Complete(GameObject *obj, int action_id, ActionCompleteReason reason);
 
@@ -161,6 +240,12 @@
   virtual void Init(GameObject *obj);
   virtual void loadSettings(GameObject *obj, bool loadSecondaryFireSettings, bool loadBuildingTargetSettings);
 
+  virtual void Force_Clear_Current_Objective();
+  dp88_AI_Objective* Get_Current_Objective() { return m_pCurrentObjective; }
+
+  // used in combination with Force_Clear_Current_Objective() to invalidate references to objectives when they are deleted (prevents use-after-free bugs)
+  static DynamicVectorClass<dp88_AI_Unit*> ListOfAIUnits;
+
 protected:
   static const int ACTION_ID_MOVE_TO_OBJECTIVE  = 7850001;
   static const int ACTION_ID_ATTACK_TARGET      = 7850002;
@@ -169,7 +254,7 @@
   void ResetAllActions(GameObject* obj);
 
   // Go to the location of the current objective
-  virtual void GoToObjective(GameObject *obj);
+  virtual void GoToObjective(GameObject *obj, float speed = 1.0f);
 
   // Attack the specified target
   virtual void AttackTarget(GameObject *obj, GameObject *target);
@@ -746,9 +831,10 @@
 
   GameObject* GetGameObject();
 
-  unsigned char GetType()   { return m_type; }
+  unsigned int GetType()    { return m_type; }
   int GetTeam()             { return m_team; }
   int GetRange()            { return m_range; }
+  const char* GetDebugTag() { return m_debugTag; }
 
   /*! Get the priority of this objective for the specified AI unit */
   float GetPriority(GameObject* obj, float distance_modifier);
@@ -757,16 +843,26 @@
   * Finds the most suitable objective of a given type for the specified unit, based on the distance
   * to the objective and their distance modifier
   */
-  static dp88_AI_Objective* GetBestObjective ( GameObject* obj, unsigned char objective_type, float distance_modifier );
+  static dp88_AI_Objective* GetBestObjective ( GameObject* obj, unsigned int objective_type, float distance_modifier, DynamicVectorClass<int> ignoredObjectives );
+
+  /*!
+  * Counts the existing objectives of a given type for the specified team
+  */
+  static int CountObjectives( int team, unsigned int objective_type );
+
+  /*!
+  * Counts the existing valid objectives of a given type for the specified unit
+  */
+  static int CountUnitObjectives( int team, unsigned int objective_type, GameObject* obj, float distance_modifier );
 
   /*! Checks if the specified objective is still valid */
   static bool IsValidObjective ( dp88_AI_Objective* pObjective );
 
   /*! \name Objective types */
   /*! @{ */
-  const static unsigned char TYPE_OFFENSIVE = 1;
-  const static unsigned char TYPE_DEFENSIVE = 2;
-  const static unsigned char TYPE_ENGINEERING = 3;
+  const static unsigned int TYPE_OFFENSIVE = 1;
+  const static unsigned int TYPE_DEFENSIVE = 2;
+  const static unsigned int TYPE_ENGINEERING = 3;
   /*! @} */
 
 protected:
@@ -783,14 +879,15 @@
   /*! Get the unit type of the specified unit */
   unsigned char GetUnitType(GameObject* obj);
 
-  int m_objID;;
+  //int m_objID;;
 
   /*! \name Cached Script Parameters */
   /*! @{ */
-  unsigned char m_type;
+  unsigned int m_type;
   int m_priority[UNITTYPE_MAX + 1];
   int m_range;
   int m_team;
+  const char* m_debugTag;
   /*! @} */
 
   static DynamicVectorClass<dp88_AI_Objective *> Objectives;
diff -urN sourceold/scripts/dp88_custom_timer_defines.h source/scripts/dp88_custom_timer_defines.h
--- sourceold/scripts/dp88_custom_timer_defines.h	2018-11-17 10:23:57.147600000 +1000
+++ source/scripts/dp88_custom_timer_defines.h	2020-09-10 09:08:27.952893600 +1000
@@ -46,6 +46,7 @@
 #define CUSTOM_ROCKETEER_VEHICLEKILLED            (DP88_CUSTOM|MISC|0x03)             //!< Used by dp88_AR_Rocketeer to notify itself when the flight vehicle dies (via JFW_Death_Send_Custom)
 #define CUSTOM_VEHICLE_DEPLOY                     (DP88_CUSTOM|MISC|0x04)             //!< Used by deployable vehicle scripts to notify other scripts of a change in the deployment state, 0 = undeployed, 1 = deploying, 2 = deployed, 3 = undeploying
 #define CUSTOM_RADAR_JAM                          (DP88_CUSTOM|MISC|0x05)             //!< Used by new Radar Jammer scripts to switch radar off and on and also play jamming sounds, 0 = radar off, 1 = radar on
+#define CUSTOM_DEPLOY_STATE_CHANGED               (DP88_CUSTOM|MISC|0x07)             //!< Used by deployable scripts to notify other scripts of a change in the deployment state, 0 = undeployed, 1 = deployed
 
 #define TIMER_MISC_TICK                           (DP88_TIMER|MISC|0x01)              //!< Miscellaneous tick timer for generic use by scripts that implement on-tick events
 #define TIMER_RANDOMWEATHER                       (DP88_TIMER|MISC|0x02)              //!< Used by dp88_randomWeather to do a weather update
@@ -136,6 +137,7 @@
 //#define CUSTOM_TURRETAI_ANIMATIONCOMPLETE         (DP88_CUSTOM|AI|0x02)               //!< Sent from dp88_AI_ChargeTurret_Animation to dp88_AI_ChargeTurret
 #define CUSTOM_AI_DISABLEAI                       (DP88_CUSTOM|AI|0x03)               //!< Custom message that can be sent to a turret AI to disable it
 #define CUSTOM_AI_ENABLEAI                        (DP88_CUSTOM|AI|0x04)               //!< Custom message that can be sent to a turret AI to enable it
+#define CUSTOM_AI_RESET_ACTIONS                   (DP88_CUSTOM|AI|0x05)               //!< Custom message that can be sent to an AI to reset its current actions
 
 #define TIMER_AI_THINK                            (DP88_TIMER|AI|0x01)                //!< Fires every second to process AI actions such as checking target status
 #define TIMER_AI_CHARGE_PRERELOAD_COMPLETE        (DP88_TIMER|AI|0x02)                //!< Used by dp88_AI_ChargedTurret to indicate when pre-reloading is complete
@@ -171,6 +173,10 @@
 #define CUSTOM_PRISMTOWER_STOP_CHARGING				1144060002
 
 
+// Custom for changing bot count on a map
+#define CUSTOM_AI_CHANGE_BOTCOUNT					1144062001
+#define CUSTOM_AI_CHANGE_BOTCOUNTTEAM				1144062002
+
 #define TIMER_CLEG_PHASEDEATH					10055222
 #define TIMER_CLEG_CHECKDROPOBJ					10055223
 #define TIMER_CLEG_CHECKRELEASETARGET			10055224
diff -urN sourceold/scripts/engine_common.h source/scripts/engine_common.h
--- sourceold/scripts/engine_common.h	2018-11-17 10:23:57.154600000 +1000
+++ source/scripts/engine_common.h	2020-04-02 12:02:44.438361500 +1000
@@ -15,7 +15,7 @@
 // (float) is merely there to make sure that it uses the float overloads to send the version over network.
 // and to make sure we do not forget the f suffix on the version number.
 #define TT_VERSION_MAGIC '!TT!'
-#define TT_VERSION ((float)4.6f)
+#define TT_VERSION ((float)4.7f)
 
 #define SAFE_DELETE_ARRAY(p)	{ delete[] p; p = NULL; }
 #define SAFE_DELETE(p)			{ delete p; p = NULL; }
diff -urN sourceold/scripts/engine_dmg.cpp source/scripts/engine_dmg.cpp
--- sourceold/scripts/engine_dmg.cpp	2018-11-17 10:23:57.156600000 +1000
+++ source/scripts/engine_dmg.cpp	2020-09-10 09:08:27.953899600 +1000
@@ -161,6 +161,20 @@
 	Commands->Set_Health(obj,health);
 }
 
+void SCRIPTS_API Set_Max_Health_Without_Healing(GameObject *obj,float health)
+{
+	if (!obj)
+	{
+		return;
+	}
+	DamageableGameObj *o = obj->As_DamageableGameObj();
+	if (!o)
+	{
+		return;
+	}
+	o->Get_Defense_Object()->Set_Health_Max(health);
+}
+
 void SCRIPTS_API Set_Max_Shield_Strength(GameObject *obj,float shieldstrength)
 {
 	if (!obj)
@@ -176,6 +190,20 @@
 	Commands->Set_Shield_Strength(obj,shieldstrength);
 }
 
+void SCRIPTS_API Set_Max_Shield_Strength_Without_Healing(GameObject *obj,float shieldstrength)
+{
+	if (!obj)
+	{
+		return;
+	}
+	DamageableGameObj *o = obj->As_DamageableGameObj();
+	if (!o)
+	{
+		return;
+	}
+	o->Get_Defense_Object()->Set_Shield_Strength_Max(shieldstrength);
+}
+
 const char SCRIPTS_API *Get_Shield_Type(GameObject *obj)
 {
 	if (!obj)
@@ -226,6 +254,8 @@
 	{
 		return;
 	}
+	Vector3 TestPosition = Position;
+	TestPosition.Z = 0;
 	SLNode<VehicleGameObj> *x = GameObjManager::VehicleGameObjList.Head();
 	while (x)
 	{
@@ -233,10 +263,9 @@
 		if (obj) 
 		{
 			Vector3 ObjPosition = Commands->Get_Position(obj);
-			Vector3 TestPosition = Position;
+
 			ObjPosition.Z = 0;
-			TestPosition.Z = 0;
-			if ((Commands->Get_Distance(ObjPosition,TestPosition) <= Distance) && (Commands->Get_ID(obj) != Commands->Get_ID(Host)))
+			if ((Vector3::Distance_Squared(ObjPosition, TestPosition) <= Distance * Distance) && (Commands->Get_ID(obj) != Commands->Get_ID(Host)))
 			{
 				Commands->Apply_Damage(obj,Damage,Warhead,Damager);
 			}
@@ -251,6 +280,8 @@
 	{
 		return;
 	}
+	Vector3 TestPosition = Position;
+	TestPosition.Z = 0;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x) 
 	{
@@ -263,10 +294,8 @@
 		if (o2)
 		{
 			Vector3 ObjPosition = Commands->Get_Position(o2);
-			Vector3 TestPosition = Position;
 			ObjPosition.Z = 0;
-			TestPosition.Z = 0;
-			if ((Commands->Get_Distance(ObjPosition,TestPosition) <= Distance) && (Commands->Get_ID(o2) != Commands->Get_ID(Host)))
+			if ((Vector3::Distance_Squared(ObjPosition, TestPosition) <= Distance * Distance) && (Commands->Get_ID(o2) != Commands->Get_ID(Host)))
 			{
 				Commands->Apply_Damage(o2,Damage,Warhead,Damager);
 			}
@@ -289,7 +318,7 @@
 				{
 					Vector3 ObjPosition = Commands->Get_Position(o);
 					Vector3 TestPosition = Position;
-					if (Commands->Get_Distance(ObjPosition,TestPosition) <= Distance)
+					if (Vector3::Distance_Squared(ObjPosition, TestPosition) <= Distance * Distance)
 					{
 						Commands->Apply_Damage(o,Damage,Warhead,Damager);
 					}
@@ -316,9 +345,7 @@
 					Vector3 pos1 = Commands->Get_Position(o);
 					Vector3 pos2 = Location;
 
-					float Distance = Commands->Get_Distance(pos1,pos2);
-
-					if (Distance <= DamageRadius)
+					if (Vector3::Distance_Squared(pos1, pos2) <= DamageRadius * DamageRadius)
 					{
 						Commands->Apply_Damage(o,Damage,Warhead,Damager);
 					}
@@ -377,9 +404,7 @@
 					Vector3 pos1 = Commands->Get_Position(o);
 					Vector3 pos2 = Location;
 
-					float Distance = Commands->Get_Distance(pos1,pos2);
-
-					if (Distance <= DamageRadius)
+					if (Vector3::Distance_Squared(pos1, pos2) <= DamageRadius * DamageRadius)
 					{
 						float Max_Health = Commands->Get_Max_Health(o);
 						float Damage = Max_Health*Percentage;
@@ -409,8 +434,7 @@
 					{
 						Vector3 pos1 = Commands->Get_Position(o);
 						Vector3 pos2 = Location;
-						float Distance = Commands->Get_Distance(pos1,pos2);
-						if (Distance <= DamageRadius)
+						if (Vector3::Distance_Squared(pos1, pos2) <= DamageRadius * DamageRadius)
 						{
 							Commands->Apply_Damage(o,Damage,Warhead,Damager);
 						}
@@ -472,8 +496,7 @@
 					{
 						Vector3 pos1 = Commands->Get_Position(o);
 						Vector3 pos2 = Location;
-						float Distance = Commands->Get_Distance(pos1,pos2);
-						if (Distance <= DamageRadius)
+						if (Vector3::Distance_Squared(pos1, pos2) <= DamageRadius * DamageRadius)
 						{
 							float Max_Health = Commands->Get_Max_Health(o);
 							float Damage = Max_Health*Percentage;
@@ -517,8 +540,8 @@
 				{
 					Vector3 pos1 = Commands->Get_Position(o);
 					Vector3 pos2 = Location;
-					float Distance = Commands->Get_Distance(pos1,pos2);
-					if (Distance <= DamageRadius)
+					float Distance = Vector3::Distance_Squared(pos1, pos2);
+					if (Distance <= DamageRadius * DamageRadius)
 					{
 						float Max_Health = Commands->Get_Max_Health(o);
 						float Max_Shield = Commands->Get_Max_Shield_Strength(o);
@@ -576,8 +599,8 @@
 				{
 					Vector3 pos1 = Commands->Get_Position(o);
 					Vector3 pos2 = Location;
-					float Distance = Commands->Get_Distance(pos1,pos2);
-					if (Distance <= Radius)
+					float Distance = Vector3::Distance_Squared(pos1, pos2);
+					if (Distance <= Radius * Radius)
 					{
 						Commands->Set_Health(o,(amount+Health));
 					}
@@ -719,8 +742,8 @@
 				{
 					Vector3 pos1 = Commands->Get_Position(o);
 					Vector3 pos2 = Location;
-					float Distance = Commands->Get_Distance(pos1,pos2);
-					if (Distance <= DamageRadius)
+					float Distance = Vector3::Distance_Squared(pos1, pos2);
+					if (Distance <= DamageRadius * DamageRadius)
 					{
 						float Max_Health = Commands->Get_Max_Health(o);
 						float Max_Shield = Commands->Get_Max_Shield_Strength(o);
@@ -767,8 +790,8 @@
 					{
 						Vector3 pos1 = Commands->Get_Position(o);
 						Vector3 pos2 = Location;
-						float Distance = Commands->Get_Distance(pos1, pos2);
-						if (Distance <= DamageRadius)
+						float Distance = Vector3::Distance_Squared(pos1, pos2);
+						if (Distance <= DamageRadius * DamageRadius)
 						{
 							float Max_Health = Commands->Get_Max_Health(o);
 							float Max_Shield = Commands->Get_Max_Shield_Strength(o);
diff -urN sourceold/scripts/engine_dmg.h source/scripts/engine_dmg.h
--- sourceold/scripts/engine_dmg.h	2016-10-31 07:17:12.471013500 +1000
+++ source/scripts/engine_dmg.h	2020-03-30 20:42:37.433301100 +1000
@@ -15,7 +15,9 @@
 void SCRIPTS_API Repair_All_Buildings_By_Team(int Team,int ConstructionYardID,float Health); //repairs all buildings for the team except the passed in ConstructionYardID,0 = Nod,1 = GDI
 void SCRIPTS_API Repair_All_Static_Vehicles_By_Team(int Team,int type); //sends a custom to all vechicles of the team with the DecorationPhys physics type (i.e. all base defense vechicles etc),0 = Nod,1 = GDI,Message is the message to send. Use with JFW_Repair_On_Custom on the vechicles you want repaired to do the actual repairing
 void SCRIPTS_API Set_Max_Health(GameObject *obj,float health); //set the max health of obj
+void SCRIPTS_API Set_Max_Health_Without_Healing(GameObject *obj,float health); //set the max health of obj without setting the actual health to maximum
 void SCRIPTS_API Set_Max_Shield_Strength(GameObject *obj,float shieldstrength); //set the max shield strength of obj
+void SCRIPTS_API Set_Max_Shield_Strength_Without_Healing(GameObject *obj,float shieldstrength); //set the max shield strength of obj without setting the actual health to maximum
 const char SCRIPTS_API *Get_Shield_Type(GameObject *obj); //gets the shield type of obj
 const char SCRIPTS_API *Get_Skin(GameObject *obj); //gets the skin type of obj
 void SCRIPTS_API Set_Skin(GameObject *obj,const char *Skintype); //sets the skin type of obj
diff -urN sourceold/scripts/engine_io.cpp source/scripts/engine_io.cpp
--- sourceold/scripts/engine_io.cpp	2018-11-17 10:23:57.160600000 +1000
+++ source/scripts/engine_io.cpp	2020-09-10 09:08:27.953899600 +1000
@@ -565,6 +565,26 @@
 	strtrim(result);
 	return (int)strlen(result);
 }
+int INIClass::Get_String_Advanced(char const *section,char const *entry,char const *defaultvalue,char *result,int size,bool updateIfNotFound) const
+{
+	if (!result || size <= 1 || !section || !entry)
+		return 0;
+	INIEntry *Entry = Find_Entry(section,entry);
+	const char *value = defaultvalue;
+	if (Entry && Entry->Value)
+		value = Entry->Value;
+	if ((!Entry || !Entry->Value) && !updateIfNotFound) 
+		return 0;
+	if (!value)
+	{
+		result[0] = 0;
+		return 0;
+	}
+	strncpy(result, value, size);
+	result[size - 1] = 0;
+	strtrim(result);
+	return (int)strlen(result);
+}
 int INIClass::Entry_Count(char const *section) const
 {
 	INISection *Section = Find_Section(section);
diff -urN sourceold/scripts/engine_io.h source/scripts/engine_io.h
--- sourceold/scripts/engine_io.h	2018-11-17 10:23:57.161600000 +1000
+++ source/scripts/engine_io.h	2020-09-10 09:08:27.954910800 +1000
@@ -132,6 +132,7 @@
 	float Get_Float(char const *section,char const *entry,float defaultvalue) const;
 	bool Get_Bool(char const *section,char const *entry,bool defaultvalue) const;
 	int Get_String(char const *section,char const *entry,char const *defaultvalue,char *result,int size) const;
+	int Get_String_Advanced(char const *section,char const *entry,char const *defaultvalue,char *result,int size,bool writeIfNotFound) const;
 	StringClass &Get_String(StringClass &str, const char* section, const char* entry, const char* default = 0) const;
 	WideStringClass &Get_Wide_String(WideStringClass &,char  const*,char  const*,wchar_t  const*) const;
 	bool Put_Wide_String(const char* section, const char* entry, const wchar_t* string);
diff -urN sourceold/scripts/engine_obj.cpp source/scripts/engine_obj.cpp
--- sourceold/scripts/engine_obj.cpp	2018-11-17 10:23:57.163600000 +1000
+++ source/scripts/engine_obj.cpp	2020-09-10 09:08:27.954910800 +1000
@@ -403,7 +403,7 @@
 
 GameObject SCRIPTS_API *Find_Closest_Non_Building_Object_By_Team(int Team,Vector3 position)
 {
-	float closestdist = FLT_MAX;
+	float closestdist_sq = FLT_MAX;
 	GameObject *closest = 0;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x)
@@ -419,10 +419,10 @@
 			if ((Get_Object_Type(o2) == Team) && !o2->As_BuildingGameObj())
 			{
 				Vector3 pos = Commands->Get_Position(o2);
-				float dist = Commands->Get_Distance(pos,position);
-				if (dist < closestdist)
+				float dist_sq = Vector3::Distance_Squared(pos, position);
+				if (dist_sq < closestdist_sq)
 				{
-					closestdist = dist;
+					closestdist_sq = dist_sq;
 					closest = o2;
 				}
 			}
@@ -434,7 +434,7 @@
 
 GameObject SCRIPTS_API *Find_Closest_Preset_By_Team(int Team,const Vector3 &pos,const char *Preset)
 {
-	float closestdist = 9999.0f;
+	float closestdist_sq = FLT_MAX;
 	GameObject *closest = 0;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x)
@@ -449,10 +449,10 @@
 		{
 			if ((Get_Object_Type(o2) == Team || Team == 2) && !_stricmp(Commands->Get_Preset_Name(o2),Preset))
 			{
-				float dist = Commands->Get_Distance(Commands->Get_Position(o2),pos);
-				if (dist < closestdist)
+				float dist_sq = Vector3::Distance_Squared(Commands->Get_Position(o2), pos);
+				if (dist_sq < closestdist_sq)
 				{
-					closestdist = dist;
+					closestdist_sq = dist_sq;
 					closest = o2;
 				}
 			}
@@ -558,6 +558,7 @@
 GameObject SCRIPTS_API *Find_Nearest_Preset(Vector3 position, const char *preset)
 {
 	GameObject *object = 0;
+	float min_dist_sq = FLT_MAX;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x)
 	{
@@ -571,18 +572,13 @@
 		{
 			if (!_stricmp(Commands->Get_Preset_Name(o2),preset))
 			{
-				if (object)
-				{
-					Vector3 obj_pos = Commands->Get_Position(object);
-					Vector3 o_pos = Commands->Get_Position(o2);
-					if (Commands->Get_Distance(position, o_pos) < Commands->Get_Distance(position, obj_pos))
-					{
-						object = o2;
-					}
-				}
-				else
+				Vector3 pos;
+				o2->Get_Position(&pos);
+				float dist_sq = Vector3::Distance_Squared(position, pos);
+				if (dist_sq < min_dist_sq)
 				{
 					object = o2;
+					min_dist_sq = dist_sq;
 				}
 			}
 		}
@@ -689,6 +685,8 @@
 	{
 		return;
 	}
+	Vector3 TestPosition = Position;
+	TestPosition.Z = 0;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x) 
 	{
@@ -701,10 +699,8 @@
 		if (o2)
 		{
 			Vector3 ObjPosition = Commands->Get_Position(o2);
-			Vector3 TestPosition = Position;
 			ObjPosition.Z = 0;
-			TestPosition.Z = 0;
-			if ((Commands->Get_Distance(ObjPosition,TestPosition) <= Distance))
+			if (Vector3::Distance_Squared(ObjPosition, TestPosition) <= Distance * Distance)
 			{
 				if ((Get_Object_Type(o2) == team) || (team == 2))
 				{
@@ -787,7 +783,7 @@
 				Vector3 pos = Commands->Get_Position(o2);
 				pos.Z = 0;
 				location.Z = 0;
-				if (Commands->Get_Distance(pos,location) <= range)
+				if (Vector3::Distance_Squared(pos, location) <= range * range)
 				{
 					if (o2->As_VehicleGameObj() && !empty)
 					{
@@ -909,7 +905,7 @@
 
 GameObject SCRIPTS_API *Get_Closest_Armed_Object_To_Object( GameObject* obj, int team )
 {
-	float closestdist = FLT_MAX;
+	float closestdist_sq = FLT_MAX;
 	GameObject *closest = 0;
 	Vector3 position = Commands->Get_Position(obj);
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
@@ -926,10 +922,10 @@
 			if ((team == 2 || Get_Object_Type(o2) == team) && o2->As_PhysicalGameObj() && o2->As_PhysicalGameObj()->As_ArmedGameObj())
 			{
 				Vector3 pos = Commands->Get_Position(o2);
-				float dist = Commands->Get_Distance(pos,position);
-				if (dist < closestdist)
+				float dist_sq = Vector3::Distance_Squared(pos, position);
+				if (dist_sq < closestdist_sq)
 				{
-					closestdist = dist;
+					closestdist_sq = dist_sq;
 					closest = o2;
 				}
 			}
diff -urN sourceold/scripts/engine_obj2.cpp source/scripts/engine_obj2.cpp
--- sourceold/scripts/engine_obj2.cpp	2018-11-17 10:23:57.165600000 +1000
+++ source/scripts/engine_obj2.cpp	2020-09-10 09:08:27.955908400 +1000
@@ -33,14 +33,14 @@
 AT2(0x006C6420,0x006C5CC0);
 ScriptZoneGameObj *ScriptZoneGameObj::Find_Closest_Zone(Vector3 &Location,ZoneConstants::ZoneType Type)
 {
-	float closest = 999999;
+	float closest = FLT_MAX;
 	ScriptZoneGameObj *closestzone = 0;
 	for (SLNode<ScriptZoneGameObj>* node = GameObjManager::ScriptZoneGameObjList.Head(); node; node = node->Next())
 	{
 		ScriptZoneGameObj *zone = node->Data();
 		if (zone->Get_Definition().Get_Type() == Type)
 		{
-			float distance = Vector3::Distance(zone->Get_Bounding_Box().Center,Location);
+			float distance = Vector3::Distance_Squared(zone->Get_Bounding_Box().Center,Location);
 			if (distance < closest)
 			{
 				closest = distance;
diff -urN sourceold/scripts/engine_phys.cpp source/scripts/engine_phys.cpp
--- sourceold/scripts/engine_phys.cpp	2018-11-17 10:23:57.167600000 +1000
+++ source/scripts/engine_phys.cpp	2020-09-10 09:08:27.956842600 +1000
@@ -150,15 +150,15 @@
 
 void SCRIPTS_API Create_Effect_All_Stealthed_Objects_Area(const Vector3 &Position,float Distance,const char *object,const Vector3 &offset,int team)
 {
+	Vector3 TestPosition = Position;
+	TestPosition.Z = 0;
 	SLNode<SmartGameObj> *x = GameObjManager::SmartGameObjList.Head();
 	while (x) 
 	{
 		SmartGameObj *obj = x->Data();
 		Vector3 ObjPosition = Commands->Get_Position(obj);
-		Vector3 TestPosition = Position;
 		ObjPosition.Z = 0;
-		TestPosition.Z = 0;
-		if ((Commands->Get_Distance(ObjPosition,TestPosition) <= Distance))
+		if ((Vector3::Distance_Squared(ObjPosition,TestPosition) <= Distance * Distance))
 		{
 			if ((Get_Object_Type(obj) == team) || (team == 2))
 			{
diff -urN sourceold/scripts/engine_player.cpp source/scripts/engine_player.cpp
--- sourceold/scripts/engine_player.cpp	2018-11-17 10:23:57.169600000 +1000
+++ source/scripts/engine_player.cpp	2020-03-28 21:10:59.023831800 +1000
@@ -76,10 +76,14 @@
 	{
 		return newstr("None");
 	}
-	if (!((cPlayer *)o->Get_Player_Data()))
+	if (!((cPlayer *)o->Get_Player_Data()) && o->Get_Bot_Tag().Is_Empty())
 	{
 		return newstr("None");
 	}
+	if (!o->Get_Bot_Tag().Is_Empty())
+	{
+		return WideCharToChar(o->Get_Bot_Tag());
+	}
 	return WideCharToChar(((cPlayer *)o->Get_Player_Data())->Get_Name());
 }
 
@@ -504,10 +508,14 @@
 	{
 		return L"None";
 	}
-	if (!((cPlayer *)o->Get_Player_Data()))
+	if (!((cPlayer *)o->Get_Player_Data()) && o->Get_Bot_Tag().Is_Empty())
 	{
 		return L"None";
 	}
+	if (!o->Get_Bot_Tag().Is_Empty())
+	{
+		return o->Get_Bot_Tag().Peek_Buffer();
+	}
 	return ((cPlayer *)o->Get_Player_Data())->Get_Name().Peek_Buffer();
 }
 
diff -urN sourceold/scripts/engine_pt.cpp source/scripts/engine_pt.cpp
--- sourceold/scripts/engine_pt.cpp	2018-11-17 10:23:57.171600000 +1000
+++ source/scripts/engine_pt.cpp	2020-09-10 09:08:27.956842600 +1000
@@ -15,10 +15,9 @@
 #include "engine_def.h"
 #include "engine_obj2.h"
 
-unsigned int SCRIPTS_API Get_Team_Cost(const char *preset,unsigned int team)
+unsigned int SCRIPTS_API Get_Team_Cost(int def_id,unsigned int team)
 {
-	int ID = Get_Definition_ID(preset);
-	if (TeamPurchaseSettingsDefClass::Get_Definition((TeamPurchaseSettingsDefClass::TEAM)PTTEAM(team))->Get_Beacon_Definition() == ID)
+	if (TeamPurchaseSettingsDefClass::Get_Definition((TeamPurchaseSettingsDefClass::TEAM)PTTEAM(team))->Get_Beacon_Definition() == def_id)
 	{
 		return TeamPurchaseSettingsDefClass::Get_Definition((TeamPurchaseSettingsDefClass::TEAM)PTTEAM(team))->Get_Beacon_Cost();
 	}
@@ -29,7 +28,7 @@
 		{
 			for (unsigned int j = 0;j < 10;j++)
 			{
-				if ((p->Get_Definition(j) == ID) || (p->Get_Alt_Definition(j,0) == ID) || (p->Get_Alt_Definition(j,1) == ID) || (p->Get_Alt_Definition(j,2) == ID))
+				if ((p->Get_Definition(j) == def_id) || (p->Get_Alt_Definition(j,0) == def_id) || (p->Get_Alt_Definition(j,1) == def_id) || (p->Get_Alt_Definition(j,2) == def_id))
 				{
 					return p->Get_Cost(j);
 				}
@@ -39,16 +38,26 @@
 	return 0;
 }
 
-unsigned int SCRIPTS_API Get_Cost(const char *preset)
+unsigned int SCRIPTS_API Get_Team_Cost(const char *preset,unsigned int team)
+{
+	return Get_Team_Cost(Get_Definition_ID(preset), team);
+}
+
+unsigned int SCRIPTS_API Get_Cost(int def_id)
 {
-	unsigned int cost = Get_Team_Cost(preset,0);
+	unsigned int cost = Get_Team_Cost(def_id,0);
 	if (!cost)
 	{
-		cost = Get_Team_Cost(preset,1);
+		cost = Get_Team_Cost(def_id,1);
 	}
 	return cost;
 }
 
+unsigned int SCRIPTS_API Get_Cost(const char *preset)
+{
+	return Get_Cost(Get_Definition_ID(preset));
+}
+
 void SCRIPTS_API Disable_Preset_By_Name(unsigned int Team,const char *Name, bool enable)
 {
 	int ID = Get_Definition_ID(Name);
diff -urN sourceold/scripts/engine_pt.h source/scripts/engine_pt.h
--- sourceold/scripts/engine_pt.h	2018-11-17 10:23:57.172600000 +1000
+++ source/scripts/engine_pt.h	2020-09-10 09:08:27.957841300 +1000
@@ -14,7 +14,9 @@
 #include "BuildingGameObjDef.h"
 using namespace BuildingConstants;
 unsigned int SCRIPTS_API Get_Team_Cost(const char *preset,unsigned int team); //Get the cost of a preset for a given team. Returns zero if the preset is not found in any of the purchase terminal data or if it is one of the free units.
+unsigned int SCRIPTS_API Get_Team_Cost(int def_id,unsigned int team); //Get the cost of a preset for a given team. Returns zero if the preset is not found in any of the purchase terminal data or if it is one of the free units.
 unsigned int SCRIPTS_API Get_Cost(const char *preset); //Get the cost of a preset. Returns zero if the preset is not found in any of the purchase terminal data or if it is one of the free units.
+unsigned int SCRIPTS_API Get_Cost(int def_id); //Get the cost of a preset. Returns zero if the preset is not found in any of the purchase terminal data or if it is one of the free units.
 void SCRIPTS_API Disable_Preset_By_Name(unsigned int Team,const char *Name, bool enable); //Disable a preset by name
 void SCRIPTS_API Hide_Preset_By_Name(unsigned int Team,const char *Name, bool enable); //Hides a preset by name
 void SCRIPTS_API Busy_Preset_By_Name(unsigned int Team,const char *Name, bool enable); //Marks a preset as busy by name
diff -urN sourceold/scripts/engine_script.cpp source/scripts/engine_script.cpp
--- sourceold/scripts/engine_script.cpp	2018-11-17 10:23:57.173600000 +1000
+++ source/scripts/engine_script.cpp	2020-09-10 09:08:27.958838700 +1000
@@ -11,6 +11,7 @@
 */
 #include "general.h"
 #include "scripts.h"
+#include "engine_script.h"
 #include "engine_vector.h"
 #include "ScriptableGameObj.h"
 #include "slist.h"
@@ -418,7 +419,7 @@
 
 SCRIPTS_API GameObject *Find_Closest_Object_With_Script(const char *script, Vector3 pos)
 {
-	float closestdist = 0.0f;
+	float closestdist = FLT_MAX;
 	GameObject *closest = NULL;
 	SLNode<BaseGameObj> *x = GameObjManager::GameObjList.Head();
 	while (x)
@@ -433,8 +434,8 @@
 		{
 			if (Is_Script_Attached(o2, script))
 			{
-				float dist = Commands->Get_Distance(Commands->Get_Position(o2),pos);
-				if (closest == NULL || dist < closestdist)
+				float dist = Vector3::Distance_Squared(Commands->Get_Position(o2), pos);
+				if (dist < closestdist)
 				{
 					closestdist = dist;
 					closest = o2;
@@ -514,6 +515,50 @@
     delete d->Data();
 }
 
+SCRIPTS_API void Find_All_Vehicles_By_Distance(SList<GameObject>& objects, Vector3 position)
+{
+  objects.Remove_All();
+
+  // Internal list of distances, in the same order as GameObjects in objects
+  SList<float> distances;
+
+  for ( SLNode<BaseGameObj>* objNode = GameObjManager::GameObjList.Head(); objNode != NULL; objNode = objNode->Next() )
+  {
+    ScriptableGameObj* obj = (objNode->Data()) ? objNode->Data()->As_ScriptableGameObj() : NULL;
+	if ( obj && obj->As_VehicleGameObj() )
+    {
+      // SList cannot contain non-pointer types... stupid thing!
+      float* distance = new float;
+      *distance = Commands->Get_Distance(Commands->Get_Position(obj), position);
+
+      SLNode<float>* d = distances.Head();
+      for ( SLNode<GameObject>* o = objects.Head();
+        d != NULL;
+        d = d->Next(), o = o->Next() )
+      {
+        if ( *distance < *d->Data() )
+        {
+          objects.insertBefore(obj, *o);
+          distances.insertBefore(distance, *d);
+          distance = NULL;
+          break;
+        }
+      }
+
+      // OK, all existing objects are closer than this one so add it to the end of the list
+      if ( distance != NULL )
+      {
+        objects.Add_Tail(obj);
+        distances.Add_Tail(distance);
+      }
+    }
+  }
+
+  // Clean up memory since SList insists on having heap objects...
+  for ( SLNode<float>* d = distances.Head(); d != NULL; d = d->Next() )
+    delete d->Data();
+}
+
 SCRIPTS_API void Send_Custom_Event_To_Objects_With_Script( GameObject *sender, const char *script, int message, int param, float delay )
 {
 	if (!sender)
@@ -560,7 +605,7 @@
 		{
 			if ( Is_Script_Attached( o2, script )
 				&& Commands->Get_ID ( sender ) != Commands->Get_ID ( o2 )
-				&& Commands->Get_Distance ( Commands->Get_Position ( sender ), Commands->Get_Position ( o2 ) ) <= range )
+				&& Vector3::Distance_Squared ( Commands->Get_Position ( sender ), Commands->Get_Position ( o2 ) ) <= range * range )
 			{
 				Commands->Send_Custom_Event(sender,o2,message,param,delay);
 			}
@@ -627,4 +672,48 @@
   va_start ( vargs, params );
   Attach_Script_V(pObj,script,params,vargs);
   va_end(vargs);
-}
\ No newline at end of file
+}
+
+int Get_Param_Type(const char *str)
+{
+	for (int i = 0; i < PARAM_TYPE_COUNT; i++)
+	{
+		if (!lstrcmpiA(str, PARAM_TYPE_STRINGS[i]))
+		{
+			return i;
+		}
+	}
+	return 0;
+}
+
+SCRIPTS_API void Get_Script_Parameters(const char *script, DynamicVectorClass<ScriptParameter> &parameters)
+{
+	parameters.Delete_All();
+	auto factory = ScriptRegistrar::GetScriptFactory(script);
+	if (!factory)
+	{
+		return;
+	}
+	char* working = newstr(factory->GetParamDescription());
+	char *param = strtok(working, ",");
+	while (param)
+	{
+		char *sname = param;
+		char *type = strchr(param, ':');
+		type[0] = '\0';
+		type++;
+		char *value = strrchr(param, '=');
+		if (value)
+		{
+			value[0] = '\0';
+			value++;
+		}
+		ScriptParameter p;
+		p.name = sname;
+		p.value = value;
+		p.type = Get_Param_Type(type);
+		parameters.Add(p);
+		param = strtok(nullptr, ",");
+	}
+	delete[] working;
+}
diff -urN sourceold/scripts/engine_script.h source/scripts/engine_script.h
--- sourceold/scripts/engine_script.h	2018-11-17 10:23:57.173600000 +1000
+++ source/scripts/engine_script.h	2020-03-28 21:10:59.024829200 +1000
@@ -12,6 +12,7 @@
 #ifndef SCRIPTS_INCLUDE__ENGINE_SCRIPT_H
 #define SCRIPTS_INCLUDE__ENGINE_SCRIPT_H
 #include "scripts.h"
+#include "engine_string.h"
 #include "SList.h"
 
 SCRIPTS_API void Remove_Script(GameObject *obj,const char *script); //removes all copies of <script> from an object
@@ -52,6 +53,7 @@
 *   The position to calculate distance from
 */
 SCRIPTS_API void Find_All_Objects_With_Script_By_Distance(const char *script, SList<GameObject>& objects, Vector3 position);
+SCRIPTS_API void Find_All_Vehicles_By_Distance(SList<GameObject>& objects, Vector3 position);
 
 SCRIPTS_API void Send_Custom_Event_To_Objects_With_Script( GameObject *sender, const char *script, int message, int param, float delay ); // Script to send a custom to all objects with a specific script
 SCRIPTS_API void Send_Custom_Event_To_Objects_With_Script_Ranged( GameObject *sender, const char *script, int message, int param, float delay, float range ); // Script to send a custom to all objects with a specific script in a specified range
@@ -113,5 +115,55 @@
 */
 SCRIPTS_API void Attach_Script_Once_V ( GameObject* pObj, const char* script, const char* params, ... );
 
+class ScriptParameter : public NoEqualsClass<ScriptParameter>
+{
+public:
+	StringClass name;
+	StringClass value;
+	int type;
+};
+
+typedef enum
+{
+	PARAM_TYPE_INT = 0,
+	PARAM_TYPE_FLOAT,
+	PARAM_TYPE_STRING,
+	PARAM_TYPE_BOOL,
+	PARAM_TYPE_ID,
+	PARAM_TYPE_VECTOR3,
+	PARAM_TYPE_ENUM,
+	PARAM_TYPE_EMITTER,
+	PARAM_TYPE_WEAPON,
+	PARAM_TYPE_AMMO,
+	PARAM_TYPE_EXPLOSION,
+	PARAM_TYPE_ANIMATION,
+	PARAM_TYPE_GANG,
+	PARAM_TYPE_FILE,
+	PARAM_TYPE_SOUND,
+	PARAM_TYPE_COLOR,
+	PARAM_TYPE_COUNT
+} PARAM_TYPES;
+
+const char * const PARAM_TYPE_STRINGS[PARAM_TYPE_COUNT] =
+{
+	"int",
+	"float",
+	"string",
+	"bool",
+	"ID",
+	"vector3",
+	"enum",
+	"emitter",
+	"weapon",
+	"ammo",
+	"explosion",
+	"animation",
+	"gang",
+	"file",
+	"sound",
+	"color",
+};
+
+SCRIPTS_API void Get_Script_Parameters(const char *script, DynamicVectorClass<ScriptParameter> &parameters);
 
 #endif
diff -urN sourceold/scripts/engine_string.h source/scripts/engine_string.h
--- sourceold/scripts/engine_string.h	2018-11-17 10:23:57.175600000 +1000
+++ source/scripts/engine_string.h	2020-09-10 09:08:27.958838700 +1000
@@ -1106,6 +1106,45 @@
     }
 };
 
+struct hash_string
+{
+	size_t operator()(const char* str) const
+	{
+		// djb2
+		unsigned long hash = 5381;
+		while (int c = *str++) hash = hash * 33 + c;
+		return hash;
+	}
+
+	size_t operator()(const StringClass& str) const
+	{
+		return (*this)(str.Peek_Buffer());
+	}
+};
+
+struct equals_string
+{
+	bool operator()(const char* a, const char* b) const
+	{
+		return strcmp(a, b) == 0;
+	}
+
+	size_t operator()(const StringClass& a, const StringClass& b) const
+	{
+		return strcmp(a.Peek_Buffer(), b.Peek_Buffer()) == 0;
+	}
+
+	size_t operator()(const StringClass& a, const char* b) const
+	{
+		return strcmp(a.Peek_Buffer(), b) == 0;
+	}
+
+	size_t operator()(const char* a, const StringClass& b) const
+	{
+		return strcmp(a, b.Peek_Buffer()) == 0;
+	}
+};
+
 SCRIPTS_API const wchar_t *CharToWideChar(const char *str); //convert a char to a wide char
 SCRIPTS_API const char *WideCharToChar(const wchar_t *wcs); //convert a wide char to a char
 SCRIPTS_API char *newstr(const char *str); //duplicate a character string
diff -urN sourceold/scripts/engine_tt.cpp source/scripts/engine_tt.cpp
--- sourceold/scripts/engine_tt.cpp	2019-06-22 07:43:44.226400000 +1000
+++ source/scripts/engine_tt.cpp	2020-09-26 08:59:57.388154600 +1000
@@ -186,6 +186,9 @@
 SCRIPTS_API rwpa Retrieve_Waypaths;
 SCRIPTS_API rwpo Retrieve_Waypoints;
 SCRIPTS_API gwp Get_Waypoint_Position;
+SCRIPTS_API trc Test_Raycast_Collision;
+SCRIPTS_API iib Is_Inside_AABox;
+SCRIPTS_API cgst Can_Generic_Soldier_Teleport;
 SCRIPTS_API cl Create_Lightning;
 SCRIPTS_API gc Get_Clouds;
 SCRIPTS_API gli Get_Lightning;
@@ -228,6 +231,39 @@
 SCRIPTS_API gpda Get_Pathfind_Distance_Async;
 SCRIPTS_API cgpd Cancel_Get_Pathfind_Distance;
 SCRIPTS_API gpdb Get_Pathfind_Distance_Blocking;
+SCRIPTS_API gmsl Get_Multiplayer_Spawn_Location;
+SCRIPTS_API esbn Enable_Spawners_By_Name;
+SCRIPTS_API ipg Is_Pathfind_Generated;
+SCRIPTS_API gcps Get_Closest_Pathfind_Spot;
+SCRIPTS_API gcpss Get_Closest_Pathfind_Spot_Size;
+SCRIPTS_API grcs Get_Radio_Command_String;
+SCRIPTS_API sei Set_Emot_Icon;
+SCRIPTS_API kmd Kill_Messages_Disabled;
+SCRIPTS_API ise Is_Sidebar_Enabled;
+SCRIPTS_API iee Is_Extras_Enabled;
+SCRIPTS_API cbg Can_Build_Ground;
+SCRIPTS_API cba Can_Build_Air;
+SCRIPTS_API cbn Can_Build_Naval;
+SCRIPTS_API isb Is_Soldier_Busy;
+SCRIPTS_API ioep Is_On_Enemy_Pedestal;
+SCRIPTS_API fcpp Find_Closest_Poly_Position;
+SCRIPTS_API sdd Say_Dynamic_Dialogue;
+SCRIPTS_API sddp Say_Dynamic_Dialogue_Player;
+SCRIPTS_API sddt Say_Dynamic_Dialogue_Team;
+SCRIPTS_API elp Enable_Letterbox_Player;
+SCRIPTS_API cst Create_Sound_Team;
+SCRIPTS_API c2dst Create_2D_Sound_Team;
+SCRIPTS_API c2dwst Create_2D_WAV_Sound_Team;
+SCRIPTS_API c2dwstd Create_2D_WAV_Sound_Team_Dialog;
+SCRIPTS_API c2dwstc Create_2D_WAV_Sound_Team_Cinematic;
+SCRIPTS_API c3dwsbt Create_3D_WAV_Sound_At_Bone_Team;
+SCRIPTS_API c3dsbt Create_3D_Sound_At_Bone_Team;
+SCRIPTS_API _Stop_Sound_Player Stop_Sound_Player;
+SCRIPTS_API _Stop_Sound_Team Stop_Sound_Team;
+SCRIPTS_API ssa Set_Subobject_Animation;
+SCRIPTS_API sts Set_Time_Scale;
+SCRIPTS_API ssap Set_Subobject_Animation_Player;
+SCRIPTS_API wfa Write_File_Async;
 
 SCRIPTS_API bool Can_Team_Build_Vehicle(int Team)
 {
@@ -243,6 +279,144 @@
 	return true;
 }
 
+SCRIPTS_API DynamicVectorClass<int> Get_Enlisted_Purchase_Items(int team)
+{
+	DynamicVectorClass<int> list;
+	TeamPurchaseSettingsDefClass *t = TeamPurchaseSettingsDefClass::Get_Definition((TeamPurchaseSettingsDefClass::TEAM)PTTEAM(team));
+	for (int i = 0; i < 4; i++)
+	{
+		if (t->Get_Enlisted_Definition(i) != 0)
+			list.Add(t->Get_Enlisted_Definition(i));
+	}
+	return list;
+}
+
+SCRIPTS_API DynamicVectorClass<int> Get_Purchase_Items(int team, int defType)
+{
+	DynamicVectorClass<int> list;
+	PurchaseSettingsDefClass *purchaseSettings = PurchaseSettingsDefClass::Find_Definition((PurchaseSettingsDefClass::TYPE)defType, (PurchaseSettingsDefClass::TEAM)PTTEAM(team));
+	if (purchaseSettings)
+	{
+		for (int i = 0; i < 10; i++)
+		{
+			if (purchaseSettings->Get_Definition(i) != 0)
+				list.Add(purchaseSettings->Get_Definition(i));
+		}
+	}
+	return list;
+}
+
+SCRIPTS_API bool Is_Preset_Purchasable_In_List(int id, int team, PurchaseSettingsDefClass::TYPE def)
+{
+	PurchaseSettingsDefClass *purchaseSettings = PurchaseSettingsDefClass::Find_Definition(def, (PurchaseSettingsDefClass::TEAM)PTTEAM(team));
+	if (purchaseSettings && !purchaseSettings->Get_Page_Hidden() && !purchaseSettings->Get_Page_Busy() && !purchaseSettings->Get_Page_Disabled())
+	{
+		for (int i = 0; i < 10; i++)
+		{
+			if ((purchaseSettings->Get_Definition(i) == id) ||
+				(purchaseSettings->Get_Alt_Definition(i, 0) == id) || (purchaseSettings->Get_Alt_Definition(i, 1) == id) || (purchaseSettings->Get_Alt_Definition(i, 2) == id))
+			{
+				// No unit limit in 4.x?
+				/*if (int unitlimit = purchaseSettings->Get_Unit_Limit(i))
+				{
+					int count = Count_Objects_Of_Preset(2, purchaseSettings->Get_Definition(i));
+					count += Count_Objects_Of_Preset(2, purchaseSettings->Get_Alt_Definition(i, 0));
+					count += Count_Objects_Of_Preset(2, purchaseSettings->Get_Alt_Definition(i, 1));
+					count += Count_Objects_Of_Preset(2, purchaseSettings->Get_Alt_Definition(i, 2));
+					if (unitlimit <= count)
+					{
+						return false;
+					}
+				}*/
+				return !purchaseSettings->Get_Disabled(i) && !purchaseSettings->Get_Busy(i) && !purchaseSettings->Get_Hidden(i);
+			}
+		}
+	}
+	return false;
+}
+
+SCRIPTS_API bool Is_Infantry_Purchaseable(const char *preset,int team)
+{
+	int id = Get_Definition_ID(preset);
+	if (!id)
+	{
+		return false;
+	}
+
+	TeamPurchaseSettingsDefClass *t = TeamPurchaseSettingsDefClass::Get_Definition((TeamPurchaseSettingsDefClass::TEAM)PTTEAM(team));
+	
+	for (int i = 0; i < 4; i++)
+	{
+		if (t->Get_Enlisted_Definition(i) == id)
+		{
+			// No unit limit in 4.x?
+			/*int unitlimit = t->Get_Unit_Limit(i);
+			if (unitlimit)
+			{
+				if (unitlimit <= Count_Objects_Of_Preset(2, t->Get_Enlisted_Definition(i)))
+				{
+					return false;
+				}
+			}*/
+			return !t->Get_Disabled(i) && !t->Get_Busy(i) && !t->Get_Hidden(i);
+		}
+	}
+
+	BaseControllerClass *baseController = BaseControllerClass::Find_Base(team); // This does not require a PurchaseSettingsDefClass team
+
+	if (!baseController || baseController->Can_Generate_Soldiers())
+	{
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_CLASSES))
+			return true;
+
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_SECRET_CLASSES))
+			return true;
+	}
+
+	return false;
+}
+
+SCRIPTS_API bool Is_Vehicle_Purchaseable(const char *preset,int team)
+{
+	int id = Get_Definition_ID(preset);
+	if (!id)
+	{
+		return false;
+	}
+
+	BaseControllerClass *baseController = BaseControllerClass::Find_Base(team); // This does not require a PurchaseSettingsDefClass team
+	
+	if (!baseController || (baseController->Can_Generate_Vehicles() && Can_Build_Ground(team)))
+	{
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_VEHICLES) && !(Get_Vehicle_Definition_Mode(preset) == VEHICLE_TYPE_FLYING && !Is_Map_Flying()))
+			return true;
+
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_SECRET_VEHICLES))
+			return true;
+	}
+
+	if (!baseController || Can_Build_Air(team))
+	{
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_AIR))
+			return true;
+
+		// Air vehicles can be present on the ground vehicle lists!
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_VEHICLES) && !(Get_Vehicle_Definition_Mode(preset) == VEHICLE_TYPE_FLYING && !Is_Map_Flying()))
+			return true;
+
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_SECRET_VEHICLES))
+			return true;
+	}
+
+	if (!baseController || Can_Build_Naval(team))
+	{
+		if (Is_Preset_Purchasable_In_List(id, team, PurchaseSettingsDefClass::TYPE_NAVAL))
+			return true;
+	}
+
+	return false;
+}
+
 SCRIPTS_API void Send_Message_With_Team_Color(int Team,const char *Msg)
 {
 	unsigned int Red = 0,Blue = 0,Green = 0;
@@ -342,137 +516,6 @@
 			}
 		}
 		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_Sound_Team(const char *soundname,const Vector3 & position,GameObject *obj,int team)
-{
-	if (!obj)
-	{
-		return;
-	}
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_Sound_Player(o,soundname,position,obj);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_2D_Sound_Team(const char *soundname,int team)
-{
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_2D_Sound_Player(o,soundname);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_2D_WAV_Sound_Team(const char *soundname,int team)
-{
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_2D_WAV_Sound_Player(o,soundname);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_2D_WAV_Sound_Team_Dialog(const char *soundname,int team)
-{
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_2D_Wave_Sound_Dialog_Player(o,soundname);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_2D_WAV_Sound_Team_Cinematic(const char *soundname,int team)
-{
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_2D_Wave_Sound_Cinematic_Player(o,soundname);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_3D_WAV_Sound_At_Bone_Team(const char *soundname,GameObject *obj,const char *bonename,int team)
-{
-	if (!obj)
-	{
-		return;
-	}
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_3D_WAV_Sound_At_Bone_Player(o,soundname,obj,bonename);
-			}
-		}
-		x = x->Next();
-	}
-}
-
-SCRIPTS_API void Create_3D_Sound_At_Bone_Team(const char *soundname,GameObject *obj,const char *bonename,int team)
-{
-	if (!obj)
-	{
-		return;
-	}
-	SLNode<SoldierGameObj> *x = GameObjManager::StarGameObjList.Head();
-	while (x)
-	{
-		GameObject *o = x->Data();
-		if (o)
-		{
-			if ((Get_Object_Type(o) == team) || (team == 2))
-			{
-				Create_3D_Sound_At_Bone_Player(o,soundname,obj,bonename);
-			}
-		}
-		x = x->Next();
 	}
 }
 
diff -urN sourceold/scripts/engine_tt.h source/scripts/engine_tt.h
--- sourceold/scripts/engine_tt.h	2019-06-22 07:43:44.227400000 +1000
+++ source/scripts/engine_tt.h	2020-09-26 08:59:57.389151600 +1000
@@ -148,6 +148,9 @@
 typedef void (*rwpa) (DynamicVectorClass<int> &WaypathIDs);
 typedef void (*rwpo) (int WaypathID, DynamicVectorClass<int> &WaypointIDs);
 typedef void (*gwp) (int WaypathID, int WaypointID, Vector3 &Pos);
+typedef bool (*trc) (const Vector3 &pos1, const Vector3 &pos2, const bool checkDynamicObjects, Vector3 *contactPoint, GameObject *compareObj);
+typedef bool (*iib) (const AABoxClass &box, const Vector3 &pos);
+typedef bool (*cgst) (Vector3 position);
 typedef void (*cl) (const AmmoDefinitionClass *ammodef,Vector3 &start,Vector3 &end);
 typedef void (*gc) (float &cloudcover, float &gloominess);
 typedef void (*gli) (float &intensity, float &startdistance, float &enddistance, float &heading, float &distribution);
@@ -185,6 +188,39 @@
 typedef uint32 (*gpda) (SmartGameObj* pathObj, const Vector3 &dest, PathfindDistanceCallback callback, void *data);
 typedef bool(*gpdb) (SmartGameObj* pathObj, const Vector3& dest, float& distanceResult, PathfindDistanceResult& pathfindResult);
 typedef bool (*cgpd) (uint32 id);
+typedef void (*gmsl) (sint32 team, SoldierGameObj* soldier, Matrix3D &location);
+typedef void (*esbn) (const char *name, bool enable);
+typedef bool (*ipg) ();
+typedef bool (*gcps) (const Vector3 &center, float maxDistance, Vector3 *returnPosition);
+typedef bool (*gcpss) (const Vector3 &center, float maxDistance, Vector3 *returnPosition, float minSectorSize);
+typedef int (*grcs) (int index);
+typedef int (*sei) (SoldierGameObj *obj, int emoticonId);
+typedef bool (*kmd) ();
+typedef bool (*ise) ();
+typedef bool (*iee) ();
+typedef bool (*cbg) (int team);
+typedef bool (*cba) (int team);
+typedef bool (*cbn) (int team);
+typedef bool (*isb) (SoldierGameObj *obj);
+typedef bool (*ioep) (BeaconGameObj *obj);
+typedef void (*fcpp) (BuildingGameObj *building, Vector3 &pos, Vector3 *polyPos);
+typedef void (*sdd) (int text_id, SoldierGameObj* speaker, int* sound_id);
+typedef void (*sddp) (GameObject* player, int text_id, SoldierGameObj* speaker, int* sound_id);
+typedef void (*sddt) (int text_id, SoldierGameObj* speaker, int* sound_id, int team);
+typedef void (*elp) (GameObject* player, bool onoff, float seconds);
+typedef int (*cst) (const char* soundname, const Vector3& position, GameObject* obj2, int team);
+typedef int (*c2dst) (const char* soundname, int team);
+typedef int (*c2dwst) (const char* soundname, int team);
+typedef int (*c2dwstd) (const char* soundname, int team);
+typedef int (*c2dwstc) (const char* soundname, int team);
+typedef int (*c3dwsbt) (const char* soundname, GameObject* obj, const char* bonename, int team);
+typedef int (*c3dsbt) (const char* soundname, GameObject* obj, const char* bonename, int team);
+typedef int (*_Stop_Sound_Player) (GameObject* player, int sound_id, bool destroy_sound);
+typedef int (*_Stop_Sound_Team) (int sound_id, bool destroy_sound, int team);
+typedef int (*ssa) (GameObject *obj,const char *animation,bool looping,const char *subobject,float startFrame,float endFrame,bool blended);
+typedef void (*sts) (float scale);
+typedef int (*ssap) (GameObject *player,GameObject *obj,const char *animation,bool looping,const char *subobject,float startFrame,float endFrame,bool blended);
+typedef int (*wfa) (const char *_FileName, const char *_Text);
 SCRIPTS_API extern gpl Get_Player_List;
 SCRIPTS_API extern gcmi Get_Current_Map_Index;
 SCRIPTS_API extern gm Get_Map;
@@ -329,6 +365,9 @@
 SCRIPTS_API extern rwpa Retrieve_Waypaths;
 SCRIPTS_API extern rwpo Retrieve_Waypoints;
 SCRIPTS_API extern gwp Get_Waypoint_Position;
+SCRIPTS_API extern trc Test_Raycast_Collision;
+SCRIPTS_API extern iib Is_Inside_AABox;
+SCRIPTS_API extern cgst Can_Generic_Soldier_Teleport;
 SCRIPTS_API extern cl Create_Lightning;
 SCRIPTS_API extern gc Get_Clouds;
 SCRIPTS_API extern gli Get_Lightning;
@@ -371,6 +410,39 @@
 SCRIPTS_API extern gpda Get_Pathfind_Distance_Async; // Returns > 0 when succusful. pathObj must be a vehicle or a soldier
 SCRIPTS_API extern cgpd Cancel_Get_Pathfind_Distance; // Cancels a pathfind distance request. Use the result from Get_Pathfind_Distance_Async/Get_Pathfind_Distance to cancel
 SCRIPTS_API extern gpdb Get_Pathfind_Distance_Blocking; // Returns > 0 when succesful. Solves distances immediately. pathObj must be a vehicle or a soldier
+SCRIPTS_API extern gmsl Get_Multiplayer_Spawn_Location;
+SCRIPTS_API extern esbn Enable_Spawners_By_Name;
+SCRIPTS_API extern ipg Is_Pathfind_Generated;
+SCRIPTS_API extern gcps Get_Closest_Pathfind_Spot;
+SCRIPTS_API extern gcpss Get_Closest_Pathfind_Spot_Size;
+SCRIPTS_API extern grcs Get_Radio_Command_String;
+SCRIPTS_API extern sei Set_Emot_Icon;
+SCRIPTS_API extern kmd Kill_Messages_Disabled;
+SCRIPTS_API extern ise Is_Sidebar_Enabled;
+SCRIPTS_API extern iee Is_Extras_Enabled;
+SCRIPTS_API extern cbg Can_Build_Ground;
+SCRIPTS_API extern cba Can_Build_Air;
+SCRIPTS_API extern cbn Can_Build_Naval;
+SCRIPTS_API extern isb Is_Soldier_Busy;
+SCRIPTS_API extern ioep Is_On_Enemy_Pedestal;
+SCRIPTS_API extern fcpp Find_Closest_Poly_Position;
+SCRIPTS_API extern sdd Say_Dynamic_Dialogue;
+SCRIPTS_API extern sddp Say_Dynamic_Dialogue_Player;
+SCRIPTS_API extern sddt Say_Dynamic_Dialogue_Team;
+SCRIPTS_API extern elp Enable_Letterbox_Player;
+SCRIPTS_API extern cst Create_Sound_Team;
+SCRIPTS_API extern c2dst Create_2D_Sound_Team;
+SCRIPTS_API extern c2dwst Create_2D_WAV_Sound_Team;
+SCRIPTS_API extern c2dwstd Create_2D_WAV_Sound_Team_Dialog;
+SCRIPTS_API extern c2dwstc Create_2D_WAV_Sound_Team_Cinematic;
+SCRIPTS_API extern c3dwsbt Create_3D_WAV_Sound_At_Bone_Team;
+SCRIPTS_API extern c3dsbt Create_3D_Sound_At_Bone_Team;
+SCRIPTS_API extern _Stop_Sound_Player Stop_Sound_Player;
+SCRIPTS_API extern _Stop_Sound_Team Stop_Sound_Team;
+SCRIPTS_API extern ssa Set_Subobject_Animation;
+SCRIPTS_API extern sts Set_Time_Scale;
+SCRIPTS_API extern ssap Set_Subobject_Animation_Player;
+SCRIPTS_API extern wfa Write_File_Async;
 
 class SCRIPTS_API JFW_Key_Hook_Base : public ScriptImpClass {
 public:
@@ -474,19 +546,16 @@
 SCRIPTS_API void Kill_All_Buildings_By_Team(int Team); //kill all buildings of the specified team,ending the game with the other team winning,0 = Nod,1 = GDI
 SCRIPTS_API void Set_Occupants_Fade(GameObject *obj,float red,float green,float blue,float opacity); //sets the screen fade of all the occupants of a vehicle
 SCRIPTS_API void Enable_Team_Radar(int Team,bool Enable); //enable radar for a given team
-SCRIPTS_API void Create_Sound_Team(const char *soundname,const Vector3 & position,GameObject *obj,int team); //play a 3D sound for a team
-SCRIPTS_API void Create_2D_Sound_Team(const char *soundname,int team); //play a 2D sound for a team
-SCRIPTS_API void Create_2D_WAV_Sound_Team(const char *soundname,int team); //play a 2D WAV sound for a team
-SCRIPTS_API void Create_2D_WAV_Sound_Team_Dialog(const char *soundname,int team); //play a 2D WAV Dialogue sound for a team
-SCRIPTS_API void Create_2D_WAV_Sound_Team_Cinematic(const char *soundname,int team); //play a 2D WAV Cinematic sound for a team
-SCRIPTS_API void Create_3D_WAV_Sound_At_Bone_Team(const char *soundname,GameObject *obj,const char *bonename,int team); //play a 3D WAV sound at a bone for a team
-SCRIPTS_API void Create_3D_Sound_At_Bone_Team(const char *soundname,GameObject *obj,const char *bonename,int team); //play a 3D sound at a bone for a team
 SCRIPTS_API void Send_Message_Team(int team,unsigned int red,unsigned int green,unsigned int blue,const char *msg); //send a message to a team
 SCRIPTS_API void Send_Message_With_Obj_Color(GameObject *obj,const char *Msg); //send a messages in a given objects color
 SCRIPTS_API void Send_Message_With_Team_Color(int Team,const char *Msg); //Send a message in a given teams color
 SCRIPTS_API void Ranged_Gap_Effect(Gap_ListNode* FirstNode); //apply gap effect to all units of a team within a range
 SCRIPTS_API void Ranged_Stealth_On_Team(Gap_ListNode* FirstNode); //Apply stealth to all units in the range of this on the relavent team
 SCRIPTS_API bool Can_Team_Build_Vehicle(int Team); //Can this team build vehicles
+SCRIPTS_API DynamicVectorClass<int> Get_Enlisted_Purchase_Items(int team);//Get all defined IDs of enlisted characters
+SCRIPTS_API DynamicVectorClass<int> Get_Purchase_Items(int team, int defType);//Get all defined IDs in the given purchase list
+SCRIPTS_API bool Is_Infantry_Purchaseable(const char *preset,int team); //is this infantry preset purchaseable? (i.e. it would appear on the sidebar and not be hidden or greyed out. Does not do the cost check but does check unit limits)
+SCRIPTS_API bool Is_Vehicle_Purchaseable(const char *preset,int team); //is this vehicle preset purchaseable? (i.e. it would appear on the sidebar and not be hidden or greyed out. Does not do the cost check but does check unit limits)
 
 SCRIPTS_API void SendShaderParam(const char* parameter, const char* value);
 SCRIPTS_API void SendShaderParam(const char* parameter, int value);
diff -urN sourceold/scripts/engine_vector.h source/scripts/engine_vector.h
--- sourceold/scripts/engine_vector.h	2018-11-17 10:23:57.180600000 +1000
+++ source/scripts/engine_vector.h	2020-09-10 09:08:27.961258800 +1000
@@ -222,6 +222,16 @@
 		}
 		return -1;
 	}
+
+	T* begin()
+	{
+		return Vector;
+	}
+
+	const T* begin() const
+	{
+		return Vector;
+	}
 }; // 0010
 
 template <class T> class DynamicVectorClass : public VectorClass<T> {
@@ -490,12 +500,52 @@
 		}
 		return &((*this)[ActiveCount++]);
 	}
+	void Add_Multiple(const DynamicVectorClass<T>& elements)
+	{
+		Add_Multiple(elements.begin(), elements.Count());
+	}
+	void Add_Multiple(const T* elements, int count)
+	{
+		int newcount = ActiveCount + count;
+		Grow(newcount);
+
+		for (int i = 0; i < count; i++)
+		{
+			Vector[ActiveCount + i] = elements[i];
+		}
+		ActiveCount = newcount;
+	}
 	void Add_Multiple(int number_to_add)
 	{
-		for (int i = 0;i < number_to_add;i++)
+		int newcount = ActiveCount + number_to_add;
+		Grow(newcount);
+		ActiveCount = newcount;
+	}
+	// Grows by 1.5x if growth step is 0, otherwise grows according to step size.
+	// Returns false if the resize fails.
+	bool Grow()
+	{
+		if (GrowthStep == 0)
+		{
+			int len = VectorClass<T>::Length();
+			int newlen = len + (len >> 1); // 1.5x
+			if (newlen < 10) newlen = 10; // minimum length 10
+			return Grow(newlen);
+		}
+		else if (GrowthStep > 0)
 		{
-			Uninitialized_Add();
+			return Grow(VectorClass<T>::Length() + GrowthStep);
 		}
+		TT_UNREACHABLE; // somebody set the growth step to negative
+	}
+	// Returns false if the resize fails or we don't need to grow (current length is greater than newlen).
+	bool Grow(int newlen)
+	{
+		if (newlen > Length() && (VectorClass<T>::IsAllocated || !VectorClass<T>::VectorMax))
+		{
+			return Resize(newlen);
+		}
+		return false;
 	}
 }; // 0018
 
@@ -807,12 +857,23 @@
 	{
 		int index = ActiveCount;
 		ActiveCount += number_to_add;
-		if (ActiveCount >= VectorMax)
+		if (ActiveCount > VectorMax)
 		{
-			Grow( ActiveCount );
+			Grow(ActiveCount);
 		}
 		return &Vector[index];
 	}
+	void Add_Multiple(const SimpleDynVecClass<T>& elements)
+	{
+		Add_Multiple(elements.begin(), elements.Count());
+	}
+	void Add_Multiple(const T* elements, int count)
+	{
+		int newcount = ActiveCount + count;
+		if (newcount > VectorMax) Grow(newcount);
+		memcpy(Vector + ActiveCount, elements, count * sizeof(T));
+		ActiveCount = newcount;
+	}
 	bool Add_Head(const T& object)
 	{
 		return Insert(0, object);
diff -urN sourceold/scripts/gmgame.cpp source/scripts/gmgame.cpp
--- sourceold/scripts/gmgame.cpp	2019-06-22 07:43:44.230400000 +1000
+++ source/scripts/gmgame.cpp	2020-03-28 21:10:59.027822400 +1000
@@ -201,7 +201,9 @@
 	Vector3	damagerpos = Commands->Get_Position(killer);
 	const char *str1 = Get_Translated_Preset_Name_Ex(obj);
 	const char *str2 = Get_Translated_Preset_Name_Ex(killer);
-	SSGMGameLog::Log_Gamelog("KILLED;%s;%d;%s;%d;%d;%d;%d;%d;%s;%d;%d;%d;%d;%s;%s;%s;%s;%s", objectType, Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), int(victimpos.Y), int(victimpos.X), int(victimpos.Z), int(Commands->Get_Facing(obj)), Commands->Get_ID(killer), killerPreset, int(damagerpos.Y), int(damagerpos.X), int(damagerpos.Z), int(Commands->Get_Facing(killer)), killerWeapon, str1, str2, Get_Player_Name(killer), Get_Player_Name(killedObj));
+	SSGMGameLog::Log_Gamelog("KILLED;%s;%d;%s;%d;%d;%d;%d;%d;%s;%d;%d;%d;%d;%s;%s;%s;%ls;%ls", objectType, Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), int(victimpos.Y), int(victimpos.X), int(victimpos.Z), int(Commands->Get_Facing(obj)), Commands->Get_ID(killer), killerPreset, int(damagerpos.Y), int(damagerpos.X), int(damagerpos.Z), int(Commands->Get_Facing(killer)), killerWeapon, str1, str2, Get_Wide_Player_Name(killer), Get_Wide_Player_Name(killedObj));
+	delete[] str1;
+	delete[] str2;
 }
 
 SCRIPTS_API void RegisterEvent(EventType evt,Plugin *plugin)
diff -urN sourceold/scripts/gmplugin.h source/scripts/gmplugin.h
--- sourceold/scripts/gmplugin.h	2018-11-17 10:23:57.190600000 +1000
+++ source/scripts/gmplugin.h	2020-04-02 12:02:44.438361500 +1000
@@ -13,7 +13,7 @@
 #define TT_INCLUDE_GMPLUGIN_H
 #include "scripts.h"
 #include "engine.h"
-#define INTERFACE_VERSION 4.6f
+#define INTERFACE_VERSION 4.7f
 
 enum EventType {
 	EVENT_GLOBAL_INI = 0,
diff -urN sourceold/scripts/gmvehicle.cpp source/scripts/gmvehicle.cpp
--- sourceold/scripts/gmvehicle.cpp	2019-06-22 07:43:44.233400000 +1000
+++ source/scripts/gmvehicle.cpp	2020-09-26 08:59:57.390149000 +1000
@@ -85,6 +85,12 @@
 				{
 					scr->IconModelID = 0;
 				}
+				if (!Is_Script_Attached(veh, "MS_AI_Marker_Vehicle_Search_Ignore"))
+				{
+					StringClass scriptParams;
+					scriptParams.Format("%d", Get_Object_Type(veh));
+					Commands->Attach_Script(veh, "MS_AI_Marker_Vehicle_Search_Ignore", scriptParams.Peek_Buffer());
+				}
 			}
 			else
 			{
@@ -131,6 +137,12 @@
 			{
 				Force_Occupant_Exit(boundveh, 0);
 			}
+			if (!Is_Script_Attached(boundveh, "MS_AI_Marker_Vehicle_Search_Ignore"))
+			{
+				StringClass scriptParams;
+				scriptParams.Format("%d", Get_Object_Type(boundveh));
+				Commands->Attach_Script(boundveh, "MS_AI_Marker_Vehicle_Search_Ignore", scriptParams.Peek_Buffer());
+			}
 		}
 		else
 		{
diff -urN sourceold/scripts/jfwcine.cpp source/scripts/jfwcine.cpp
--- sourceold/scripts/jfwcine.cpp	2018-11-17 10:23:57.197600000 +1000
+++ source/scripts/jfwcine.cpp	2020-09-10 09:08:27.963257800 +1000
@@ -845,7 +845,7 @@
 	{
 		Command_Add_Object(command);
 	}
-	else if (Title_Match(&command,"Create_Exploision"))
+	else if (Title_Match(&command,"Create_Explosion"))
 	{
 		Command_Create_Explosion(command);
 	}
@@ -1048,7 +1048,8 @@
 		GameObject *obj = Commands->Find_Object(Slots[slot]);
 		if (obj)
 		{
-			Commands->Set_Animation(obj,animation,loop,subobject,f2,-1,blended);
+			Update_Network_Object(obj);
+			Set_Subobject_Animation(obj,animation,loop,subobject,f2,-1,blended);
 			if (CameraControl)
 			{
 				Commands->Innate_Disable(obj);
diff -urN sourceold/scripts/jfwdef.cpp source/scripts/jfwdef.cpp
--- sourceold/scripts/jfwdef.cpp	2018-10-27 22:04:56.455400000 +1000
+++ source/scripts/jfwdef.cpp	2020-09-10 09:08:27.964255000 +1000
@@ -27,14 +27,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -105,14 +105,6 @@
 	}
 }
 
-void JFW_User_Controllable_Base_Defence::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_User_Controllable_Base_Defence::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -180,14 +172,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -236,14 +228,6 @@
 	}
 }
 
-void JFW_Base_Defence::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -307,14 +291,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -366,14 +350,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_Aircraft::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_Aircraft::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -437,14 +413,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -496,14 +472,6 @@
 	}
 }
 
-void JFW_Base_Defence_Aircraft_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_Aircraft_Only::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -568,14 +536,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -632,14 +600,6 @@
 	}
 }
 
-void JFW_Base_Defence_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -705,14 +665,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -772,14 +732,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_Aircraft_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_Aircraft_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -845,14 +797,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -912,14 +864,6 @@
 	}
 }
 
-void JFW_Base_Defence_Aircraft_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_Aircraft_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -1008,11 +952,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1070,11 +1009,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_Aircraft::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_Aircraft::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1132,11 +1066,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Aircraft_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Aircraft_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1200,11 +1129,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1272,11 +1196,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_Aircraft_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_Aircraft_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1344,11 +1263,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Aircraft_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Aircraft_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -1380,14 +1294,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -1439,14 +1353,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_VTOL::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_VTOL::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -1548,14 +1454,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -1607,14 +1513,6 @@
 	}
 }
 
-void JFW_Base_Defence_VTOL_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_VTOL_Only::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -1679,14 +1577,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -1746,14 +1644,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_VTOL_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_VTOL_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -1819,14 +1709,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -1886,14 +1776,6 @@
 	}
 }
 
-void JFW_Base_Defence_VTOL_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_VTOL_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -1985,11 +1867,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_VTOL::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_VTOL::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2047,11 +1924,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_VTOL_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_VTOL_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2118,11 +1990,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_VTOL_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_VTOL_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2190,11 +2057,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_VTOL_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_VTOL_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2251,11 +2113,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2314,11 +2171,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_Aircraft::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_Aircraft::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2377,11 +2229,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_Aircraft_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_Aircraft_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2446,11 +2293,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2519,11 +2361,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_Aircraft_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_Aircraft_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2592,11 +2429,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_Aircraft_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_Aircraft_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2656,11 +2488,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_VTOL::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_VTOL::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2719,11 +2546,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_VTOL_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_VTOL_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2791,11 +2613,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_VTOL_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_VTOL_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2864,11 +2681,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_VTOL_Only_Secondary::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_VTOL_Only_Secondary::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -2901,14 +2713,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -2957,14 +2769,6 @@
 	}
 }
 
-void JFW_Base_Defence_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -3030,14 +2834,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -3088,14 +2892,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_Aircraft_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_Aircraft_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -3161,14 +2957,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -3219,14 +3015,6 @@
 	}
 }
 
-void JFW_Base_Defence_Aircraft_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_Aircraft_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -3316,11 +3104,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3380,11 +3163,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_Aircraft_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_Aircraft_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3444,11 +3222,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Aircraft_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Aircraft_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3481,14 +3254,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -3540,14 +3313,6 @@
 	}
 }
 
-void JFW_Base_Defence_No_VTOL_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_No_VTOL_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -3613,14 +3378,14 @@
 	Commands->Innate_Enable(obj);
 	Commands->Enable_Enemy_Seen(obj,true);
 	pos = Commands->Get_Position(obj);
-	pos1.X = pos.X - 10;
-	pos1.Y = pos.Y - 10;
+	pos1.X = pos.X - 5;
+	pos1.Y = pos.Y + 8.66025f;
 	pos1.Z = pos.Z + 2;
 	pos2.X = pos.X + 10;
 	pos2.Y = pos.Y;
 	pos2.Z = pos.Z + 2;
-	pos3.X = pos.X + 10;
-	pos3.Y = pos.Y - 10;
+	pos3.X = pos.X - 5;
+	pos3.Y = pos.Y - 8.66025f;
 	pos3.Z = pos.Z + 2;
 	object = Commands->Create_Object("Invisible_Object",pos1);
 	if (object)
@@ -3672,14 +3437,6 @@
 	}
 }
 
-void JFW_Base_Defence_VTOL_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 2)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void JFW_Base_Defence_VTOL_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	ActionParamsStruct var;
@@ -3772,11 +3529,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_No_VTOL_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_No_VTOL_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3836,11 +3588,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_VTOL_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_VTOL_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3898,11 +3645,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -3963,11 +3705,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_Aircraft_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_Aircraft_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -4028,11 +3765,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_Aircraft_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_Aircraft_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -4093,11 +3825,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_No_VTOL_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_No_VTOL_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -4158,11 +3885,6 @@
 	}
 }
 
-void JFW_Base_Defence_Animated_Sound_VTOL_Only_Swap::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void JFW_Base_Defence_Animated_Sound_VTOL_Only_Swap::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -4412,6 +4134,7 @@
 
 ScriptRegistrant<JFW_User_Controllable_Base_Defence> JFW_User_Controllable_Base_Defence_Registrant("JFW_User_Controllable_Base_Defence","MinAttackDistance=0.0:float,MaxAttackDistance=300.0:float,AttackTimer=10.0:float");
 ScriptRegistrant<JFW_Base_Defence> JFW_Base_Defence_Registrant("JFW_Base_Defence","MinAttackDistance=0.0:float,MaxAttackDistance=300.0:float,AttackTimer=10.0:float");
+ScriptRegistrant<JFW_Base_Defence> M00_Base_Defense_Registrant("M00_Base_Defense","MinAttackDistance=0:int,MaxAttackDistance=300:int,AttackTimer=10:int");
 ScriptRegistrant<JFW_Base_Defence_No_Aircraft> JFW_Base_Defence_No_Aircraft_Registrant("JFW_Base_Defence_No_Aircraft","MinAttackDistance=0.0:float,MaxAttackDistance=300.0:float,AttackTimer=10.0:float,Preset1:string,Preset2:string,Preset3:string,Preset4:string,Preset5:string,Preset6:string,Preset7:string,Preset8:string");
 ScriptRegistrant<JFW_Base_Defence_Aircraft_Only> JFW_Base_Defence_Aircraft_Only_Registrant("JFW_Base_Defence_Aircraft_Only","MinAttackDistance=0.0:float,MaxAttackDistance=300.0:float,AttackTimer=10.0:float,Preset1:string,Preset2:string,Preset3:string,Preset4:string,Preset5:string,Preset6:string,Preset7:string,Preset8:string");
 ScriptRegistrant<JFW_Base_Defence_Secondary> JFW_Base_Defence_Secondary_Registrant("JFW_Base_Defence_Secondary","MinAttackDistance=0.0:float,MaxAttackDistance=300.0:float,AttackTimer=10.0:float");
diff -urN sourceold/scripts/jfwdef.h source/scripts/jfwdef.h
--- sourceold/scripts/jfwdef.h	2018-10-27 22:04:56.456400000 +1000
+++ source/scripts/jfwdef.h	2020-09-10 09:08:27.964852500 +1000
@@ -19,7 +19,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -47,7 +46,6 @@
 	unsigned int objtype;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -72,7 +70,6 @@
 	unsigned int objtype;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -97,7 +94,6 @@
 	unsigned int objtype;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -119,7 +115,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -144,7 +139,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -169,7 +163,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -189,7 +182,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -209,7 +201,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -228,7 +219,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -248,7 +238,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -268,7 +257,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -288,7 +276,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -309,7 +296,6 @@
 	unsigned int objtype;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -342,7 +328,6 @@
 	unsigned int objtype;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -364,7 +349,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -386,7 +370,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -405,7 +388,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -424,7 +406,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -445,7 +426,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -466,7 +446,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -476,7 +455,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -486,7 +464,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -496,7 +473,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -507,7 +483,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -518,7 +493,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -529,7 +503,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -539,7 +512,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -549,7 +521,6 @@
 	bool attack;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -560,7 +531,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -571,7 +541,6 @@
 	bool primary;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -585,7 +554,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -599,7 +567,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -613,7 +580,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -625,7 +591,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -637,7 +602,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -649,7 +613,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -663,7 +626,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -677,7 +639,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -689,7 +650,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -701,7 +661,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -713,7 +672,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -725,7 +683,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -737,7 +694,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -749,7 +705,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -761,7 +716,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
diff -urN sourceold/scripts/jfwdmg.cpp source/scripts/jfwdmg.cpp
--- sourceold/scripts/jfwdmg.cpp	2018-11-17 10:23:57.202600000 +1000
+++ source/scripts/jfwdmg.cpp	2020-03-28 21:10:59.029816200 +1000
@@ -15,6 +15,7 @@
 #include "engine.h"
 #include "jfwdmg.h"
 #include "BuildingGameObj.h"
+#include "SoldierGameObj.h"
 #include "VehicleGameObjDef.h"
 
 void JFW_Spawn_Object_Death::Killed(GameObject *obj,GameObject *killer)
@@ -1033,6 +1034,8 @@
 	INIClass *config = Get_INI(Get_Parameter("ConfigFile"));
 	count = config->Get_Int("PresetNames","PresetNameCount",0);
 	messages = new KillMessage[count];
+	KillerIsMSBot = false;
+	KilledIsMSBot = false;
 	char preset[50];
 	char c[50];
 	for (int i = 0;i < count;i++)
@@ -1056,21 +1059,37 @@
 	}
 	if (type == Get_Int_Parameter("Message"))
 	{
+		KillerIsMSBot = false;
+		KilledIsMSBot = false;
+
 		if (Commands->Is_A_Star(sender))
 		{
 			KilledID = 0;
 			KilledPlayerID = Get_Player_ID(sender);
 		}
+		else if (sender->As_SoldierGameObj() && sender->As_SoldierGameObj()->Is_Bot())
+		{
+			KilledID = 0;
+			KilledPlayerID = sender->Get_ID();
+			KilledIsMSBot = true;
+		}
 		else if (sender->As_VehicleGameObj() && (Get_Vehicle_Driver(sender)))
 		{
 			KilledID = Commands->Get_Preset_ID(sender);
-			KilledPlayerID = Get_Player_ID(Get_Vehicle_Driver(sender));
+			if (Get_Vehicle_Driver(sender)->As_SoldierGameObj()->Is_Bot())
+			{
+				KilledIsMSBot = true;
+				KilledPlayerID = Get_Vehicle_Driver(sender)->Get_ID();
+			}
+			else
+				KilledPlayerID = Get_Player_ID(Get_Vehicle_Driver(sender));
 		}
 		else
 		{
 			KilledID = Commands->Get_Preset_ID(sender);
 			KilledPlayerID = 0;
 		}
+
 		GameObject *Killer = Commands->Find_Object(param);
 		GameObject *explosion = GetExplosionObj();
 		if (!explosion || (explosion->As_VehicleGameObj() && Get_Vehicle_Mode(explosion) != VEHICLE_TYPE_TURRET))
@@ -1085,6 +1104,12 @@
 				KillerPlayerID = Get_Player_ID(Killer);
 				KillerTeam = Get_Object_Type(Killer);
 			}
+			else if (Killer->As_SoldierGameObj()->Is_Bot())
+			{
+				KillerIsMSBot = true;
+				KillerPlayerID = Killer->Get_ID();
+				KillerTeam = Get_Object_Type(Killer);
+			}
 			else
 			{
 				KillerPlayerID = 0;
@@ -1108,7 +1133,14 @@
 			KillerTeam = Get_Object_Type(Killer);
 			if (Get_Vehicle_Gunner(Killer))
 			{
-				KillerPlayerID = Get_Player_ID(Get_Vehicle_Gunner(Killer));
+				if (Get_Vehicle_Gunner(Killer)->As_SoldierGameObj()->Is_Bot())
+				{
+					KillerIsMSBot = true;
+					KillerPlayerID = Get_Vehicle_Gunner(Killer)->Get_ID();
+				}
+				else
+					KillerPlayerID = Get_Player_ID(Get_Vehicle_Gunner(Killer));
+
 				KillerTeam = Get_Object_Type(Get_Vehicle_Gunner(Killer));
 			}
 			else
@@ -1127,7 +1159,14 @@
 	}
 	else if (type == CUSTOM_EVENT_VHEICLE_DRIVER_ID)
 	{
-		KillerPlayerID = Get_Player_ID(Commands->Find_Object(param));
+		if (Commands->Find_Object(param)->As_SoldierGameObj()->Is_Bot())
+		{
+			KillerIsMSBot = true;
+			KillerPlayerID = param;
+		}
+		else
+			KillerPlayerID = Get_Player_ID(Commands->Find_Object(param));
+
 		KillerTeam = Get_Object_Type(Commands->Find_Object(param));
 	}
 	else
@@ -1168,7 +1207,10 @@
 	}
 	if (KilledPlayerID)
 	{
-		KilledPlayerString = Get_Player_Name_By_ID(KilledPlayerID);
+		if (KilledIsMSBot)
+			KilledPlayerString = Get_Player_Name(Commands->Find_Object(KilledPlayerID));
+		else
+			KilledPlayerString = Get_Player_Name_By_ID(KilledPlayerID);
 	}
 	else
 	{
@@ -1176,7 +1218,10 @@
 	}
 	if (KillerPlayerID)
 	{
-		KillerPlayerString = Get_Player_Name_By_ID(KillerPlayerID);
+		if (KillerIsMSBot)
+			KillerPlayerString = Get_Player_Name(Commands->Find_Object(KillerPlayerID));
+		else
+			KillerPlayerString = Get_Player_Name_By_ID(KillerPlayerID);
 	}
 	else
 	{
diff -urN sourceold/scripts/jfwdmg.h source/scripts/jfwdmg.h
--- sourceold/scripts/jfwdmg.h	2018-11-17 10:23:57.202600000 +1000
+++ source/scripts/jfwdmg.h	2020-03-28 21:10:59.030813100 +1000
@@ -420,6 +420,8 @@
 	unsigned int KillerPlayerID;
 	unsigned int KilledID;
 	unsigned int KilledPlayerID;
+	bool KillerIsMSBot;
+	bool KilledIsMSBot;
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 public:
diff -urN sourceold/scripts/jfwscr.cpp source/scripts/jfwscr.cpp
--- sourceold/scripts/jfwscr.cpp	2018-11-17 10:23:57.216600000 +1000
+++ source/scripts/jfwscr.cpp	2020-03-30 20:42:37.441278900 +1000
@@ -274,6 +274,31 @@
 	}
 }
 
+void JFW_Attach_Script_Custom_Until_Custom::Custom(GameObject *obj, int type, int param, GameObject *sender)
+{
+	if (type == Get_Int_Parameter("AttachMessage"))
+	{
+		const char *script = Get_Parameter("Script");
+		const char *paramx = Get_Parameter("Params");
+		char *params = newstr(paramx);
+		char delim = Get_Parameter("Delim")[0];
+		size_t x = strlen(params);
+		for (size_t i = 0; i < x; i++)
+		{
+			if (params[i] == delim)
+			{
+				params[i] = ',';
+			}
+		}
+		Commands->Attach_Script(obj, script, params);
+		delete[] params;
+	}
+	else if (type == Get_Int_Parameter("RemoveMessage"))
+	{
+		Remove_Script(obj, Get_Parameter("Script"));
+	}
+}
+
 void JFW_Remove_All_Scripts_Custom::Custom(GameObject *obj,int type,int param,GameObject *sender)
 {
 	if (type == Get_Int_Parameter("Message"))
@@ -484,6 +509,7 @@
 ScriptRegistrant<JFW_Remove_Script_Custom> JFW_Remove_Script_Custom_Registrant("JFW_Remove_Script_Custom","Script:string,Message:int");
 ScriptRegistrant<JFW_Remove_Script_Death> JFW_Remove_Script_Death_Registrant("JFW_Remove_Script_Death","Script:string");
 ScriptRegistrant<JFW_Attach_Script_Custom> JFW_Attach_Script_Custom_Registrant("JFW_Attach_Script_Custom","Script:string,Params:string,Delim:string,Message:int");
+ScriptRegistrant<JFW_Attach_Script_Custom_Until_Custom> JFW_Attach_Script_Custom_Until_Custom_Registrant("JFW_Attach_Script_Custom_Until_Custom","Script:string,Params:string,Delim:string,AttachMessage:int,RemoveMessage:int");
 ScriptRegistrant<JFW_Remove_All_Scripts_Custom> JFW_Remove_All_Scripts_Custom_Registrant("JFW_Remove_All_Scripts_Custom","Message:int");
 ScriptRegistrant<JFW_Attach_Script_Preset_Once_Custom> JFW_Attach_Script_Preset_Once_Custom_Registrant("JFW_Attach_Script_Preset_Once_Custom","Script:string,Params:string,Delim:string,Message:int,Preset:string,Player_Type:int");
 ScriptRegistrant<JFW_Attach_Script_Type_Once_Custom> JFW_Attach_Script_Type_Once_Custom_Registrant("JFW_Attach_Script_Type_Once_Custom","Script:string,Params:string,Delim:string,Message:int,Type:int,Player_Type:int");
diff -urN sourceold/scripts/jfwscr.h source/scripts/jfwscr.h
--- sourceold/scripts/jfwscr.h	2018-11-17 10:23:57.217600000 +1000
+++ source/scripts/jfwscr.h	2020-03-30 20:42:37.442276500 +1000
@@ -63,6 +63,11 @@
   void Custom(GameObject *obj,int type,int param,GameObject *sender);
 };
 
+class JFW_Attach_Script_Custom_Until_Custom : public ScriptImpClass
+{
+  void Custom(GameObject *obj, int type, int param, GameObject *sender);
+};
+
 class JFW_Remove_All_Scripts_Custom : public ScriptImpClass {
 	void Custom(GameObject *obj,int type,int param,GameObject *sender);
 };
diff -urN sourceold/scripts/jfwws.cpp source/scripts/jfwws.cpp
--- sourceold/scripts/jfwws.cpp	2018-11-17 10:23:57.224600000 +1000
+++ source/scripts/jfwws.cpp	2020-09-10 09:08:27.965610500 +1000
@@ -13,6 +13,7 @@
 
 #include "scripts.h"
 #include "jfwws.h"
+#include "engine_tt.h"
 
 void JFW_Building_Explode_No_Damage::Killed(GameObject *obj,GameObject *killer)
 {
@@ -236,7 +237,10 @@
 	if ((Get_Int_Parameter("Frequency_Min") != -1) && (type == CUSTOM_EVENT_SOUND_ENDED))
 	{
 		float random = Commands->Get_Random(Get_Float_Parameter("Frequency_Min"),Get_Float_Parameter("Frequency_Max"));
-		Commands->Start_Timer(obj,this,random,0);
+		if(!Has_Timer(obj,this,0))
+		{
+			Commands->Start_Timer(obj,this,random,0);
+		}
 	}
 }
 
@@ -714,6 +718,7 @@
 ScriptRegistrant<JFW_Enable_Physical_Collision> JFW_Enable_Physical_Collision_Registrant("JFW_Enable_Physical_Collision","");
 ScriptRegistrant<JFW_DestroyedStateObject> JFW_DestroyedStateObject_Registrant("JFW_DestroyedStateObject","OriginalModelFacing:float,DestroyedModelPreset:string");
 ScriptRegistrant<JFW_Play_Sound> JFW_Play_Sound_Registrant("JFW_Play_Sound","Sound_Preset:string,Is_3D=1:int,Offset:vector3,Offset_Randomness:vector3,Frequency_Min=-1:float,Frequency_Max:float");
+ScriptRegistrant<JFW_Play_Sound> M00_Play_Sound_Registrant("M00_Play_Sound","Sound_Preset:string,Is_3D=1:int,Offset:vector3,Offset_Randomness:vector3,Frequency_Min=-1:float,Frequency_Max:float");
 ScriptRegistrant<JFW_Engine_Sound> JFW_Engine_Sound_Registrant("JFW_Engine_Sound","Preset:string,Bone:string");
 ScriptRegistrant<JFW_Mobius_Script> JFW_Mobius_Script_Registrant("JFW_Mobius_Script","");
 ScriptRegistrant<JFW_Mobius_Script> Dr_Mobius_Script_Registrant("Dr_Mobius_Script","");
diff -urN sourceold/scripts/jmgBearHunter.cpp source/scripts/jmgBearHunter.cpp
--- sourceold/scripts/jmgBearHunter.cpp	2019-08-20 10:11:06.805000100 +1000
+++ source/scripts/jmgBearHunter.cpp	2020-09-26 08:59:57.398127800 +1000
@@ -89,6 +89,7 @@
 		Commands->Start_Timer(obj,this,24.0f,3);
 		Commands->Start_Timer(obj,this,34.0f,4);
 		PlayerData.players[playerId]->killsWithoutGettingHurt = 0;
+		PlayerData.players[playerId]->hasAnnounced10 = false;
 	}
 	if (PlayerData.players[playerId]->firstJoin)
 		Commands->Start_Timer(obj,this,0.1f,5);
@@ -106,17 +107,13 @@
 	if (JMG_Bear_Hunter_Game_Control::bearTransition && JMG_Bear_Hunter_Game_Control::bearTransition < 100)
 		Commands->Start_Timer(obj,this,0.1f,7);
 	Commands->Start_Timer(obj,this,0.1f,1);
-	if ((JMG_Bear_Hunter_Game_Control::gameState != JMG_Bear_Hunter_Game_Control::BossArrive || JMG_Bear_Hunter_Game_Control::remainingLives[playerId] >= 0) && Get_Player_Name(obj) && _wcsicmp(WideStringClass(Get_Player_Name(obj)), The_Game()->Get_Mvp_Name()) == 0 && !_stricmp("CNC_Spawned_Soldier",Commands->Get_Preset_Name(obj)))
-		switch (The_Game()->Get_Mvp_Count())
-		{
-		case 0:break;
-		case 1:Commands->Set_Model(obj,"c_h_deer");break;
-		case 2:Commands->Set_Model(obj,"c_s_deer");break;
-		default:Commands->Set_Model(obj,"c_gi_deer");break;
-		}
 	Attach_Script_Once(obj,"JFW_Send_Self_Custom_On_Key","VehBind,454269,0");
 	Commands->Attach_Script(obj,"JMG_Rp2_Dedicated_Server_Sound_Emulator","");
 	Commands->Start_Timer(obj,this,2.5f,8);
+	if (The_Game()->Get_Game_Duration_S() < 0.25f)
+		Commands->Start_Timer(obj,this,0.25f,9);
+	else
+		Timer_Expired(obj,9);
 }
 void JMG_Bear_Hunter_Player_Soldier::Custom(GameObject *obj,int message,int param,GameObject *sender)
 {
@@ -168,34 +165,29 @@
 		Commands->Start_Timer(obj,this,0.1f,1);
 	}
 	if (number == 2)
+	{
 		switch (JMG_Bear_Hunter_Game_Control::gameState)
 		{
 		case JMG_Bear_Hunter_Game_Control::LeaveBase:case JMG_Bear_Hunter_Game_Control::HuntBears:
-			JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Hello, you must be here for the money, as I'm sure you've heard, Bear Rug Co. is in desperate need for bear rugs, and we are willing to pay 30 dollars a pelt.");
-			Create_2D_Wave_Sound_Dialog_Player(obj,"BH1.mp3");return;
+			BearHunterVoiceSystem::PlayVoice(obj,"BH1.mp3","Bear Rug Co: Hello, you must be here for the money, as I'm sure you've heard, Bear Rug Co. is in desperate need for bear rugs, and we are willing to pay 30 dollars a pelt.");return;
 		case JMG_Bear_Hunter_Game_Control::MutationStart:
-			JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Hello, I'm sure you came down to make some money killing bears, got some good news for you: a tiberium meteorite hit, and mutated the wildlife, mutant bear pelts are far more valuable, head north and join in the fun!");
-			Create_2D_Wave_Sound_Dialog_Player(obj,"BH2.mp3");return;
+			BearHunterVoiceSystem::PlayVoice(obj,"BH2.mp3","Hello, I'm sure you came down to make some money killing bears, got some good news for you: a tiberium meteorite hit, and mutated the wildlife, mutant bear pelts are far more valuable, head north and join in the fun!");return;
 		case JMG_Bear_Hunter_Game_Control::FallBack:case JMG_Bear_Hunter_Game_Control::DefendPrez:
-			JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Welcome, its a bad time for you to have joined us, a tiberium meteorite hit, and now we're under attack by mutant wildlife, grab a gun and help us defend the President of Corporate America!");
-			Create_2D_Wave_Sound_Dialog_Player(obj,"BH3.mp3");return;
+			BearHunterVoiceSystem::PlayVoice(obj,"BH3.mp3","Bear Rug Co: Welcome, its a bad time for you to have joined us, a tiberium meteorite hit, and now we're under attack by mutant wildlife, grab a gun and help us defend the President of Corporate America!");return;
 		case JMG_Bear_Hunter_Game_Control::SecureBase:
-			JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Greetings, victory is at hand, all we need to do is kill the remaining mutants inside the base!");
-			Create_2D_Wave_Sound_Dialog_Player(obj,"BH4.mp3");return;
+			BearHunterVoiceSystem::PlayVoice(obj,"BH4.mp3","Bear Rug Co: Greetings, victory is at hand, all we need to do is kill the remaining mutants inside the base!");return;
 		case JMG_Bear_Hunter_Game_Control::BossArrive:
-			JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Greetings, Kill the boss and this will be all over!");
-			Create_2D_Wave_Sound_Dialog_Player(obj,"BH5.mp3");return;
+			BearHunterVoiceSystem::PlayVoice(obj,"BH5.mp3","Bear Rug Co: Greetings, Kill the boss and this will be all over!");return;
 		}
+	}
 	if (number == 3)
 	{
-		JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Feel free to buy weapons at your nearest PCT, and once your ready, head north into the swamp and start making some money!");
-		Create_2D_Wave_Sound_Dialog_Player(obj,"BH6.mp3");
+		BearHunterVoiceSystem::PlayVoice(obj,"BH6.mp3","Bear Rug Co: Feel free to buy weapons at your nearest PCT, and once your ready, head north into the swamp and start making some money!");
 	}
 	if (number == 4)
 	{
 		PlayerData.players[playerId]->hasGotIntro = true;
-		JmgUtility::DisplayChatMessage(obj,127,127,255,"Bear Rug Co: Oh, and as a tempory promotion, we are giving out an extra $50.00 every time someone kills 10 in a row without getting hurt! And we'll give out $1,000.00 to anyone that can kill 250!");
-		Create_2D_Wave_Sound_Dialog_Player(obj,"BH7.mp3");
+		BearHunterVoiceSystem::PlayVoice(obj,"BH7.mp3","Bear Rug Co: Oh, and as a tempory promotion, we are giving out an extra $50.00 every time someone kills 10 in a row without getting hurt! And we'll give out $1,000.00 to anyone that can kill 250!");
 	}
 	if (number == 5)
 	{
@@ -226,10 +218,31 @@
 		MoveablePhysClass *mphys = obj->As_PhysicalGameObj() ? obj->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
 		if (mphys && !mphys->Can_Teleport(Matrix3D(pos)))
 		{
-			mphys->Find_Teleport_Location(pos,2.5f,&pos);
+			mphys->Find_Teleport_Location(pos,1.25f,&pos);
 			Commands->Set_Position(obj,pos);
 		}
 	}
+	if (number == 9)
+	{
+		BearHunterScoreSystem::BHScoreNode *scoreNode = bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId);
+		if ((JMG_Bear_Hunter_Game_Control::gameState != JMG_Bear_Hunter_Game_Control::BossArrive || JMG_Bear_Hunter_Game_Control::remainingLives[playerId] >= 0) && Get_Player_Name(obj) && !_stricmp("CNC_Spawned_Soldier",Commands->Get_Preset_Name(obj)))
+			switch (scoreNode->Rank)
+			{
+			case 0:break;
+			case 1:Commands->Set_Model(obj,"c_masterbleat");break;
+			case 2:Commands->Set_Model(obj,"c_knight_deer");break;
+			case 3:Commands->Set_Model(obj,"c_fremen_deer");break;
+			case 4:Commands->Set_Model(obj,"c_gz_deer");break;
+			case 5:Commands->Set_Model(obj,"c_apb_deer");break;
+			case 6:Commands->Set_Model(obj,"c_gi_deer");break;
+			case 7:Commands->Set_Model(obj,"c_ia_deer");break;
+			case 8:Commands->Set_Model(obj,"c_cmd_deer");break;
+			case 9:Commands->Set_Model(obj,"c_ren_deer");break;
+			case 10:Commands->Set_Model(obj,"c_hobo_deer");break;
+			case 11:Commands->Set_Model(obj,"c_tsr_deer");break;
+			}
+		GrantSpecialUnlocks(obj);
+	}
 }
 void JMG_Bear_Hunter_Player_Soldier::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
@@ -366,6 +379,28 @@
 		break;
 	}
 }
+void JMG_Bear_Hunter_Player_Soldier::GrantSpecialUnlocks(GameObject *obj)
+{
+	BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId);
+	if (node->KilledMutantBears+node->KilledMutantDeer+node->KilledMutantCats+node->KilledMutantCatsB+node->KilledMutantCatsR+node->KilledMutantRabbits+node->MutantDogKilled+node->BlueDeerKilled+node->MooseKilled+node->MutantSquirrelsKilled >= 4000)
+		Grant_Weapon(obj,"Weapon_Knife",true,1,true);
+	if (node->KilledBears+node->KilledBlackBears+node->WolfKilled+node->TinyDeerKilled+node->WildDeerKilled+node->WildSquirrelsKilled+node->KilledMice >= 1000)
+		Grant_Weapon(obj,"Weapon_Fire_Axe",true,1,true);
+	if (node->PlayTime > 60000)
+		Grant_Weapon(obj,"Weapon_Ruger_10/22",true,500,true);
+	if (NewObjectiveSystemControl.Get_Objective_Status(27) == NewObjectiveSystem::Accomplished)
+	{
+		Grant_Weapon(obj,"Weapon_Milk_Whole",true,500,true);
+		Grant_Weapon(obj,"Weapon_Milk_2Percent",true,500,true);
+	}
+	if (!PlayerData.players[playerId]->portablePumpjackId && bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId)->BonusObjectivesCompleted >= 100)
+	{
+		Grant_Weapon(obj,"Weapon_Portable_Pumpjack_Beacon",true,1,true);
+		Commands->Attach_Script(obj,"JMG_Bear_Hunter_PumpJack_Detect_Oil","");
+	}
+	if (bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId)->KilledMice >= 25)
+		Grant_Weapon(obj,"Weapon_Pepper_A_Salt",true,80,true);
+}
 void JMG_Rp2_Dedicated_Server_Sound_Emulator::Created(GameObject *obj)
 {
 	weaponSoundRange = 40000.0f;
@@ -425,13 +460,13 @@
 			LastWeaponID = WeaponID;
 			switch (WeaponID)
 			{
-				case 409610052:case 409610005:case 409610127:case 1000001422: weaponSoundRange = 0.0f;break;
-				case 1393:case 3078:case 409610116:case 409610117:case 409610114:case 409610115:case 1000001128: weaponSoundRange = 2500.0f;break;
-				case 1000001082:case 409610007:case 409610006:case 1000001394:case 1000001423: weaponSoundRange = 10000.0f;break;
-				case 1000001073:case 2264:case 1000001399: weaponSoundRange = 22500.0f;break;
-				case 409610124:case 409610125: weaponSoundRange = 160000.0f;break;
-				case 409610123: weaponSoundRange = 250000.0f;break;
-				default: weaponSoundRange = 40000.0f;break;
+			case 409610052:case 409610005:case 409610127:case 1000001422:case 1000005027:case 1000010023:case 1000001665:case 1000010026:case 1000005017:case 1000001453: weaponSoundRange = 0.0f;break;
+			case 1393:case 3078:case 409610116:case 409610117:case 409610114:case 409610115:case 1000001128:case 1000005000: weaponSoundRange = 2500.0f;break;
+			case 1000001082:case 409610007:case 409610006:case 1000001394:case 1000001423: weaponSoundRange = 10000.0f;break;
+			case 1000001073:case 2264:case 1000001399: weaponSoundRange = 22500.0f;break;
+			case 409610124:case 409610125:case 1000010027: weaponSoundRange = 160000.0f;break;
+			case 409610123: weaponSoundRange = 250000.0f;break;
+			default: weaponSoundRange = 40000.0f;break;
 			}
 		}
 		LastBullets = CurrentBullets;
@@ -461,6 +496,7 @@
 	bearHunterScoreSystem = BearHunterScoreSystem();
 	AIPurchaseSystemControl = AIPurchaseSystem();
 }
+int BearHunterVoiceSystem::voiceId[128] = {0};
 void JMG_Bear_Hunter_Game_Control::Created(GameObject *obj)
 {
 	bonusObjectiveCount = 0;
@@ -480,17 +516,20 @@
 	deathWarheadId = ArmorWarheadManager::Get_Warhead_Type("Death");
 	JMG_Bear_Hunter_Player_Assist_AI::maxWait = -1;
 	JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount = 0;
+	JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount = 0;
 	for (int x = 0;x < 128;x++)
 	{
 		aiIgnorePlayers[x] = 0;
 		remainingLives[x] = 0;
 		playerAlive[x] = true;
 		wanderingAiIgnorePlayers[x] = 0;
+		BearHunterVoiceSystem::voiceId[x] = 0;
 	}
 	bossRabitDead = 0;
 	bearTransition = 0;
 	objective = 12693;
 	NewObjectiveSystemControl.Add_Objective(1,NewObjectiveSystem::Primary,NewObjectiveSystem::Pending,12693,"",12693,Vector3(15.593f,-93.453f,-0.166f));
+	NewObjectiveSystemControl.Add_Objective(26,NewObjectiveSystem::Bonus,NewObjectiveSystem::Hidden,12842,"",12842);
 	spawnGroup = 0;
 	atCabin = false;
 	winWait = 0;
@@ -552,7 +591,6 @@
 	}
 	weaponsUnlocked[6] = false;
 	blackBearId = 0;
-	int pumpJackIds[7] = {601041,601038,601044,601040,601045,601039,601043};
 	for (int x = 0;x < 7;x++)
 	{
 		GameObject *pumpJack = Commands->Find_Object(pumpJackIds[x]);
@@ -583,6 +621,7 @@
 	Commands->Attach_Script(obj,"JMG_Utility_Dynamic_Clock_Control","");
 	Commands->Attach_Script(obj,"JMG_Bear_Hunter_Armored_Car_Controller","");
 	Commands->Attach_Script(obj,"JMG_Utility_Basic_Spawner_In_Radius","Skittish_Animal_Wander_Point,0.1,0.0,500,0.0,450.0,0.0,500.0,0.0 0.0 -1000.0,1.0,1.0,1,5,0.25,-1,-1,-1,1,1,0.0 0.0 1.0,0.0,1,5.0,1,0,0,0.0 0.0 0.0,-1,0.0");
+	Commands->Attach_Script(obj,"JMG_Utility_Basic_Spawner_In_Radius","AI_Mouse_Wanderpoint,0.1,0.0,250,0.0,135.0,0.0,250.0,0.0 -703.0 -1000.0,1.0,1.0,1,5,0.25,-1,-1,-1,1,1,0.0 0.0 1.0,0.0,1,5.0,1,0,0,0.0 0.0 0.0,-1,0.0");
 }
 void JMG_Bear_Hunter_Game_Control::Timer_Expired(GameObject *obj,int number)
 {
@@ -716,9 +755,8 @@
 					Commands->Set_Objective_HUD_Info_Position(3,2,"POGCabin.tga",7387,Vector3(15.593f,-93.453f,-0.166f));
 					if (NewObjectiveSystemControl.Get_Objective_Status(13) == NewObjectiveSystem::NotDefined)
 					{
-						JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: There is an alert system placed at the cabin, activating it would defintly attract attention of the bears.");
+						BearHunterVoiceSystem::PlayVoice("BH8.mp3","Bear Rug Co: There is an alert system placed at the cabin, activating it would defintly attract attention of the bears.");
 						NewObjectiveSystemControl.Add_Objective(13,NewObjectiveSystem::Secondary,NewObjectiveSystem::Pending,12739,"",12739);
-						JmgUtility::Create_2D_Wave_Sound_Dialog("BH8.mp3");
 					}
 				} 
 				if (!atCabin)
@@ -792,8 +830,7 @@
 						GameObject *player = Get_GameObj(x);
 						if (!player || Get_Player_Type(player) == -4 || JmgUtility::SimpleDistance(Commands->Get_Position(switchObj),Commands->Get_Position(player)) >= 22500)
 							continue;
-						JmgUtility::DisplayChatMessage(player,127,127,255,"Bear Rug Co: Great work holding those bears off, here is a small bonus!");
-						Create_2D_Wave_Sound_Dialog_Player(player,"BH9.mp3");
+						BearHunterVoiceSystem::PlayVoice(player,"BH9.mp3","Bear Rug Co: Great work holding those bears off, here is a small bonus!");
 						Commands->Give_Money(player,250.0f,0);
 						BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(x);
 						if (node)
@@ -822,8 +859,7 @@
 				gameTime = 1800;
 				JMG_Bear_Hunter_Player_Assist_AI::maxWait = 0;
 				NewObjectiveSystemControl.Set_Objective_Status(1,NewObjectiveSystem::Accomplished);
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We need more rugs, get out there and kill us some bears!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH10.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH10.mp3","Bear Rug Co: We need more rugs, get out there and kill us some bears!");
 				NewObjectiveSystemControl.Add_Objective(2,NewObjectiveSystem::Primary,NewObjectiveSystem::Pending,12694,"",12694);
 				sprintf(currentSong,"SpecForce_Sneak_02.mp3");
 				Commands->Fade_Background_Music("SpecForce_Sneak_02.mp3",10000,10000);
@@ -856,6 +892,7 @@
 					Attach_Script_Once(mlion,"JMG_Security_Camera_Behavior_Ignore","");
 					wildMountainLionIds[x] = Commands->Get_ID(mlion);
 				}
+				CreateCow();
 				return;
 			}
 		}
@@ -872,47 +909,40 @@
 				attackSubstationChance = 0.0f;
 			if (gameTime == 1780)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: There is a little cabin up north with some supplies, if you head up there you'll have easy hunting.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH11.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH11.mp3","Bear Rug Co: There is a little cabin up north with some supplies, if you head up there you'll have easy hunting.");
 				NewObjectiveSystemControl.Add_Objective(11,NewObjectiveSystem::Secondary,NewObjectiveSystem::Pending,12729,"",12729,Vector3(15.593f,-93.453f,-0.166f));
 			}
 			if (gameTime == 1770)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: 3 Pump Jacks have fallen into disrepair in the East, repair them for additional profit.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH12.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH12.mp3","Bear Rug Co: 3 Pump Jacks have fallen into disrepair in the East, repair them for additional profit.");
 				IncreaseBonusObjectiveCount(17);
 				NewObjectiveSystemControl.Add_Objective(17,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12748,"",12748,bonusObjectiveCount);
 			}
 			if (gameTime == 1760)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: 7 of our oil rigs to the north have gone off-line, if you head up there and activate them we'll give you a cut of the profits.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH13.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH13.mp3","Bear Rug Co: 7 of our oil rigs to the north have gone off-line, if you head up there and activate them we'll give you a cut of the profits.");
 				IncreaseBonusObjectiveCount(16);
 				NewObjectiveSystemControl.Add_Objective(16,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12758,"",12758,bonusObjectiveCount);
 			}
 			if (gameTime == 1730)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Oh also, we'll pay everyone big bucks if you can deliver the turret supplies from the warehouse in the North East.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH14.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH14.mp3","Bear Rug Co: Oh also, we'll pay everyone big bucks if you can deliver the turret supplies from the warehouse in the North East.");
 				NewObjectiveSystemControl.Add_Objective(9,NewObjectiveSystem::Tertiary,NewObjectiveSystem::Pending,12727,"",12727,Vector3(434.101f,539.071f,1.142f));
 			}
 			if (gameTime == 1700)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: One last thing, we had a helicopter go down in the swamp to the North West there are 5 surviving engineers that could use an escort back to base; we will of course pay for their safe return.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH15.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH15.mp3","Bear Rug Co: One last thing, we had a helicopter go down in the swamp to the North West there are 5 surviving engineers that could use an escort back to base; we will of course pay for their safe return.");
 				IncreaseBonusObjectiveCount(15);
 				NewObjectiveSystemControl.Add_Objective(15,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12741,"",12741,bonusObjectiveCount);
 			}
 			if (gameTime == 1500)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Half way there, you guys better get those bears while you can, because they won't be worth much when we hit our quota!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH16.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH16.mp3","Bear Rug Co: Half way there, you guys better get those bears while you can, because they won't be worth much when we hit our quota!");
 			}
 			if (gameTime == 1196)
 			{
 				gameState = MutationStart;
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Wow! Was that a Tiberium meteorite that just hit?");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH17.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH17.mp3","Bear Rug Co: Wow! Was that a Tiberium meteorite that just hit?");
 				BearHunterWolfHivemindSystem::BearHunterWolfHivemindNode *node = bearHunterWolfHivemindControl.find(2);
 				if (node)
 					node->maxPackSize = 5;
@@ -940,7 +970,6 @@
 			if (gameTime == 1190)
 			{
 				int livingPumpjacks = 0,inactivePumpjackCount = 0;
-				int pumpJackIds[7] = {601041,601038,601044,601040,601045,601039,601043};
 				for (int x = 0;x < 7;x++)
 				{
 					GameObject *pumpJack = Commands->Find_Object(pumpJackIds[x]);
@@ -959,31 +988,23 @@
 					{
 						char successMsg[220];
 						sprintf(successMsg,"Bear Rug Co: There are %d inactive pumpjacks to the North that survied that meteor impact, hurry up and activate them.",inactivePumpjackCount);
-						JmgUtility::Create_2D_Wave_Sound_Dialog("BH18.mp3");
-						JmgUtility::MessageAllPlayers(127,127,255,successMsg);
+						BearHunterVoiceSystem::PlayVoice("BH18.mp3",successMsg);
 						unsigned int textId = (7-inactivePumpjackCount == 1) ? 12752 : (7-inactivePumpjackCount == 2) ? 12753 : (7-inactivePumpjackCount == 3) ? 12754 : (7-inactivePumpjackCount == 4) ? 12755 : 12756;
 						NewObjectiveSystemControl.Set_Objective_Mission(16,textId,textId);
 					}
 					if (!livingPumpjacks)
 					{
 						if (!inactivePumpjackCount)
-						{
-							JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: All of the pump jacks to the North were destroyed by that meteor, guess we should have repaired them!");
-							JmgUtility::Create_2D_Wave_Sound_Dialog("BH19.mp3");
-						}
+							BearHunterVoiceSystem::PlayVoice("BH19.mp3","Bear Rug Co: All of the pump jacks to the North were destroyed by that meteor, guess we should have repaired them!");
 						else
-						{
-							JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like that meteor impact also wiped out all the pumpjacks to the North!");
-							JmgUtility::Create_2D_Wave_Sound_Dialog("BH20.mp3");
-						}
+							BearHunterVoiceSystem::PlayVoice("BH20.mp3","Bear Rug Co: Looks like that meteor impact also wiped out all the pumpjacks to the North!");
 						NewObjectiveSystemControl.Set_Objective_Status(16,NewObjectiveSystem::Failed);
 					}
 				}
 			}
 			if (gameTime == 1180)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like it was, and mutation has already spread to the local bears, I bet those pelts would be worth way more than a normal bears'... You should head North, there is a little house with supplies.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH21.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH21.mp3","Bear Rug Co: Looks like it was, and mutation has already spread to the local bears, I bet those pelts would be worth way more than a normal bears'... You should head North, there is a little house with supplies.");
 				objective = 12696;
 				JmgUtility::SetHUDHelpText(objective,Vector3(0,1,0));
 				NewObjectiveSystemControl.Set_Objective_Status(2,NewObjectiveSystem::Accomplished);
@@ -1001,8 +1022,7 @@
 				node = bearHunterWolfHivemindControl.find(3);
 				if (node)
 					node->maxPackSize = 0;
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Local deer have begun to show effects of mutation, we're locking the base down for the President of Corporate America's safety!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH22.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH22.mp3","Bear Rug Co: Local deer have begun to show effects of mutation, we're locking the base down for the President of Corporate America's safety!");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650184,"BH_GATE_1.BH_GATE_1");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650183,"BH_GATE_1.BH_GATE_1");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650190,"BH_GATE_1.BH_GATE_1");
@@ -1010,8 +1030,7 @@
 			}
 			if (gameTime == 875)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Shit! Looks like there has been a failure in the gate control mainframe, fall back and defend the President of Corporate America!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH23.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH23.mp3","Bear Rug Co: Shit! Looks like there has been a failure in the gate control mainframe, fall back and defend the President of Corporate America!");
 				sprintf(currentSong,"SpecForce_Moments02.mp3");
 				Commands->Fade_Background_Music("SpecForce_Moments02.mp3",10000,10000);
 				objective = 12697;
@@ -1028,25 +1047,22 @@
 			}
 			if (gameTime == 835 && !JMG_Bear_Hunter_Game_Control::hasGotTurrets && NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::Pending)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Our engineers report if the truck isn't back in the next minute they won't be able to get them set up!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH24.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH24.mp3","Bear Rug Co: Our engineers report if the truck isn't back in the next minute they won't be able to get them set up!");
 			}
 			if (gameTime == 775)
 			{
 				gameState = FallBack;
 				spawnGroup = 2;
-				Vector3 truckPos;
+				GameObject *truck = Commands->Find_Object(JMG_Bear_Hunter_Security_Turret_Truck::id);
 				if (!JMG_Bear_Hunter_Game_Control::hasGotTurrets)
-					if (NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::Pending && NewObjectiveSystemControl.Get_Radar_Blip_Position(10,&truckPos) && JmgUtility::SimpleFlatDistance(JMG_Bear_Hunter_Game_Control::centerOfBase,truckPos) <= 160000)
+					if (NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::Pending && truck && JmgUtility::SimpleFlatDistance(JMG_Bear_Hunter_Game_Control::centerOfBase,Commands->Get_Position(truck)) <= 160000)
 					{
 						truckTimeExtended = true;
-						JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We see the truck is getting close, the engineers are going to give you another 45 seconds! Everyone else fall back to base!");
-						JmgUtility::Create_2D_Wave_Sound_Dialog("BH25.mp3");
+						BearHunterVoiceSystem::PlayVoice("BH25.mp3","Bear Rug Co: We see the truck is getting close, the engineers are going to give you another 45 seconds! Everyone else fall back to base!");
 					}
 					else
 					{
-						JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Forget about the turret parts there isn't enough time! Fall back to base!");
-						JmgUtility::Create_2D_Wave_Sound_Dialog("BH26.mp3");
+						BearHunterVoiceSystem::PlayVoice("BH26.mp3","Bear Rug Co: Forget about the turret parts there isn't enough time! Fall back to base!");
 						if (NewObjectiveSystemControl.Get_Objective_Status(10) != NewObjectiveSystem::Accomplished)
 						{
 							NewObjectiveSystemControl.Remove_Objective(9);
@@ -1054,10 +1070,7 @@
 						}
 					}
 				else
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Everyone fall back to base!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH27.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH27.mp3","Bear Rug Co: Everyone fall back to base!");
 				Commands->Enable_Spawner(600536,false);
 				if (NewObjectiveSystemControl.Get_Objective_Status(14) == NewObjectiveSystem::Pending)
 					NewObjectiveSystemControl.Remove_Objective(14);
@@ -1066,19 +1079,15 @@
 			}
 			if (gameTime == 730 && !JMG_Bear_Hunter_Game_Control::hasGotTurrets && truckTimeExtended)
 			{
-				Vector3 truckPos;
-				if (NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::Pending && NewObjectiveSystemControl.Get_Radar_Blip_Position(10,&truckPos) && JmgUtility::SimpleFlatDistance(JMG_Bear_Hunter_Game_Control::centerOfBase,truckPos) <= 10000)
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We're giving you another 15 seconds to get those turret parts inside DERP Industries beings your so close!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH28.mp3");
-				}
+				GameObject *truck = Commands->Find_Object(JMG_Bear_Hunter_Security_Turret_Truck::id);
+				if (NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::Pending && truck && JmgUtility::SimpleFlatDistance(JMG_Bear_Hunter_Game_Control::centerOfBase,Commands->Get_Position(truck)) <= 10000)
+					BearHunterVoiceSystem::PlayVoice("BH28.mp3","Bear Rug Co: We're giving you another 15 seconds to get those turret parts inside DERP Industries beings your so close!");
 				else
 				{
 					truckTimeExtended = false;
 					if (NewObjectiveSystemControl.Get_Objective_Status(10) != NewObjectiveSystem::Accomplished)
 					{
-						JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We need all the engineers back to work on the gates, there is no more time for the turret parts!");
-						JmgUtility::Create_2D_Wave_Sound_Dialog("BH29.mp3");
+						BearHunterVoiceSystem::PlayVoice("BH29.mp3","Bear Rug Co: We need all the engineers back to work on the gates, there is no more time for the turret parts!");
 						NewObjectiveSystemControl.Remove_Objective(9);
 						NewObjectiveSystemControl.Remove_Objective(10);
 					}
@@ -1089,27 +1098,21 @@
 				truckTimeExtended = false;
 				if (NewObjectiveSystemControl.Get_Objective_Status(10) != NewObjectiveSystem::Accomplished)
 				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Forget the turret parts and fall back to base, there isn't any more time to set them up!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH30.mp3");
+					BearHunterVoiceSystem::PlayVoice("BH30.mp3","Bear Rug Co: Forget the turret parts and fall back to base, there isn't any more time to set them up!");
 					NewObjectiveSystemControl.Remove_Objective(9);
 					NewObjectiveSystemControl.Remove_Objective(10);
 				}
 			}
 			if (gameTime == 700)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: This could get pretty intense, if you have a chance try to head West, you will find a radio tower, if you can activate it we can call for backup.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH31.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH31.mp3","Bear Rug Co: This could get pretty intense, if you have a chance try to head West, you will find a radio tower, if you can activate it we can call for backup.");
 				NewObjectiveSystemControl.Add_Objective(21,NewObjectiveSystem::Secondary,NewObjectiveSystem::Pending,12789,"",12789,Vector3(-512.0f,-270.705f,76.11f));
 			}
 			if (gameTime == 624)
-			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Watch your backs, there are mutant dogs out there now!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH32.mp3");
-			}
+				BearHunterVoiceSystem::PlayVoice("BH32.mp3","Bear Rug Co: Watch your backs, there are mutant dogs out there now!");
 			if (gameTime == 400 && objective != 12698)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Mutant squirrels incoming, this is just getting to be too much, fall back and defend the President of Corporate America!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH33.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH33.mp3","Bear Rug Co: Mutant squirrels incoming, this is just getting to be too much, fall back and defend the President of Corporate America!");
 				NewObjectiveSystemControl.Remove_Objective(5);
 				JmgUtility::SetHUDHelpText(objective,Vector3(0,1,0));
 				objective = 12698;
@@ -1121,8 +1124,7 @@
 			}
 			if (gameTime == 320)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like cats are infected now too, I think I'll just be leaving...");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH34.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH34.mp3","Bear Rug Co: Looks like cats are infected now too, I think I'll just be leaving...");
 				sprintf(currentSong,"304824_Goliath__Club_Remix_.mp3");
 				Commands->Fade_Background_Music("304824_Goliath__Club_Remix_.mp3",10000,10000);
 			}
@@ -1135,31 +1137,21 @@
 			if (gameTime == 240)
 			{
 				if (NewObjectiveSystemControl.Get_Objective_Status(21) == NewObjectiveSystem::Pending && NewObjectiveSystemControl.Get_Mission_Text_Id(21) != 12790)
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The gate will be back on-line in the next 4 minutes, if you can't get the signal sent within the next minute don't bother!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH35.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH35.mp3","Bear Rug Co: The gate will be back on-line in the next 4 minutes, if you can't get the signal sent within the next minute don't bother!");
 				gameState = DefendPrez;
 			}
 			if (gameTime == 180)
 			{
 				if (NewObjectiveSystemControl.Get_Objective_Status(21) == NewObjectiveSystem::Pending && NewObjectiveSystemControl.Get_Mission_Text_Id(21) != 12790)
 				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Forget about the communications tower, there isn't enough time anymore; focus all resources on defending the president!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH36.mp3");
+					BearHunterVoiceSystem::PlayVoice("BH36.mp3","Bear Rug Co: Forget about the communications tower, there isn't enough time anymore; focus all resources on defending the president!");
 					NewObjectiveSystemControl.Remove_Objective(21);
 				}
 			}
 			if (gameTime == 150)
-			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Oh god, is that a moose? What are they doing out here... Never mind, defend the President of Corperate America!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH37.mp3");
-			}
+				BearHunterVoiceSystem::PlayVoice("BH37.mp3","Bear Rug Co: Oh god, is that a moose? What are they doing out here... Never mind, defend the President of Corperate America!");
 			if (gameTime == 60)
-			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: And... Here comes the cougars... looks like everything is going straight for the President of Corperate America!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH38.mp3");
-			}
+				BearHunterVoiceSystem::PlayVoice("BH38.mp3","Bear Rug Co: And... Here comes the cougars... looks like everything is going straight for the President of Corperate America!");
 			if ((gameTime == 600 || gameTime == 300) || (!(gameTime % 60) && gameTime > 0 && gameTime < 300))
 			{
 				char timeMsg[220];
@@ -1180,8 +1172,7 @@
 			}
 			if (gameTime == 0)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Mainframe back on-line, gates are closing!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH39.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH39.mp3","Bear Rug Co: Mainframe back on-line, gates are closing!");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650185,"BH_GATE_1.BH_GATE_1");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650186,"BH_GATE_1.BH_GATE_1");
 				Commands->Static_Anim_Phys_Goto_Last_Frame(650187,"BH_GATE_1.BH_GATE_1");
@@ -1200,83 +1191,24 @@
 			CreatePositionZ = 500-1000*(1-gameTime/1800.0f)+2;
 			GamePercent = JmgUtility::MathClamp(1-(gameTime-Get_Player_Count()*2.5f)/1210.0f,0.0f,10.0f);//Was 7.5
 			int normalBears = (int)(!bearTransition ? maxTotalEnemies : (bearTransition == 100 ? 0 : maxTotalEnemies*(1-bearTransition/100.0f)));
-			if ((int)(normalBears * 0.25f) > JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount)
-			{
-				bool deer = Commands->Get_Random(0.0f,1.0f) < 0.5f;
-				Vector3 pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(Commands->Get_Random(-500.0f,500.0f),Commands->Get_Random(-350.0f,500.0f)));
-				GameObject *ai = deer ? Commands->Create_Object("Normal_Buck_Twiddler",pos) : Commands->Create_Object("Normal_Squirrel_Twiddler",pos);
-				Commands->Set_Facing(ai,Commands->Get_Random(-180,180));
-				MoveablePhysClass *mphys = ai->As_PhysicalGameObj() ? ai->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
-				if (mphys)
-					mphys->Find_Teleport_Location(pos,2.0f,&pos);
-				Commands->Set_Position(ai,pos);
-				if (deer)
-				{
-					Attach_Script_Once(ai,"JMG_Utility_AI_Skittish_Herd_Animal","0,4,511,25.0,30.0,50.0,200.0,6.0,24.0,10.0,25.0,0.1,0.125");
-					Commands->Set_Model(ai,Commands->Get_Random(0.0f,1.0f) < 0.25f ? "BUCK_" : "DOE_");
-				}
-				else
-					Attach_Script_Once(ai,"JMG_Bear_Hunter_AI_Avoid_Enemies","");
-				Attach_Script_Once(ai,"JMG_Security_Camera_Behavior_Ignore","");
-			}
+			CreateNormalAnimals(normalBears);
 			if (!normalBears && JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount)
-			{
 				for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
 				{
 					SmartGameObj* o = current->Data();
-					if (o && Is_Script_Attached(o,"JMG_Bear_Hunter_AI_Avoid_Enemies"))
-						Commands->Apply_Damage(o,1000.0f,"None",0);
-				}
-			}
-			for (int x = bearObjectListControl.objectCount;x < normalBears;x++)
-			{
-				Vector3 pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(Commands->Get_Random(-500.0f,500.0f),Commands->Get_Random(-350.0f,500.0f)));
-				bool isBlackBear = Commands->Get_Random(0.0f,1.0f) < 0.1 ? true : false;
-				GameObject *ai = isBlackBear ? Commands->Create_Object("Bear_Black_Twiddler",pos) : Commands->Create_Object("Normal_Bear_Twiddler",pos);
-				Commands->Set_Facing(ai,Commands->Get_Random(-180,180));
-				MoveablePhysClass *mphys = ai->As_PhysicalGameObj() ? ai->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
-				if (mphys)
-					mphys->Find_Teleport_Location(pos,2.0f,&pos);
-				pos.Z += 0.1f;
-				Commands->Set_Position(ai,pos);
-				Commands->Set_Loiters_Allowed(ai,true);
-				ai->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
-				bearObjectListControl += ai;
-				Attach_Script_Once(ai,"JMG_Rp2_Hostile_Mutant_AI","");
-				Attach_Script_Once(ai,"JMG_Bear_Hunter_Bear_Tracker","");
-				Attach_Script_Once(ai,"JMG_Bear_Hunter_Animal_Control","");
-				Attach_Script_Once(ai,"JMG_Bear_Hunter_Player_Count_Scaled_Object_Health","");
-			}
-			if (bearObjectListControl.objectCount > normalBears)
-				bearObjectListControl.DestroyLeastSignificant();
-			if (JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue)
-			{
-				GameObject *statuePlayer = Get_GameObj(JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue);
-				if (statuePlayer)
-				{
-					int deerCount = JmgUtility::MathClampInt(3+(int)(0.33f*Get_Player_Count()),3,25);
-					for (int x = 0;x < deerCount;x++)
+					if (o && (Is_Script_Attached(o,"JMG_Bear_Hunter_AI_Avoid_Enemies") || Is_Script_Attached(o,"JMG_Utility_AI_Skittish_Herd_Animal")))
 					{
-						if (Commands->Find_Object(JMG_Bear_Hunter_Golden_Deer_Statue::tinyDeerIds[x]))
+						JMG_Bear_Hunter_AI_Avoid_Enemies *script = (JMG_Bear_Hunter_AI_Avoid_Enemies*)Find_Script_On_Object(o,"JMG_Bear_Hunter_AI_Avoid_Enemies");
+						if (script && script->mouse)
 							continue;
-						Vector3 playerPos = Commands->Get_Position(statuePlayer);
-						if (BearHunterGameControlSystem.getRandomPosition(&playerPos,500.0f,2500.0f))
-						{
-							GameObject *tinyDeer = Commands->Create_Object("Tiny_Deer_Twiddler",playerPos);
-							MoveablePhysClass *mphys = tinyDeer->As_PhysicalGameObj() ? tinyDeer->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
-							if (mphys && !mphys->Can_Teleport(Matrix3D(playerPos)))
-							{
-								mphys->Find_Teleport_Location(playerPos,1.5f,&playerPos);
-								Commands->Set_Position(tinyDeer,playerPos);
-							}
-							tinyDeer->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
-							Commands->Attach_Script(tinyDeer,"JMG_Utility_AI_Goto_Player","-1.0,1.0,1.0,0.0,0,1.0,-1.0,0.0,0,1.0,-1,1.0,1,0,0.0");
-							Commands->Attach_Script(tinyDeer,"JMG_Bear_Hunter_Kill_Score_Tracker","");
-							JMG_Bear_Hunter_Golden_Deer_Statue::tinyDeerIds[x] = Commands->Get_ID(tinyDeer);
-						}
+						Commands->Apply_Damage(o,1000.0f,"None",0);
 					}
 				}
-			}
+			CreateMouse();
+			CreateBears(normalBears);
+			if (bearObjectListControl.objectCount > normalBears)
+				bearObjectListControl.DestroyLeastSignificant();
+			SpawnAngryTinyDeer();
 			if (gameTime == 1200)
 			{
 				GameObject *Meteor = Commands->Create_Object("Meteorite_Animation",BearHunterGameControlSystem.LookupNearestPosition(Vector2(0.0f,200.0f)));
@@ -1285,13 +1217,13 @@
 				Commands->Send_Custom_Event(Meteor,obj,987988,0,1.0f);
 			}
 		}
+		TriggerCowObjective();
 		Commands->Start_Timer(obj,this,1.0f,3);
 	}
 	if (number == 4)
 	{
 		gameState = SecureBase;
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Kill all remaining mutants inside the walls!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH40.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH40.mp3","Bear Rug Co: Kill all remaining mutants inside the walls!");
 		objective = 12699;
 		JmgUtility::SetHUDHelpText(objective,Vector3(0,1,0));
 		NewObjectiveSystemControl.Set_Objective_Status(6,NewObjectiveSystem::Accomplished);
@@ -1354,15 +1286,9 @@
 				if (!JMG_Bear_Hunter_Game_Control::hasBeenInjured)
 					spawnKarma += 2;
 				if (!JMG_Bear_Hunter_Game_Control::hasBeenInjured)
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: I cannot believe it; the president did not even get hurt once, if I did not know better, I would think you were cheating!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH41.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH41.mp3","Bear Rug Co: I cannot believe it; the president did not even get hurt once, if I did not know better, I would think you were cheating!");
 				else
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Great work guys, I think thats all of them!");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH42.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH42.mp3","Bear Rug Co: Great work guys, I think thats all of them!");
 			}
 			if (winWait == 13)
 			{
@@ -1383,8 +1309,7 @@
 					turretState = 2;
 				if (turretState)
 				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Just in time too, looks like the turret control computer was about to burn up. Shutting it down now...");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH43.mp3");
+					BearHunterVoiceSystem::PlayVoice("BH43.mp3","Bear Rug Co: Just in time too, looks like the turret control computer was about to burn up. Shutting it down now...");
 				}
 				if (0.7f < (1.0f*NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Secondary,NewObjectiveSystem::Accomplished)+NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Tertiary,NewObjectiveSystem::Accomplished)+NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Quaternary,NewObjectiveSystem::Accomplished))/(1.0f*NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Secondary)+NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Tertiary)+NewObjectiveSystemControl.Get_Objective_Priority_Count(NewObjectiveSystem::Quaternary)))
 					spawnKarma++;
@@ -1449,20 +1374,11 @@
 			if (winWait == 23)
 			{
 				if (spawnKarma <= 1)
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Haha, its a good thing this isn't a video game, otherwise I'd have expected some sort of super mutant boss.");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH44.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH44.mp3","Bear Rug Co: Haha, its a good thing this isn't a video game, otherwise I'd have expected some sort of super mutant boss.");
 				else if (spawnKarma <= 3)
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Haha, its a good thing this isn't a video game, otherwise I'd have expected some sort of super mutant boss, with maybe some support bosses because you did so well.");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH45.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH45.mp3","Bear Rug Co: Haha, its a good thing this isn't a video game, otherwise I'd have expected some sort of super mutant boss, with maybe some support bosses because you did so well.");
 				else
-				{
-					JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: However, it is a good thing you people are not cheaters, otherwise karma would try to find a way to balance the odds.");
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH46.mp3");
-				}
+					BearHunterVoiceSystem::PlayVoice("BH46.mp3","Bear Rug Co: However, it is a good thing you people are not cheaters, otherwise karma would try to find a way to balance the odds.");
 				Commands->Set_Lightning(1.0f,0.0f,0.25f,0.0f,1.0f,20.0f);
 				sprintf(currentSong,"304557_Goliath.mp3");
 			}
@@ -1470,8 +1386,7 @@
 				Commands->Fade_Background_Music("304557_Goliath.mp3",10000,10000);
 			if (winWait == 43)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Well, we should have seen this coming, hurry guys, kill it and we'll call it a day!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH47.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH47.mp3","Bear Rug Co: Well, we should have seen this coming, hurry guys, kill it and we'll call it a day!");
 				Commands->Create_Object("Mutant_Rabbit_Boss",Vector3(0.0f,0.0f,0.0f));
 				if (spawnKarma >= 2)
 					for (int x = 0;x < spawnKarma;x++)
@@ -1524,8 +1439,7 @@
 			Vector3 switchPos = Commands->Get_Position(switchObj);
 			if (CheckIfPlayerInRange(switchPos,22500))
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: By the way, there is an active security system in the complex, you might want to deactivate it before you take the truck.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH48.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH48.mp3","Bear Rug Co: By the way, there is an active security system in the complex, you might want to deactivate it before you take the truck.");
 				NewObjectiveSystemControl.Add_Objective(14,NewObjectiveSystem::Quaternary,NewObjectiveSystem::Pending,12740,"",12740);
 				return;
 			}
@@ -1591,18 +1505,22 @@
 					Commands->Action_Goto(ai,params);
 					Commands->Attach_Script(ai,"JMG_Bear_Hunter_Engineer_AI","Weapon_RepairGun_Player,Weapon_Pistol_Player,0.5 0.75 0.9");
 					entineersReturned++;
-					float money = 125;
+					float money = 75;
 					if (entineersReturned == 5)
-						money = 500;
+						money = 250;
 					JMG_Bear_Hunter_Game_Control::ObjectiveCompleteReward(money);
 					GameObject *guide = Get_GameObj(JMG_Bear_Hunter_Engineer_Follow_Player_When_Near::followPlayerId[x]);
 					if (guide)
 					{
-						Commands->Give_Points(guide,250.0f,false);
-						BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(guide));
-						node->BonusObjectivesCompleted++;
-						node->totalObjectivesCompleted++;
-						node->ObjectiveEngineersSaved++;
+						int playerId = JmgUtility::JMG_Get_Player_ID(guide);
+						if (playerId)
+						{
+							Set_Score(playerId,Get_Score(playerId)+250.0f);
+							BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId);
+							node->BonusObjectivesCompleted++;
+							node->totalObjectivesCompleted++;
+							node->ObjectiveEngineersSaved++;
+						}
 					}
 					missionUpdate = true;
 				}
@@ -1615,8 +1533,7 @@
 			engineerScanTime = 10;
 			if (entineersReturned == 5)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Thats all of them, great work guys!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH49.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH49.mp3","Bear Rug Co: Thats all of them, great work guys!");
 				NewObjectiveSystemControl.Set_Objective_Status(15,NewObjectiveSystem::Accomplished);
 			}
 			else
@@ -1626,15 +1543,13 @@
 				if (totalEngineers == 5)
 				{
 					sprintf(successMsg,"Bear Rug Co: Looks like we saved %d of the 5 engineers, it's good to see some of them back.",entineersReturned);
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH50.mp3");
-					JmgUtility::MessageAllPlayers(127,127,255,successMsg);
+					BearHunterVoiceSystem::PlayVoice("BH50.mp3",successMsg);
 					NewObjectiveSystemControl.Remove_Objective(15);
 				}
 				else
 				{
 					sprintf(successMsg,"Bear Rug Co: Good work bringing another engineer home, %d more to go.",5-totalEngineers);
-					JmgUtility::Create_2D_Wave_Sound_Dialog("BH51.mp3");
-					JmgUtility::MessageAllPlayers(127,127,255,successMsg);
+					BearHunterVoiceSystem::PlayVoice("BH51.mp3",successMsg);
 					unsigned int textId = (totalEngineers == 1) ? 12743 : (totalEngineers == 2) ? 12744 : (totalEngineers == 3) ? 12745 : 12746;
 					NewObjectiveSystemControl.Set_Objective_Mission(15,textId,textId);
 				}
@@ -1647,16 +1562,11 @@
 			lastUpdateEngineersDead = JMG_Bear_Hunter_Engineer_AI::engineersDead;
 			switch (JMG_Bear_Hunter_Engineer_AI::engineersDead)
 			{
-			case 1:JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like we lost an engineer.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH52.mp3");break;
-			case 2:JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Another engineer has died.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH53.mp3");break;
-			case 3:JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We've lost another engineer! Be more careful, these people have families!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH54.mp3");break;
-			case 4:JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Four engineers dead, maybe you should just let the last one hide.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH55.mp3");break;
-			case 5:JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We've lost all the engineers, I understand you guys didn't have training in fighting killer animals, but we still expected better.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH56.mp3");break;
+			case 1:BearHunterVoiceSystem::PlayVoice("BH52.mp3","Bear Rug Co: Looks like we lost an engineer.");break;
+			case 2:BearHunterVoiceSystem::PlayVoice("BH53.mp3","Bear Rug Co: Another engineer has died.");break;
+			case 3:BearHunterVoiceSystem::PlayVoice("BH54.mp3","Bear Rug Co: We've lost another engineer! Be more careful, these people have families!");break;
+			case 4:BearHunterVoiceSystem::PlayVoice("BH55.mp3","Bear Rug Co: Four engineers dead, maybe you should just let the last one hide.");break;
+			case 5:BearHunterVoiceSystem::PlayVoice("BH56.mp3","Bear Rug Co: We've lost all the engineers, I understand you guys didn't have training in fighting killer animals, but we still expected better.");break;
 			}
 			int totalEngineers = entineersReturned+JMG_Bear_Hunter_Engineer_AI::engineersDead;
 			if (JMG_Bear_Hunter_Engineer_AI::engineersDead == 5)
@@ -1665,8 +1575,7 @@
 			{
 				char engineerMsg[220];
 				sprintf(engineerMsg,"Bear Rug Co: Looks like we saved %d of the 5 engineers, it's good to see some of them back.",entineersReturned);
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH50.mp3");
-				JmgUtility::MessageAllPlayers(127,127,255,engineerMsg);
+				BearHunterVoiceSystem::PlayVoice("BH50.mp3",engineerMsg);
 				NewObjectiveSystemControl.Remove_Objective(15);
 			}
 			else
@@ -1686,8 +1595,7 @@
 				continue;
 			if (JmgUtility::SimpleFlatDistance(Vector3(50.0f,-685.0f,0),Commands->Get_Position(player)) > 2500.0f)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: While you are hunting for bears keep an eye out for weapons containers, 6 of them have been misplaced and we will let you use the weapons if you bring them back to base.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH57.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH57.mp3","Bear Rug Co: While you are hunting for bears keep an eye out for weapons containers, 6 of them have been misplaced and we will let you use the weapons if you bring them back to base.");
 				IncreaseBonusObjectiveCount(18);
 				NewObjectiveSystemControl.Add_Objective(18,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12772,"",12772,bonusObjectiveCount);
 				return;
@@ -1697,6 +1605,8 @@
 	}
 	if (number == 12)
 	{
+		for (int x = 0;x < 5;x++)
+			CreateMouse();
 		GameObject *giantDeer = Commands->Create_Object("Giant_Deer",Vector3(424.87f,133.22f,0.22f));
 		Commands->Attach_Script(giantDeer,"JMG_Bear_Hunter_Giant_Deer_Boss","");
 		blackBearId = Commands->Get_ID(giantDeer);
@@ -1883,7 +1793,6 @@
 		for (int x = 0;x < 3;x++)
 			Commands->Create_Sound("SFX.Dynamite_Explosion2",Commands->Get_Position(sender),sender);
 		Commands->Create_Explosion("Explosion_Meteor",Commands->Get_Position(sender),sender);
-		int pumpJackIds[7] = {601041,601038,601044,601040,601045,601039,601043};
 		for (int x = 0;x < 7;x++)
 		{
 			GameObject *pumpJack = Commands->Find_Object(pumpJackIds[x]);
@@ -1917,9 +1826,6 @@
 			}
 		}
 		Commands->Destroy_Object(sender);
-		GameObject *deerStatue = Commands->Find_Object(JMG_Bear_Hunter_Golden_Deer_Statue::statueId);
-		if (deerStatue)
-			Commands->Destroy_Object(deerStatue);
 		if (NewObjectiveSystemControl.Get_Objective_Status(25) != NewObjectiveSystem::Failed && NewObjectiveSystemControl.Get_Objective_Status(25) != NewObjectiveSystem::Accomplished)
 			for (int x = 0;x < 8;x++)
 			{
@@ -1927,6 +1833,9 @@
 				if (mlion)
 					Commands->Destroy_Object(mlion);
 			}
+		GameObject *deerStatue = Commands->Find_Object(JMG_Bear_Hunter_Golden_Deer_Statue::statueId);
+		if (deerStatue)
+			Commands->Destroy_Object(deerStatue);
 		GameObject *deerPlayer = Get_GameObj(JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue);
 		if (deerPlayer)
 		{
@@ -1938,8 +1847,7 @@
 		}
 		if (NewObjectiveSystemControl.Get_Objective_Status(24) != NewObjectiveSystem::NotDefined && NewObjectiveSystemControl.Get_Objective_Status(24) != NewObjectiveSystem::Accomplished)
 		{
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The statue has vanished, forget about it.");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH58.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH58.mp3","Bear Rug Co: The statue has vanished, forget about it.");
 			NewObjectiveSystemControl.Set_Objective_Status(24,NewObjectiveSystem::Failed);
 		}
 	}
@@ -1949,8 +1857,7 @@
 		{
 			if (NewObjectiveSystemControl.Get_Objective_Status(25) != NewObjectiveSystem::NotDefined)
 				return;
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Oh look, a pride of cougars... The boys bet that you guys can't pet them all, $50.00 per cat if you can!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH59.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH59.mp3","Bear Rug Co: Oh look, a pride of cougars... The boys bet that you guys can't pet them all, $50.00 per cat if you can!");
 			IncreaseBonusObjectiveCount(25);
 			NewObjectiveSystemControl.Add_Objective(25,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12832,"",12832,bonusObjectiveCount);
 		}
@@ -1968,7 +1875,8 @@
 			if (NewObjectiveSystemControl.Get_Objective_Status(25) != NewObjectiveSystem::Pending)
 			{
 				bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(sender))->FailedPettingCougars++;
-				NewObjectiveSystemControl.Add_Objective(25,NewObjectiveSystem::Unknown,NewObjectiveSystem::Hidden,12832,"",12832,obj);
+				if (NewObjectiveSystemControl.Get_Objective_Status(25) == NewObjectiveSystem::NotDefined)
+					NewObjectiveSystemControl.Add_Objective(25,NewObjectiveSystem::Unknown,NewObjectiveSystem::Hidden,12832,"",12832);
 				NewObjectiveSystemControl.Set_Objective_Status(25,NewObjectiveSystem::Failed);
 				return;
 			}
@@ -1977,8 +1885,7 @@
 				bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(sender))->FailedPettingCougars++;
 				NewObjectiveSystemControl.Set_Objective_Status(25,NewObjectiveSystem::Failed);
 			}
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like one died, now the bet is off!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH60.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH60.mp3","Bear Rug Co: Looks like one died, now the bet is off!");
 		}
 	}
 	if (message == 2684270)
@@ -2004,8 +1911,7 @@
 				Commands->Set_Player_Type(mlion,1);
 				Commands->Attach_Script(mlion,"JMG_Bear_Hunter_Friendly_Cougar","1.0,10.0,1.0,1.5,3.0,0.0,3,6,5.0");
 			}
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: I don't believe it! Those cougars look like they're your best friends now!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH61.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH61.mp3","Bear Rug Co: I don't believe it! Those cougars look like they're your best friends now!");
 			NewObjectiveSystemControl.Set_Objective_Status(25,NewObjectiveSystem::Accomplished);
 			for (int x = 1;x < 128;x++)
 			{
@@ -2017,12 +1923,35 @@
 				{
 					Commands->Give_Points(player,50.0f*node->tmpPettedCougars,false);
 					node->BonusObjectivesCompleted++;
+					node->totalObjectivesCompleted++;
 				}
 			}
 		}
 		else
 			NewObjectiveSystemControl.Set_Objective_Mission(25,wildMountainLionsPet+12832,wildMountainLionsPet+12832);
 	}
+	if (message == 4588)
+	{
+		GameObject *cow = Commands->Find_Object(cowId);
+		if (!cow)
+			return;
+		char params[256];
+		sprintf(params,"%d,0.33,-1.0,1,1,1.0,0.0,1,0,0",cowWanderId);
+		Commands->Attach_Script(cow,"JMG_Bear_Hunter_AI_Guardian_Generic",params);
+	}
+	if (message == 4590)
+	{
+		NewObjectiveSystemControl.Set_Objective_Status(27,NewObjectiveSystem::Accomplished);
+		BearHunterVoiceSystem::PlayVoice("BH98.mp3","Bear Rug Co: Thanks for bringing the cow back, enjoy some fresh milk!");
+		for (int x = 1;x < 128;x++)
+		{
+			GameObject *player = Get_GameObj(x);
+			if (!player)
+				continue;
+			Grant_Weapon(player,"Weapon_Milk_Whole",true,500,true);
+			Grant_Weapon(player,"Weapon_Milk_2Percent",true,500,true);
+		}
+	}
 }
 void JMG_Bear_Hunter_Game_Control::Destroyed(GameObject *obj)
 {
@@ -2099,6 +2028,143 @@
 		JMG_Bear_Hunter_Oil_Rig::pumpJacks[x] = JMG_Bear_Hunter_Oil_Rig::PumpjackNode(0);
 	cleanUpNeeded = false;
 }
+void JMG_Bear_Hunter_Game_Control::CreateNormalAnimals(int normalBears)
+{
+	if ((int)(normalBears * 0.25f) > JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount)
+	{
+		bool deer = Commands->Get_Random(0.0f,1.0f) < 0.5f;
+		Vector3 pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(Commands->Get_Random(-500.0f,500.0f),Commands->Get_Random(-350.0f,500.0f)));
+		GameObject *ai = deer ? Commands->Create_Object("Normal_Buck_Twiddler",pos) : Commands->Create_Object("Normal_Squirrel_Twiddler",pos);
+		Commands->Set_Facing(ai,Commands->Get_Random(-180,180));
+		MoveablePhysClass *mphys = ai->As_PhysicalGameObj() ? ai->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+		if (mphys)
+			mphys->Find_Teleport_Location(pos,2.0f,&pos);
+		Commands->Set_Position(ai,pos);
+		if (deer)
+		{
+			Attach_Script_Once(ai,"JMG_Utility_AI_Skittish_Herd_Animal","0,4,511,25.0,30.0,50.0,200.0,6.0,24.0,10.0,25.0,0.1,0.125");
+			Commands->Set_Model(ai,Commands->Get_Random(0.0f,1.0f) < 0.25f ? "BUCK_" : "DOE_");
+		}
+		else
+			Attach_Script_Once(ai,"JMG_Bear_Hunter_AI_Avoid_Enemies","0");
+		Attach_Script_Once(ai,"JMG_Security_Camera_Behavior_Ignore","");
+		Attach_Script_Once(ai,"JMG_Bear_Hunter_Give_AI_Cash_For_Kills","");
+	}
+}
+void JMG_Bear_Hunter_Game_Control::CreateMouse()
+{
+	if (gameTime == 0 ? 5 : 1 > JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount)
+	{
+		Vector3 pos = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(751)->position;
+		GameObject *mouse = Commands->Create_Object("Normal_Mouse_Twiddler",pos);
+		Commands->Set_Facing(mouse,Commands->Get_Random(-180,180));
+		MoveablePhysClass *mphys = mouse->As_PhysicalGameObj() ? mouse->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+		if (mphys)
+			mphys->Find_Teleport_Location(pos,0.5f,&pos);
+		Commands->Set_Position(mouse,pos);
+		Attach_Script_Once(mouse,"JMG_Bear_Hunter_AI_Avoid_Enemies","1");
+		Attach_Script_Once(mouse,"JMG_Security_Camera_Behavior_Ignore","");
+		Commands->Set_Is_Visible(mouse,false);
+	}
+}
+void JMG_Bear_Hunter_Game_Control::CreateBears(int normalBears)
+{
+	for (int x = bearObjectListControl.objectCount;x < normalBears;x++)
+	{
+		Vector3 pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(Commands->Get_Random(-500.0f,500.0f),Commands->Get_Random(-350.0f,500.0f)));
+		bool isBlackBear = Commands->Get_Random(0.0f,1.0f) < 0.1 ? true : false;
+		GameObject *ai = isBlackBear ? Commands->Create_Object("Bear_Black_Twiddler",pos) : Commands->Create_Object("Normal_Bear_Twiddler",pos);
+		Commands->Set_Facing(ai,Commands->Get_Random(-180,180));
+		MoveablePhysClass *mphys = ai->As_PhysicalGameObj() ? ai->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+		if (mphys)
+			mphys->Find_Teleport_Location(pos,2.0f,&pos);
+		pos.Z += 0.1f;
+		Commands->Set_Position(ai,pos);
+		Commands->Set_Loiters_Allowed(ai,true);
+		ai->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
+		bearObjectListControl += ai;
+		Attach_Script_Once(ai,"JMG_Rp2_Hostile_Mutant_AI","");
+		Attach_Script_Once(ai,"JMG_Bear_Hunter_Bear_Tracker","");
+		Attach_Script_Once(ai,"JMG_Bear_Hunter_Animal_Control","");
+		Attach_Script_Once(ai,"JMG_Bear_Hunter_Player_Count_Scaled_Object_Health","");
+	}
+}
+void JMG_Bear_Hunter_Game_Control::SpawnAngryTinyDeer()
+{
+	if (JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue)
+	{
+		GameObject *statuePlayer = Get_GameObj(JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue);
+		if (statuePlayer)
+		{
+			int deerCount = JmgUtility::MathClampInt(3+(int)(0.33f*Get_Player_Count()),3,25);
+			for (int x = 0;x < deerCount;x++)
+			{
+				if (Commands->Find_Object(JMG_Bear_Hunter_Golden_Deer_Statue::tinyDeerIds[x]))
+					continue;
+				Vector3 playerPos = Commands->Get_Position(statuePlayer);
+				if (BearHunterGameControlSystem.getRandomPosition(&playerPos,500.0f,2500.0f))
+				{
+					GameObject *tinyDeer = Commands->Create_Object("Tiny_Deer_Twiddler",playerPos);
+					MoveablePhysClass *mphys = tinyDeer->As_PhysicalGameObj() ? tinyDeer->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+					if (mphys && !mphys->Can_Teleport(Matrix3D(playerPos)))
+					{
+						mphys->Find_Teleport_Location(playerPos,1.5f,&playerPos);
+						Commands->Set_Position(tinyDeer,playerPos);
+					}
+					tinyDeer->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
+					Commands->Attach_Script(tinyDeer,"JMG_Utility_AI_Goto_Player","-1.0,1.0,1.0,0.0,0,1.0,-1.0,0.0,0,1.0,-1,1.0,1,0,0.0");
+					Commands->Attach_Script(tinyDeer,"JMG_Bear_Hunter_Kill_Score_Tracker","");
+					JMG_Bear_Hunter_Golden_Deer_Statue::tinyDeerIds[x] = Commands->Get_ID(tinyDeer);
+				}
+			}
+		}
+	}
+}
+void JMG_Bear_Hunter_Game_Control::CreateCow()
+{
+	cowWanderId = Commands->Get_Random(0.0f,1.0f) < 0.5f ? 4588 : 4589;
+	Rp2SimplePositionSystem::SimplePositionNode *node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(cowWanderId);
+	if (!node)
+		return;
+	GameObject *cow = Commands->Create_Object("Cow_Twiddler",node->position);
+	Vector3 pos = node->position;
+	MoveablePhysClass *mphys = cow->As_PhysicalGameObj() ? cow->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+	if (mphys)
+		mphys->Find_Teleport_Location(pos,2.0f,&pos);
+	char params[256];
+	sprintf(params,"%d,0.33,-1.0,1,1,1.0,0.0,1,0,0",cowWanderId);
+	Commands->Attach_Script(cow,"JMG_Bear_Hunter_AI_Guardian_Generic",params);
+	cowId = Commands->Get_ID(cow);
+}
+void JMG_Bear_Hunter_Game_Control::TriggerCowObjective()
+{
+	if (NewObjectiveSystemControl.Get_Objective_Status(27) != NewObjectiveSystem::NotDefined)
+		return;
+	GameObject *cow = Commands->Find_Object(cowId);
+	if (!cow)
+		return;
+	for (int x = 1;x < 128;x++)
+	{
+		GameObject *player = Get_GameObj(x);
+		if (!player)
+			continue;
+		player = Get_Vehicle(player) ? Get_Vehicle(player) : player;
+		SmartGameObj *object = player->As_SmartGameObj();
+		if (object->Is_Visible() && cow->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;
+			}
+			BearHunterVoiceSystem::PlayVoice("BH97.mp3","Bear Rug Co: Hey there's our lost cow! Why not give it a poke and lead it back to the barn!");
+			IncreaseBonusObjectiveCount(27);
+			NewObjectiveSystemControl.Add_Objective(27,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12857,"",12857,bonusObjectiveCount);
+			return;
+		}
+	}
+}
 GameObject *JMG_Bear_Hunter_Game_Control::MutantSpawn()
 {
 	const char *preset;
@@ -2129,7 +2195,7 @@
 	pos.Z += 1.0f;
 	Commands->Set_Position(ai,pos);
 	float maxHealth = Commands->Get_Max_Health(ai);
-	float calculatedHealth = JmgUtility::MathClamp(maxHealth*(GamePercent*0.5f+Commands->Get_Random(0.0f,0.1f)),1.0f,maxHealth*(0.055f+(Get_Player_Count()-1)*0.0375f));//0.025 easy
+	float calculatedHealth = JmgUtility::MathClamp(maxHealth*(GamePercent*0.5f+Commands->Get_Random(0.0f,0.1f)),1.0f,maxHealth*(0.06f+(Get_Player_Count()-1)*0.0375f));//0.025 easy
 	Commands->Set_Health(ai,calculatedHealth);
 	Set_Max_Health(ai,calculatedHealth*2.0f);
 	mutantObjectListControl += ai;
@@ -2140,6 +2206,7 @@
 	Attach_Script_Once(ai,"JMG_Bear_Hunter_Mutant_Tracker","");
 	Attach_Script_Once(ai,"JMG_Bear_Hunter_Animal_Control","");
 	Attach_Script_Once(ai,"JMG_Bear_Hunter_Mutant_Respawn_Tracker","");
+	Attach_Script_Once(ai,"JMG_Bear_Hunter_Give_AI_Cash_For_Kills","");
 	return ai;
 }
 void JMG_Bear_Hunter_Game_Control::IncreaseBonusObjectiveCount(int objectiveId)
@@ -2498,7 +2565,9 @@
 	if (ignoreEnemies)
 	{
 		int presetId = Commands->Get_Preset_ID(seen);
-		if (presetId != 1000001055 && presetId != 1000001536 && presetId != 1000001688 && presetId != 1000001690 && presetId != 1000001820)
+		if (JMG_Bear_Hunter_Game_Control::gameTime < 60 && presetId != 1000001040)
+			return;
+		if (presetId != 1000001055 && presetId != 1000001536 && presetId != 1000001688 && presetId != 1000001690 && presetId != 1000001820 && presetId != 1000001040)
 			return;
 	}
 	if (myNode->enemyId && JmgUtility::SimpleDistance(Commands->Get_Position(seen),Commands->Get_Position(obj)) > targetDistance)
@@ -2559,6 +2628,8 @@
 		float dist = JmgUtility::SimpleDistance(pos,lastPos);
 		if (dist < 1.0f)	
 		{
+			if (targetDistance < 100.0f && myNode->enemyId && (myNode->enemyId == JMG_Bear_Hunter_Game_Control::mutantTargetId || myNode->enemyId == JMG_Bear_Hunter_Radio_Tower_Switch::supportId || myNode->enemyId == JMG_Bear_Hunter_Power_Transformer::mctIds[0] || myNode->enemyId == JMG_Bear_Hunter_Power_Transformer::mctIds[1]))
+				stuckCount = 0;
 			stuckCount++;
 			if (stuckCount == 25)
 			{
@@ -2712,7 +2783,7 @@
 			for (int x = 0;x < maxTotalBearHunterPlayerAssistAI;x++)
 				if (JMG_Bear_Hunter_Player_Assist_AI::aiIds[x] == killerId)
 				{
-					JMG_Bear_Hunter_Player_Assist_AI::cash[x] += Get_Death_Points(obj)*2.0f;
+					JMG_Bear_Hunter_Player_Assist_AI::cash[x] += Get_Death_Points(obj)+Get_Damage_Points(obj)*Get_Max_Hitpoints(obj);
 					break;
 				}
 		return;
@@ -2722,15 +2793,17 @@
 	if (!(PlayerData.players[playerId]->killsWithoutGettingHurt % 10))
 	{
 		Commands->Give_Money(killer,50.0f,false);
-		JmgUtility::DisplayChatMessage(killer,11,194,255,"Bear Rug Co: Congrats, here's your $50.00 for 10 in a row without being hurt!");
-		Create_2D_Wave_Sound_Dialog_Player(killer,"BH62.mp3");
+		if (!PlayerData.players[playerId]->hasAnnounced10)
+			BearHunterVoiceSystem::PlayVoice(killer,"BH62.mp3","Bear Rug Co: Congrats, here's your $50.00 for 10 in a row without being hurt!");
+		else
+			JmgUtility::DisplayChatMessage(killer,127,127,255,"Bear Rug Co: Congrats, here's your $50.00 for 10 in a row without being hurt!");
+		PlayerData.players[playerId]->hasAnnounced10 = true;
 	}
 	PlayerData.players[playerId]->totalKills++;
 	if (!(PlayerData.players[playerId]->totalKills % 250))
 	{
 		Commands->Give_Money(killer,1000.0f,false);
-		JmgUtility::DisplayChatMessage(killer,11,194,255,"Bear Rug Co: $1,000.00 for the 250 pelts!");
-		Create_2D_Wave_Sound_Dialog_Player(killer,"BH63.mp3");
+		BearHunterVoiceSystem::PlayVoice(killer,"BH63.mp3","Bear Rug Co: $1,000.00 for the 250 pelts!");
 	}
 	if (Commands->Get_Random(0.0f,1.0f) < (1.0f-JMG_Bear_Hunter_Game_Control::gameTime/2000.0f)*0.25f)
 	{
@@ -2761,10 +2834,12 @@
 }
 void JMG_Security_Camera_Behavior::Created(GameObject *obj)
 {
-	zHeight = 1000.0f*tan(Get_Float_Parameter("IdleAimZAngleModifier")*PI180);
+	updateRateMultiplier = Get_Float_Parameter("UpdateRateMultiplier");
+	idleZAimAngleModifier = Get_Float_Parameter("IdleAimZAngleModifier");
 	enabled = true;
 	SeenTime = 0;
 	EnemyID = 0;
+	scanAngle = Get_Float_Parameter("Angle");
 	GameObject *floodLight = Commands->Create_Object_At_Bone(obj,Get_Parameter("FloodLightPreset"),"headlight00");
 	if (floodLight)
 	{
@@ -2886,43 +2961,16 @@
 {
 	if (number == 1)
 	{
-		Vector3 Pos = Commands->Get_Position(obj);
-		float Angle = Get_Float_Parameter("Angle")/4;
-		float Facing = Commands->Get_Facing(obj);
+		zHeight = 1000.0f*tan(idleZAimAngleModifier*PI180);
+		float Angle = scanAngle/4;
 		CameraFacingCount = 0;
 		IncreaseOrDecreaseCount = 1;
-		Facing = Facing-(Angle*2);
-		float ZPos = Commands->Get_Bone_Position(obj,"MuzzleA0").Z+zHeight;
-		float TempFacing = Facing*PI180;
-		CameraFacingLocation[0] = Pos;
-		CameraFacingLocation[0].X += cos(TempFacing)*1000.0f;
-		CameraFacingLocation[0].Y += sin(TempFacing)*1000.0f;
-		CameraFacingLocation[0].Z = ZPos;
-		Facing = Facing+Angle;
-		TempFacing = Facing*PI180;
-		CameraFacingLocation[1] = Pos;
-		CameraFacingLocation[1].X += cos(TempFacing)*1000.0f;
-		CameraFacingLocation[1].Y += sin(TempFacing)*1000.0f;
-		CameraFacingLocation[1].Z = ZPos;
-		Facing = Facing+Angle;
-		TempFacing = Facing*PI180;
-		CameraFacingLocation[2] = Pos;
-		CameraFacingLocation[2].X += cos(TempFacing)*1000.0f;
-		CameraFacingLocation[2].Y += sin(TempFacing)*1000.0f;
-		CameraFacingLocation[2].Z = ZPos;
-		Facing = Facing+Angle;
-		TempFacing = Facing*PI180;
-		CameraFacingLocation[3] = Pos;
-		CameraFacingLocation[3].X += cos(TempFacing)*1000.0f;
-		CameraFacingLocation[3].Y += sin(TempFacing)*1000.0f;
-		CameraFacingLocation[3].Z = ZPos;
-		Facing = Facing+Angle;
-		TempFacing = Facing*PI180;
-		CameraFacingLocation[4] = Pos;
-		CameraFacingLocation[4].X += cos(TempFacing)*1000.0f;
-		CameraFacingLocation[4].Y += sin(TempFacing)*1000.0f;
-		CameraFacingLocation[4].Z = ZPos;
-		CameraFacingUpdateTime = Get_Float_Parameter("Angle")*0.0085f;
+		cameraAngles[0] = -(Angle*2);
+		cameraAngles[1] = -Angle;
+		cameraAngles[2] = 0.0f;
+		cameraAngles[3] = Angle;
+		cameraAngles[4] = (Angle*2);
+		CameraFacingUpdateTime = scanAngle*0.0085f;
 		if (CameraFacingUpdateTime < 0.01f)
 			CameraFacingUpdateTime = 0.1f;
 		Commands->Start_Timer(obj,this,0.0f,2);
@@ -2932,7 +2980,7 @@
 	{
 		if (!enabled)
 		{
-			Commands->Start_Timer(obj,this,CameraFacingUpdateTime*Get_Float_Parameter("UpdateRateMultiplier"),2);
+			Commands->Start_Timer(obj,this,CameraFacingUpdateTime*updateRateMultiplier,2);
 			return;
 		}
 		if (!EnemyID)
@@ -2942,10 +2990,15 @@
 				IncreaseOrDecreaseCount = IncreaseOrDecreaseCount == -1 ? 1 : -1;
 			ActionParamsStruct params;
 			params.Set_Basic(this,70,1);
-			params.Set_Attack(CameraFacingLocation[CameraFacingCount],0,0,true);
+			Vector3 pos = Commands->Get_Position(obj);
+			float tempFacing = (Commands->Get_Facing(obj)+cameraAngles[CameraFacingCount])*PI180;
+			pos.X += cos(tempFacing)*1000.0f;
+			pos.Y += sin(tempFacing)*1000.0f;
+			pos.Z = Commands->Get_Bone_Position(obj,"MuzzleA0").Z+zHeight;
+			params.Set_Attack(pos,0,0,true);
 			Commands->Action_Attack(obj,params);
 		}
-		Commands->Start_Timer(obj,this,CameraFacingUpdateTime*Get_Float_Parameter("UpdateRateMultiplier"),2);
+		Commands->Start_Timer(obj,this,CameraFacingUpdateTime*updateRateMultiplier,2);
 	}
 	if (number == 3)
 	{
@@ -3051,8 +3104,7 @@
 		if (playerScoreNode)
 			playerScoreNode->ReturnedMedicalNeedle++;
 		JmgUtility::DisplayChatMessage(poker,200,127,255,"President of Corporate America: Thanks!");
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Good work, he'll feel better in no time!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH64.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH64.mp3","Bear Rug Co: Good work, he'll feel better in no time!");
 		for (int x = 1;x < 128;x++)
 		{
 			GameObject *player = Get_GameObj(x);
@@ -3060,6 +3112,8 @@
 				continue;
 			Commands->Give_Money(player,100.0f,0);
 		}
+		for (int x = 0;x < maxTotalBearHunterPlayerAssistAI;x++)
+			JMG_Bear_Hunter_Player_Assist_AI::cash[x] += 100.0f;
 		const char *weap = Get_Current_Weapon(poker);
 		if (weap && !_stricmp(weap,"Weapon_Needle"))
 			Commands->Select_Weapon(poker,"Weapon_Pistol_Player");
@@ -3145,8 +3199,7 @@
 	Commands->Fade_Background_Music("Unreal2_awakening_Hell_Ending.mp3",1000,1000);
 	for (int x = 0;x < 5;x++)
 		Commands->Create_Sound("Mission_Failed",Commands->Get_Position(obj),obj);
-	JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We lost the President of Corporate America, our company is screwed!");
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH96.mp3");
+	BearHunterVoiceSystem::PlayVoice("BH96.mp3","Bear Rug Co: We lost the President of Corporate America, our company is screwed!");
 }
 void JMG_Bear_Hunter_President_Controller::damagedEvent(GameObject *obj,bool announce,bool palyinjured)
 {
@@ -3301,8 +3354,7 @@
 	bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(killer))->KilledTurrets++;
 	if (JMG_Bear_Hunter_Game_Control::gameTime && JMG_Bear_Hunter_Game_Control::gameTime <= 700 && JMG_Bear_Hunter_Game_Control::objective != 12698 && JMG_Bear_Hunter_Game_Control::turretsDestroyed >= 5)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: This is just getting to be too much, fall back and defend the President of Corporate America!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH65.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH65.mp3","Bear Rug Co: This is just getting to be too much, fall back and defend the President of Corporate America!");
 		NewObjectiveSystemControl.Remove_Objective(5);
 		JmgUtility::SetHUDHelpText(JMG_Bear_Hunter_Game_Control::objective,Vector3(0,1,0));
 		JMG_Bear_Hunter_Game_Control::objective = 12698;
@@ -3313,8 +3365,7 @@
 	float compassDegree = atan2(turretPos.Y,turretPos.X)* (180.0f / PI);
 	char turretMsg[220];
 	sprintf(turretMsg,"Bear Rug Co: The %s Cannon Emplacement was destroyed!",JmgUtility::JMG_Get_Compass_Directions(compassDegree));
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH66.mp3");
-	JmgUtility::MessageAllPlayers(127,127,255,turretMsg);
+	BearHunterVoiceSystem::PlayVoice("BH66.mp3",turretMsg);
 }
 void JMG_Powerup_Grant_Cash::Custom(GameObject *obj,int message,int param,GameObject *sender)
 {
@@ -3335,7 +3386,12 @@
 {
 	if (message == CUSTOM_EVENT_POWERUP_GRANTED)
 	{
-		JMG_Bear_Hunter_Player_Soldier::hasGrenadeVest[JmgUtility::JMG_Get_Player_ID(sender)] = true;
+		int playerId = JmgUtility::JMG_Get_Player_ID(sender);
+		if (JMG_Bear_Hunter_Player_Soldier::hasGrenadeVest[playerId])
+			return;
+		BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(playerId);
+		node->C4VestPowerups++;
+		JMG_Bear_Hunter_Player_Soldier::hasGrenadeVest[playerId] = true;
 	}
 }
 void JMG_Powerup_Grant_Weapon_Clips::Custom(GameObject *obj,int message,int param,GameObject *sender)
@@ -3559,6 +3615,7 @@
 }
 void JMG_Bear_Hunter_Security_Turret_Truck::Created(GameObject *obj)
 {
+	driver = NULL;
 	firstTime = true;
 	Commands->Set_Animation(obj,"v_secPartsTruck.v_secPartsTruck",false,0,0.0f,0.0f,false);
 	if (JMG_Bear_Hunter_Game_Control::gameTime && JMG_Bear_Hunter_Game_Control::gameTime > 775)
@@ -3573,27 +3630,22 @@
 }
 void JMG_Bear_Hunter_Security_Turret_Truck::Custom(GameObject *obj,int message,int param,GameObject *sender)
 {
-	if (Get_Vehicle_Driver(obj) && Get_Vehicle_Driver(obj) != sender)
-		return;
-	if (message == CUSTOM_EVENT_VEHICLE_EXITED && !Get_Vehicle_Driver(obj) && (JMG_Bear_Hunter_Game_Control::gameTime > 775 || JMG_Bear_Hunter_Game_Control::truckTimeExtended) && !JMG_Bear_Hunter_Game_Control::hasGotTurrets)
+	if (message == CUSTOM_EVENT_VEHICLE_EXITED && driver == sender && (JMG_Bear_Hunter_Game_Control::gameTime > 775 || JMG_Bear_Hunter_Game_Control::truckTimeExtended) && !JMG_Bear_Hunter_Game_Control::hasGotTurrets)
 	{
+		driver = NULL;
 		NewObjectiveSystemControl.Set_Radar_Blip(10,obj);
-		JmgUtility::DisplayChatMessage(sender,127,127,255,"Bear Rug Co: Get back in that truck, it must make it back to base!");
-		Create_2D_Wave_Sound_Dialog_Player(sender,"BH67.mp3");
+		BearHunterVoiceSystem::PlayVoice(sender,"BH67.mp3","Bear Rug Co: Get back in that truck, it must make it back to base!");
 	}
-	if (message == CUSTOM_EVENT_VEHICLE_ENTERED && (JMG_Bear_Hunter_Game_Control::gameTime > 775 || JMG_Bear_Hunter_Game_Control::truckTimeExtended) && !JMG_Bear_Hunter_Game_Control::hasGotTurrets)
+	if (message == CUSTOM_EVENT_VEHICLE_ENTERED && (JMG_Bear_Hunter_Game_Control::gameTime > 775 || JMG_Bear_Hunter_Game_Control::truckTimeExtended) && !JMG_Bear_Hunter_Game_Control::hasGotTurrets && Get_Vehicle_Driver(obj) == sender)
 	{
+		driver = sender;
 		if (NewObjectiveSystemControl.Get_Objective_Status(14) == NewObjectiveSystem::Pending)
 		{
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Looks like you forgot to deactivate the alarm, get out of there while you can, we're tracking lots of movement in the swamp!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH68.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH68.mp3","Bear Rug Co: Looks like you forgot to deactivate the alarm, get out of there while you can, we're tracking lots of movement in the swamp!");
 			NewObjectiveSystemControl.Set_Objective_Status(14,NewObjectiveSystem::Failed);
 		}
 		else
-		{
-			JmgUtility::DisplayChatMessage(sender,127,127,255,"Bear Rug Co: Now all you have to do is return the truck to base.");
-			Create_2D_Wave_Sound_Dialog_Player(sender,"BH69.mp3");
-		}
+			BearHunterVoiceSystem::PlayVoice(sender,"BH69.mp3","Bear Rug Co: Now all you have to do is return the truck to base.");
 		if (firstTime)
 		{
 			firstTime = false;
@@ -3601,7 +3653,8 @@
 			Commands->Send_Custom_Event(obj,switchObj,5911911,60,0);
 		}
 		NewObjectiveSystemControl.Set_Objective_Status(9,NewObjectiveSystem::Accomplished);
-		NewObjectiveSystemControl.Add_Objective(10,NewObjectiveSystem::Tertiary,NewObjectiveSystem::Pending,12722,"",12722,JMG_Bear_Hunter_Game_Control::centerOfBase);
+		if (NewObjectiveSystemControl.Get_Objective_Status(10) == NewObjectiveSystem::NotDefined)
+			NewObjectiveSystemControl.Add_Objective(10,NewObjectiveSystem::Tertiary,NewObjectiveSystem::Pending,12722,"",12722,JMG_Bear_Hunter_Game_Control::centerOfBase);
 		NewObjectiveSystemControl.Set_Radar_Blip(10,JMG_Bear_Hunter_Game_Control::centerOfBase);
 	}
 }
@@ -3614,10 +3667,7 @@
 		NewObjectiveSystemControl.Remove_Objective(9);
 		NewObjectiveSystemControl.Remove_Objective(10);
 		if (JMG_Bear_Hunter_Game_Control::gameTime > 775)
-		{
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The truck was lost, GMax will pull another one out of the storage area for you shortly.");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH70.mp3");
-		}
+			BearHunterVoiceSystem::PlayVoice("BH70.mp3","Bear Rug Co: The truck was lost, GMax will pull another one out of the storage area for you shortly.");
 	}
 }
 void JMG_Bear_Hunter_Security_Turret_Truck_Zone::Entered(GameObject *obj,GameObject *enterer)
@@ -3637,11 +3687,10 @@
 	}
 	if (JMG_Bear_Hunter_Game_Control::gameTime <= 775 && !JMG_Bear_Hunter_Game_Control::truckTimeExtended)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Thanks for the turret parts, its too late for us to set up the turrets, but here is your payment!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH71.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH71.mp3","Bear Rug Co: Thanks for the turret parts, its too late for us to set up the turrets, but here is your payment!");
 		return;
 	}
-	GameObject *driver = Get_Vehicle_Driver(obj);
+	GameObject *driver = Get_Vehicle_Driver(enterer);
 	if (driver)
 	{
 		Commands->Give_Points(driver,250.0f,false);
@@ -3662,8 +3711,7 @@
 	turret = Commands->Create_Object("CnC_Ramjet_Camera",Vector3(50.220f,-701.401f,11.333f));
 	Commands->Set_Facing(turret,90.0f);
 	Commands->Attach_Script(turret,"JMG_AI_Ignore_Object","");
-	JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Thanks for the turret parts, we'll get these up right away, and here is your payment!");
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH72.mp3");
+	BearHunterVoiceSystem::PlayVoice("BH72.mp3","Bear Rug Co: Thanks for the turret parts, we'll get these up right away, and here is your payment!");
 }
 void JMG_Bear_Hunt_Final_Boss::Created(GameObject *obj)
 {
@@ -3921,8 +3969,7 @@
 	else
 		sprintf(deathMsg,"A %s killed the giant Mutant Rabbit!",Get_Translated_Preset_Name(killer));
 	JmgUtility::MessageAllPlayers(255,255,0,deathMsg);
-	JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We did it, the President of Corporate America has been saved!");
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH73.mp3");
+	BearHunterVoiceSystem::PlayVoice("BH73.mp3","Bear Rug Co: We did it, the President of Corporate America has been saved!");
 	NewObjectiveSystemControl.Set_Objective_Status(8,NewObjectiveSystem::Accomplished);
 }
 void JMG_Bear_Hunt_Final_Boss::AttackTarget(GameObject *obj,GameObject *target,float distance)
@@ -5452,6 +5499,7 @@
 }
 void JMG_Bear_Hunter_Oil_Rig::Created(GameObject *obj)
 {
+	intPayout = 0.0f;
 	Commands->Enable_HUD_Pokable_Indicator(obj,true);
 	active = false;
 	char animation[32];
@@ -5478,12 +5526,21 @@
 		float frame = Get_Animation_Frame(obj);
 		if (active && frame >= Get_Float_Parameter("StartFrame") && frame <= Get_Float_Parameter("EndFrame"))
 		{
+			bool giveMoneyScore = false;
+			intPayout += 0.15f;
+			if (intPayout > 1.0f)
+			{
+				intPayout -= 1.0f;
+				giveMoneyScore = true;
+			}
 			for (int x = 1;x < 128;x++)
 			{
 				GameObject *player = Get_GameObj(x);
 				if (!player)
 					continue;
 				Commands->Give_Money(player,0.15f,false);
+				if (giveMoneyScore)
+					bearHunterScoreSystem.Get_Current_Player_Score_Node(x)->PumpJackMoney++;
 			}
 			for (int x = 0;x < maxTotalBearHunterPlayerAssistAI;x++)
 				JMG_Bear_Hunter_Player_Assist_AI::cash[x] += 0.3f;
@@ -5512,16 +5569,14 @@
 			oilRigCount++;
 			if (oilRigCount == 7)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: That's all of the pump jacks in the North East, great work guys!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH74.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH74.mp3","Bear Rug Co: That's all of the pump jacks in the North East, great work guys!");
 				NewObjectiveSystemControl.Set_Objective_Status(16,NewObjectiveSystem::Accomplished);
 			}
 			else
 			{
 				char successMsg[220];
 				sprintf(successMsg,"Bear Rug Co: Oil rig on-line, %d more to go.",7-oilRigCount);
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH75.mp3");
-				JmgUtility::MessageAllPlayers(127,127,255,successMsg);
+				BearHunterVoiceSystem::PlayVoice("BH75.mp3",successMsg);
 				unsigned int textId = (oilRigCount == 1) ? 12752 : (oilRigCount == 2) ? 12753 : (oilRigCount == 3) ? 12754 : (oilRigCount == 4) ? 12755 : 12756;
 				NewObjectiveSystemControl.Set_Objective_Mission(16,textId,textId);
 			}
@@ -5559,13 +5614,16 @@
 		char params[256];
 		sprintf(params,"%s,%.2f,%.2f,1",Get_Parameter("RepairAnimation"),Get_Float_Parameter("StartFrame"),Get_Float_Parameter("EndFrame"));
 		Commands->Attach_Script(rig,"JMG_Bear_Hunter_Oil_Rig",params);
-		BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(obj));
+		BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(damager));
 		if (NewObjectiveSystemControl.Set_Objective_Status(17,NewObjectiveSystem::Accomplished))
 		{
 			Commands->Give_Points(damager,250.0f,false);
-			node->BonusObjectivesCompleted++;
-			node->totalObjectivesCompleted++;
-			node->ObjectiveOilRigsRepaired++;
+			if (node)
+			{
+				node->BonusObjectivesCompleted++;
+				node->totalObjectivesCompleted++;
+				node->ObjectiveOilRigsRepaired++;
+			}
 		}
 		Commands->Destroy_Object(obj);
 	}
@@ -5618,7 +5676,7 @@
 			else
 				NewObjectiveSystemControl.Set_Objective_Mission(18,12772+count,12772+count);
 			Commands->Give_Points(sender,250.0f,false);
-			BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(obj));
+			BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(sender));
 			node->BonusObjectivesCompleted++;
 			node->totalObjectivesCompleted++;
 			node->ObjectiveWeaponsFound++;
@@ -5660,27 +5718,13 @@
 			JMG_Bear_Hunter_Game_Control::weaponsUnlocked[Get_Int_Parameter("WeaponType")] = true;
 			switch (Get_Int_Parameter("WeaponType"))
 			{
-			case 0:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Good work, all infantry classes now have a Twin Bow.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH76.mp3");break;
-			case 1:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Great work, basic infantry now have a 10 gauge shotgun.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH77.mp3");break;
-			case 2:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Due to your hard work, tier 1 infantry now have a FAMAS F1.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH78.mp3");break;
-			case 3:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Excellent work, tier 2 infantry now have the PPSh-41!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH79.mp3");break;
-			case 4:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Enjoy the AA12 with your tier 3 infantry!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH80.mp3");break;
-			case 5:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Engineers can now use the Beretta 93R.");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH81.mp3");break;
-			case 6:
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Wow, looks like that deer had blueprints for a plasma rifle, all classes now have a plasma rifle!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH82.mp3");break;
+			case 0:BearHunterVoiceSystem::PlayVoice("BH76.mp3","Bear Rug Co: Good work, all infantry classes now have a Twin Bow.");break;
+			case 1:BearHunterVoiceSystem::PlayVoice("BH77.mp3","Bear Rug Co: Great work, basic infantry now have a 10 gauge shotgun.");break;
+			case 2:BearHunterVoiceSystem::PlayVoice("BH78.mp3","Bear Rug Co: Due to your hard work, tier 1 infantry now have a FAMAS F1.");break;
+			case 3:BearHunterVoiceSystem::PlayVoice("BH79.mp3","Bear Rug Co: Excellent work, tier 2 infantry now have the PPSh-41!");break;
+			case 4:BearHunterVoiceSystem::PlayVoice("BH80.mp3","Bear Rug Co: Enjoy the AA12 with your tier 3 infantry!");break;
+			case 5:BearHunterVoiceSystem::PlayVoice("BH81.mp3","Bear Rug Co: Engineers can now use the Beretta 93R.");break;
+			case 6:BearHunterVoiceSystem::PlayVoice("BH82.mp3","Bear Rug Co: Wow, looks like that deer had blueprints for a plasma rifle, all classes now have a plasma rifle!");break;
 			}
 			if (Get_Int_Parameter("WeaponType") < 6)
 			{
@@ -5786,8 +5830,7 @@
 	}
 	if (NewObjectiveSystemControl.Get_Objective_Status(20) == NewObjectiveSystem::NotDefined)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: That deer looks real exotic, kill it so we can stuff it and sell it for big bucks!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH83.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH83.mp3","Bear Rug Co: That deer looks real exotic, kill it so we can stuff it and sell it for big bucks!");
 		JMG_Bear_Hunter_Game_Control::IncreaseBonusObjectiveCount(20);
 		NewObjectiveSystemControl.Add_Objective(20,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12786,"",12786,JMG_Bear_Hunter_Game_Control::bonusObjectiveCount);
 	}
@@ -5911,16 +5954,14 @@
 	currentTargetID = Commands->Get_ID(damager);
 	if (NewObjectiveSystemControl.Get_Objective_Status(20) == NewObjectiveSystem::NotDefined)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: That deer looks real exotic, kill it so we can stuff it and sell it for big bucks!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH83.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH83.mp3","Bear Rug Co: That deer looks real exotic, kill it so we can stuff it and sell it for big bucks!");
 		JMG_Bear_Hunter_Game_Control::IncreaseBonusObjectiveCount(20);
 		NewObjectiveSystemControl.Add_Objective(20,NewObjectiveSystem::Bonus,NewObjectiveSystem::Pending,12786,"",12786,JMG_Bear_Hunter_Game_Control::bonusObjectiveCount);
 	}
 }
 void JMG_Bear_Hunter_Giant_Deer_Boss::Killed(GameObject *obj,GameObject *killer)
 {
-	JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Good work killing that exotic deer, grab its carcass and return it to base and we'll swap it out for a new weapon prototype we're working on.");
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH84.mp3");
+	BearHunterVoiceSystem::PlayVoice("BH84.mp3","Bear Rug Co: Good work killing that exotic deer, grab its carcass and return it to base and we'll swap it out for a new weapon prototype we're working on.");
 	NewObjectiveSystemControl.Set_Objective_Mission(20,12787,12787);
 	Commands->Give_Points(killer,250.0f,false);
 	Vector3 pos = Commands->Get_Position(obj);
@@ -5994,6 +6035,8 @@
 		case 1000003643:case 1000003647:case 1000003649:node->FriendlyTinyDeerKilled++;break;
 		case 1000000024:case 1000000026:case 1000000028:Commands->Get_Player_Type(obj) == 1 ? node->KilledFriendlyCougars++ : node->KilledCougars++;break;
 		case 1000000036:case 1000000038:case 1000000040:node->KilledMutantCougars++;break;
+		case 1000000249:case 1000000263:case 1000000265:node->KilledCows++;break;
+		case 1000000260:case 1000000267:case 1000000269:node->KilledMice++;break;
 		default:node->KilledHumanAi++;break;
 		}
 }
@@ -6023,8 +6066,6 @@
 			node->PickedupAmmoTotal += Get_Int_Parameter("Value");
 			break;
 		case 4:
-			if (!JMG_Bear_Hunter_Player_Soldier::hasGrenadeVest[JmgUtility::JMG_Get_Player_ID(sender)])
-				node->C4VestPowerups++;
 			break;
 		}
 	}
@@ -6108,21 +6149,18 @@
 		return;
 	if (NewObjectiveSystemControl.Get_Objective_Status(21) == NewObjectiveSystem::NotDefined)
 	{
-		JmgUtility::DisplayChatMessage(poker,127,127,255,"Bear Rug Co: We have the Communications Tower turned off right now because it was scaring off the wild life, leave it off!");
-		Create_2D_Wave_Sound_Dialog_Player(poker,"BH85.mp3");
+		BearHunterVoiceSystem::PlayVoice(poker,"BH85.mp3","Bear Rug Co: We have the Communications Tower turned off right now because it was scaring off the wild life, leave it off!");
 		return;
 	}
 	if (NewObjectiveSystemControl.Get_Objective_Status(21) == NewObjectiveSystem::Removed)
 	{
-		JmgUtility::DisplayChatMessage(poker,127,127,255,"Bear Rug Co: Sorry, its too late to activate the tower now.");
-		Create_2D_Wave_Sound_Dialog_Player(poker,"BH86.mp3");
+		BearHunterVoiceSystem::PlayVoice(poker,"BH86.mp3","Bear Rug Co: Sorry, its too late to activate the tower now.");
 		return;
 	}
 	Commands->Enable_HUD_Pokable_Indicator(obj,false);
 	Commands->Give_Points(obj,250.0f,false);
 	activated = true;
-	JmgUtility::MessageAllPlayers(127,127,255,"Distress Signal Transmission In Progress: We are under attack by mutants on Bear Island and we need help!");
-	JmgUtility::Create_2D_Wave_Sound_Dialog("BH87.mp3");
+	BearHunterVoiceSystem::PlayVoice("BH87.mp3","Distress Signal Transmission In Progress: We are under attack by mutants on Bear Island and we need help!");
 	NewObjectiveSystemControl.Set_Objective_Mission(21,12790,12790);
 	Commands->Set_Animation(obj,"RdoTwrSwtch.RdoTwrSwtch",false,0,2.0f,2.0f,false);
 	GameObject *radioTower = Commands->Find_Object(JMG_Bear_Hunter_Radio_Tower_Control::radioTowerId);
@@ -6145,8 +6183,7 @@
 	Commands->Send_Custom_Event(radioTower,radioTower,57459,0,5.0f);
 	if (NewObjectiveSystemControl.Get_Objective_Status(21) != NewObjectiveSystem::Accomplished)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The communications tower was destroyed before anyone could answer our distress call!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH88.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH88.mp3","Bear Rug Co: The communications tower was destroyed before anyone could answer our distress call!");
 		NewObjectiveSystemControl.Set_Objective_Status(21,NewObjectiveSystem::Failed);
 	}
 	JMG_Bear_Hunter_Radio_Tower_Control::destroyed = true;
@@ -6505,7 +6542,7 @@
 }
 void JMG_Bear_Hunter_Camera_Behavior::Created(GameObject *obj)
 {
-	playerScoreNode = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(obj));
+	playerScoreNode = bearHunterScoreSystem.Get_Current_Player_Score_Node(Get_Int_Parameter("PlayerID"));
 	if (playerScoreNode)
 		playerScoreNode->SentryTurretsPlaced++;
 	enabled = true;
@@ -6752,8 +6789,7 @@
 {
 	if (NewObjectiveSystemControl.Get_Objective_Status(22) == NewObjectiveSystem::NotDefined)
 	{
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The President of Corporate America had some painkillers called in, they airdropped them outside of DERP, we'll pay everyone $100.00 if you can return it to him.");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH89.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH89.mp3","Bear Rug Co: The President of Corporate America had some painkillers called in, they airdropped them outside of DERP, we'll pay everyone $100.00 if you can return it to him.");
 		NewObjectiveSystemControl.Add_Objective(22,NewObjectiveSystem::Tertiary,NewObjectiveSystem::Pending,12795,"",12795,obj);
 	}
 	else
@@ -6820,8 +6856,7 @@
 		{
 			if (Commands->Get_Shield_Strength(obj) < 490.0f)
 			{
-				JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The power substation is under attack, defend it or else we'll lose power to the turrets!");
-				JmgUtility::Create_2D_Wave_Sound_Dialog("BH90.mp3");
+				BearHunterVoiceSystem::PlayVoice("BH90.mp3","Bear Rug Co: The power substation is under attack, defend it or else we'll lose power to the turrets!");
 				NewObjectiveSystemControl.Add_Objective(23,NewObjectiveSystem::Secondary,NewObjectiveSystem::Pending,12798,"",12798,obj);
 				objectiveActive = true;
 			}
@@ -6829,8 +6864,7 @@
 		}
 		else if (difftime(clock(),announceTime) > 10000.0f)
 		{
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The power substation is still under attack!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH91.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH91.mp3","Bear Rug Co: The power substation is still under attack!");
 			announceTime = clock();
 		}
 	if (JMG_Bear_Hunter_Power_Transformer::online && !Commands->Get_Shield_Strength(obj))
@@ -6841,8 +6875,7 @@
 		if (playerScoreNode)
 			playerScoreNode->RepairedSubstation--;
 		Commands->Set_Player_Type(obj,-2);
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The power substation has been critically damaged, all turrets are down, get out there and repair it!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH92.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH92.mp3","Bear Rug Co: The power substation has been critically damaged, all turrets are down, get out there and repair it!");
 		Commands->Create_Explosion("Explosion_IonCannonBeacon",Vector3(187.902f,-721.522f,1.852f),0);
 		NewObjectiveSystemControl.Set_Objective_Mission(23,12799,12799);
 		Commands->Set_Animation(obj,"transformer.transformer",false,0,1.0,1.0,true);
@@ -6857,8 +6890,7 @@
 		if (playerScoreNode)
 			playerScoreNode->RepairedSubstation++;
 		Commands->Set_Player_Type(obj,1);
-		JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: The power substation has been repaired, turrets back on-line, try to defend it this time!");
-		JmgUtility::Create_2D_Wave_Sound_Dialog("BH93.mp3");
+		BearHunterVoiceSystem::PlayVoice("BH93.mp3","Bear Rug Co: The power substation has been repaired, turrets back on-line, try to defend it this time!");
 		NewObjectiveSystemControl.Set_Objective_Mission(23,12798,12798);
 		Commands->Set_Animation(obj,"transformer.transformer",false,0,0,0,true);
 		ToggleTurrets(true);
@@ -8290,6 +8322,19 @@
 	*position = node->position;
 	return true;
 }
+bool JMG_Bear_Hunter_AI_Guardian_Generic::Get_A_Defense_Point(Vector3 *position)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (Get_Int_Parameter("WanderingAIGroupID") != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(Get_Int_Parameter("WanderingAIGroupID"));
+	if (!node)
+	{
+		Console_Input("msg JMG_Bear_Hunter_AI_Guardian_Generic 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::Get_A_Defense_Point(Vector3 *position,float *facing)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
@@ -8358,6 +8403,21 @@
 	*facing = node->facing;
 	return true;
 }
+bool JMG_Utility_Zone_Teleport_To_Random_WP_Boss::Get_A_Defense_Point(Vector3 *position,float *facing,GameObject *boss)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (wanderPointGroup != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetFurthestInGroup(wanderPointGroup,Commands->Get_Position(boss));
+	if (!node)
+	{
+		if (!retryOnFailure)
+			Console_Input("msg JMG_Utility_Zone_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_Goto_Player::GetRandomPosition(Vector3 *position)
 {
 	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
@@ -8413,6 +8473,42 @@
 	*position = node->position;
 	return true;
 }
+bool JMG_Utility_Custom_Create_Object_At_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)
+	{
+		Console_Input("msg JMG_Utility_Custom_Teleport_Players_Outside_Range_To_Wanderpoints ERROR: No wander points could be found for that group!");
+		return false;
+	}
+	*position = node->position;
+	*facing = node->facing;
+	return true;
+}
+bool JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point::Get_A_Defense_Point(Vector3 *position,float *facing)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (wanderPointGroup != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetNonVisibleSpotFromPlayers(wanderPointGroup);
+	if (!node)
+		return false;
+	*position = node->position;
+	*facing = node->facing;
+	return true;
+}
+bool JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach::Get_A_Defense_Point(Vector3 *position,float *facing)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = NULL;
+	if (wanderPointGroup != -1)
+		node = JMG_Wandering_AI_Controller::wanderPoints.GetNonVisibleSpotFromPlayers(wanderPointGroup);
+	if (!node)
+		return false;
+	*position = node->position;
+	*facing = node->facing;
+	return true;
+}
 int JMG_Bear_Hunter_Golden_Deer_Statue::statueId = 0;
 int JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue = 0;
 int JMG_Bear_Hunter_Golden_Deer_Statue::tinyDeerIds[25] = {0};
@@ -8426,8 +8522,7 @@
 	{
 		if (NewObjectiveSystemControl.Get_Objective_Status(24) == NewObjectiveSystem::NotDefined)
 		{
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: Not only does that statue look valuable, but those little tiny deer seem to love it, bring it back to base!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH94.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH94.mp3","Bear Rug Co: Not only does that statue look valuable, but those little tiny deer seem to love it, bring it back to base!");
 			NewObjectiveSystemControl.Add_Objective(24,NewObjectiveSystem::Tertiary,NewObjectiveSystem::Pending,12821,"",12821,JMG_Bear_Hunter_Game_Control::centerOfBase);
 		}
 		else
@@ -8492,8 +8587,7 @@
 		{
 			Remove_Weapon(obj,"Weapon_Golden_Deer_Statue");
 			JMG_Bear_Hunter_Golden_Deer_Statue::playerWithTheStatue = 0;
-			JmgUtility::MessageAllPlayers(127,127,255,"Bear Rug Co: We've placed the deer statue on the power substation, those tiny deer will now guard it with their lives!");
-			JmgUtility::Create_2D_Wave_Sound_Dialog("BH95.mp3");
+			BearHunterVoiceSystem::PlayVoice("BH95.mp3","Bear Rug Co: We've placed the deer statue on the power substation, those tiny deer will now guard it with their lives!");
 			bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(obj))->ReturnedDeerStatue++;
 			Commands->Give_Money(obj,500.0f,false);
 			NewObjectiveSystemControl.Set_Objective_Status(24,NewObjectiveSystem::Accomplished);
@@ -8523,31 +8617,52 @@
 	bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(obj))->DroppedDeerStatue++;
 }
 int JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount = 0;
+int JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount = 0;
 void JMG_Bear_Hunter_AI_Avoid_Enemies::Created(GameObject *obj)
 {
-	JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount++;
-	fleeTime = 0;
-	if (Has_Weapon(obj,"Weapon_Melee_Deer_Grazing") && Commands->Get_Random(0.0f,1.0f) < 0.75f)
-		Commands->Select_Weapon(obj,"Weapon_Melee_Deer_Grazing");
+	mouse = Get_Int_Parameter("Mouse") ? true : false;
+	if (mouse)
+		JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount++;
+	else
+		JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount++;
 	Commands->Innate_Disable(obj);
 	Commands->Set_Loiters_Allowed(obj,true);
 	Commands->Enable_Enemy_Seen(obj,true);
 	getRandomLocation(obj);
+	closestEnemyId = 0;
+	seenTime = 0;
 	Commands->Start_Timer(obj,this,1.0f,1);
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::Enemy_Seen(GameObject *obj,GameObject *seen)	
 {
+	if (seen->As_PhysicalGameObj() && seen->As_PhysicalGameObj()->Peek_Physical_Object() && seen->As_PhysicalGameObj()->Peek_Physical_Object()->As_DecorationPhysClass())
+		return;
 	if (!Commands->Get_Health(seen))
 		return;
-	if (fleeTime < originalFleeTime)
+	float dist = JmgUtility::SimpleDistance(Commands->Get_Position(obj),Commands->Get_Position(seen));
+	if (!closestEnemyId || dist < enemyDist)
+	{
+		seenTime = Commands->Get_Random_Int(3,6);
+		closestEnemyId = Commands->Get_ID(seen);
+		enemyDist = dist;
 		setRetreatLocation(obj,seen);
+	}
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1 && Commands->Get_Health(obj))
 	{
-		if (fleeTime)
-			fleeTime--;
+		if (seenTime)
+		{
+			seenTime--;
+			if (!seenTime)
+				closestEnemyId = 0;
+		}
+		GameObject *enemy = Commands->Find_Object(closestEnemyId);
+		if (enemy)
+			enemyDist = JmgUtility::SimpleDistance(Commands->Get_Position(obj),Commands->Get_Position(enemy));
+		else
+			closestEnemyId = 0;
 		if (JmgUtility::SimpleDistance(Commands->Get_Position(obj),movePosition) < 25.0f)
 			getRandomLocation(obj);
 		Commands->Start_Timer(obj,this,1.0f,1);
@@ -8555,24 +8670,46 @@
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::Damaged(GameObject *obj,GameObject *damager,float damage)
 {
-	if (fleeTime < originalFleeTime)
-		setRetreatLocation(obj,damager);
+	if (damage <= 0.0f || !damager || obj == damager)
+		return;
+	Enemy_Seen(obj,damager);
+}
+void JMG_Bear_Hunter_AI_Avoid_Enemies::Killed(GameObject *obj,GameObject *killer)
+{
+	if (!mouse)
+		return;
+	JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount--;
+	if (JMG_Bear_Hunter_AI_Avoid_Enemies::mouseAnimalCount)
+		return;
+	BearHunterScoreSystem::BHScoreNode *node = bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(killer));
+	if (NewObjectiveSystemControl.Set_Objective_Status(26,NewObjectiveSystem::Accomplished))
+	{
+		BearHunterVoiceSystem::PlayVoice("BH99.mp3","Bear Rug Co: Wow, we didn't even ask you to deal with our pest problem, here's an extra $50.00 being you clearly need it, it'll be nice to see fewer rodents running around.");
+		Commands->Give_Points(killer,50.0f,false);
+		if (node)
+		{
+			node->BonusObjectivesCompleted++;
+			node->totalObjectivesCompleted++;
+			node->CompledMiceObjective++;
+		}
+	}
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::Destroyed(GameObject *obj)
 {
-	JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount--;
+	if (!mouse)
+		JMG_Bear_Hunter_AI_Avoid_Enemies::wildAnimalCount--;
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::GotoLocation(GameObject *obj,const Vector3 &pos,GameObject *Enemy,float speed)
 {
 	ActionParamsStruct params;
 	params.Set_Basic(this,100,10);
-	params.Set_Movement(pos,speed,1.0f,false);
+	params.Set_Movement(pos,mouse ? 0.36f : speed,1.0f,false);
 	movePosition = pos;
 	if (!Enemy && JmgUtility::SimpleDistance(Commands->Get_Position(obj),pos) < 625 && Commands->Get_Random(0.0f,1.0f) < 0.25f)
 	{
 		params.MoveCrouched = true;
 		params.AttackCrouched = true;
-		params.MoveSpeed = Commands->Get_Random(0.25f,1.0f);
+		params.MoveSpeed = mouse ? Commands->Get_Random(0.09f,0.1f) : Commands->Get_Random(0.25f,1.0f);
 	}
 	params.MovePathfind = true;
 	params.Set_Basic(this,100,10);
@@ -8581,7 +8718,11 @@
 void JMG_Bear_Hunter_AI_Avoid_Enemies::getRandomLocation(GameObject *obj)
 {
 	Vector3 originalPos = Commands->Get_Position(obj);
-	Vector3 pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(originalPos.X+Commands->Get_Random(-50.0f,50.0f),originalPos.Y+Commands->Get_Random(-50.0f,50.0f)));
+	Vector3 pos;
+	if (mouse)
+		pos = JMG_Wandering_AI_Controller::wanderPoints.GetRandomFromGroup(751)->position;
+	else
+		pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(originalPos.X+Commands->Get_Random(-50.0f,50.0f),originalPos.Y+Commands->Get_Random(-50.0f,50.0f)));
 	GotoLocation(obj,pos,NULL,1.0f);
 }
 void JMG_Bear_Hunter_AI_Avoid_Enemies::setRetreatLocation(GameObject *obj,GameObject *enemy)
@@ -8590,12 +8731,15 @@
 	Vector3 mypos = Commands->Get_Position(enemy);
 	pos.X -= mypos.X;
 	pos.Y -= mypos.Y;
-	float TempRotation = atan2(pos.Y,pos.X);
+	float tempRotation = atan2(pos.Y,pos.X)*180.0f/PI+Commands->Get_Random(45.0f,45.0f);
+	pos = Commands->Get_Position(obj);
 	float dist = Commands->Get_Random(50.0f,200.0f);
-	pos.X += cos(TempRotation)*dist;
-	pos.Y += sin(TempRotation)*dist;
-	pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(pos.X,pos.Y));
-	originalFleeTime = fleeTime = Commands->Get_Random_Int(10,25);
+	pos.X += cos(tempRotation*PI180)*dist;
+	pos.Y += sin(tempRotation*PI180)*dist;
+	if (mouse)
+		pos = JMG_Wandering_AI_Controller::wanderPoints.GetNearestFromGroup(751,pos)->position;
+	else
+		pos = BearHunterGameControlSystem.LookupNearestPosition(Vector2(pos.X,pos.Y));
 	GotoLocation(obj,pos,enemy,1.0f);
 }
 void JMG_Security_Camera_Behavior_Ignore::Created(GameObject *obj)
@@ -8773,6 +8917,351 @@
 	randomSelectableSpawnPoints[4] += node;
 	Commands->Destroy_Object(obj);
 }
+Rp2SimplePositionSystem::SimplePositionNode *JMG_Utility_Control_Point_Wander_Point::AddAndReturnWanderpoint(GameObject *obj)
+{
+	Rp2SimplePositionSystem::SimplePositionNode *node = new Rp2SimplePositionSystem::SimplePositionNode(obj);
+	JMG_Wandering_AI_Controller::wanderPoints += node;
+	return node;
+}
+void JMG_Bear_Hunter_PumpJack_Beacon::Created(GameObject *obj)
+{
+	GameObject *Player = NULL;
+	Player = JmgUtility::FindNearestPlayer(Commands->Get_Position(obj));
+	if (!Player)
+	{
+		Commands->Destroy_Object(obj);
+		return;
+	}
+	PlayerData.players[JmgUtility::JMG_Get_Player_ID(Player)]->portablePumpjackBeingPlaced = true;
+	Vector3 PlacePosition = Commands->Get_Position(Player);
+	PlacePosition.Z += 0.0f;
+	GameObject *pPumpjack = Commands->Create_Object("Portable_Pumpjack",PlacePosition);
+	Commands->Set_Facing(pPumpjack,Commands->Get_Facing(Player));
+	char params[256];
+	sprintf(params,"%d,%d",Commands->Get_ID(obj),JmgUtility::JMG_Get_Player_ID(Player));
+	Commands->Attach_Script(pPumpjack,"JMG_Utility_Set_Collision_Group","7");
+	Commands->Attach_Script(pPumpjack,"JMG_Bear_Hunter_PumpJack_Powerup",params);
+}
+void JMG_Bear_Hunter_PumpJack_Powerup::Created(GameObject *obj)
+{
+	Count = 0;
+	GameObject *Player = NULL;
+	Player = JmgUtility::FindNearestPlayer(Commands->Get_Position(obj));
+	if (!Player)
+	{
+		Commands->Destroy_Object(obj);
+		return;
+	}
+	GameObject *tester = Commands->Create_Object("CNC_Spectator",Vector3(-476.546f,-43.671f,0.193f));
+	char params[512];
+	sprintf(params,"%.2f %.2f %.2f,%d",Commands->Get_Position(obj).X,Commands->Get_Position(obj).Y,Commands->Get_Position(obj).Z,Commands->Get_ID(obj));
+	Commands->Attach_Script(tester,"JMG_Bear_Hunter_Sentry_Turret_Placement_Tester",params);
+	Commands->Start_Timer(obj,this,0.1f,4);
+}
+void JMG_Bear_Hunter_PumpJack_Powerup::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 4)
+	{
+		Count++;
+		GameObject *player = Get_GameObj(Get_Int_Parameter("PlayerID"));
+		GameObject *aBeacon = Commands->Find_Object(Get_Int_Parameter("BeaconID"));
+		if (player)
+			Commands->Set_Facing(obj,Commands->Get_Facing(player));
+		if (Count >= 140)
+		{
+			PlayerData.players[Get_Int_Parameter("PlayerID")]->portablePumpjackId = Commands->Get_ID(obj);
+			PlayerData.players[Get_Int_Parameter("PlayerID")]->portablePumpjackBeingPlaced = false;
+			if (aBeacon)
+				Commands->Destroy_Object(aBeacon);
+			Vector3 pos = Commands->Get_Position(player);
+			if (!JMG_Bear_Hunter_Game_Control::winWait && JMG_Bear_Hunter_Game_Control::gameState != JMG_Bear_Hunter_Game_Control::BossArrive)
+			{
+				char params[512];
+				sprintf(params,"%d",Get_Int_Parameter("PlayerID"));
+				Commands->Attach_Script(obj,"JMG_Bear_Hunter_Portable_Pumpjack",params);
+			}
+			else
+			{
+				Remove_All_Scripts(obj);
+				Commands->Attach_Script(obj,"JMG_Bear_Hunter_AI_Ignore_Up_To_25_Meters","");
+			}
+			return;
+		}
+		if (!aBeacon || !Commands->Get_Health(aBeacon))
+		{
+			Commands->Destroy_Object(obj);
+			return;
+		}
+		Commands->Start_Timer(obj,this,0.1f,4);
+	}
+}
+void JMG_Bear_Hunter_PumpJack_Powerup::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == 4987321)
+	{
+		Commands->Destroy_Object(obj);
+		GameObject *player = Get_GameObj(Get_Int_Parameter("PlayerID"));
+		PlayerData.players[Get_Int_Parameter("PlayerID")]->portablePumpjackId = 0;
+		PlayerData.players[Get_Int_Parameter("PlayerID")]->portablePumpjackBeingPlaced = false;
+		JmgUtility::DisplayChatMessage(player,255,25,25,"This cannot be placed here.");
+	}
+}
+void JMG_Bear_Hunter_PumpJack_Powerup::Destroyed(GameObject *obj)
+{
+	GameObject *aBeacon = Commands->Find_Object(Get_Int_Parameter("BeaconID"));
+	if (aBeacon)
+		Commands->Destroy_Object(aBeacon);
+}
+void JMG_Bear_Hunter_Portable_Pumpjack::Created(GameObject *obj)
+{
+	playerScoreNode = bearHunterScoreSystem.Get_Current_Player_Score_Node(Get_Int_Parameter("PlayerID"));
+	if (playerScoreNode)
+		playerScoreNode->PortablePumpJacksPlaced++;
+	giveMoney = JMG_Bear_Hunter_PumpJack_Detect_Oil::CalculateEffectiveness(obj)*0.4f;
+	intPayout = 0.0f;
+	Commands->Set_Model(obj,"ToyPumpJack");
+	Commands->Set_Animation(obj,"ToyPumpJack.ToyPumpJack",true,0,0.0f,-1.0f,true);
+	Commands->Start_Timer(obj,this,0.666f,1);
+}
+void JMG_Bear_Hunter_Portable_Pumpjack::Timer_Expired(GameObject *obj,int number)
+{
+	if (1 == number && !The_Game()->Is_Game_Over())
+	{
+		int myId = Commands->Get_ID(obj);
+		GameObject *player = NULL;
+		for (int x = 1;x < 128;x++)
+		{
+			if (PlayerData.players[x] && PlayerData.players[x]->portablePumpjackId == myId)
+			{
+				player = Get_GameObj(x);
+				break;
+			}
+		}
+		if (player)
+		{
+			intPayout += giveMoney;
+			if (intPayout > 1.0f)
+			{
+				intPayout -= 1.0f;
+				Commands->Give_Money(player,1.0f,false);
+				playerScoreNode->MobilePumpJackMoney++;
+			}
+		}
+		Commands->Start_Timer(obj,this,0.666f,1);
+	}
+}
+void JMG_Bear_Hunter_Portable_Pumpjack::Killed(GameObject *obj,GameObject *killer)
+{
+	if (playerScoreNode)
+		playerScoreNode->PortablePumpJacksLost++;
+	bearHunterScoreSystem.Get_Current_Player_Score_Node(JmgUtility::JMG_Get_Player_ID(killer))->KilledPortablePumpJacks++;
+}
+void JMG_Bear_Hunter_PumpJack_Detect_Oil::Created(GameObject *obj)
+{
+	playerId = JmgUtility::JMG_Get_Player_ID(obj);
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Bear_Hunter_PumpJack_Detect_Oil::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (!PlayerData.players[playerId]->portablePumpjackId)
+			Set_Bullets(obj,"Weapon_Portable_Pumpjack_Beacon",(int)(CalculateEffectiveness(obj)*100.0f));
+		else
+			Set_Bullets(obj,"Weapon_Portable_Pumpjack_Beacon",0);
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+float JMG_Bear_Hunter_PumpJack_Detect_Oil::FindClosestPumpjack(GameObject *obj)
+{
+	Vector3 pos = Commands->Get_Position(obj);
+	GameObject *closest = NULL;
+	float dist = -1;
+	for (int x = 0;x < 7;x++)
+	{
+		GameObject *pumpJack = Commands->Find_Object(JMG_Bear_Hunter_Game_Control::pumpJackIds[x]);
+		if (!pumpJack)
+			continue;
+		float tempDist = JmgUtility::SimpleDistance(pos,Commands->Get_Position(pumpJack));
+		if (!closest || tempDist < dist)
+		{
+			dist = tempDist;
+			closest = pumpJack;
+		}
+	}
+	for (int x = 0;x < 3;x++)
+	{
+		GameObject *pumpJack = Commands->Find_Object(JMG_Bear_Hunter_Game_Control::damagedPumpJackIds[x]);
+		if (!pumpJack)
+			continue;
+		float tempDist = JmgUtility::SimpleDistance(pos,Commands->Get_Position(pumpJack));
+		if (!closest || tempDist < dist)
+		{
+			dist = tempDist;
+			closest = pumpJack;
+		}
+	}
+	return dist;
+}
+float JMG_Bear_Hunter_PumpJack_Detect_Oil::CalculateEffectiveness(GameObject *obj)
+{
+	float dist = FindClosestPumpjack(obj);
+	if (dist < 100.0f)
+		return 1.0f;
+	float payout = max(0.0f,(1.0f-((dist*dist)/100000000000.0f)));
+	float baseDist = JmgUtility::SimpleDistance(Commands->Get_Position(obj),Vector3(0.0f,-700.0f,0.0f));
+	float primaryBaseEffect = max(0.0f,(1.0f-((baseDist*baseDist)/10000000000.0f)));
+	float gmaxDist = JmgUtility::SimpleDistance(Commands->Get_Position(obj),Vector3(428.223f,578.698f,0.0f));
+	float gmaxBaseEffect = max(0.0f,(1.0f-((gmaxDist*gmaxDist)/1000000000.0f)));
+	float houseDist = JmgUtility::SimpleDistance(Commands->Get_Position(obj),Vector3(16.2f,-94.0f,0.0f));
+	float houseEffect = max(0.0f,(1.0f-((houseDist*houseDist)/10000000.0f)));
+	return max(0.0f,payout-primaryBaseEffect-gmaxBaseEffect-houseEffect);
+}
+void JMG_Bear_Hunter_Milk_Drink::Created(GameObject *obj)
+{
+	GameObject *player = NULL;
+	if (obj->As_PhysicalGameObj()->As_BeaconGameObj())
+		player = Get_Beacon_Planter(obj);
+	else
+		player = Commands->Get_A_Star(Commands->Get_Position(obj));
+	if (!player)
+	{
+		Commands->Apply_Damage(obj,9999.9f,"BlamoKiller",0);
+		return;
+	}
+	if (Get_Int_Parameter("WholeMilk"))
+		Commands->Attach_Script(player,"JMG_Utility_Regen_HitPoints","1,0.1,0.0,0,1.0,0.0,0.01,0.0,-7.5,0.0");
+	else
+		Commands->Attach_Script(player,"JMG_Utility_Regen_HitPoints","1,0.075,0.0,0,1.0,0.0,0.01,0.0,-7.5,0.0");
+	Commands->Apply_Damage(obj,9999.9f,"BlamoKiller",0);
+}
+void JMG_Bear_Hunter_AI_Guardian_Generic::Created(GameObject *obj)
+{
+	flightHeight = Get_Float_Parameter("FlightHeight");
+	primaryFire = Get_Int_Parameter("UseSecondaryAttack") ? false : true;
+	arriveDistance = Get_Float_Parameter("ArriveDistance");
+	arriveDistanceSq = (arriveDistance+1.0f)*(arriveDistance+1.0f);
+	dpPosition = Commands->Get_Position(obj);
+	EnemyID = 0;
+	EnemyTimeOutTime = 0;
+	LastPos = Commands->Get_Position(obj);
+	stealthModeOverride = Get_Int_Parameter("StealthModeOverride");
+	float weaponRange = 0.0f;
+	const AmmoDefinitionClass *ammo = Get_Weapon_Ammo_Definition(Get_Current_Weapon(obj),true);
+	if (ammo)
+		weaponRange = ammo->Range;
+	fireRange = Get_Float_Parameter("FireRange") >= 0 ? Get_Float_Parameter("FireRange") : weaponRange;
+	Commands->Enable_Enemy_Seen(obj,true);
+	Commands->Enable_Engine(obj,true);
+	Commands->Start_Timer(obj,this,1.0f,1);
+}
+void JMG_Bear_Hunter_AI_Guardian_Generic::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (Commands->Get_Max_Health(obj) >= Commands->Get_Health(obj))
+		{
+			if (EnemyTimeOutTime)
+			{
+				EnemyTimeOutTime--;
+				if (!EnemyTimeOutTime)
+				{
+					EnemyID = 0;
+					Goto_Location(obj);
+				}
+			}
+			if (EnemyID)
+			{
+				GameObject *Target = Commands->Find_Object(EnemyID);
+				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);
+				}
+			}
+			Vector3 Pos = Commands->Get_Position(obj);
+			Pos.Z -= flightHeight;
+			if (JmgUtility::SimpleDistance(Pos,LastPos) < arriveDistanceSq)
+			{
+				if (Get_A_Defense_Point(&dpPosition))
+					Goto_Location(obj);
+			}
+			LastPos = Pos;
+		}
+		Commands->Start_Timer(obj,this,1.0f,1);
+	}
+}
+void JMG_Bear_Hunter_AI_Guardian_Generic::Enemy_Seen(GameObject *obj,GameObject *seen)
+{
+	if (!Commands->Get_Health(seen))
+		return;
+	if (Is_Script_Attached(seen,"JMG_Utility_AI_Guardian_Ignored"))
+		return;
+	if (!JmgUtility::CanSeeStealth(stealthModeOverride,obj,seen))
+		return;
+	if (!EnemyID || !EnemyTimeOutTime)
+	{
+		EnemyID = Commands->Get_ID(seen);
+		EnemyTimeOutTime = Commands->Get_Random_Int(2,5);
+		Goto_Location(obj);
+	}
+	else if (EnemyID == Commands->Get_ID(seen))
+		EnemyTimeOutTime = Commands->Get_Random_Int(2,5);
+}
+void JMG_Bear_Hunter_AI_Guardian_Generic::Damaged(GameObject *obj,GameObject *damager,float damage)
+{
+	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);
+	Goto_Location(obj);
+}
+void JMG_Bear_Hunter_AI_Guardian_Generic::Goto_Location(GameObject *obj)
+{
+	ActionParamsStruct params;
+	Commands->Action_Reset(obj,100);
+	Vector3 GoToPos = dpPosition;
+	if (flightHeight)
+		GoToPos.Z += flightHeight;
+	params.MovePathfind = flightHeight ? false : true;
+	params.Set_Movement(GoToPos,Get_Float_Parameter("WanderSpeed"),arriveDistance,false);
+	params.ShutdownEngineOnArrival = Get_Int_Parameter("TurnOffEngineOnArrival") ? true : false;
+	params.Set_Basic(this,100,10);
+	GameObject *Target = Commands->Find_Object(EnemyID);
+	if (Target)
+	{
+		if (Get_Int_Parameter("FaceTarget"))
+			params.Set_Face_Location(Commands->Get_Position(Target),5.0f);
+		else
+			params.AttackFaceTarget = false;
+		params.Set_Attack(Target,fireRange,0,primaryFire);
+		params.AttackCheckBlocked = Get_Int_Parameter("CheckBlocked") ? true : false;
+		Commands->Action_Attack(obj,params);
+	}
+	else
+	{
+		if (Get_Int_Parameter("FaceTarget"))
+			params.Set_Face_Location(GoToPos,5.0f);
+		else
+			params.AttackFaceTarget = false;
+		Commands->Action_Goto(obj,params);
+	}
+}
+void JMG_Bear_Hunter_Give_AI_Cash_For_Kills::Killed(GameObject *obj,GameObject *killer)
+{
+	if (!Commands->Is_A_Star(killer))
+	{
+		int killerId = Commands->Get_ID(killer);
+		if (Is_Script_Attached(killer,"JMG_Bear_Hunter_Player_Assist_AI"))
+			for (int x = 0;x < maxTotalBearHunterPlayerAssistAI;x++)
+				if (JMG_Bear_Hunter_Player_Assist_AI::aiIds[x] == killerId)
+				{
+					JMG_Bear_Hunter_Player_Assist_AI::cash[x] += Get_Death_Points(obj)+Get_Damage_Points(obj)*Get_Max_Hitpoints(obj);
+					break;
+				}
+		return;
+	}
+}
 ScriptRegistrant<JMG_Bear_Hunter_Player_Soldier> JMG_Bear_Hunter_Player_Soldier_Registrant("JMG_Bear_Hunter_Player_Soldier","");
 ScriptRegistrant<JMG_Rp2_Dedicated_Server_Sound_Emulator> JMG_Rp2_Dedicated_Server_Sound_Emulator_Registrant("JMG_Rp2_Dedicated_Server_Sound_Emulator","");
 ScriptRegistrant<JMG_Bear_Hunter_Game_Control> JMG_Bear_Hunter_Game_Control_Registrant("JMG_Bear_Hunter_Game_Control","PositionFile=DhSwampDeerPositions.ecw:string");
@@ -8862,8 +9351,16 @@
 ScriptRegistrant<JMG_Bear_Hunter_Golden_Deer_Statue> JMG_Bear_Hunter_Golden_Deer_Statue_Registrant("JMG_Bear_Hunter_Golden_Deer_Statue","");
 ScriptRegistrant<JMG_Bear_Hunter_Golden_Deer_Statue_Standin> JMG_Bear_Hunter_Golden_Deer_Statue_Standin_Registrant("JMG_Bear_Hunter_Golden_Deer_Statue_Standin","");
 ScriptRegistrant<JMG_Bear_Hunter_Golden_Deer_Statue_Attached> JMG_Bear_Hunter_Golden_Deer_Statue_Attached_Registrant("JMG_Bear_Hunter_Golden_Deer_Statue_Attached","");
-ScriptRegistrant<JMG_Bear_Hunter_AI_Avoid_Enemies> JMG_Bear_Hunter_AI_Avoid_Enemies_Registrant("JMG_Bear_Hunter_AI_Avoid_Enemies","");
+ScriptRegistrant<JMG_Bear_Hunter_AI_Avoid_Enemies> JMG_Bear_Hunter_AI_Avoid_Enemies_Registrant("JMG_Bear_Hunter_AI_Avoid_Enemies","Mouse:int");
 ScriptRegistrant<JMG_Security_Camera_Behavior_Ignore> JMG_Security_Camera_Behavior_Ignore_Registrant("JMG_Security_Camera_Behavior_Ignore","");
 ScriptRegistrant<JMG_Bear_Hunter_Friendly_Cougar> JMG_Bear_Hunter_Friendly_Cougar_Registrant("JMG_Bear_Hunter_Friendly_Cougar","ReturnHomeSpeed=1.0:float,ReturnHomeArriveDistance=1.0:float,ChaseSpeed=1.0:float,AttackArriveDistance=0.0:float,StartAttackDistance=3.0:float,MaxHuntDistance=0.0:float,MinLooseVisibilityTime=3:int,MaxLooseVisibilityTime=6:int,NoPathfindDistance=5.0:float");
 ScriptRegistrant<JMG_Bear_Hunter_SpawnPoint_Final_Boss_Line_Of_Sight> JMG_Bear_Hunter_SpawnPoint_Final_Boss_Line_Of_Sight_Registrant("JMG_Bear_Hunter_SpawnPoint_Final_Boss_Line_Of_Sight","Value:int");
 ScriptRegistrant<JMG_Bear_Hunter_SpawnPoint_Final_Boss> JMG_Bear_Hunter_SpawnPoint_Final_Boss_Registrant("JMG_Bear_Hunter_SpawnPoint_Final_Boss","Value:int");
+ScriptRegistrant<JMG_Bear_Hunter_PumpJack_Beacon> JMG_Bear_Hunter_PumpJack_Beacon_Registrant("JMG_Bear_Hunter_PumpJack_Beacon","");
+ScriptRegistrant<JMG_Bear_Hunter_PumpJack_Powerup> JMG_Bear_Hunter_PumpJack_Powerup_Registrant("JMG_Bear_Hunter_PumpJack_Powerup","BeaconID:int,PlayerID:int");
+ScriptRegistrant<JMG_Bear_Hunter_Portable_Pumpjack> JMG_Bear_Hunter_Portable_Pumpjack_Registrant("JMG_Bear_Hunter_Portable_Pumpjack","PlayerID:int");
+ScriptRegistrant<JMG_Bear_Hunter_PumpJack_Detect_Oil> JMG_Bear_Hunter_PumpJack_Detect_Oil_Registrant("JMG_Bear_Hunter_PumpJack_Detect_Oil","");
+ScriptRegistrant<JMG_Bear_Hunter_Milk_Drink> JMG_Bear_Hunter_Milk_Drink_Registrant("JMG_Bear_Hunter_Milk_Drink","WholeMilk:int");
+ScriptRegistrant<JMG_Bear_Hunter_AI_Guardian_Generic> JMG_Bear_Hunter_AI_Guardian_Generic_Registrant("JMG_Bear_Hunter_AI_Guardian_Generic","WanderingAIGroupID:int,WanderSpeed=1.0:float,FireRange=-1.0:float,FaceTarget=1:int,CheckBlocked=1:int,ArriveDistance=1.0:float,FlightHeight=0.0:float,TurnOffEngineOnArrival=1:int,UseSecondaryAttack=0:int,StealthModeOverride=0:int");
+ScriptRegistrant<JMG_Bear_Hunter_Give_AI_Cash_For_Kills> JMG_Bear_Hunter_Give_AI_Cash_For_Kills_Registrant("JMG_Bear_Hunter_Give_AI_Cash_For_Kills","");
+ 
\ No newline at end of file
diff -urN sourceold/scripts/jmgBearHunter.h source/scripts/jmgBearHunter.h
--- sourceold/scripts/jmgBearHunter.h	2019-08-20 10:11:06.807000100 +1000
+++ source/scripts/jmgBearHunter.h	2020-09-26 08:59:57.399125500 +1000
@@ -12,6 +12,7 @@
 	{
 		bool hasGotIntro;
 		int killsWithoutGettingHurt;
+		bool hasAnnounced10;
 		int totalKills;
 		time_t friendlyFireTime;
 		time_t talkToPrezTime;
@@ -23,6 +24,8 @@
 		unsigned int deaths;
 		int binocularTime;
 		int sentryTurretId;
+		bool portablePumpjackBeingPlaced;
+		int portablePumpjackId;
 		bool displayedKillMessage;
 		TempPlayerDataNode(const char *playerName)
 		{
@@ -38,6 +41,7 @@
 			deaths = 0;
 			binocularTime = 100;
 			sentryTurretId = 0;
+			portablePumpjackId = 0;
 			if (playerName)
 				sprintf(this->playerName,"%s",playerName);
 			else
@@ -184,357 +188,98 @@
 		return *this;
 	}
 };
-PowerupLimiterSystem PowerupLimiterSystemControl = PowerupLimiterSystem();
-class Rp2SimplePositionSystem
+class BearHunterVoiceSystem
 {
 public:
-	struct SimplePositionNode
-	{
-		int id;
-		float facing;
-		Vector3 position;
-		int value;
-		struct SimplePositionNode *next;
-		SimplePositionNode(GameObject *obj,int value = 0)
-		{
-			this->id = Commands->Get_ID(obj);
-			this->facing = Commands->Get_Facing(obj);
-			this->position = Commands->Get_Position(obj);
-			this->value = value;
-			this->next = NULL;
-		}
-	};
-	SimplePositionNode *SimplePositionNodeList;
-	int ObjectCount;
-	Rp2SimplePositionSystem()
-	{
-		ObjectCount = 0;
-		SimplePositionNodeList = NULL;
-	}
-	Rp2SimplePositionSystem &operator += (GameObject *obj)
-	{
-		int id = Commands->Get_ID(obj);
-		SimplePositionNode *Current = SimplePositionNodeList;
-		if (!SimplePositionNodeList)
-			SimplePositionNodeList = new SimplePositionNode(obj);
-		while (Current)
-		{
-			if (Current->id == id)
-				return *this;
-			if (!Current->next)
-			{
-				Current->next = new SimplePositionNode(obj);
-				break;
-			}
-			Current = Current->next;
-		}
-		ObjectCount++;
-		return *this;
-	};
-	Rp2SimplePositionSystem &operator += (SimplePositionNode *node)
-	{
-		SimplePositionNode *Current = SimplePositionNodeList;
-		if (!SimplePositionNodeList)
-			SimplePositionNodeList = node;
-		while (Current)
-		{
-			if (Current->id == node->id)
-				return *this;
-			if (!Current->next)
-			{
-				Current->next = node;
-				break;
-			}
-			Current = Current->next;
-		}
-		ObjectCount++;
-		return *this;
-	};
-	void Empty_List()
-	{
-		ObjectCount = 0;
-		SimplePositionNode *temp,*die;
-		temp = SimplePositionNodeList;
-		while (temp)
-		{
-			die = temp;
-			temp = temp->next;
-			delete die;
-		}
-		SimplePositionNodeList = NULL;
-	}
-	SimplePositionNode *GetRandomFromGroup(int value)
-	{
-		int random = Commands->Get_Random_Int(0,ObjectCount*2),original;
-		original = random;
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			if (random && value == Current->value)
-				random--;
-			if (!random && value == Current->value)
-				return Current;
-			Current = Current->next;
-			if (!Current && original != random && original)
-				Current = SimplePositionNodeList;
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetNextFromGroup(int value,SimplePositionNode *last)
+	static int voiceId[128];
+	static void PlayVoice(GameObject *obj,const char *soundFile,const char *text)
 	{
-		bool found = false;
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			if (found && value == Current->value)
-				return Current;
-			if (Current == last && value == Current->value)
-				found = true;
-			Current = Current->next;
-			if (!Current)
-				Current = SimplePositionNodeList;
-		}
-		return NULL;
+		JmgUtility::DisplayChatMessage(obj,127,127,255,text);
+		int playerId = JmgUtility::JMG_Get_Player_ID(obj);
+		Stop_Sound_Player(obj,voiceId[playerId],true);
+		voiceId[playerId] = Create_2D_Wave_Sound_Dialog_Player(obj,soundFile);
 	}
-	SimplePositionNode *GetRandom()
+	static void PlayVoice(const char *soundFile,const char *text)
 	{
-		int random = Commands->Get_Random_Int(0,ObjectCount*2);
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			if (random)
-				random--;
-			if (!random)
-				return Current;
-			Current = Current->next;
-			if (!Current)
-				Current = SimplePositionNodeList;
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetNearest(Vector3 pos)
-	{
-		float LongestDistance = 0;
-		SimplePositionNode *TempObject = NULL;
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			float Temp = JmgUtility::SimpleDistance(Current->position,pos);
-			if (!TempObject || Temp < LongestDistance)
-			{
-				TempObject = Current;
-				LongestDistance = Temp;
-			}
-			Current = Current->next;
-		}
-		return TempObject;
-	}
-	Vector3 GetNearestVector(Vector3 pos)
-	{
-		SimplePositionNode *TempObject = GetNearest(pos);
-		if (TempObject)
-			return TempObject->position;
-		return Vector3();
-	}
-	SimplePositionNode *GetNearestFlat(Vector3 pos)
-	{
-		float LongestDistance = 0;
-		SimplePositionNode *TempObject = NULL;
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			float Temp = JmgUtility::SimpleFlatDistance(Current->position,pos);
-			if (!TempObject || Temp < LongestDistance)
-			{
-				TempObject = Current;
-				LongestDistance = Temp;
-			}
-			Current = Current->next;
-		}
-		return TempObject;
-	}
-	SimplePositionNode *GetRandom(int minVal)
-	{
-		int repeatLimit = ObjectCount;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2);
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
+		JmgUtility::MessageAllPlayers(127,127,255,text);
+		for (int x = 1;x < 128;x++)
 		{
-			if (random && Current->value <= minVal)
-				random--;
-			if (!random && Current->value <= minVal)
-				return Current;
-			Current = Current->next;
-			if (!Current && repeatLimit)
-			{
-				repeatLimit--;
-				Current = SimplePositionNodeList;
-			}
+			GameObject *player = Get_GameObj(x);
+			if (!player)
+				continue;
+			Stop_Sound_Player(player,voiceId[x],true);
+			voiceId[x] = Create_2D_Wave_Sound_Dialog_Player(player,soundFile);
 		}
-		return NULL;
 	}
-	SimplePositionNode *GetRandomExcluding(SimplePositionNode *node)
-	{
-		int repeatLimit = ObjectCount;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2);
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
+};
+Rp2SimplePositionSystem::SimplePositionNode *Rp2SimplePositionSystem::GetSpotNotVisibileFromSpot(Vector3 pos)
+{
+	pos.Z += 2;
+	SimplePositionNode *current = SimplePositionNodeList;
+	int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+	int originalRandom = random;
+	while (current)
+	{
+		Vector3 targetpos = current->position;
+		targetpos.Z += 2.0f;
+		CastResultStruct res;
+		LineSegClass ray(pos,targetpos);
+		PhysRayCollisionTestClass coltest(ray, &res, SOLDIER_GHOST_COLLISION_GROUP);
+		PhysicsSceneClass::Get_Instance()->Cast_Ray(coltest,false);
+		if (JmgUtility::SimpleDistance(pos,current->position) > 5625 && coltest.CollidedRenderObj)
 		{
 			if (random)
 				random--;
-			if (!random && node != Current)
-				return Current;
-			Current = Current->next;
-			if (!Current && repeatLimit)
-			{
-				repeatLimit--;
-				Current = SimplePositionNodeList;
-			}
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetRandomLowestValue()
-	{
-		int lowest = -1;
-		SimplePositionNode *current = SimplePositionNodeList;
-		while (current)
-		{
-			if (lowest == -1 || current->value <= lowest)
-				lowest = current->value;
-			current = current->next;
-		}
-		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
-		int originalRandom = random;
-		current = SimplePositionNodeList;
-		while (current)
-		{
-			if (random && current->value == lowest)
-				random--;
 			if (!random)
 				return current;
-			current = current->next;
-			if (!current && originalRandom != random)
-				current = SimplePositionNodeList;
 		}
-		return NULL;
+		current = current->next;
+		if (!current && originalRandom != random)
+			current = SimplePositionNodeList;
 	}
-	SimplePositionNode *GetRandomOutsideOfRange(float range,Vector3 pos)
-	{
-		range *= range;
-		SimplePositionNode *current = SimplePositionNodeList;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
-		int originalRandom = random;
-		while (current)
-		{
-			if (JmgUtility::SimpleDistance(current->position,pos) > range && random)
-				random--;
-			if (!random)
-				return current;
-			current = current->next;
-			if (!current && originalRandom != random)
-				current = SimplePositionNodeList;
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetRandomOutsideOfRangeInGroup(int value,float range,Vector3 pos)
-	{
-		range *= range;
-		SimplePositionNode *current = SimplePositionNodeList;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
-		int originalRandom = random;
-		while (current)
-		{
-			if (current->value == value && JmgUtility::SimpleDistance(current->position,pos) > range && random)
-				random--;
-			if (current->value == value && !random)
-				return current;
-			current = current->next;
-			if (!current && originalRandom != random)
-				current = SimplePositionNodeList;
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetRandomInsideOfRangeInGroup(int value,float range,Vector3 pos)
-	{
-		range *= range;
-		SimplePositionNode *current = SimplePositionNodeList;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
-		int originalRandom = random;
-		while (current)
-		{
-			if (current->value == value && JmgUtility::SimpleDistance(current->position,pos) <= range && random)
-				random--;
-			if (value && !random)
-				return current;
-			current = current->next;
-			if (!current && originalRandom != random)
-				current = SimplePositionNodeList;
-		}
-		return NULL;
-	}
-	SimplePositionNode *GetLowestValueFurthestFromSpot(Vector3 pos)
+	return NULL;
+}
+Rp2SimplePositionSystem::SimplePositionNode *Rp2SimplePositionSystem::GetNonVisibleSpotFromPlayers(int value)
+{
+	SimplePositionNode *current = SimplePositionNodeList;
+	int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+	int originalRandom = random;
+	while (current)
 	{
-		int lowest = -1;
-		SimplePositionNode *current = SimplePositionNodeList,*bestNode = NULL;
-		while (current)
+		if (current->value == value)
 		{
-			if (lowest == -1 || current->value <= lowest)
-				lowest = current->value;
-			current = current->next;
-		}
-
-		current = SimplePositionNodeList;
-		float dist = 0;
-		while (current)
-		{
-			float tempDist = JmgUtility::SimpleDistance(pos,current->position);
-			if (!bestNode || tempDist > dist)
+			bool visibile = false;
+			for (int x = 1;x < 128;x++)
 			{
-				bestNode = current;
-				dist = tempDist;
+				GameObject *player = Get_GameObj(x);
+				if (!player)
+					continue;
+				Vector3 pos = Commands->Get_Position(player);
+				Vector3 targetpos = current->position;
+				targetpos.Z += 1.0f;
+				pos.Z += 1.0;
+				CastResultStruct res;
+				LineSegClass ray(pos,targetpos);
+				PhysRayCollisionTestClass coltest(ray, &res, SOLDIER_GHOST_COLLISION_GROUP);
+				PhysicsSceneClass::Get_Instance()->Cast_Ray(coltest,false);
+				if (!coltest.CollidedRenderObj)
+					visibile = true;
 			}
-			current = current->next;
-		}
-		return bestNode;
-	}
-	SimplePositionNode *GetSpotNotVisibileFromSpot(Vector3 pos)
-	{
-		pos.Z += 2;
-		SimplePositionNode *current = SimplePositionNodeList;
-		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
-		int originalRandom = random;
-		while (current)
-		{
-			Vector3 targetpos = current->position;
-			targetpos.Z += 2.0f;
-			CastResultStruct res;
-			LineSegClass ray(pos,targetpos);
-			PhysRayCollisionTestClass coltest(ray, &res, SOLDIER_GHOST_COLLISION_GROUP);
-			PhysicsSceneClass::Get_Instance()->Cast_Ray(coltest,false);
-			if (JmgUtility::SimpleDistance(pos,current->position) > 5625 && coltest.CollidedRenderObj)
+			if (!visibile)
 			{
 				if (random)
 					random--;
 				if (!random)
 					return current;
 			}
-			current = current->next;
-			if (!current && originalRandom != random)
-				current = SimplePositionNodeList;
 		}
-		return NULL;
+		current = current->next;
+		if (!current && originalRandom != random)
+			current = SimplePositionNodeList;
 	}
-	void DecreaseValue()
-	{
-		SimplePositionNode *Current = SimplePositionNodeList;
-		while (Current)
-		{
-			if (Current->value)
-				Current->value--;
-			Current = Current->next;
-		}
-	}
-};
+	return GetFurthestSpotFromPlayers(value);
+}
+PowerupLimiterSystem PowerupLimiterSystemControl = PowerupLimiterSystem();
 Rp2SimplePositionSystem randomSelectableSpawnPoints[7] = {Rp2SimplePositionSystem()};
 Rp2SimplePositionSystem aiDefensePoints[6] = {Rp2SimplePositionSystem()};
 Rp2SimplePositionSystem randomWeaponContainerSpawnPositions[6] = {Rp2SimplePositionSystem()};
@@ -818,6 +563,7 @@
 	void CreateExplosion(GameObject *obj,const char *explosion,Vector3 pos,float *distance,int *explosives);
 	bool LifeSystem(GameObject *obj);
 	void GrantUnlockedWeapons(GameObject *obj);
+	void GrantSpecialUnlocks(GameObject *obj);
 public:
 	struct ArmoredCarWeapon
 	{
@@ -1229,12 +975,20 @@
 	int wildMountainLionIds[8];
 	int wildMountainLionRespawn[8];
 	int wildMountainLionsPet;
+	int cowId;
+	int cowWanderId;
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj,int number);
 	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 	void Destroyed(GameObject *obj);
 	bool CheckIfPlayerInRange(Vector3 pos,float distance);
 	void EndGameDataCleanup(GameObject *obj,bool saveData);
+	void CreateMouse();
+	void CreateNormalAnimals(int normalBears);
+	void CreateBears(int normalBears);
+	void SpawnAngryTinyDeer();
+	void CreateCow();
+	void TriggerCowObjective();
 	static GameObject *MutantSpawn();
 public:
 	JMG_Bear_Hunter_Game_Control();
@@ -1281,6 +1035,8 @@
 	static int wanderingAiIgnorePlayers[128];
 	static void IncreaseBonusObjectiveCount(int objectiveId);
 	static int bonusObjectiveCount;
+	static int pumpJackIds[7];
+	static int damagedPumpJackIds[3];
 };
 GameObject *JMG_Bear_Hunter_Game_Control::myObject = NULL;
 int JMG_Bear_Hunter_Game_Control::mutantTargetId = 600172;
@@ -1324,11 +1080,13 @@
 int JMG_Bear_Hunter_Game_Control::friendlyTinyDeerRespawnTime[5] = {0};
 int JMG_Bear_Hunter_Game_Control::wanderingAiIgnorePlayers[128] = {0};
 int JMG_Bear_Hunter_Game_Control::bonusObjectiveCount = 0;
+int JMG_Bear_Hunter_Game_Control::pumpJackIds[7] = {601041,601038,601044,601040,601045,601039,601043};
+int JMG_Bear_Hunter_Game_Control::damagedPumpJackIds[3] = {601061,601062,601063};
 
 struct BearHunterScoreSystem
 {
 public:
-	#define BHHighScoreListCount 111
+	#define BHHighScoreListCount 122
 	struct BHScoreNode
 	{
 		char PlayerName[256];
@@ -1444,6 +1202,18 @@
 		unsigned long KilledFriendlyCougars;
 		unsigned long DiedTryingToEscapeBase;
 		JmgUtility::GenericDateTime LastPlayTime;
+		unsigned long RescuedCows;
+		unsigned long KilledCows;
+		unsigned long LostCows;
+		unsigned long CompletedCowObjective;
+		unsigned long KilledMice;
+		unsigned long CompledMiceObjective;
+		unsigned long KilledPortablePumpJacks;
+		unsigned long PortablePumpJacksPlaced;
+		unsigned long PortablePumpJacksLost;
+		unsigned long PumpJackMoney;
+		unsigned long MobilePumpJackMoney;
+		unsigned long Rank;
 
 		bool startedRound;
 		unsigned long totalObjectivesCompleted;
@@ -1461,6 +1231,7 @@
 		float tmpSupportReceivedHpInfantrySelf;
 		float tmpSupportReceivedHpVehicleSelf;
 		int tmpPettedCougars;
+		double weightedRank;
 		BHScoreNode *next;
 		BHScoreNode(const char *PlayerName)
 		{
@@ -1577,7 +1348,19 @@
 			KilledFriendlyCougars = 0;
 			DiedTryingToEscapeBase = 0;
 			JmgUtility::GenericDateTime LastPlayTime = JmgUtility::GenericDateTime();
-			
+			RescuedCows = 0;
+			KilledCows = 0;
+			LostCows = 0;
+			CompletedCowObjective = 0;
+			KilledMice = 0;
+			CompledMiceObjective = 0;
+			KilledPortablePumpJacks = 0;
+			PortablePumpJacksPlaced = 0;
+			PortablePumpJacksLost = 0;
+			PumpJackMoney = 0;
+			MobilePumpJackMoney = 0;
+			Rank = 0;
+
 			startedRound = JMG_Bear_Hunter_Game_Control::gameState >= JMG_Bear_Hunter_Game_Control::HuntBears ? true : false;
 			totalObjectivesCompleted = 0;
 			totalPowerupsPickedup = 0;
@@ -1626,6 +1409,81 @@
 		return NULL;
 	}
 	int lastDispalyedHighScore;
+	BHScoreNode *mergeList(BHScoreNode *split1,BHScoreNode *split2)
+	{
+		if(split1 == NULL)
+			return split2;
+		if(split2 == NULL)
+			return split1;
+		BHScoreNode *newhead = NULL;
+		if(split1->weightedRank >= split2->weightedRank)
+		{
+			newhead = split1;
+			newhead->next = mergeList(split1->next,split2);
+		}
+		else
+		{
+			newhead = split2;
+			newhead->next = mergeList(split1,split2->next);
+		}
+		return newhead;
+	}
+	void splitList(BHScoreNode *head,BHScoreNode **split1,BHScoreNode **split2)
+	{
+		BHScoreNode *slow = head;
+		BHScoreNode *fast = head->next;
+		while(fast != NULL)
+		{
+			fast = fast->next;
+			if(fast != NULL)
+			{
+				slow = slow->next;
+				fast = fast->next;
+			}
+		}
+		*split1 = head;
+		*split2 = slow->next;
+		slow->next = NULL;
+	}
+	void mergeSort(BHScoreNode **refToHead)
+	{
+		BHScoreNode *head = *refToHead;
+		BHScoreNode *split1,*split2;
+		if(head == NULL || head->next == NULL)
+			return;
+		splitList(head,&split1,&split2);
+		mergeSort(&split1);
+		mergeSort(&split2);
+		*refToHead = mergeList(split1,split2);
+		return;    
+	}
+	void CalculateRank()
+	{
+		BHScoreNode *current = BHScoreNodeList;
+		unsigned long maxRoundsWon = 0,maxKills = 0,maxBonusObjectivesCompleted = 0,maxPlayTime = 0;
+		while (current)
+		{
+			if (current->RoundsWon > maxRoundsWon)
+				maxRoundsWon = current->RoundsWon;
+			if (current->Kills > maxKills)
+				maxKills = current->Kills;
+			if (current->BonusObjectivesCompleted > maxBonusObjectivesCompleted)
+				maxBonusObjectivesCompleted = current->BonusObjectivesCompleted;
+			if (current->PlayTime > maxPlayTime)
+				maxPlayTime = current->PlayTime;
+			current = current->next;
+		}
+		current = BHScoreNodeList;
+		while (current)
+		{
+			current->weightedRank = current->RoundsWon/(double)maxRoundsWon*1.0+
+									current->Kills/(double)maxKills*0.5+
+									current->BonusObjectivesCompleted/(double)maxBonusObjectivesCompleted*0.5+
+									current->PlayTime/(double)maxPlayTime*0.1;
+			current = current->next;
+		}
+		mergeSort(&BHScoreNodeList);
+	}
 public:
 	BearHunterScoreSystem()
 	{
@@ -1691,6 +1549,8 @@
 		sprintf(textPath,"%sBearHunterPlayerRecords.txt",savePath);
 		SaveScores = fopen(tempPath,"w");
 		SaveScores2 = fopen(textPath,"w");
+		CalculateRank();
+		int currentRank = 0;
 		BHScoreNode *Current = BHScoreNodeList;
 		while (Current)
 		{
@@ -1707,8 +1567,16 @@
 			Current->SupportGrantedHpVehicle += (unsigned long)Current->tmpSupportGrantedHpVehicle;
 			Current->SupportReceivedHpInfantrySelf += (unsigned long)Current->tmpSupportReceivedHpInfantrySelf;
 			Current->SupportReceivedHpVehicleSelf += (unsigned long)Current->tmpSupportReceivedHpVehicleSelf;
+			Current->Rank = ++currentRank;
 			char EncryptString[2048];
- 			sprintf(EncryptString,"%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu 0",Current->PlayTime,Current->PreGameTime,Current->IdleTime,Current->RoundsPlayed,Current->RoundsCompleted,Current->RoundsQuit,Current->RoundsWon,Current->RoundsLost,Current->MostKillsInARound,Current->MostDeathsInARound,Current->MostBonusObjectivesCompletedInARound,Current->Deaths,Current->Kills,Current->VehicleKills,Current->KilledSelf,Current->KilledPlayers,Current->KilledPresident,Current->KilledTurrets,Current->KilledBears,Current->KilledBlackBears,Current->KilledMutantBears,Current->KilledMutantDeer,Current->KilledMutantCats,Current->KilledMutantCatsB,Current->KilledMutantCatsR,Current->KilledMutantRabbits,Current->ObjectiveActivatedAlarm,Current->ObjectiveTurretTruck,Current->ObjectiveTurretTruckAlarm,Current->ObjectiveOilRigsActivated,Current->ObjectiveOilRigsRepaired,Current->ObjectiveEngineersSaved,Current->ObjectiveWeaponsFound,Current->ObjectiveWeaponsReturned,Current->ObjectivePlasmaRifleReturned,Current->BonusObjectivesCompleted,Current->PickedupHealthPowerups,Current->PickedupArmorPowerups,Current->PickedupCashPowerups,Current->PickedupAmmoPowerups,Current->PickedupHealthTotal,Current->PickedupArmorTotal,Current->PickedupCashTotal,Current->PickedupAmmoTotal,Current->PickedupTotalPowerups,Current->PickedupTotalPowerupsInARound,Current->KilledHumanAi,Current->VehiclesDestroyed,Current->VehiclesLost,Current->JazzsLost,Current->CleasansLost,Current->TrucksLost,Current->TanksLost,Current->TurretTruckLost,Current->C4VestPowerups,Current->ActivatedCommTower,Current->PlayedGamesWithDefenseTurrets,Current->PlayedGamesWithGuardianHelicopter,Current->TimesDrown,Current->TimesFallen,Current->KillsWithSentryTurret,Current->KilledSentryTurrets,Current->SentryTurretsPlaced,Current->SentryTurretsLost,Current->PickedUpMedicalNeedle,Current->ReturnedMedicalNeedle,Current->RepairedSubstation,Current->SubstationOnLineAtEnd,Current->SubstationNotDamaged,Current->GiantDeerKilled,Current->SurvivedAlarm,Current->WolfKilled,Current->MutantDogKilled,Current->BlueDeerKilled,Current->CheatedRounds,Current->NeverInjured,Current->MooseKilled,Current->MooseKilled,Current->EatenByRabbit,Current->EatenByRabbit,Current->PickedUpDeerStatue,Current->DroppedDeerStatue,Current->ReturnedDeerStatue,Current->TinyDeerKilled,Current->MutantSquirrelsKilled,Current->WildDeerKilled,Current->WildSquirrelsKilled,Current->ArmoredCarsLost,Current->WarriorsLost,Current->TimeOnFoot,Current->TimeInAJazzs,Current->TimeInACleasans,Current->TimeInASecurityTruck,Current->TimeInArmoredCars,Current->TimeInAUDVs,Current->TimeInGatlingTanks,Current->TimeInIFVs,Current->FriendlyTinyDeerKilled,Current->LastPlayTime.day,Current->LastPlayTime.month,Current->LastPlayTime.year,Current->LastPlayTime.second,Current->LastPlayTime.minute,Current->LastPlayTime.hour,Current->LastPlayTime.lTime,Current->SupportReceivedInfantryHp,Current->SupportReceivedVehicleHp,Current->SupportReceivedAmmo,Current->SupportGrantedHpInfantry,Current->SupportGrantedHpVehicle,Current->SupportGrantedAmmo,Current->SupportReceivedHpInfantrySelf,Current->SupportReceivedHpVehicleSelf,Current->SupportReceivedAmmoSelf,Current->PettedCougars,Current->KilledCougars,Current->KilledMutantCougars,Current->FailedPettingCougars,Current->KilledFriendlyCougars,Current->DiedTryingToEscapeBase);
+ 			sprintf(EncryptString,"%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu 0",
+				Current->PlayTime,Current->PreGameTime,Current->IdleTime,Current->RoundsPlayed,Current->RoundsCompleted,Current->RoundsQuit,Current->RoundsWon,Current->RoundsLost,Current->MostKillsInARound,Current->MostDeathsInARound,Current->MostBonusObjectivesCompletedInARound,Current->Deaths,Current->Kills,Current->VehicleKills,Current->KilledSelf,Current->KilledPlayers,Current->KilledPresident,Current->KilledTurrets,Current->KilledBears,Current->KilledBlackBears,Current->KilledMutantBears,Current->KilledMutantDeer,Current->KilledMutantCats,Current->KilledMutantCatsB,
+				Current->KilledMutantCatsR,Current->KilledMutantRabbits,Current->ObjectiveActivatedAlarm,Current->ObjectiveTurretTruck,Current->ObjectiveTurretTruckAlarm,Current->ObjectiveOilRigsActivated,Current->ObjectiveOilRigsRepaired,Current->ObjectiveEngineersSaved,Current->ObjectiveWeaponsFound,Current->ObjectiveWeaponsReturned,Current->ObjectivePlasmaRifleReturned,Current->BonusObjectivesCompleted,Current->PickedupHealthPowerups,Current->PickedupArmorPowerups,Current->PickedupCashPowerups,Current->PickedupAmmoPowerups,Current->PickedupHealthTotal,Current->PickedupArmorTotal,
+				Current->PickedupCashTotal,Current->PickedupAmmoTotal,Current->PickedupTotalPowerups,Current->PickedupTotalPowerupsInARound,Current->KilledHumanAi,Current->VehiclesDestroyed,Current->VehiclesLost,Current->JazzsLost,Current->CleasansLost,Current->TrucksLost,Current->TanksLost,Current->TurretTruckLost,Current->C4VestPowerups,Current->ActivatedCommTower,Current->PlayedGamesWithDefenseTurrets,Current->PlayedGamesWithGuardianHelicopter,Current->TimesDrown,Current->TimesFallen,Current->KillsWithSentryTurret,Current->KilledSentryTurrets,Current->SentryTurretsPlaced,
+				Current->SentryTurretsLost,Current->PickedUpMedicalNeedle,Current->ReturnedMedicalNeedle,Current->RepairedSubstation,Current->SubstationOnLineAtEnd,Current->SubstationNotDamaged,Current->GiantDeerKilled,Current->SurvivedAlarm,Current->WolfKilled,Current->MutantDogKilled,Current->BlueDeerKilled,Current->CheatedRounds,Current->NeverInjured,Current->MooseKilled,Current->MooseKilled,Current->EatenByRabbit,Current->EatenByRabbit,Current->PickedUpDeerStatue,Current->DroppedDeerStatue,Current->ReturnedDeerStatue,Current->TinyDeerKilled,Current->MutantSquirrelsKilled,
+				Current->WildDeerKilled,Current->WildSquirrelsKilled,Current->ArmoredCarsLost,Current->WarriorsLost,Current->TimeOnFoot,Current->TimeInAJazzs,Current->TimeInACleasans,Current->TimeInASecurityTruck,Current->TimeInArmoredCars,Current->TimeInAUDVs,Current->TimeInGatlingTanks,Current->TimeInIFVs,Current->FriendlyTinyDeerKilled,Current->LastPlayTime.day,Current->LastPlayTime.month,Current->LastPlayTime.year,Current->LastPlayTime.second,Current->LastPlayTime.minute,Current->LastPlayTime.hour,Current->LastPlayTime.lTime,Current->SupportReceivedInfantryHp,
+				Current->SupportReceivedVehicleHp,Current->SupportReceivedAmmo,Current->SupportGrantedHpInfantry,Current->SupportGrantedHpVehicle,Current->SupportGrantedAmmo,Current->SupportReceivedHpInfantrySelf,Current->SupportReceivedHpVehicleSelf,Current->SupportReceivedAmmoSelf,Current->PettedCougars,Current->KilledCougars,Current->KilledMutantCougars,Current->FailedPettingCougars,Current->KilledFriendlyCougars,Current->DiedTryingToEscapeBase,Current->RescuedCows,Current->KilledCows,Current->LostCows,Current->CompletedCowObjective,Current->KilledMice,
+				Current->CompledMiceObjective,Current->KilledPortablePumpJacks,Current->PortablePumpJacksPlaced,Current->PortablePumpJacksLost,Current->PumpJackMoney,Current->MobilePumpJackMoney,Current->Rank);
 			fprintf(SaveScores2,"%s\n%s\n",Current->PlayerName,EncryptString);
 			fprintf(SaveScores,"%s\n%s",JmgUtility::Rp2Encrypt(Current->PlayerName,25,5),JmgUtility::Rp2Encrypt2(EncryptString,Current->PlayerName[0],Current->PlayerName[1]));
 			fprintf(SaveScores,"\n%s",JmgUtility::Rp2Encrypt(EncryptString,Current->PlayerName[1],Current->PlayerName[0]));
@@ -1760,7 +1628,14 @@
 							break;
 						}
 				if (match)
-					sscanf(decryptedString,"%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",&Current->PlayTime,&Current->PreGameTime,&Current->IdleTime,&Current->RoundsPlayed,&Current->RoundsCompleted,&Current->RoundsQuit,&Current->RoundsWon,&Current->RoundsLost,&Current->MostKillsInARound,&Current->MostDeathsInARound,&Current->MostBonusObjectivesCompletedInARound,&Current->Deaths,&Current->Kills,&Current->VehicleKills,&Current->KilledSelf,&Current->KilledPlayers,&Current->KilledPresident,&Current->KilledTurrets,&Current->KilledBears,&Current->KilledBlackBears,&Current->KilledMutantBears,&Current->KilledMutantDeer,&Current->KilledMutantCats,&Current->KilledMutantCatsB,&Current->KilledMutantCatsR,&Current->KilledMutantRabbits,&Current->ObjectiveActivatedAlarm,&Current->ObjectiveTurretTruck,&Current->ObjectiveTurretTruckAlarm,&Current->ObjectiveOilRigsActivated,&Current->ObjectiveOilRigsRepaired,&Current->ObjectiveEngineersSaved,&Current->ObjectiveWeaponsFound,&Current->ObjectiveWeaponsReturned,&Current->ObjectivePlasmaRifleReturned,&Current->BonusObjectivesCompleted,&Current->PickedupHealthPowerups,&Current->PickedupArmorPowerups,&Current->PickedupCashPowerups,&Current->PickedupAmmoPowerups,&Current->PickedupHealthTotal,&Current->PickedupArmorTotal,&Current->PickedupCashTotal,&Current->PickedupAmmoTotal,&Current->PickedupTotalPowerups,&Current->PickedupTotalPowerupsInARound,&Current->KilledHumanAi,&Current->VehiclesDestroyed,&Current->VehiclesLost,&Current->JazzsLost,&Current->CleasansLost,&Current->TrucksLost,&Current->TanksLost,&Current->TurretTruckLost,&Current->C4VestPowerups,&Current->ActivatedCommTower,&Current->PlayedGamesWithDefenseTurrets,&Current->PlayedGamesWithGuardianHelicopter,&Current->TimesDrown,&Current->TimesFallen,&Current->KillsWithSentryTurret,&Current->KilledSentryTurrets,&Current->SentryTurretsPlaced,&Current->SentryTurretsLost,&Current->PickedUpMedicalNeedle,&Current->ReturnedMedicalNeedle,&Current->RepairedSubstation,&Current->SubstationOnLineAtEnd,&Current->SubstationNotDamaged,&Current->GiantDeerKilled,&Current->SurvivedAlarm,&Current->WolfKilled,&Current->MutantDogKilled,&Current->BlueDeerKilled,&Current->CheatedRounds,&Current->NeverInjured,&Current->MooseKilled,&Current->MooseKilled,&Current->EatenByRabbit,&Current->EatenByRabbit,&Current->PickedUpDeerStatue,&Current->DroppedDeerStatue,&Current->ReturnedDeerStatue,&Current->TinyDeerKilled,&Current->MutantSquirrelsKilled,&Current->WildDeerKilled,&Current->WildSquirrelsKilled,&Current->ArmoredCarsLost,&Current->WarriorsLost,&Current->TimeOnFoot,&Current->TimeInAJazzs,&Current->TimeInACleasans,&Current->TimeInASecurityTruck,&Current->TimeInArmoredCars,&Current->TimeInAUDVs,&Current->TimeInGatlingTanks,&Current->TimeInIFVs,&Current->FriendlyTinyDeerKilled,&Current->LastPlayTime.day,&Current->LastPlayTime.month,&Current->LastPlayTime.year,&Current->LastPlayTime.second,&Current->LastPlayTime.minute,&Current->LastPlayTime.hour,&Current->LastPlayTime.lTime,&Current->SupportReceivedInfantryHp,&Current->SupportReceivedVehicleHp,&Current->SupportReceivedAmmo,&Current->SupportGrantedHpInfantry,&Current->SupportGrantedHpVehicle,&Current->SupportGrantedAmmo,&Current->SupportReceivedHpInfantrySelf,&Current->SupportReceivedHpVehicleSelf,&Current->SupportReceivedAmmoSelf,&Current->PettedCougars,&Current->KilledCougars,&Current->KilledMutantCougars,&Current->FailedPettingCougars,&Current->KilledFriendlyCougars,&Current->DiedTryingToEscapeBase);
+					sscanf(decryptedString,"%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+						&Current->PlayTime,&Current->PreGameTime,&Current->IdleTime,&Current->RoundsPlayed,&Current->RoundsCompleted,&Current->RoundsQuit,&Current->RoundsWon,&Current->RoundsLost,&Current->MostKillsInARound,&Current->MostDeathsInARound,&Current->MostBonusObjectivesCompletedInARound,&Current->Deaths,&Current->Kills,&Current->VehicleKills,&Current->KilledSelf,&Current->KilledPlayers,&Current->KilledPresident,&Current->KilledTurrets,&Current->KilledBears,&Current->KilledBlackBears,&Current->KilledMutantBears,&Current->KilledMutantDeer,&Current->KilledMutantCats,&Current->KilledMutantCatsB,
+						&Current->KilledMutantCatsR,&Current->KilledMutantRabbits,&Current->ObjectiveActivatedAlarm,&Current->ObjectiveTurretTruck,&Current->ObjectiveTurretTruckAlarm,&Current->ObjectiveOilRigsActivated,&Current->ObjectiveOilRigsRepaired,&Current->ObjectiveEngineersSaved,&Current->ObjectiveWeaponsFound,&Current->ObjectiveWeaponsReturned,&Current->ObjectivePlasmaRifleReturned,&Current->BonusObjectivesCompleted,&Current->PickedupHealthPowerups,&Current->PickedupArmorPowerups,&Current->PickedupCashPowerups,&Current->PickedupAmmoPowerups,&Current->PickedupHealthTotal,&Current->PickedupArmorTotal,
+						&Current->PickedupCashTotal,&Current->PickedupAmmoTotal,&Current->PickedupTotalPowerups,&Current->PickedupTotalPowerupsInARound,&Current->KilledHumanAi,&Current->VehiclesDestroyed,&Current->VehiclesLost,&Current->JazzsLost,&Current->CleasansLost,&Current->TrucksLost,&Current->TanksLost,&Current->TurretTruckLost,&Current->C4VestPowerups,&Current->ActivatedCommTower,&Current->PlayedGamesWithDefenseTurrets,&Current->PlayedGamesWithGuardianHelicopter,&Current->TimesDrown,&Current->TimesFallen,&Current->KillsWithSentryTurret,&Current->KilledSentryTurrets,&Current->SentryTurretsPlaced,
+						&Current->SentryTurretsLost,&Current->PickedUpMedicalNeedle,&Current->ReturnedMedicalNeedle,&Current->RepairedSubstation,&Current->SubstationOnLineAtEnd,&Current->SubstationNotDamaged,&Current->GiantDeerKilled,&Current->SurvivedAlarm,&Current->WolfKilled,&Current->MutantDogKilled,&Current->BlueDeerKilled,&Current->CheatedRounds,&Current->NeverInjured,&Current->MooseKilled,&Current->MooseKilled,&Current->EatenByRabbit,&Current->EatenByRabbit,&Current->PickedUpDeerStatue,&Current->DroppedDeerStatue,&Current->ReturnedDeerStatue,&Current->TinyDeerKilled,&Current->MutantSquirrelsKilled,
+						&Current->WildDeerKilled,&Current->WildSquirrelsKilled,&Current->ArmoredCarsLost,&Current->WarriorsLost,&Current->TimeOnFoot,&Current->TimeInAJazzs,&Current->TimeInACleasans,&Current->TimeInASecurityTruck,&Current->TimeInArmoredCars,&Current->TimeInAUDVs,&Current->TimeInGatlingTanks,&Current->TimeInIFVs,&Current->FriendlyTinyDeerKilled,&Current->LastPlayTime.day,&Current->LastPlayTime.month,&Current->LastPlayTime.year,&Current->LastPlayTime.second,&Current->LastPlayTime.minute,&Current->LastPlayTime.hour,&Current->LastPlayTime.lTime,&Current->SupportReceivedInfantryHp,
+						&Current->SupportReceivedVehicleHp,&Current->SupportReceivedAmmo,&Current->SupportGrantedHpInfantry,&Current->SupportGrantedHpVehicle,&Current->SupportGrantedAmmo,&Current->SupportReceivedHpInfantrySelf,&Current->SupportReceivedHpVehicleSelf,&Current->SupportReceivedAmmoSelf,&Current->PettedCougars,&Current->KilledCougars,&Current->KilledMutantCougars,&Current->FailedPettingCougars,&Current->KilledFriendlyCougars,&Current->DiedTryingToEscapeBase,&Current->RescuedCows,&Current->KilledCows,&Current->LostCows,&Current->CompletedCowObjective,&Current->KilledMice,&Current->CompledMiceObjective,
+						&Current->KilledPortablePumpJacks,&Current->PortablePumpJacksPlaced,&Current->PortablePumpJacksLost,&Current->PumpJackMoney,&Current->MobilePumpJackMoney,&Current->Rank);
 			}
 			fclose(LoadScores);	
 		}
@@ -1894,6 +1769,17 @@
 		case 108: return EveluateHighestScore(High->FailedPettingCougars,Current->FailedPettingCougars,High,Current);
 		case 109: return EveluateHighestScore(High->KilledFriendlyCougars,Current->KilledFriendlyCougars,High,Current);
 		case 110: return EveluateHighestScore(High->DiedTryingToEscapeBase,Current->DiedTryingToEscapeBase,High,Current);
+		case 111: return EveluateHighestScore(High->RescuedCows,Current->RescuedCows,High,Current);
+		case 112: return EveluateHighestScore(High->KilledCows,Current->KilledCows,High,Current);
+		case 113: return EveluateHighestScore(High->LostCows,Current->LostCows,High,Current);
+		case 114: return EveluateHighestScore(High->CompletedCowObjective,Current->CompletedCowObjective,High,Current);
+		case 115: return EveluateHighestScore(High->KilledMice,Current->KilledMice,High,Current);
+		case 116: return EveluateHighestScore(High->CompledMiceObjective,Current->CompledMiceObjective,High,Current);
+		case 117: return EveluateHighestScore(High->KilledPortablePumpJacks,Current->KilledPortablePumpJacks,High,Current);
+		case 118: return EveluateHighestScore(High->PortablePumpJacksPlaced,Current->PortablePumpJacksPlaced,High,Current);
+		case 119: return EveluateHighestScore(High->PortablePumpJacksLost,Current->PortablePumpJacksLost,High,Current);
+		case 120: return EveluateHighestScore(High->PumpJackMoney,Current->PumpJackMoney,High,Current);
+		case 121: return EveluateHighestScore(High->PumpJackMoney,Current->PumpJackMoney,High,Current);
 		default: return High;
 		}
 	}
@@ -2014,6 +1900,17 @@
 		case 108: return Node->FailedPettingCougars ? true : false;
 		case 109: return Node->KilledFriendlyCougars ? true : false;
 		case 110: return Node->DiedTryingToEscapeBase ? true : false;
+		case 111: return Node->RescuedCows ? true : false;
+		case 112: return Node->KilledCows ? true : false;
+		case 113: return Node->LostCows ? true : false;
+		case 114: return Node->CompletedCowObjective ? true : false;
+		case 115: return Node->KilledMice ? true : false;
+		case 116: return Node->CompledMiceObjective ? true : false;
+		case 117: return Node->KilledPortablePumpJacks ? true : false;
+		case 118: return Node->PortablePumpJacksPlaced ? true : false;
+		case 119: return Node->PortablePumpJacksLost ? true : false;
+		case 120: return Node->PumpJackMoney ? true : false;
+		case 121: return Node->MobilePumpJackMoney ? true : false;
 		default: Console_Input("msg SCORE SYSTEM ERROR: Out of bounds!");return false;
 		}
 	}
@@ -2132,9 +2029,20 @@
 		case 105: sprintf(RetChar,"Server Record: %s has petted %s cougars.",High->PlayerName,JmgUtility::formatDigitGrouping(High->PettedCougars));return RetChar;
 		case 106: sprintf(RetChar,"Server Record: %s has killed %s wild cougars.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledCougars));return RetChar;
 		case 107: sprintf(RetChar,"Server Record: %s has killed %s mutant cougars.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledMutantCougars));return RetChar;
-		case 108: sprintf(RetChar,"Server Record: %s has killed %s a cougar instead of petting it.",High->PlayerName,JmgUtility::formatDigitGrouping(High->FailedPettingCougars));return RetChar;
+		case 108: sprintf(RetChar,"Server Record: %s has killed %s cougars instead of petting them.",High->PlayerName,JmgUtility::formatDigitGrouping(High->FailedPettingCougars));return RetChar;
 		case 109: sprintf(RetChar,"Server Record: %s has killed %s friendly cougars.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledFriendlyCougars));return RetChar;
 		case 110: sprintf(RetChar,"Server Record: %s has died trying to escape the base %s times when the boss was lurking about.",High->PlayerName,JmgUtility::formatDigitGrouping(High->DiedTryingToEscapeBase));return RetChar;
+		case 111: sprintf(RetChar,"Server Record: %s has rescued %s cows.",High->PlayerName,JmgUtility::formatDigitGrouping(High->RescuedCows));return RetChar;
+		case 112: sprintf(RetChar,"Server Record: %s has killed %s cows.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledCows));return RetChar;
+		case 113: sprintf(RetChar,"Server Record: %s has lost %s cows.",High->PlayerName,JmgUtility::formatDigitGrouping(High->LostCows));return RetChar;
+		case 114: sprintf(RetChar,"Server Record: %s has completed the cow objective %s times.",High->PlayerName,JmgUtility::formatDigitGrouping(High->CompletedCowObjective));return RetChar;
+		case 115: sprintf(RetChar,"Server Record: %s has killed %s mice.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledMice));return RetChar;
+		case 116: sprintf(RetChar,"Server Record: %s has completed the pest objective %s times.",High->PlayerName,JmgUtility::formatDigitGrouping(High->CompledMiceObjective));return RetChar;
+		case 117: sprintf(RetChar,"Server Record: %s has destroyed %s Portable Pumpjacks.",High->PlayerName,JmgUtility::formatDigitGrouping(High->KilledPortablePumpJacks));return RetChar;
+		case 118: sprintf(RetChar,"Server Record: %s has placed %s Portable Pumpjacks.",High->PlayerName,JmgUtility::formatDigitGrouping(High->PortablePumpJacksPlaced));return RetChar;
+		case 119: sprintf(RetChar,"Server Record: %s has lost %s Portable Pumpjacks.",High->PlayerName,JmgUtility::formatDigitGrouping(High->PortablePumpJacksLost));return RetChar;
+		case 120: sprintf(RetChar,"Server Record: %s has made $%s.00 from Pumpjacks.",High->PlayerName,JmgUtility::formatDigitGrouping(High->PumpJackMoney));return RetChar;
+		case 121: sprintf(RetChar,"Server Record: %s has made $%s.00 from Portable Pumpjacks.",High->PlayerName,JmgUtility::formatDigitGrouping(High->MobilePumpJackMoney));return RetChar;
 		default: sprintf(RetChar,"Server Record ERROR: Record index out of bounds!"); return RetChar;
 		}
 	}
@@ -2366,6 +2274,8 @@
 * \ingroup JmgUtility
 */
 class JMG_Security_Camera_Behavior : public ScriptImpClass {
+	float scanAngle;
+	float updateRateMultiplier;
 	float zHeight;
 	int originalTeam;
 	bool enabled;
@@ -2375,8 +2285,9 @@
 	int IncreaseOrDecreaseCount;
 	int SeenTime;
 	int stealthModeOverride;
-	Vector3 CameraFacingLocation[5];
+	float cameraAngles[5];
 	float CameraFacingUpdateTime;
+	float idleZAimAngleModifier;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *seen);
 	void Timer_Expired(GameObject *obj,int number);
@@ -2467,6 +2378,7 @@
 };
 
 class JMG_Bear_Hunter_Security_Turret_Truck : public ScriptImpClass {
+	GameObject *driver;
 	bool firstTime;
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj,int message,int param,GameObject *sender);
@@ -2852,6 +2764,7 @@
 		}
 	};
 private:
+	float intPayout;
 	bool active;
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj,int number);
@@ -3516,6 +3429,7 @@
 					char params[512];
 					sprintf(params,"%d,%d",current->id,controllerId);
 					Commands->Attach_Script(packMember,"JMG_Bear_Hunter_Wolf",params);
+					Commands->Attach_Script(packMember,"JMG_Bear_Hunter_Give_AI_Cash_For_Kills","");
 					MoveablePhysClass *mphys = packMember->As_PhysicalGameObj() ? packMember->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
 					if (mphys && !mphys->Can_Teleport(Matrix3D(createLocation)))
 					{
@@ -3644,19 +3558,23 @@
 };
 
 class JMG_Bear_Hunter_AI_Avoid_Enemies : public ScriptImpClass {
-	int fleeTime;
-	int originalFleeTime;
+	int closestEnemyId;
+	float enemyDist;
+	int seenTime;
 	Vector3 movePosition;
 	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 Killed(GameObject *obj,GameObject *killer);
 	void Destroyed(GameObject *obj);
 	void GotoLocation(GameObject *obj,const Vector3 &pos,GameObject *Enemy,float speed);
 	void setRetreatLocation(GameObject *obj,GameObject *enemy);
 	void getRandomLocation(GameObject *obj);
 public:
+	bool mouse;
 	static int wildAnimalCount;
+	static int mouseAnimalCount;
 };
 
 class JMG_Security_Camera_Behavior_Ignore : public ScriptImpClass {
@@ -3694,4 +3612,61 @@
 
 class JMG_Bear_Hunter_SpawnPoint_Final_Boss : public ScriptImpClass {
 	void Created(GameObject *obj);
+};
+
+class JMG_Bear_Hunter_PumpJack_Beacon : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+class JMG_Bear_Hunter_PumpJack_Powerup : public ScriptImpClass {
+	int Count;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Destroyed(GameObject *obj);
+};
+
+class JMG_Bear_Hunter_Portable_Pumpjack : public ScriptImpClass {
+	BearHunterScoreSystem::BHScoreNode *playerScoreNode;
+	float intPayout;
+	float giveMoney;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Killed(GameObject *obj,GameObject *killer);
+};
+
+class JMG_Bear_Hunter_PumpJack_Detect_Oil : public ScriptImpClass {
+	int playerId;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	static float FindClosestPumpjack(GameObject *obj);
+public:
+	static float CalculateEffectiveness(GameObject *obj);
+};
+
+class JMG_Bear_Hunter_Milk_Drink : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+class JMG_Bear_Hunter_AI_Guardian_Generic : public ScriptImpClass {
+	float fireRange;
+	float flightHeight;
+	float arriveDistance;
+	float arriveDistanceSq;
+	Vector3 dpPosition;
+	int EnemyID;
+	int EnemyTimeOutTime;
+	Vector3 LastPos;
+	bool primaryFire;
+	int stealthModeOverride;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Enemy_Seen(GameObject *obj,GameObject *seen);
+	void Damaged(GameObject *obj,GameObject *damager,float damage);
+	void Goto_Location(GameObject *obj);
+	bool Get_A_Defense_Point(Vector3 *position);
+};
+
+class JMG_Bear_Hunter_Give_AI_Cash_For_Kills : public ScriptImpClass {
+	void Killed(GameObject *obj,GameObject *killer);
 };
\ No newline at end of file
diff -urN sourceold/scripts/jmgMetroid.cpp source/scripts/jmgMetroid.cpp
--- sourceold/scripts/jmgMetroid.cpp	2019-08-20 10:11:06.810000100 +1000
+++ source/scripts/jmgMetroid.cpp	2020-04-02 12:02:44.440356300 +1000
@@ -4856,6 +4856,8 @@
 }
 void JMG_AI_Artillery_Targeting_Fire_At_Custom::Created(GameObject *obj)
 {
+	if (obj->As_SoldierGameObj())
+		obj->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
 	const char *weap = Get_Current_Weapon(obj);
 	if (!weap)
 		return;
@@ -4875,7 +4877,7 @@
 			seen = sender;
 		if (Get_Vehicle_Driver(seen))
 			seen = Get_Vehicle_Driver(seen);
-		Vector3 myPos = Commands->Get_Position(obj),enemyPos = Commands->Get_Position(seen);
+		Vector3 myPos = obj->As_SoldierGameObj() ? Commands->Get_Bone_Position(obj,"gunbone") : Commands->Get_Bone_Position(obj,"MuzzleA0"),enemyPos = Commands->Get_Position(seen);
 		float targetDist = JmgUtility::SimpleDistance(myPos,enemyPos),targetFlatDistance = JmgUtility::SimpleFlatDistance(myPos,enemyPos),minDistanceSquared = Get_Float_Parameter("MinDistance")*Get_Float_Parameter("MinDistance");
 		if (targetDist < minDistanceSquared)
 			return;
@@ -4884,17 +4886,17 @@
 			useHighAngle = false;
 		else if (Get_Int_Parameter("UseLowAngleWhenAboveMinDistance") && targetFlatDistance <= minDistanceSquared)
 			useHighAngle = false;
-		else if (Get_Float_Parameter("UseLowAngleTargetAboveHeight") && Commands->Get_Position(obj).Z+Get_Float_Parameter("UseLowAngleTargetAboveHeight") < Commands->Get_Position(seen).Z)
+		else if (Get_Float_Parameter("UseLowAngleTargetAboveHeight") && myPos.Z+Get_Float_Parameter("UseLowAngleTargetAboveHeight") < Commands->Get_Position(seen).Z)
 			useHighAngle = false;
 		double height = enemyPos.Z-myPos.Z,angle;
 		myPos.Z = enemyPos.Z = 0.0f;
 		if (!CalculateAngle(&angle,Commands->Get_Distance(enemyPos,myPos),height,useHighAngle))
 			return;
 		float TempRotation = atan2(enemyPos.Y-myPos.Y,enemyPos.X-myPos.X);
-		Vector3 pos = Commands->Get_Bone_Position(obj,"barrel");
-		pos.X += (float)cos(TempRotation);
-		pos.Y += (float)sin(TempRotation);
-		pos.Z += (float)tan(angle*PI/180);
+		Vector3 pos = obj->As_SoldierGameObj() ? Commands->Get_Bone_Position(obj,"gunbone") : Commands->Get_Bone_Position(obj,"MuzzleA0");
+		pos.X += (float)cos(TempRotation)*10.0f;
+		pos.Y += (float)sin(TempRotation)*10.0f;
+		pos.Z += (float)tan(angle*PI/180)*10.0f;
 		ActionParamsStruct params;
 		params.Set_Attack(pos,100000.0f,0,1);
 		params.AttackForceFire = false;
diff -urN sourceold/scripts/jmgUtility.cpp source/scripts/jmgUtility.cpp
--- sourceold/scripts/jmgUtility.cpp	2019-08-20 10:11:06.814000100 +1000
+++ source/scripts/jmgUtility.cpp	2020-09-10 09:08:27.970221800 +1000
@@ -12,8 +12,8 @@
 #include "engine_player.h"
 #include "DefinitionMgrClass.h"
 #include "AudibleSoundDefinitionClass.h"
+#include "TranslateDBClass.h"
 bool JmgUtility::hasStatedDeathMessage[128] = {false};
-
 void JMG_Utility_Check_If_Script_Is_In_Library::Created(GameObject *obj)
 {
 	Commands->Start_Timer(obj,this,0.1f,1);
@@ -1450,7 +1450,29 @@
 }
 void JMG_Utility_Dynamic_Clock_Object::Created(GameObject *obj)
 {
+	for (int x = 0;x < 128;x++)
+		animSynced[x] = false;
 	DynamicClockSystemControl += obj;
+	Commands->Start_Timer(obj,this,1.0f,1);
+}
+void JMG_Utility_Dynamic_Clock_Object::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1) 
+	{
+		for (int x = 1;x < 128;x++)
+		{
+			GameObject *player = Get_GameObj(x);
+			if (player && animSynced[x] < 3)
+			{
+				animSynced[x]++;
+				if (animSynced[x] == 3)
+					Set_Subobject_Animation_Player(player,obj,"s_ecwClock.a_ecwClock",false,0,Get_Animation_Frame(obj),Get_Animation_Frame(obj),false);
+			}
+			if (!player && animSynced[x])
+				animSynced[x] = 0;
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
 }
 void JMG_Utility_Dynamic_Clock_Object::Destroyed(GameObject *obj)
 {
@@ -2251,7 +2273,7 @@
 	sprintf(originalSkin,"");
 	sprintf(originalArmor,"");
 	sprintf(originalModel,"");
-	for (SLNode<ScriptZoneGameObj>* node = GameObjManager::ScriptZoneGameObjList.Head(); node; node = node->Next())
+	for (SLNode<ScriptZoneGameObj>* node = GameObjManager::ScriptZoneGameObjList.Head();node;node = node->Next())
 	{
 		GameObject *zone = (GameObject *)node->Data();
 		if (Is_Script_Attached(zone,"JMG_Utility_Swimming_Zone") && IsInsideZone(zone,obj))
@@ -3170,6 +3192,10 @@
 		node = aggressiveAttackSpotSystemControl->addNode(obj,Get_Int_Parameter("GroupId"),Get_Vector3_Parameter("AttackOffset"));
 	}
 }
+void JMG_Utility_AI_Aggressive_Attack_Spot_Point::Killed(GameObject *obj,GameObject *killer)
+{
+	Destroyed(obj);
+}
 void JMG_Utility_AI_Aggressive_Attack_Spot_Point::Destroyed(GameObject *obj)
 {
 	if (!The_Game()->Is_Game_Over() && node)
@@ -3692,6 +3718,7 @@
 	controllerPlaced = true;
 	for (int x = 0;x < 128;x++)
 		playerNames[x][0] = '\0';
+	BasicObjectiveSystem.controllerId = Commands->Get_ID(obj);
 	BasicObjectiveSystem = NewObjectiveSystem(Get_Int_Parameter("Team"),Get_Int_Parameter("ShowMarkersOnRadar") ? true : false,Get_Parameter("PrimaryObjectiveModel"),Get_Parameter("SecondaryObjectiveModel"),Get_Parameter("TertiaryObjectiveModel"));
 	Commands->Start_Timer(obj,this,1.0f,1);
 }
@@ -3763,11 +3790,13 @@
 				Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Enter ERROR: Could not find objective marker object!");
 				return;
 			}
+			bool added = false;
 			if (ObjectiveMarker)
-				BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
 			else
-				BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
-			JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+			if (added)
+				JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
 		}
 	}
 }
@@ -3794,11 +3823,13 @@
 			Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Pickup ERROR: Could not find objective marker object!");
 			return;
 		}
+		bool added = false;
 		if (ObjectiveMarker)
-			BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+			added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
 		else
-			BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
-		JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+			added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+		if (added)
+			JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
 	}
 }
 void JMG_Utility_Objective_System_Objective_Update_Killed::Killed(GameObject *obj, GameObject *damager)
@@ -3822,11 +3853,13 @@
 		Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Killed ERROR: Could not find objective marker object!");
 		return;
 	}
+	bool added = false;
 	if (ObjectiveMarker)
-		BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+		added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
 	else
-		BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
-	JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+		added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+	if (added)
+		JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
 }
 void JMG_Utility_Objective_System_Objective_Update_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
 {
@@ -3849,11 +3882,13 @@
 			Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Custom ERROR: Could not find objective marker object!");
 			return;
 		}
+		bool added = false;
 		if (ObjectiveMarker)
-			BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+			added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
 		else
-			BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
-		JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+			added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+		if (added)
+			JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
 	}
 }
 void JMG_Utility_Destroyed_Apply_Damage::Destroyed(GameObject *obj)
@@ -3964,6 +3999,8 @@
 	armorPerPlayer = Get_Float_Parameter("ArmorPerPlayer");
 	damageDelay = Get_Float_Parameter("DamageDelay");
 	rate = Get_Float_Parameter("Rate");
+	scaleHealthPerHeal = Get_Float_Parameter("ScaleHealthPerHeal")*0.00001f;
+	scaleArmorPerHeal = Get_Float_Parameter("ScaleArmorPerHeal")*0.00001f;
 	enabled = true;
 	Commands->Start_Timer(obj,this,rate,1);
 }
@@ -3977,24 +4014,31 @@
 			float maxHealth = Commands->Get_Max_Health(obj);
 			float armor = Commands->Get_Shield_Strength(obj);
 			float maxArmor = Commands->Get_Max_Shield_Strength(obj);
-			if (regenArmor == -1 && armor < maxArmor)
+			if (regenArmor == -1 && armor < maxArmor && armorAmount+armorPerPlayer)
 			{
 				Commands->Set_Shield_Strength(obj,armor+armorAmount+armorPerPlayer*Get_Player_Count());
 				if (Commands->Get_Shield_Strength(obj) > maxArmor)
 					Commands->Set_Shield_Strength(obj,maxArmor);
 			}
-			else if (regenHealth && health < maxHealth)
+			else if (regenHealth && health < maxHealth && healthAmount+healthPerPlayer)
 			{
 				Commands->Set_Health(obj,health+healthAmount+healthPerPlayer*Get_Player_Count());
 				if (Commands->Get_Health(obj) > maxHealth)
 					Commands->Set_Health(obj,maxHealth);
 			}
-			else if (regenArmor && armor < maxArmor)
+			else if (regenArmor && armor < maxArmor && armorAmount+armorPerPlayer)
 			{
 				Commands->Set_Shield_Strength(obj,armor+armorAmount+armorPerPlayer*Get_Player_Count());
 				if (Commands->Get_Shield_Strength(obj) > maxArmor)
 					Commands->Set_Shield_Strength(obj,maxArmor);
 			}
+			healthAmount = ScaleValue(healthAmount,scaleHealthPerHeal);
+			armorAmount = ScaleValue(armorAmount,scaleArmorPerHeal);
+			if ((!regenHealth || healthAmount == 0.0f) && (!regenArmor || armorAmount == 0.0f))
+			{
+				Destroy_Script();
+				return;
+			}
 		}
 		Commands->Start_Timer(obj,this,rate,1);
 	}
@@ -4011,6 +4055,15 @@
 	Stop_Timer2(obj,this,2);
 	Commands->Start_Timer(obj,this,damageDelay,2);
 }
+float JMG_Utility_Regen_HitPoints::ScaleValue(float value,float scale)
+{
+	if (scale == 0.0f || value == 0.0f)
+		return value;
+	value += scale;
+	if (value < 0.0f)
+		return 0.0f;
+	return value;
+}
 void JMG_Utility_Toggle_Flight_On_Delay::Created(GameObject *obj)
 {
 	Commands->Start_Timer(obj,this,Get_Float_Parameter("Delay"),1);
@@ -4749,6 +4802,8 @@
 		}
 		if (nearest)
 			Commands->Destroy_Object(nearest);
+		if (Get_Int_Parameter("TriggerOnce"))
+			Destroy_Script();
 	}
 }
 void JMG_Utility_Zone_Set_Spawner::Entered(GameObject *obj,GameObject *enter)
@@ -5098,11 +5153,13 @@
 				Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Create ERROR: Could not find objective marker object!");
 				return;
 			}
+			bool added = false;
 			if (ObjectiveMarker)
-				BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
 			else
-				BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
-			JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+			if (added)
+				JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
 		}
 	}
 }
@@ -7448,6 +7505,10 @@
 	Commands->Set_Player_Type(obj,Commands->Get_Player_Type(poker));
 	Commands->Enable_HUD_Pokable_Indicator(obj,false);
 }
+void JMG_Utility_AI_Follow_Player_On_Poke::Killed(GameObject *obj, GameObject *killer)
+{
+	Destroyed(obj);
+}
 void JMG_Utility_AI_Follow_Player_On_Poke::Destroyed(GameObject *obj)
 {
 	if (detached)
@@ -10753,7 +10814,7 @@
 	originalWidthScale = 0.0f;
 	drownDamageRate = drownDamageRate;
 	weaponSwitchForward = true;
-	for (SLNode<ScriptZoneGameObj>* node = GameObjManager::ScriptZoneGameObjList.Head(); node; node = node->Next())
+	for (SLNode<ScriptZoneGameObj> *node = GameObjManager::ScriptZoneGameObjList.Head();node;node = node->Next())
 	{
 		GameObject *zone = (GameObject *)node->Data();
 		if (Is_Script_Attached(zone,"JMG_Utility_Swimming_Zone") && IsInsideZone(zone,obj))
@@ -11143,8 +11204,6 @@
 }
 void JMG_Utility_Killed_Send_Custom_From_Killer::Killed(GameObject *obj,GameObject *killer)
 {
-	if (!Commands->Is_A_Star(killer))
-		return;
 	int id = Get_Int_Parameter("ID");
 	GameObject *object = id ? (id == -1 ? killer : Commands->Find_Object(id)) : obj;
 	Commands->Send_Custom_Event(killer,object,Get_Int_Parameter("Custom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
@@ -11608,6 +11667,8 @@
 }
 float JMG_Utility_Base_Defense_Simple::GetPriority(GameObject *seen)
 {
+	if (!seen)
+		return 0;
 	if (seen->As_VehicleGameObj())
 		switch (Get_Vehicle_Mode(seen))
 		{
@@ -11980,6 +12041,7 @@
 			else if (useLowAngleTargetAboveHeight && Commands->Get_Position(obj).Z+useLowAngleTargetAboveHeight < Commands->Get_Position(seen).Z)
 				useHighAngle = false;
 			double height = enemyPos.Z-myPos.Z,angle;
+			myPos.Z = enemyPos.Z = 0.0f;
 			if (!CalculateAngle(&angle,Commands->Get_Distance(enemyPos,myPos),height,useHighAngle))
 			{
 				Commands->Start_Timer(obj,this,0.01f,1);
@@ -12148,6 +12210,2395 @@
 		Commands->Start_Timer(obj,this,0.1f,1);
 	}
 }
+void JMG_Utility_Enter_Play_Animation_And_Relocate::Created(GameObject *obj)
+{
+	sprintf(presetName,"%s",Get_Parameter("PresetName"));
+	sprintf(animation,"%s",Get_Parameter("Animation"));
+	sprintf(attachedModel,"%s",Get_Parameter("AttachedModel"));
+	sprintf(attachedAnimation,"%s",Get_Parameter("AttachedAnimation"));
+	centerToZone = Get_Int_Parameter("CenterToZone") ? true : false;
+	custom = Get_Int_Parameter("Custom");
+	customParam = Get_Int_Parameter("Param");
+	OBBoxClass *zone = Get_Zone_Box(obj);
+	if (!zone)
+	{
+		Console_Input("msg JMG_Utility_Enter_Play_Animation_And_Relocate ERROR: Must be attached to a script zone!");
+		Destroy_Script();
+		return;
+	}
+	zoneRotation = zone->Basis.Get_Z_Rotation()*(180/PI);
+}
+void JMG_Utility_Enter_Play_Animation_And_Relocate::Entered(GameObject *obj,GameObject *enterer)
+{
+	if (Is_Script_Attached(enterer,"JMG_Utility_Enter_Play_Animation_And_Relocate_Attached"))
+		return;
+	if (!enterer->As_SmartGameObj())
+		return;
+	if (!_stricmp(presetName,"") || !_stricmp(presetName,Commands->Get_Preset_Name(enterer)))
+	{
+		if (centerToZone)
+			Commands->Set_Position(enterer,Commands->Get_Position(obj));
+		if (enterer->As_SoldierGameObj())
+		{
+			enterer->As_SoldierGameObj()->Set_Override_Weapon_Hold_Style(14);
+			enterer->As_SoldierGameObj()->Set_Can_Play_Damage_Animations(false);
+		}
+		Commands->Set_Animation(enterer,animation,false,NULL,0.0f,-1.0f,true);
+		char params[512];
+		sprintf(params,"%s,%s,%.2f,%d,%d",attachedModel,attachedAnimation,zoneRotation,custom,customParam);
+		Attach_Script_Once(enterer,"JMG_Utility_Enter_Play_Animation_And_Relocate_Attached",params);
+	}
+}
+void JMG_Utility_Enter_Play_Animation_And_Relocate_Attached::Created(GameObject *obj)
+{
+	GameObject *holder = Commands->Create_Object("Daves Arrow",Commands->Get_Position(obj));
+	Commands->Set_Model(holder,Get_Parameter("AttachedModel"));
+	Commands->Set_Animation(holder,Get_Parameter("AttachedAnimation"),false,NULL,0.0f,-1.0f,true);
+	Commands->Set_Facing(holder,Get_Float_Parameter("Facing"));
+	Commands->Attach_To_Object_Bone(obj,holder,"attach");
+	holderId = Commands->Get_ID(holder);
+}
+void JMG_Utility_Enter_Play_Animation_And_Relocate_Attached::Animation_Complete(GameObject *obj,const char *anim)
+{
+	if (obj->As_SoldierGameObj())
+	{
+		obj->As_SoldierGameObj()->Set_Override_Weapon_Hold_Style(-1);
+		obj->As_SoldierGameObj()->Set_Can_Play_Damage_Animations(true);
+		Commands->Action_Reset(obj,100.0f);
+	}
+	obj->As_PhysicalGameObj()->Peek_Physical_Object()->Set_Collision_Group(Collision_Group_Type::SOLDIER_GHOST_COLLISION_GROUP);
+	Commands->Send_Custom_Event(obj,obj,Get_Int_Parameter("Custom"),Get_Int_Parameter("Param"),0);
+	Destroyed(obj);
+	Destroy_Script();
+}
+void JMG_Utility_Enter_Play_Animation_And_Relocate_Attached::Destroyed(GameObject *obj)
+{
+	GameObject *holder = Commands->Find_Object(holderId);
+	if (holder)
+		Commands->Destroy_Object(holder);
+}
+void JMG_Utility_Custom_Destroy_Closest_Object_To_Self::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Custom_Destroy_Closest_Object_To_Self::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		GameObject *nearest = NULL;
+		float dist = 0.0f;
+		Vector3 pos = Commands->Get_Position(obj);
+		const char *presetName = Get_Parameter("PresetName");
+		float maxDist = Get_Float_Parameter("MaxDistance");
+		maxDist *= maxDist;
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (!_stricmp(presetName,Commands->Get_Preset_Name(o)))
+			{
+				float tempDistance = JmgUtility::SimpleDistance(pos,Commands->Get_Position(o));
+				if (!nearest || (tempDistance < dist && (maxDist == 0.0 || tempDistance <= maxDist)))
+				{
+					nearest = o;
+					dist = tempDistance;
+				}
+			}
+		}
+		if (nearest)
+			Commands->Destroy_Object(nearest);
+		if (Get_Int_Parameter("TriggerOnce"))
+			Destroy_Script();
+	}
+}
+void JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		GameObject *nearest = NULL;
+		float dist = 0.0f;
+		Vector3 pos = Commands->Get_Position(obj);
+		const char *presetName = Get_Parameter("PresetName");
+		float maxDist = Get_Float_Parameter("MaxDistance");
+		maxDist *= maxDist;
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (!_stricmp(presetName,Commands->Get_Preset_Name(o)))
+			{
+				float tempDistance = JmgUtility::SimpleDistance(pos,Commands->Get_Position(o));
+				if (!nearest || (tempDistance < dist && (maxDist == 0.0 || tempDistance <= maxDist)))
+				{
+					nearest = o;
+					dist = tempDistance;
+				}
+			}
+		}
+		if (nearest)
+			Commands->Apply_Damage(nearest,Get_Float_Parameter("DamageAmount"),Get_Parameter("Warhead"),sender);
+		if (Get_Int_Parameter("TriggerOnce"))
+			Destroy_Script();
+	}
+}
+void JMG_Utility_Custom_Restore_Building::Created(GameObject *obj)
+{
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("Custom");
+	triggerOnce = Get_Int_Parameter("TriggerOnce") ? true : false;
+}
+void JMG_Utility_Custom_Restore_Building::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		GameObject *building = obj;
+		if (id)
+			if (id < 0)
+				building = sender;
+			else
+				building = Commands->Find_Object(id);
+		Restore_Building(building);
+		if (triggerOnce)
+			Destroy_Script();
+	}
+}
+void JMG_Utility_Timer_Custom_Random::Created(GameObject *obj)
+{
+	time = Get_Float_Parameter("Time");
+	random = Get_Float_Parameter("Random");
+	repeat = Get_Int_Parameter("Repeat") ? true : false;
+	id = Get_Int_Parameter("ID");
+	message = Get_Int_Parameter("Message");
+	param = Get_Int_Parameter("Param");
+	Commands->Start_Timer(obj,this,time+(random ? Commands->Get_Random(-random,random) : 0.0f),1);
+}
+void JMG_Utility_Timer_Custom_Random::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		GameObject *object = !id ? obj : Commands->Find_Object(id);
+		if (object)
+			Commands->Send_Custom_Event(obj,object,message,param,0);
+		if (repeat)
+			Commands->Start_Timer(obj,this,time+(random ? Commands->Get_Random(-random,random) : 0.0f),1);
+	}
+}
+DynamicAttachScript JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript;
+void JMG_Utility_Dynamic_Script_Controller::Destroyed(GameObject *obj)
+{
+	JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript.EmptyList();
+}
+void JMG_Utility_Dynamic_Script_Definition::Created(GameObject *obj)
+{
+	JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript.AddDynamicScript(Get_Int_Parameter("ScriptID"),Get_Parameter("ScriptName"));
+	Destroy_Script();
+}
+void JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter::Created(GameObject *obj)
+{
+	Commands->Start_Timer(obj,this,0.01f,1);
+}
+void JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		const char *parameters = Get_Parameter("Param");
+		char *params = newstr(parameters);
+		char delim = Get_Parameter("Delim")[0];
+		if (delim != '\0')
+		{
+			unsigned int length = strlen(parameters);
+			for (unsigned int x = 0;x < length;x++)
+				if (params[x] == delim)
+					params[x] = ',';
+		}
+		JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript.AddParamToScript(Get_Int_Parameter("ScriptID"),Get_Int_Parameter("Index"),params);
+		Destroy_Script();
+	}
+}
+void JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		const char *parameters = Get_Parameter("Param");
+		char *params = newstr(parameters);
+		char delim = Get_Parameter("Delim")[0];
+		if (delim != '\0')
+		{
+			unsigned int length = strlen(parameters);
+			for (unsigned int x = 0;x < length;x++)
+				if (params[x] == delim)
+					params[x] = ',';
+		}
+		JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript.AddParamToScript(Get_Int_Parameter("ScriptID"),Get_Int_Parameter("Index"),params);
+	}
+}
+void JMG_Utility_Dynamic_Script_Custom_Attach::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Dynamic_Script_Custom_Attach::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		DynamicAttachScript::DynamicAttachScriptNode *script = JMG_Utility_Dynamic_Script_Controller::dynamicAttachScript.FindDynamicScript(Get_Int_Parameter("ScriptID"));
+		if (!script)
+			return;
+		char attachParams[2048];
+		sprintf(attachParams,"");
+		int addedParams = 0;
+		for (int x = 0;x < 128 && addedParams < script->dynamicAttachScriptParams.objectCount;x++)
+		{
+			DynamicAttachScript::DynamicAttachScriptParams::DynamicAttachScriptParamNode *scriptParam = script->dynamicAttachScriptParams.FindParam(x);
+			if (scriptParam)
+			{
+				sprintf(attachParams,"%s%s%s",attachParams,addedParams ? "," : "",scriptParam->param);
+				addedParams++;
+			}
+		}
+		Commands->Attach_Script(sender,script->scriptName,attachParams);
+	}
+}
+void JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		if (!JMG_Utility_Objective_System_Controller::controllerPlaced)
+		{
+			Console_Input("msg JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom ERROR: Make sure you have a JMG_Utility_Objective_System_Controller placed on the map!");
+			return;
+		}
+		if (Get_Int_Parameter("CompleteObjectiveID") && BasicObjectiveSystem.Get_Objective_Status(Get_Int_Parameter("CompleteObjectiveID")) != NewObjectiveSystem::Failed)
+		{
+			BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("CompleteObjectiveID"),NewObjectiveSystem::Primary,NewObjectiveSystem::Hidden,0,"",0);
+			BasicObjectiveSystem.Set_Objective_Status(Get_Int_Parameter("CompleteObjectiveID"),NewObjectiveSystem::Accomplished);
+		}
+		int objectiveMarkerId = Get_Int_Parameter("ObjectiveMarkerObjectID");
+		GameObject *ObjectiveMarker = objectiveMarkerId == -1 ? sender : Commands->Find_Object(objectiveMarkerId);
+		if (!ObjectiveMarker && objectiveMarkerId)
+		{
+			Console_Input("msg JMG_Utility_Objective_System_Objective_Update_Custom ERROR: Could not find objective marker object!");
+			return;
+		}
+		int status = BasicObjectiveSystem.Get_Objective_Status(Get_Int_Parameter("NewObjectiveID"));
+		if (status == NewObjectiveSystem::NotDefined || status == NewObjectiveSystem::Removed)
+		{
+			int id = Get_Int_Parameter("ID");
+			int Param = Get_Int_Parameter("Param");
+			GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+			Commands->Send_Custom_Event(obj,object,Get_Int_Parameter("SendCustom"),Param == -1 ? param : Param,Get_Float_Parameter("CustomDelay"));
+			bool added = false;
+			if (ObjectiveMarker)
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"),ObjectiveMarker);
+			else
+				added = BasicObjectiveSystem.Add_Objective(Get_Int_Parameter("NewObjectiveID"),(NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority"),NewObjectiveSystem::Pending,Get_Int_Parameter("NewObjectiveStringID"),"",Get_Int_Parameter("NewObjectiveStringID"));
+			if (added)
+				JmgUtility::SetHUDHelpText(Get_Int_Parameter("NewObjectiveStringID"),BasicObjectiveSystem.Get_Hud_Help_Text_Color((NewObjectiveSystem::Priority)Get_Int_Parameter("NewObjectivePriority")));
+		}
+	}
+}
+void JMG_Utility_Objective_System_Custom_Send_Custom_Status::Created(GameObject *obj)
+{
+	objectiveId = Get_Int_Parameter("ObjectiveID");
+	status = Get_Int_Parameter("Status");
+	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");
+}
+void JMG_Utility_Objective_System_Custom_Send_Custom_Status::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		if (BasicObjectiveSystem.Get_Objective_Status(objectiveId) != status)
+			return;
+		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_Objective_System_Custom_Send_Custom_Does_Not_Exist::Created(GameObject *obj)
+{
+	objectiveId = Get_Int_Parameter("ObjectiveID");
+	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");
+}
+void JMG_Utility_Objective_System_Custom_Send_Custom_Does_Not_Exist::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		int status = BasicObjectiveSystem.Get_Objective_Status(objectiveId);
+		if (status != NewObjectiveSystem::NotDefined && status != NewObjectiveSystem::Removed)
+			return;
+		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_Objective_System_Send_Random_Objective_As_Custom::Created(GameObject *obj)
+{
+	for (int x = 0;x < 10;x++)
+	{
+		char objectiveName[15];
+		sprintf(objectiveName,"ObjectiveID%0d",x);
+		objectiveId[x] = Get_Int_Parameter(objectiveName);
+	}
+	recieveMessage = Get_Int_Parameter("Custom");
+	id = Get_Int_Parameter("ID");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+}
+void JMG_Utility_Objective_System_Send_Random_Objective_As_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		int random = Commands->Get_Random_Int(1,10);
+		int originalRandom = random;
+		int tmpObjectiveId = -1;
+		for (int x = 0;;x++)
+		{
+			if (x == 10)
+			{
+				if (random == originalRandom)
+					return;
+				x = 0;
+			}
+			if (objectiveId[x] == -999)
+				continue;
+			int status = BasicObjectiveSystem.Get_Objective_Status(objectiveId[x]);
+			if (status != NewObjectiveSystem::NotDefined && status != NewObjectiveSystem::Removed)
+				continue;
+			random--;
+			if (!random)
+			{
+				tmpObjectiveId = objectiveId[x];
+				break;
+			}
+		}
+		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+ 		Commands->Send_Custom_Event(obj,object,tmpObjectiveId,Param == -1 ? param : Param,delay);
+	}
+}
+void JMG_Utility_Objective_System_Custom_Send_Custom_Not_Status::Created(GameObject *obj)
+{
+	objectiveId = Get_Int_Parameter("ObjectiveID");
+	status = Get_Int_Parameter("Status");
+	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");
+}
+void JMG_Utility_Objective_System_Custom_Send_Custom_Not_Status::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage)
+	{
+		if (BasicObjectiveSystem.Get_Objective_Status(objectiveId) == status)
+			return;
+		GameObject *object = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+ 		Commands->Send_Custom_Event(obj,object,custom,Param == -1 ? param : Param,delay);
+	}
+}
+GlobalKeycardSystem globalKeycardSystem = GlobalKeycardSystem();
+void GlobalKeycardSystem::AddKeycardToPlayerObjects(int keycardId,int groupId)
+{
+	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+	{
+		SmartGameObj* o = current->Data();
+		if (o)
+		{
+			JMG_Utility_Global_Keycard_System_Soldier *keycardScript = (JMG_Utility_Global_Keycard_System_Soldier*)Find_Script_On_Object(o,"JMG_Utility_Global_Keycard_System_Soldier");
+			if (keycardScript && keycardScript->groupId == groupId)
+				Commands->Grant_Key(o,keycardId,true);
+		}
+	}
+}
+void GlobalKeycardSystem::RemoveKeycardFromPlayerObjects(int keycardId,int groupId)
+{
+	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+	{
+		SmartGameObj* o = current->Data();
+		if (o)
+		{
+			JMG_Utility_Global_Keycard_System_Soldier *keycardScript = (JMG_Utility_Global_Keycard_System_Soldier*)Find_Script_On_Object(o,"JMG_Utility_Global_Keycard_System_Soldier");
+			if (keycardScript && keycardScript->groupId == groupId)
+				Commands->Grant_Key(o,keycardId,false);
+		}
+	}
+}
+void JMG_Utility_Global_Keycard_System_Controller::Destroyed(GameObject *obj)
+{
+	globalKeycardSystem.emptyList();
+}
+void JMG_Utility_Global_Keycard_System_Created_Add_Keycard::Created(GameObject *obj)
+{
+	globalKeycardSystem.AddKeycard(Get_Int_Parameter("KeycardID"),Get_Int_Parameter("GroupID"));
+}
+void JMG_Utility_Global_Keycard_System_Custom_Add_Keycard::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Global_Keycard_System_Custom_Add_Keycard::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		globalKeycardSystem.AddKeycard(Get_Int_Parameter("KeycardID"),Get_Int_Parameter("GroupID"));
+	}
+}
+void JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		globalKeycardSystem.RemoveKeycard(Get_Int_Parameter("KeycardID"),Get_Int_Parameter("GroupID"));
+	}
+}
+void JMG_Utility_Global_Keycard_System_Soldier::Created(GameObject *obj)
+{
+	groupId = Get_Int_Parameter("GroupID");
+	globalKeycardSystem.GrantKeycards(obj,groupId);
+}
+void JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change::Created(GameObject *obj)
+{
+	occupiedId = Get_Int_Parameter("OccupiedID");
+	occupiedCustom = Get_Int_Parameter("OccupiedCustom");
+	occupiedParam = Get_Int_Parameter("OccupiedParam");
+	occupiedDelay = Get_Float_Parameter("OccupiedDelay");
+	vacantId = Get_Int_Parameter("VacantID");
+	vacantCustom = Get_Int_Parameter("VacantCustom");
+	vacantParam = Get_Int_Parameter("VacantParam");
+	vacantDelay = Get_Float_Parameter("VacantDelay");
+}
+void JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change::Entered(GameObject *obj,GameObject *enterer)
+{
+	if (!Commands->Is_A_Star(enterer))
+		return;
+	int playerId = JmgUtility::JMG_Get_Player_ID(enterer);
+	if (inZone[playerId])
+		return;
+	inZone[playerId] = true;
+	if (!count && occupiedCustom)
+	{
+		GameObject *object = occupiedId ? (occupiedId == -1 ? enterer : Commands->Find_Object(occupiedId)) : obj;
+ 		Commands->Send_Custom_Event(obj,object,occupiedCustom,occupiedParam,occupiedDelay);
+	}
+	count++;
+	char params[128];
+	sprintf(params,"%d",Commands->Get_ID(obj));
+	Commands->Attach_Script(enterer,"JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached",params);
+}
+void JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change::Exited(GameObject *obj,GameObject *exiter)
+{
+	if (!Commands->Is_A_Star(exiter))
+		return;
+	int playerId = JmgUtility::JMG_Get_Player_ID(exiter);
+	if (!inZone[playerId])
+		return;
+	inZone[playerId] = false;
+	count--;
+	if (!count && vacantCustom)
+	{
+		GameObject *object = vacantId ? (vacantId == -1 ? exiter : Commands->Find_Object(vacantId)) : obj;
+ 		Commands->Send_Custom_Event(obj,object,vacantCustom,vacantParam,vacantDelay);
+	}
+}
+void JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached::Destroyed(GameObject *obj)
+{
+	GameObject *controller = Commands->Find_Object(Get_Int_Parameter("ID"));
+	if (!controller)
+		return;
+	JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change *occupationScript = (JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change*)Find_Script_On_Object(controller,"JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change");
+	if (!occupationScript)
+		return;
+	occupationScript->Exited(controller,obj);
+}
+bool JMG_Utility_Silent_Countdown_Controller::controllerPlaced = false;
+JMG_Utility_Silent_Countdown_Controller::SendCustomIdNode *JMG_Utility_Silent_Countdown_Controller::sendCustomIdNodeList = NULL;
+void JMG_Utility_Silent_Countdown_Controller::Created(GameObject *obj)
+{
+	controllerPlaced = true;
+}
+void JMG_Utility_Silent_Countdown_Controller::Destroyed(GameObject *obj)
+{
+	EmptyList();
+}
+void JMG_Utility_Silent_Countdown_Timer::Created(GameObject *obj)
+{
+	paused = true;
+	sendCustomIdNode = JMG_Utility_Silent_Countdown_Controller::FindOrCreateCountdownId(Get_Int_Parameter("TimerID"));
+	time = Get_Int_Parameter("Time");
+	startCustom = Get_Int_Parameter("StartCustom");
+	pausedCustom = Get_Int_Parameter("PausedCustom");
+	cancelCustom = Get_Int_Parameter("CancelCustom");
+}
+void JMG_Utility_Silent_Countdown_Timer::Timer_Expired(GameObject *obj,int number)
+{
+	if (1 == number && JMG_Utility_Silent_Countdown_Controller::controllerPlaced && time)
+	{
+		time--;
+		JMG_Utility_Silent_Countdown_Controller::NodeSendCustom(obj,sendCustomIdNode,time);
+		if (!paused)
+			Commands->Start_Timer(obj,this,1.0f,1);
+	}
+}
+void JMG_Utility_Silent_Countdown_Timer::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (startCustom == message)
+	{
+		if (paused)
+			Commands->Start_Timer(obj,this,1.0f,1);
+		paused = false;
+	}
+	if (pausedCustom == message)
+	{
+		Stop_Timer(obj,1);
+		paused = true;
+	}
+	if (cancelCustom == message)
+	{
+		Stop_Timer(obj,1);
+		paused = true;
+		time = Get_Int_Parameter("Time");
+	}
+}
+void JMG_Utility_Silent_Countdown_Send_Custom::Created(GameObject *obj)
+{
+	int id = Get_Int_Parameter("ID");
+	id = id ? id : Commands->Get_ID(obj);
+	JMG_Utility_Silent_Countdown_Controller::AddSecondNode(Get_Int_Parameter("TimerID"),Get_Int_Parameter("TriggerTime"),id,Get_Int_Parameter("Custom"),Get_Int_Parameter("Param"),Get_Float_Parameter("Delay"));
+}
+int JMG_Utility_Zone_Teleport_To_Random_WP_Boss::BossObjectId = 0;
+void JMG_Utility_Zone_Teleport_To_Random_WP_Boss::Created(GameObject *obj)
+{
+	retryOnFailure = Get_Int_Parameter("RetryOnFailure") ? true : false;
+	changeGroupIDCustom = Get_Int_Parameter("ChangeGroupIDCustom");
+	playerType = Get_Int_Parameter("PlayerType");
+	safeTeleportDistance = Get_Float_Parameter("SafeTeleportDistance");
+	wanderPointGroup = Get_Int_Parameter("WanderingAIGroupID");
+	aiOnly = Get_Int_Parameter("AiOnly") != 0;
+	faceBoss = Get_Int_Parameter("FaceBoss") ? true : false;
+}
+void JMG_Utility_Zone_Teleport_To_Random_WP_Boss::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == changeGroupIDCustom)
+	{
+		wanderPointGroup = param;
+	}
+}
+void JMG_Utility_Zone_Teleport_To_Random_WP_Boss::Entered(GameObject *obj,GameObject *enter)
+{
+	if (aiOnly && Commands->Is_A_Star(enter))
+		return;
+	if (CheckPlayerType(enter,playerType))
+		return;
+	Grab_Teleport_Spot(enter,5);
+}
+bool JMG_Utility_Zone_Teleport_To_Random_WP_Boss::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;
+	GameObject *boss = Commands->Find_Object(JMG_Utility_Zone_Teleport_To_Random_WP_Boss::BossObjectId);
+	if (!Get_A_Defense_Point(&targetPos,&facing,boss))
+	{
+		char errormsg[220];
+		sprintf(errormsg,"msg JMG_Utility_Zone_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);
+		if (faceBoss)
+		{
+			Vector3 bossPos = Commands->Get_Position(boss);
+			bossPos.X -= targetPos.X;
+			bossPos.Y -= targetPos.Y;
+			facing = atan2(bossPos.Y,bossPos.X) * 180 / PI;
+		}
+		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_Zone_Teleport_To_Random_WP_Boss_Object::Created(GameObject *obj)
+{
+	JMG_Utility_Zone_Teleport_To_Random_WP_Boss::BossObjectId = Commands->Get_ID(obj);
+}
+void JMG_Utility_Custom_Set_Soldier_Speed::Created(GameObject *obj)
+{
+	if (!obj->As_SoldierGameObj())
+	{
+		Console_Input("msg JMG_Utility_Custom_Set_Soldier_Speed ERROR: Script only works on soldiers!");
+		Destroy_Script();
+		return;
+	}
+	custom = Get_Int_Parameter("Custom");
+}
+void JMG_Utility_Custom_Set_Soldier_Speed::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		obj->As_SoldierGameObj()->Set_Max_Speed(Get_Float_Parameter("Speed"));
+	}
+}
+SList<JMG_Utility_Control_Point_Controller::ControlPointTeamData> JMG_Utility_Control_Point_Controller::controlPointTeamData;
+SList<GameObject> JMG_Utility_Control_Point_Controller::controlPoints;
+SList<GameObject> JMG_Utility_Control_Point_Controller::wanderPoints;
+bool JMG_Utility_Control_Point_Controller::controllerPlaced = false;
+bool JMG_Utility_Control_Point_Controller::allSetupComplete = false;
+void JMG_Utility_Control_Point_Controller::Created(GameObject *obj)
+{
+	JMG_Utility_Control_Point_Controller::controlPointTeamData.Add_Tail(new JMG_Utility_Control_Point_Controller::ControlPointTeamData(-2,Get_Parameter("NeutralTeamName"),Get_Int_Parameter("NeutralPlayerType"),Get_Int_Parameter("NeutralRadarBlipColor"),Get_Parameter("NeutralDefaultPointModel"),Get_Parameter("NeutralDefaultAnimation"),Get_Float_Parameter("NeutralAnimationLength"),Get_Parameter("NeutralCaptureSound"),Get_Parameter("NeutralLostSound"),Get_Parameter("NeutralLockedModel"),Get_Parameter("NeutralLockedAnim"),Get_Float_Parameter("NeutralLockedAnimLength"),Get_Int_Parameter("NeutralDefenseMultiplier"),Get_Int_Parameter("NeutralCaptureMultiplier"),Get_Int_Parameter("NeutralUnoccupiedMultiplier")));
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Control_Point_Controller::Timer_Expired(GameObject *obj,int number)
+{
+	if (1 == number)
+	{
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data()->As_ScriptableGameObj();
+			if (!Is_Script_Attached(o,"JMG_Utility_Control_Point"))
+				continue;
+			JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point");
+			if (!cpScript || !cpScript->setupComplete)
+			{
+				Commands->Start_Timer(obj,this,0.1f,1);
+				return;
+			}
+		}
+		allSetupComplete = true;
+	}
+}
+void JMG_Utility_Control_Point_Controller::Destroyed(GameObject *obj)
+{
+	controllerPlaced = false;
+	controlPointTeamData.Remove_All();
+	controlPoints.Remove_All();
+	wanderPoints.Remove_All();
+	allSetupComplete = false;
+}
+void JMG_Utility_Control_Point_Team_Setting::Created(GameObject *obj)
+{
+	if (!JMG_Utility_Control_Point_Controller::controllerPlaced)
+	{
+		Console_Input("msg JMG_Utility_Control_Point_Team_Setting ERROR: JMG_Utility_Control_Point_Controller must be placed on the map!");
+		return;
+	}
+	if (Get_Int_Parameter("TeamID") == -2)
+	{
+		Console_Input("msg JMG_Utility_Control_Point_Team_Setting ERROR: Team -2 is reserved for team neutral!");
+		return;
+	}
+	JMG_Utility_Control_Point_Controller::controlPointTeamData.Add_Tail(new JMG_Utility_Control_Point_Controller::ControlPointTeamData(Get_Int_Parameter("TeamID"),Get_Parameter("TeamName"),Get_Int_Parameter("PlayerType"),Get_Int_Parameter("RadarBlipColor"),Get_Parameter("PointModel"),Get_Parameter("Animation"),Get_Float_Parameter("AnimationLength"),Get_Parameter("CaptureSound"),Get_Parameter("LostSound"),Get_Parameter("LockedModel"),Get_Parameter("LockedAnim"),Get_Float_Parameter("LockedAnimLength"),Get_Int_Parameter("DefenseMultiplier"),Get_Int_Parameter("CaptureMultiplier"),Get_Int_Parameter("UnoccupiedMultiplier")));
+	Destroy_Script();
+}
+void JMG_Utility_Control_Point::Created(GameObject *obj)
+{
+	occupied = false;
+	sprintf(controlPointName,"%s",Get_Parameter("ControlPointName"));
+	controllingTeamId = Get_Int_Parameter("ControllingTeamID");
+	maxCapturePoints = Get_Int_Parameter("MaxCapturePoints");
+	controlDistance = Get_Float_Parameter("ControlDistance");
+	controlDistance *= controlDistance;
+	controlHeightMinMax = Get_Vector3_Parameter("ControlHeight[Min|Max]");
+	zoneId = Get_Int_Parameter("ZoneID");
+	captureScore = Get_Float_Parameter("CaptureScore");
+	neutralizeScore = Get_Float_Parameter("NeutralizeScore");
+	currerntCapturePoints = controllingTeamId != -2 ? maxCapturePoints : 0;
+	currentFrame = 0;
+	captured = controllingTeamId != -2 ? true : false;
+	lockCustom = Get_Int_Parameter("LockCustom");
+	specialLocked = Get_Int_Parameter("StartsLocked") ? true : false;
+	locked = false;
+	cpGroupId = Get_Int_Parameter("ControlGroupID");
+	controllingTeamOverride = NULL;
+	controllingTeam = NULL;
+	wasLocked = false;
+	radarBipType = obj->As_PhysicalGameObj()->Get_Radar_Blip_Shape_Type();
+	JMG_Utility_Control_Point_Controller::controlPoints.Add_Head(obj);
+	Commands->Set_Is_Visible(obj,false);
+	Commands->Start_Timer(obj,this,0.25f,1);
+}
+void JMG_Utility_Control_Point::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == lockCustom)
+	{
+		specialLocked = param ? true : false;
+		if (!controllingTeam)
+			return;
+		ChangeModelAndTeam(obj);
+		UpdateAnimation(obj);
+	}
+}
+void JMG_Utility_Control_Point::Timer_Expired(GameObject *obj,int number)
+{
+	if (1 == number)
+	{
+		UpdateTeam(controllingTeamId);
+		UpdateTeamOverride(controllingTeamId);
+		for (SLNode<JMG_Utility_Control_Point_Controller::ControlPointTeamData> *node = JMG_Utility_Control_Point_Controller::controlPointTeamData.Head();node != NULL;node = node->Next())
+			if (JMG_Utility_Control_Point_Controller::ControlPointTeamData *data = node->Data())
+				if (data->teamId == -2)
+				{
+					neutralTeam = data;
+					break;
+				}
+		if (!controllingTeam)
+		{
+			char errorMsg[220];
+			sprintf(errorMsg,"msg JMG_Utility_Control_Point ERROR: Team %d has not been defined!",controllingTeamId);
+			Console_Input(errorMsg);
+			return;
+		}
+		Final_CP_Calculation(obj);
+		Commands->Set_Player_Type(obj,controllingTeam->playerType);
+		setupComplete = true;
+		if (!zoneId)
+			Timer_Expired(obj,2);
+		else
+			Timer_Expired(obj,3);
+	}
+	if (2 == number)
+	{
+		Vector3 pos = Commands->Get_Position(obj);
+		occupied = false;
+		if (!locked && !specialLocked)
+			for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+			{
+				SmartGameObj* o = current->Data();
+				if (!o)
+					continue;
+				Vector3 unitPos = Commands->Get_Position(o);
+				if (controlHeightMinMax.X && (pos.Z + controlHeightMinMax.X) > unitPos.Z)
+					continue;
+				if (controlHeightMinMax.Y && (pos.Z + controlHeightMinMax.Y) < unitPos.Z)
+					continue;
+				if (JmgUtility::SimpleDistance(pos,unitPos) > controlDistance)
+					continue;
+				JMG_Utility_Control_Point_Team_Member *unitScript = (JMG_Utility_Control_Point_Team_Member*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Team_Member");
+				if (!unitScript || !unitScript->setupComplete)
+					continue;
+				AddUpCpPoints(obj,unitScript->teamData,unitScript->multiplier);
+			}
+		ChangeModelAndTeam(obj);
+		Final_CP_Calculation(obj);
+		Commands->Start_Timer(obj,this,0.25f,2);
+	}
+	if (3 == number)
+	{
+		GameObject *zone = Commands->Find_Object(zoneId);
+		if (!zone)
+		{
+			Console_Input("msg JMG_Utility_Control_Point ERROR: Zone not found!");
+			return;
+		}
+		Vector3 pos = Commands->Get_Position(obj);
+		occupied = false;
+		if (!locked && !specialLocked)
+			for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+			{
+				SmartGameObj* o = current->Data();
+				if (!o)
+					continue;
+				Vector3 unitPos = Commands->Get_Position(o);
+				if (!IsInsideZone(zone,o))
+					continue;
+				JMG_Utility_Control_Point_Team_Member *unitScript = (JMG_Utility_Control_Point_Team_Member*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Team_Member");
+				if (!unitScript || !unitScript->setupComplete)
+					continue;
+				AddUpCpPoints(obj,unitScript->teamData,unitScript->multiplier);
+			}
+		ChangeModelAndTeam(obj);
+		Final_CP_Calculation(obj);
+		Commands->Start_Timer(obj,this,0.25f,3);
+	}
+}
+void JMG_Utility_Control_Point::AddUpCpPoints(GameObject *obj,JMG_Utility_Control_Point_Controller::ControlPointTeamData *unitTeamData,int multiplier)
+{
+	if (unitTeamData->teamId != controllingTeamId)
+		currerntCapturePoints -= unitTeamData->captureMultiplier*multiplier;
+	if (unitTeamData->teamId == controllingTeamId)
+		currerntCapturePoints += unitTeamData->defenseMultiplier*multiplier;
+	if (currerntCapturePoints < 0 && !captured)
+	{
+		currerntCapturePoints = abs(currerntCapturePoints);
+		controllingTeamId = unitTeamData->teamId;
+	}
+	if (!currerntCapturePoints && !captured)
+		controllingTeamId = neutralTeam->teamId;
+	occupied = true;
+}
+void JMG_Utility_Control_Point::Final_CP_Calculation(GameObject *obj)
+{
+	if (!occupied)
+	{
+		if (captured)
+			currerntCapturePoints += controllingTeam->unoccupiedMultiplier;
+		else
+			currerntCapturePoints += neutralTeam->unoccupiedMultiplier;
+	}
+	if (currerntCapturePoints > maxCapturePoints)
+	{
+		if (!captured)
+		{
+			captured = true;
+			UpdateTeam(controllingTeamId);
+			UpdateTeamOverride(controllingTeamId);
+			ChangeModelAndTeam(obj);
+			CaptureControlPoint(obj);
+		}
+		currerntCapturePoints = maxCapturePoints;
+	}
+	if (currerntCapturePoints < 0)
+	{
+		lastTeam = controllingTeam;
+		lastTeamOverride = controllingTeamOverride;
+		currerntCapturePoints = 0;
+		controllingTeam = neutralTeam;
+		UpdateTeamOverride(controllingTeam->teamId);
+		if (captured)
+		{
+			captured = false;
+			ChangeModelAndTeam(obj);
+			LostControlPoint(obj);
+		}
+	}
+	UpdateAnimation(obj);
+}
+void JMG_Utility_Control_Point::ChangeModelAndTeam(GameObject *obj)
+{
+	Commands->Set_Player_Type(obj,controllingTeam->playerType);
+	obj->As_PhysicalGameObj()->Set_Radar_Blip_Color_Type(controllingTeam->radarBlipColor);
+	if (!locked && !specialLocked)
+	{
+		if (wasLocked)
+			obj->As_PhysicalGameObj()->Set_Radar_Blip_Shape_Type(radarBipType);
+		if (controllingTeamOverride && _stricmp(controllingTeamOverride->pointModelOverride,""))
+			Commands->Set_Model(obj,controllingTeamOverride->pointModelOverride);
+		else
+			Commands->Set_Model(obj,controllingTeam->pointModel);
+	}
+	else
+	{
+		if (!wasLocked)
+			obj->As_PhysicalGameObj()->Set_Radar_Blip_Shape_Type(RADAR_BLIP_SHAPE_NONE);
+		if (controllingTeamOverride && _stricmp(controllingTeamOverride->lockedPointModelOverride,""))
+				Commands->Set_Model(obj,controllingTeamOverride->lockedPointModelOverride);
+			else
+				Commands->Set_Model(obj,controllingTeam->lockedModel);
+	}
+	wasLocked = locked | specialLocked;
+}
+void JMG_Utility_Control_Point::UpdateAnimation(GameObject *obj)
+{
+	if (!locked && !specialLocked)
+		if (controllingTeamOverride && _stricmp(controllingTeamOverride->animOverride,""))
+			SetAnimation(obj,controllingTeamOverride->animOverride,controllingTeamOverride->animationLength);
+		else
+			SetAnimation(obj,controllingTeam->defaultAnimation,controllingTeam->animationLength);
+	else
+		if (controllingTeamOverride && _stricmp(controllingTeamOverride->lockedAnimOverride,""))
+			SetAnimation(obj,controllingTeamOverride->lockedAnimOverride,controllingTeamOverride->lockedAnimationLength);
+		else
+			SetAnimation(obj,controllingTeam->lockedAnim,controllingTeam->lockedAnimLen);
+}
+void JMG_Utility_Control_Point::SetAnimation(GameObject *obj,const char *anim,float length)
+{
+	float frame = length-((float)currerntCapturePoints/(float)maxCapturePoints)*length;
+	if (frame != currentFrame)
+	{
+		Commands->Set_Animation(obj,anim,false,0,frame,frame,false);
+		currentFrame = frame;
+	}
+}
+void JMG_Utility_Control_Point::UpdateTeamOverride(int teamId)
+{
+	for (SLNode<ControlPointSettingOverride> *node = controlPointSettingOverride.Head();node != NULL;node = node->Next())
+		if (ControlPointSettingOverride *data = node->Data())
+			if (data->teamId == teamId)
+			{
+				controllingTeamOverride = data;
+				break;
+			}
+}
+void JMG_Utility_Control_Point::UpdateTeam(int teamId)
+{
+	for (SLNode<JMG_Utility_Control_Point_Controller::ControlPointTeamData> *node = JMG_Utility_Control_Point_Controller::controlPointTeamData.Head();node != NULL;node = node->Next())
+		if (JMG_Utility_Control_Point_Controller::ControlPointTeamData *data = node->Data())
+			if (data->teamId == controllingTeamId)
+			{
+				controllingTeam = data;
+				break;
+			}
+}
+void JMG_Utility_Control_Point::LostControlPoint(GameObject *obj)
+{
+	if (lastTeam)
+	{
+		char LostMessage[512];
+		sprintf(LostMessage,"%s lost control of %s!",lastTeam->teamName,controlPointName);
+		JmgUtility::MessageAllPlayers(127,255,127,LostMessage);
+		Commands->Create_2D_Sound(lastTeam->lostSound);
+		if (lastTeamOverride)
+		{
+			GameObject *object = lastTeamOverride->id ? Commands->Find_Object(lastTeamOverride->id) : obj;
+			Commands->Send_Custom_Event(obj,object,lastTeamOverride->lostCustom,0,0.0f);
+		}
+		GrantPointsToTeamMembersInRange(obj,neutralizeScore,false);
+		TriggerAssaultLinesUpdate();
+		UpdateControllerWanderPoints();
+	}
+}
+void JMG_Utility_Control_Point::CaptureControlPoint(GameObject *obj)
+{
+	char CapMessage[512];
+	sprintf(CapMessage,"%s gained control of %s!",controllingTeam->teamName,controlPointName);
+	JmgUtility::MessageAllPlayers(127,255,127,CapMessage);
+	Commands->Create_2D_Sound(controllingTeam->captureSound);
+	GrantPointsToTeamMembersInRange(obj,captureScore,true);
+	if (controllingTeamOverride)
+	{
+		GameObject *object = controllingTeamOverride->id ? Commands->Find_Object(controllingTeamOverride->id) : obj;
+		Commands->Send_Custom_Event(obj,object,controllingTeamOverride->captureCustom,0,0.0f);
+	}
+	TriggerAssaultLinesUpdate();
+	UpdateControllerWanderPoints();
+}
+void JMG_Utility_Control_Point::GrantPointsToTeamMembersInRange(GameObject *obj,float points,bool matchTeam)
+{
+	int UnitCount = 0;
+	Vector3 pos = Commands->Get_Position(obj);
+	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+	{
+		SmartGameObj* o = current->Data();
+		if (!o)
+			continue;
+		if (JmgUtility::SimpleDistance(pos,Commands->Get_Position(o)) > controlDistance)
+			continue;
+		JMG_Utility_Control_Point_Team_Member *unitScript = (JMG_Utility_Control_Point_Team_Member*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Team_Member");
+		if (!unitScript || !unitScript->setupComplete || (matchTeam && unitScript->teamId != controllingTeamId) || (!matchTeam && unitScript->teamId == controllingTeamId))
+			continue;
+		UnitCount++;
+	}
+	float GivePoints = points/UnitCount;
+	for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+	{
+		SmartGameObj* o = current->Data();
+		if (!o)
+			continue;
+		if (JmgUtility::SimpleDistance(pos,Commands->Get_Position(o)) > controlDistance)
+			continue;
+		JMG_Utility_Control_Point_Team_Member *unitScript = (JMG_Utility_Control_Point_Team_Member*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Team_Member");
+		if (!unitScript || !unitScript->setupComplete || (matchTeam && unitScript->teamId != controllingTeamId) || (!matchTeam && unitScript->teamId == controllingTeamId))
+			continue;
+		Commands->Give_Points(o,GivePoints,true);
+	}
+}
+void JMG_Utility_Control_Point::Destroyed(GameObject *obj)
+{
+	controlPointSettingOverride.Remove_All();
+	controlPointWanderPoints.Remove_All();
+}
+void JMG_Utility_Control_Point::TriggerAssaultLinesUpdate()
+{
+	if (!JMG_Utility_Control_Point_Assault_Mode::controllerId)
+		return;
+	GameObject *assaultController = Commands->Find_Object(JMG_Utility_Control_Point_Assault_Mode::controllerId);
+	if (!assaultController)
+		return;
+	JMG_Utility_Control_Point_Assault_Mode *assaultScript = (JMG_Utility_Control_Point_Assault_Mode*)Find_Script_On_Object(assaultController,"JMG_Utility_Control_Point_Assault_Mode");
+	if (!assaultScript)
+		return;
+	assaultScript->UpdateAssaultLine(assaultController,false);
+}
+void JMG_Utility_Control_Point::UpdateControllerWanderPoints()
+{
+	for (SLNode<GameObject> *current = JMG_Utility_Control_Point_Controller::wanderPoints.Head();current;current = current->Next())
+	{
+		GameObject* o = current->Data();
+		if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point_Wander_Point"))
+			continue;
+		JMG_Utility_Control_Point_Wander_Point *cpScript = (JMG_Utility_Control_Point_Wander_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Wander_Point");
+		if (!cpScript)
+			continue;
+		cpScript->UpdateWanderpointSettings();
+	}
+}
+void JMG_Utility_Control_Point_Setting_Override::Created(GameObject *obj)
+{
+	JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(obj,"JMG_Utility_Control_Point");
+	if (!cpScript)
+	{
+		Console_Input("msg JMG_Utility_Control_Point_Setting_Override ERROR: JMG_Utility_Control_Point must be on the attached object!");
+		return;
+	}
+	cpScript->controlPointSettingOverride.Add_Tail(new JMG_Utility_Control_Point::ControlPointSettingOverride(Get_Int_Parameter("TeamID"),Get_Int_Parameter("ID"),Get_Int_Parameter("CaptureCustom"),Get_Int_Parameter("LostCustom"),Get_Parameter("PointModelOverride"),Get_Parameter("AnimationOverride"),Get_Float_Parameter("AnimationLength"),Get_Parameter("LockedPointModelOverride"),Get_Parameter("LockedAnimationOverride"),Get_Float_Parameter("LockedAnimationLength")));
+	Destroy_Script();
+}
+void JMG_Utility_Control_Point_Team_Member::Created(GameObject *obj)
+{
+	if (!JMG_Utility_Control_Point_Controller::controllerPlaced)
+	{
+		Destroy_Script();
+		return;
+	}
+	teamId = Get_Int_Parameter("TeamID");
+	multiplier = Get_Int_Parameter("Multiplier");
+	Timer_Expired(obj,1);
+}
+void JMG_Utility_Control_Point_Team_Member::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		for (SLNode<JMG_Utility_Control_Point_Controller::ControlPointTeamData> *node = JMG_Utility_Control_Point_Controller::controlPointTeamData.Head();node != NULL;node = node->Next())
+			if (JMG_Utility_Control_Point_Controller::ControlPointTeamData *data = node->Data())
+				if (data->teamId == teamId)
+				{
+					teamData = data;
+					setupComplete =  true;
+					return;
+				}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+int JMG_Utility_Control_Point_Assault_Mode::teamId = 0;
+int JMG_Utility_Control_Point_Assault_Mode::controllerId = 0;
+int JMG_Utility_Control_Point_Assault_Mode::frontLineGroup = 0;
+int JMG_Utility_Control_Point_Assault_Mode::spawnGroup = 0;
+int JMG_Utility_Control_Point_Assault_Mode::enemySpawnGroup = 0;
+void JMG_Utility_Control_Point_Assault_Mode::Created(GameObject *obj)
+{
+	id = Get_Int_Parameter("ID");
+	advanceCustom = Get_Int_Parameter("AdvanceCustom");
+	pushedBackCustom = Get_Int_Parameter("PushBackCustom");
+	controlAllCustom = Get_Int_Parameter("ControlAllCustom");
+	lostAllCustom = Get_Int_Parameter("LostAllCustom");
+	frontLineGroup = -1;
+	spawnGroup = -1;
+	enemySpawnGroup = -1;
+	teamId = Get_Int_Parameter("TeamID");
+	controllerId = Commands->Get_ID(obj);
+	Timer_Expired(obj,1);
+}
+void JMG_Utility_Control_Point_Assault_Mode::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (!JMG_Utility_Control_Point_Controller::allSetupComplete)
+		{
+			Commands->Start_Timer(obj,this,0.1f,1);
+			return;
+		}
+		for (SLNode<GameObject> *current = JMG_Utility_Control_Point_Controller::controlPoints.Head();current;current = current->Next())
+		{
+			GameObject* o = current->Data();
+			if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point"))
+				continue;
+			JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point");
+			if (!cpScript || !cpScript->setupComplete)
+			{
+				Commands->Start_Timer(obj,this,0.1f,1);
+				return;
+			}
+		}
+		UpdateAssaultLine(obj,true);
+	}
+}
+void JMG_Utility_Control_Point_Assault_Mode::UpdateAssaultLine(GameObject *obj,bool initialSetup)
+{
+	int friendlyCount = 0;
+	int enemyCount = 0;
+	int maxEnemyGroup = 0;
+	int maxEnemySpawnGroup = 0;
+	int minFriendlyGroup = -1;
+	int minSpawnGroup = -1;
+	for (SLNode<GameObject> *current = JMG_Utility_Control_Point_Controller::controlPoints.Head();current;current = current->Next())
+	{
+		GameObject* o = current->Data();
+		if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point"))
+			continue;
+		JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point");
+		if (!cpScript || !cpScript->setupComplete || cpScript->cpGroupId < 0)
+			continue;
+		if (cpScript->controllingTeamId != teamId || !cpScript->captured)
+			enemyCount++;
+		if (cpScript->controllingTeamId == teamId || !cpScript->captured)
+			friendlyCount++;
+		if ((cpScript->controllingTeamId != teamId || !cpScript->captured) && cpScript->cpGroupId > maxEnemyGroup)
+			maxEnemyGroup = cpScript->cpGroupId;
+		if (cpScript->controllingTeamId != teamId && cpScript->captured && cpScript->cpGroupId > maxEnemySpawnGroup)
+			maxEnemySpawnGroup = cpScript->cpGroupId;
+		if ((cpScript->controllingTeamId == teamId || !cpScript->captured) && (minFriendlyGroup == -1 || cpScript->cpGroupId < minFriendlyGroup))
+			minFriendlyGroup = cpScript->cpGroupId;
+		if ((cpScript->controllingTeamId == teamId && cpScript->captured) && (minSpawnGroup == -1 || cpScript->cpGroupId < minSpawnGroup))
+			minSpawnGroup = cpScript->cpGroupId;
+	}
+	for (SLNode<GameObject> *current = JMG_Utility_Control_Point_Controller::controlPoints.Head();current;current = current->Next())
+	{
+		GameObject* o = current->Data();
+		if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point"))
+			continue;
+		JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point");
+		if (!cpScript || !cpScript->setupComplete || cpScript->cpGroupId < 0)
+			continue;
+		if (cpScript->cpGroupId != minFriendlyGroup && cpScript->cpGroupId != maxEnemyGroup && !cpScript->locked)
+		{
+			cpScript->locked = true;
+			cpScript->ChangeModelAndTeam(o);
+			cpScript->UpdateAnimation(o);
+		}
+		if ((cpScript->cpGroupId == minFriendlyGroup || cpScript->cpGroupId == maxEnemyGroup) && cpScript->locked)
+		{
+			cpScript->locked = false;
+			cpScript->ChangeModelAndTeam(o);
+			cpScript->UpdateAnimation(o);
+		}
+	}
+	if (!initialSetup)
+	{
+		if (spawnGroup != minSpawnGroup && minSpawnGroup != -1 && enemyCount)
+		{
+			if (spawnGroup < minSpawnGroup && spawnGroup != -1)
+				SendCustom(obj,pushedBackCustom,frontLineGroup);
+			else
+				SendCustom(obj,advanceCustom,maxEnemySpawnGroup);
+		}
+		if (!enemyCount)
+			SendCustom(obj,controlAllCustom,frontLineGroup);
+		if (!friendlyCount)
+			SendCustom(obj,lostAllCustom,frontLineGroup);
+	}
+	frontLineGroup = minFriendlyGroup;
+	spawnGroup = minSpawnGroup;
+	enemySpawnGroup = maxEnemySpawnGroup;
+}
+void JMG_Utility_Control_Point_Assault_Mode::Destroyed(GameObject *obj)
+{
+	controllerId = 0;
+}
+void JMG_Utility_Control_Point_Assault_Mode::SendCustom(GameObject* obj,int custom,int frontLineGroup)
+{
+	if (!custom)
+		return;
+	GameObject *object = id ? Commands->Find_Object(id) : obj;
+	Commands->Send_Custom_Event(obj,object,custom,frontLineGroup,0.0f);
+}
+void JMG_Utility_Control_Point_Wander_Point::Created(GameObject *obj)
+{
+	controlPointId = Get_Int_Parameter("ControlPointID");
+	teamId = Get_Int_Parameter("TeamID");
+	spawnableGroupId = Get_Int_Parameter("SpawnableGroupID");
+	unspawnableGroupId = Get_Int_Parameter("UnspawnableGroupId");
+	wanderPoint = AddAndReturnWanderpoint(obj);
+	wanderPoint->value = -999;
+	Commands->Set_Is_Rendered(obj,false);
+	Commands->Set_Is_Visible(obj,false);
+	Timer_Expired(obj,1);
+}
+void JMG_Utility_Control_Point_Wander_Point::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		GameObject *controller = Commands->Find_Object(controlPointId);
+		if (!controller)
+		{
+			Commands->Start_Timer(obj,this,0.1f,1);
+			return;
+		}
+		cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(controller,"JMG_Utility_Control_Point");
+		if (!cpScript || !cpScript->setupComplete)
+		{
+			Commands->Start_Timer(obj,this,0.1f,1);
+			return;
+		}
+		cpScript->controlPointWanderPoints.Add_Tail(obj);
+		JMG_Utility_Control_Point_Controller::wanderPoints.Add_Tail(obj);
+		UpdateWanderpointSettings();
+	}
+}
+void JMG_Utility_Control_Point_Wander_Point::UpdateWanderpointSettings()
+{
+	if (!cpScript || !cpScript->setupComplete || cpScript->cpGroupId < 0)
+		return;
+	if (spawnableGroupId != -999 && cpScript->captured && cpScript->controllingTeamId == teamId && (!cpScript->locked || 
+	   (cpScript->cpGroupId == JMG_Utility_Control_Point_Assault_Mode::spawnGroup && JMG_Utility_Control_Point_Assault_Mode::teamId == teamId) || 
+	   (cpScript->cpGroupId == JMG_Utility_Control_Point_Assault_Mode::enemySpawnGroup && JMG_Utility_Control_Point_Assault_Mode::teamId != teamId)))
+		wanderPoint->value = spawnableGroupId;
+	else if (unspawnableGroupId != -999)
+		wanderPoint->value = unspawnableGroupId;
+}
+void JMG_Utility_Control_Point_Select_Spawn_System::Created(GameObject *obj)
+{
+	stringId = Get_Int_Parameter("StringId");
+	controlPointId = 0;
+	ungroupedControlPointId = 0;
+	selectedCpId = 0;
+	teamId = Get_Int_Parameter("TeamID");
+	groupChangeCustom = Get_Int_Parameter("GroupChangeCustom");
+	ungroupedChangeCustom = Get_Int_Parameter("UngroupedChangeCustom");
+	spawnCustom = Get_Int_Parameter("SpawnCustom");
+	safeTeleportDistance = Get_Float_Parameter("SafeTeleportDistance");
+	maxSpawnTime = spawnTime = Get_Int_Parameter("SpawnTime")*10;
+	maxWanderRange = Get_Float_Parameter("MaxWanderRange");
+	startFadeRange = maxWanderRange*0.833f;
+	maxWanderRange *= maxWanderRange;
+	startFadeRange *= startFadeRange;
+	lastSpawnGroup = -1;
+	Commands->Set_Player_Type(obj,-4);
+	Lock_Soldier_Collision_Group(obj,Collision_Group_Type::SOLDIER_GHOST_COLLISION_GROUP);
+	Set_Screen_Fade_Opacity_Player(obj,0.25f,0.0f);
+	Timer_Expired(obj,1);
+}
+void JMG_Utility_Control_Point_Select_Spawn_System::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (!JMG_Utility_Control_Point_Controller::allSetupComplete)
+		{
+			Commands->Start_Timer(obj,this,0.1f,1);
+			return;
+		}
+		Custom(obj,groupChangeCustom,0,obj);
+		if (!selectedCpId)
+			Commands->Start_Timer(obj,this,0.1f,1);
+		else
+			Timer_Expired(obj,2);
+	}
+	if (number == 2)
+	{
+		if (selectedCpId)
+		{
+			GameObject *controlPoint = Commands->Find_Object(selectedCpId);
+			float spawnTimeRatio = spawnTime/(float)maxSpawnTime;
+			Vector3 spawnScreenColors = Vector3(spawnTimeRatio*0.75f,abs((spawnTimeRatio*0.75f)-((1-spawnTimeRatio)*0.39f)),(1-spawnTimeRatio)*0.78f);
+			float tempSimpDist = JmgUtility::SimpleDistance(Commands->Get_Position(controlPoint),Commands->Get_Position(obj));
+			float tempDistCalc = min(max(tempSimpDist-startFadeRange,0.0f)/(maxWanderRange-startFadeRange),1.0f);
+			spawnScreenColors *= (1-tempDistCalc);
+			Set_Screen_Fade_Color_Player(obj,min(tempDistCalc+spawnScreenColors.X,1.0f),spawnScreenColors.Y,spawnScreenColors.Z,0.1f);
+			if (tempSimpDist > maxWanderRange)
+				MoveToControlledWanderPointForCp(obj,selectedCpId);
+		}
+		if (spawnTime)
+		{
+			if ((spawnTime <= 100 && !(spawnTime%10)) || (spawnTime > 100 && !(spawnTime%100)))
+			{
+				char disString[220];
+				sprintf(disString,"%d",spawnTime/10);
+				Set_HUD_Help_Text_Player_Text(obj,stringId,disString,Vector3(1.0f,0.5f,0.0f));
+			}
+			spawnTime--;
+			if (!spawnTime)
+			{
+				Set_HUD_Help_Text_Player_Text(obj,stringId,"Left click to spawn",Vector3(1.0f,0.5f,0.0f));
+				Commands->Start_Timer(obj,this,5.0f,3);
+			}
+		}
+		Commands->Start_Timer(obj,this,0.1f,2);
+	}
+	if (number == 3)
+	{
+		Set_HUD_Help_Text_Player_Text(obj,stringId,"Right click to cycle front line",Vector3(1.0f,0.5f,0.0f));
+		Commands->Start_Timer(obj,this,5.0f,4);
+	}
+	if (number == 4)
+	{
+		Set_HUD_Help_Text_Player_Text(obj,stringId,"Middle click to cycle bonus points",Vector3(1.0f,0.5f,0.0f));
+		Commands->Start_Timer(obj,this,5.0f,2);
+	}
+}
+void JMG_Utility_Control_Point_Select_Spawn_System::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == spawnCustom)
+	{
+		if (spawnTime)
+			return;
+		Set_HUD_Help_Text_Player_Text(obj,stringId,"",Vector3(1.0f,0.5f,0.0f));
+		Set_Screen_Fade_Opacity_Player(obj,0.0f,0.1f);
+		Change_Character(obj,Get_Parameter("SpawnPreset"));
+	}
+	if (message == groupChangeCustom)
+	{
+		if (lastSpawnGroup != JMG_Utility_Control_Point_Assault_Mode::spawnGroup)
+			controlPointId = 0;
+		lastSpawnGroup = JMG_Utility_Control_Point_Assault_Mode::spawnGroup;
+		selectedCpId = controlPointId = SelectCpToSpawnFrom(obj,controlPointId,true);
+	}
+	if (message == ungroupedChangeCustom)
+	{
+		selectedCpId = ungroupedControlPointId = SelectCpToSpawnFrom(obj,ungroupedControlPointId,false);
+	}
+}
+int JMG_Utility_Control_Point_Select_Spawn_System::SelectCpToSpawnFrom(GameObject *obj,int cpId,bool assaultLines)
+{
+	JMG_Utility_Control_Point *cpScript = NULL;
+	int originalCpId = cpId;
+	bool foundCp = false;
+	SLNode<GameObject> *current = JMG_Utility_Control_Point_Controller::controlPoints.Head();
+	while (current)
+	{
+		GameObject* o = current->Data();
+		if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point"))
+			goto JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_FIRST_LOOP;
+		cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point");
+		if (!cpScript || !cpScript->setupComplete)
+			goto JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_FIRST_LOOP;
+		if (cpScript->captured && cpScript->controllingTeamId == teamId && 
+			(assaultLines && ((!cpScript->locked && cpScript->cpGroupId >= 0) || (cpScript->cpGroupId == JMG_Utility_Control_Point_Assault_Mode::spawnGroup && JMG_Utility_Control_Point_Assault_Mode::teamId == teamId))) || 
+			(!assaultLines && cpScript->cpGroupId < 0))
+		{
+			int tmpId = Commands->Get_ID(o);
+			if (!cpId || foundCp)
+			{
+				cpId = tmpId;
+				break;
+			}
+			if (originalCpId == tmpId)
+				foundCp = true;
+		}
+JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_FIRST_LOOP:
+		current = current->Next();
+		if (!current)
+		{
+			if (originalCpId == cpId && !foundCp)
+				break;
+			current = JMG_Utility_Control_Point_Controller::controlPoints.Head();
+		}
+	}
+	if (!cpId || !cpScript)
+		return cpId;
+	if (!MoveToControlledWanderPointForCp(obj,cpId))
+		return 0;
+	char selectCpMsg[220];
+	sprintf(selectCpMsg,"Selected Control Point is %s.",cpScript->controlPointName);
+	JmgUtility::DisplayChatMessage(obj,255,127,0,selectCpMsg);	
+	return cpId;
+}
+bool JMG_Utility_Control_Point_Select_Spawn_System::MoveToControlledWanderPointForCp(GameObject *obj,int cpId)
+{
+	JMG_Utility_Control_Point *cpScript = (JMG_Utility_Control_Point*)Find_Script_On_Object(Commands->Find_Object(cpId),"JMG_Utility_Control_Point");
+	if (!cpScript || !cpScript->setupComplete || cpScript->controlPointWanderPoints.Is_Empty())
+		return false;
+	int randomPoint = Commands->Get_Random_Int(0,cpScript->controlPointWanderPoints.Get_Count())+1;
+	int origianlRandom = randomPoint;
+	SLNode<GameObject> *current = cpScript->controlPointWanderPoints.Head();
+	while (current)
+	{
+		GameObject* o = current->Data();
+		if (!o || !Is_Script_Attached(o,"JMG_Utility_Control_Point_Wander_Point"))
+			goto JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_LOOP_END;
+		JMG_Utility_Control_Point_Wander_Point *wanderScript = (JMG_Utility_Control_Point_Wander_Point*)Find_Script_On_Object(o,"JMG_Utility_Control_Point_Wander_Point");
+		if (!wanderScript || !wanderScript->teamId != teamId || wanderScript->controlPointId != cpId)
+			goto JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_LOOP_END;
+		if (randomPoint)
+			randomPoint--;
+		if (!randomPoint)
+		{
+			Vector3 pos = Commands->Get_Position(o);
+			MoveablePhysClass *mphys = obj->As_PhysicalGameObj() ? obj->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : NULL;
+			if (mphys && mphys->Find_Teleport_Location(pos,safeTeleportDistance,&pos))
+			{
+				Toggle_Fly_Mode(obj);
+				Commands->Set_Position(obj,pos);
+				Force_Position_Update(obj);
+				Toggle_Fly_Mode(obj);
+				char params[220];
+				sprintf(params,"0.1,%.2f",Commands->Get_Facing(o));
+				Commands->Attach_Script(obj,"JMG_Utility_Delay_Then_Rotate_Camera",params);
+			}
+			return true;
+		}
+JMG_UTILITY_CONTROL_POINT_SELECT_SPAWN_SYSTEM_LOOP_END:
+		current = current->Next();
+		if (!current)
+		{
+			if (origianlRandom == randomPoint)
+				return false;
+			current = cpScript->controlPointWanderPoints.Head();
+		}
+	}
+	return false;
+}
+void JMG_Utility_Custom_And_Param_Send_Custom::Created(GameObject *obj)
+{
+	recieveMessage = Get_Int_Parameter("Custom");
+	recieveParam = Get_Int_Parameter("Param");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("SendCustom");
+	Param = Get_Int_Parameter("SendParam");
+	delay = Get_Float_Parameter("Delay");
+	randomDelay = Get_Float_Parameter("RandomDelay");
+	randomChance = Get_Float_Parameter("RandomChance");
+}
+void JMG_Utility_Custom_And_Param_Send_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage && param == recieveParam)
+	{
+		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,custom,Param == -1 ? param : Param,delay+(randomDelay > 0 ? Commands->Get_Random(0.0f,randomDelay) : 0.0f));
+	}
+}
+void JMG_Utility_Create_Move_To_Nearest_Pathfind::Created(GameObject *obj)
+{
+	Vector3 pos;
+	Get_Closest_Pathfind_Spot_Size(Commands->Get_Position(obj),999999.0,&pos,0.5f);
+	Commands->Set_Position(obj,pos);
+}
+void JMG_Utility_Objective_System_Objective_GameObject::Created(GameObject *obj)
+{
+	objectiveId = Get_Int_Parameter("ObjectiveID");
+	objectivePriority = Get_Int_Parameter("ObjectivePriority");
+	sprintf(preset,"%s",Get_Parameter("Preset"));
+	attach = Get_Int_Parameter("Attach") ? true : false;
+}
+void JMG_Utility_Objective_System_Objective_GameObject_Tracker::Created(GameObject *obj)
+{
+	gameObjectId = Get_Int_Parameter("GameObjectID");
+	markerId = Get_Int_Parameter("MarkerID");
+}
+void JMG_Utility_Attach_Script_To_Object_With_Weapon::Created(GameObject *obj)
+{
+	sprintf(weaponName,"%s",Get_Parameter("WeaponName"));
+	rate = Get_Float_Parameter("Rate");
+	sprintf(script,"%s",Get_Parameter("Script"));
+	const char *parameters = Get_Parameter("Params");
+	params = newstr(parameters);
+	char delim = Get_Parameter("Delim")[0];
+	unsigned int length = strlen(parameters);
+	for (unsigned int x = 0;x < length;x++)
+		if (params[x] == delim)
+			params[x] = ',';
+	playerType = Get_Int_Parameter("PlayerType");
+	Commands->Start_Timer(obj,this,rate,1);
+}
+void JMG_Utility_Attach_Script_To_Object_With_Weapon::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		Vector3 mypos = Commands->Get_Position(obj);
+		for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+		{
+			SmartGameObj *o = current->Data();
+			if (!o)
+				continue;
+			const char *weap = Get_Current_Weapon(o);
+			if (!weap || _stricmp(weap,weaponName) || CheckPlayerType(o,playerType))
+				continue;
+			Attach_Script_Once(o,script,params);
+		}
+		Commands->Start_Timer(obj,this,rate,1);
+	}
+}
+void JMG_Utility_Drop_Object_On_Death_And_Attach_Script::Killed(GameObject *obj,GameObject *killer)
+{
+	if (Commands->Get_Random(0,100) <= Get_Float_Parameter("Probability"))
+	{
+		Vector3 dropLocation = Commands->Get_Position(obj);
+		dropLocation.Z += Get_Float_Parameter("Drop_Height");
+		GameObject *drop = Commands->Create_Object(Get_Parameter("Drop_Object"),dropLocation);
+		Commands->Set_Facing(drop,Commands->Get_Facing(obj));
+		char script[128];
+		sprintf(script,"%s",Get_Parameter("Script"));
+		if (_stricmp(script,""))
+		{
+			const char *parameters = Get_Parameter("Params");
+			char *params = newstr(parameters);
+			char delim = Get_Parameter("Delim")[0];
+			unsigned int length = strlen(parameters);
+			for (unsigned int x = 0;x < length;x++)
+				if (params[x] == delim)
+					params[x] = ',';
+			Commands->Attach_Script(drop,script,params);
+		}
+	}
+}
+void JMG_SinglePlayer_M04_Modifier::Created(GameObject *obj)
+{
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_SinglePlayer_M04_Modifier::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		if (!setupComplete)
+		{
+			setupComplete = true;
+			float difficultyModifier = 100.0f-(Commands->Get_Difficulty_Level()/3.0f)*100.0f;
+			char params[256];
+			sprintf(params,"Weapon_Shotgun_Ai,JMG_Utility_Drop_Object_On_Death_And_Attach_Script,POW_Shotgun_Player@0.25@%.2f@SUR_Timed_Death@30.0^9999.0^BlamoKiller@^,@,1.0,0",difficultyModifier);
+			Commands->Attach_Script(obj,"JMG_Utility_Attach_Script_To_Object_With_Weapon",params);
+			sprintf(params,"Weapon_Shotgun_Ai_BH,JMG_Utility_Drop_Object_On_Death_And_Attach_Script,POW_Shotgun_Player@0.25@%.2f@SUR_Timed_Death@30.0^9999.0^BlamoKiller@^,@,1.0,0",difficultyModifier);
+			Commands->Attach_Script(obj,"JMG_Utility_Attach_Script_To_Object_With_Weapon",params);
+			sprintf(params,"Weapon_Shotgun_Player,JMG_Utility_Drop_Object_On_Death_And_Attach_Script,POW_Shotgun_Player@0.25@%.2f@SUR_Timed_Death@30.0^9999.0^BlamoKiller@^,@,1.0,0",difficultyModifier);
+			Commands->Attach_Script(obj,"JMG_Utility_Attach_Script_To_Object_With_Weapon",params);
+			GameObject *bubble = Commands->Create_Object("Daves Arrow",Vector3(0.0f,0.0f,-14.648f));
+			Commands->Set_Model(bubble,"m04_bubbler");
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_SinglePlayer_M04_Modifier::Register_Auto_Save_Variables()
+{
+}
+void JMG_Utility_Custom_Send_Delayed_Ordered_Customs::Created(GameObject *obj)
+{
+	delay = false;
+	id = Get_Int_Parameter("ID");
+	for (int x = 0;x < 10;x++)
+	{
+		char param[20];
+		sprintf(param,"Custom%0d",x);
+		customs[x] = Get_Int_Parameter(param);
+		sprintf(param,"Time%0d",x);
+		times[x] = Get_Float_Parameter(param);
+	}
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Custom_Send_Delayed_Ordered_Customs::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		SLNode<TimedOrderedCustom> *current = timedOrderedCustom.Head();
+		if (current)
+		{
+			TimedOrderedCustom *data = current->Data();
+			GameObject *object = !id ? obj : (id == -1 ? Commands->Find_Object(data->senderId) : Commands->Find_Object(id));
+			Commands->Send_Custom_Event(Commands->Find_Object(data->senderId),object,data->custom,data->param,0.0f);
+			Commands->Start_Timer(obj,this,data->time,1);
+			timedOrderedCustom.Remove_Head();
+		}
+		else
+			delay = false;
+	}
+}
+void JMG_Utility_Custom_Send_Delayed_Ordered_Customs::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	for (int x = 0;x < 10;x++)
+		if (customs[x] == message)
+		{
+			if (delay)
+				timedOrderedCustom.Add_Tail(new TimedOrderedCustom(Commands->Get_ID(sender),message,param,times[x]));
+			else
+			{
+				GameObject *object = !id ? obj : (id == -1 ? sender : Commands->Find_Object(id));
+				Commands->Send_Custom_Event(sender,object,message,param,0.0f);
+				Commands->Start_Timer(obj,this,times[x],1);
+				delay = true;
+			}
+			break;
+		}
+}
+void JMG_Utility_Custom_Send_Delayed_Ordered_Customs::Destroyed(GameObject *obj)
+{
+	timedOrderedCustom.Remove_All();
+}
+void JMG_Utility_Custom_Send_Delayed_Ordered_Customs::Detach(GameObject *obj)
+{
+	timedOrderedCustom.Remove_All();
+}
+void JMG_Utility_Custom_Send_Custom_If_Model::Created(GameObject *obj)
+{
+	sprintf(model,"%s",Get_Parameter("Model"));
+	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");
+}
+void JMG_Utility_Custom_Send_Custom_If_Model::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == recieveMessage && !_stricmp(model,Get_Model(obj)))
+	{
+		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_Grant_Weapon_Destroy_Self::Created(GameObject *obj)
+{
+	Commands->Enable_HUD_Pokable_Indicator(obj,true);
+}
+void JMG_Utility_Poke_Grant_Weapon_Destroy_Self::Poked(GameObject *obj, GameObject *poker)
+{
+	if (Commands->Get_Player_Type(poker) == -4)
+		return;
+	Grant_Weapon(poker,Get_Parameter("Weapon"),true,Get_Int_Parameter("Rounds"),Get_Int_Parameter("Backpack") ? true : false);
+	Commands->Destroy_Object(obj);
+}
+void JMG_Utility_Killed_Drop_Object_If_Weapon_Present::Killed(GameObject *obj,GameObject *killer)
+{
+	Destroyed(obj);
+}
+void JMG_Utility_Killed_Drop_Object_If_Weapon_Present::Destroyed(GameObject *obj)
+{
+	if (dropped)
+		return;
+	dropped = true;
+	if (!Has_Weapon(obj,Get_Parameter("Weapon")))
+		return;
+	Vector3 pos = Commands->Get_Position(obj);
+	pos.Z += Get_Float_Parameter("Height");
+	GameObject *drop = Commands->Create_Object(Get_Parameter("DropPreset"),pos);
+	Commands->Set_Facing(drop,Commands->Get_Facing(obj));
+}
+void JMG_Utility_Killed_Drop_Object_If_Weapon_Present::Detach(GameObject *obj)
+{
+	if (Exe == 4)
+		return;
+	Destroyed(obj);
+}
+void JMG_Utility_Poke_Send_Custom_If_Has_Weapon::Created(GameObject *obj)
+{
+	id = Get_Int_Parameter("ID");
+	playerType = Get_Int_Parameter("PlayerType");
+	custom = Get_Int_Parameter("Custom");
+	param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	triggerOnce = Get_Int_Parameter("TriggerOnce");
+	sprintf(weapon,"%s",Get_Parameter("Weapon"));
+	removeWeapon = Get_Int_Parameter("RemoveWeapon");
+	mustBeHeld = Get_Int_Parameter("MustBeHeld");
+	Commands->Enable_HUD_Pokable_Indicator(obj,true);
+}
+void JMG_Utility_Poke_Send_Custom_If_Has_Weapon::Poked(GameObject *obj, GameObject *poker)
+{
+	if (CheckPlayerType(poker,playerType) || Commands->Get_Player_Type(poker) == -4)
+		return;
+	if (mustBeHeld)
+	{
+		const char *pWeapon = Get_Current_Weapon(poker);
+		if (!pWeapon || _stricmp(weapon,pWeapon))
+			return;
+	}
+	else if (!Has_Weapon(poker,weapon))
+		return;
+	GameObject *object = id ? (id == -1 ? poker : Commands->Find_Object(id)) : obj;
+	if (object)
+		Commands->Send_Custom_Event(poker,object,custom,param,delay);
+	if (removeWeapon)
+		Remove_Weapon(poker,weapon);
+	if (triggerOnce)
+	{
+		Commands->Enable_HUD_Pokable_Indicator(obj,false);
+		Destroy_Script();
+	}
+}
+void JMG_Utility_Send_Custom_If_Weapon_Held_In_Range::Created(GameObject *obj)
+{
+	range = Get_Float_Parameter("Range");
+	range *= range;
+	id = Get_Int_Parameter("ID");
+	playerType = Get_Int_Parameter("PlayerType");
+	custom = Get_Int_Parameter("Custom");
+	param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	triggerOnce = Get_Int_Parameter("TriggerOnce");
+	sprintf(weapon,"%s",Get_Parameter("Weapon"));
+	removeWeapon = Get_Int_Parameter("RemoveWeapon");
+	mustBeHeld = Get_Int_Parameter("MustBeHeld");
+	Commands->Enable_HUD_Pokable_Indicator(obj,true);
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Send_Custom_If_Weapon_Held_In_Range::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		Vector3 pos = Commands->Get_Position(obj);
+		for (int x = 1;x < 128;x++)
+		{
+			GameObject *player = Get_GameObj(x);
+			if (!player || JmgUtility::SimpleDistance(pos,Commands->Get_Position(player)) > range)
+				continue;
+			if (CheckPlayerType(player,playerType) || Commands->Get_Player_Type(player) == -4)
+				continue;
+			if (mustBeHeld)
+			{
+				const char *pWeapon = Get_Current_Weapon(player);
+				if (!pWeapon || _stricmp(weapon,pWeapon))
+					continue;
+			}
+			else if (!Has_Weapon(player,weapon))
+				continue;
+			GameObject *object = id ? (id == -1 ? player : Commands->Find_Object(id)) : obj;
+			if (object)
+				Commands->Send_Custom_Event(player,object,custom,param,delay);
+			if (removeWeapon)
+				Remove_Weapon(player,weapon);
+			if (triggerOnce)
+			{
+				Commands->Enable_HUD_Pokable_Indicator(obj,false);
+				Destroy_Script();
+			}
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Custom_Refect_Custom_If_Model::Created(GameObject *obj)
+{
+	sendFromSelf = Get_Int_Parameter("SendFromSelf");
+	sprintf(model,"%s",Get_Parameter("Model"));
+}
+void JMG_Utility_Custom_Refect_Custom_If_Model::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (obj != sender && !_stricmp(model,Get_Model(obj)))
+	{
+		if (sendFromSelf)
+ 			Commands->Send_Custom_Event(sender,sender,message,param,0);
+		else
+ 			Commands->Send_Custom_Event(obj,sender,message,param,0);
+	}
+}
+void JMG_Utility_Custom_Set_Team::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	team = Get_Int_Parameter("Team");
+}
+void JMG_Utility_Custom_Set_Team::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Commands->Set_Player_Type(obj,team);
+	}
+}
+void JMG_Utility_Custom_Create_Object_At_Random_Wander_Point::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	sprintf(preset,"%s",Get_Parameter("Preset"));
+	wanderPointGroup = Get_Int_Parameter("WanderingAIGroupID");
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Custom_Create_Object_At_Random_Wander_Point::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Vector3 targetPos;
+		float facing;
+		if (!Get_A_Defense_Point(&targetPos,&facing))
+		{
+			Console_Input("msg No Wanderpoint found for that group!");
+			return;
+		}
+		GameObject *created = Commands->Create_Object(Get_Parameter("Preset"),targetPos);
+		Commands->Set_Facing(created,facing);
+	}
+}
+void JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point::Created(GameObject *obj)
+{
+	retryOnFailure = Get_Int_Parameter("RetryOnFailure") ? true : false;
+	changeGroupIDCustom = Get_Int_Parameter("ChangeGroupIDCustom");
+	playerType = Get_Int_Parameter("PlayerType");
+	safeTeleportDistance = Get_Float_Parameter("SafeTeleportDistance");
+	wanderPointGroup = Get_Int_Parameter("WanderingAIGroupID");
+	aiOnly = Get_Int_Parameter("AiOnly") != 0;
+}
+void JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == changeGroupIDCustom)
+	{
+		wanderPointGroup = param;
+	}
+}
+void JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point::Entered(GameObject *obj,GameObject *enter)
+{
+	if (aiOnly && Commands->Is_A_Star(enter))
+		return;
+	if (CheckPlayerType(enter,playerType))
+		return;
+	Grab_Teleport_Spot(enter,5);
+}
+bool JMG_Utility_Zone_Teleport_To_Non_Visible_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_Non_Visible_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_Non_Visible_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_Zone_Teleport_To_Non_Visible_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_Zone_Teleport_To_Non_Visible_Wander_Point_Attach::Created(GameObject *obj)
+{
+	safeTeleportDistance = Get_Float_Parameter("SafeTeleportDistance");
+	wanderPointGroup = Get_Int_Parameter("WanderingAIGroupID");
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		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))
+		{
+			Toggle_Fly_Mode(obj);
+			Commands->Set_Position(obj,targetPos);
+			Force_Position_Update(obj);
+			Toggle_Fly_Mode(obj);
+			char params[220];
+			sprintf(params,"0.1,%.2f",facing);
+			Commands->Attach_Script(obj,"JMG_Utility_Delay_Then_Rotate_Camera",params);
+		}
+		else
+		{
+			Commands->Start_Timer(obj,this,0.25f,1);
+			return;
+		}
+		Destroy_Script();
+	}
+}
+void JMG_Utility_Security_System_Random_NumberPad_Control::Created(GameObject *obj)
+{
+	if (Get_Int_Parameter("RandomCombinationDigits") >= 25)
+	{
+		Console_Input("msg JMG_Utility_Security_System_Random_NumberPad_Control ERROR: Check length of arrays!");
+		return;
+	}
+	char presetName[220],boneName[220],params[225];
+	const char *realPresetName = Commands->Get_Preset_Name(obj);
+	for (int x = 0;x < 10;x++)
+	{
+		sprintf(presetName,"%s_Key_%0d",realPresetName,x);
+		sprintf(boneName,"Number%0d",x);
+		GameObject *button = Commands->Create_Object(presetName,Commands->Get_Position(obj));
+		Commands->Attach_To_Object_Bone(button,obj,boneName);
+		sprintf(params,"%d,4356341,%0d,null",Commands->Get_ID(obj),x);
+		Commands->Attach_Script(button,"JMG_Utility_Custom_Combination_Lock_Key",params);
+		buttonIds[x] = Commands->Get_ID(button);
+	}
+	sprintf(presetName,"%s_Key_Clear",realPresetName);
+	GameObject *button = Commands->Create_Object(presetName,Commands->Get_Position(obj));
+	Commands->Attach_To_Object_Bone(button,obj,"Clear");
+	sprintf(params,"%d,4356340,0,null",Commands->Get_ID(obj));
+	Commands->Attach_Script(button,"JMG_Utility_Custom_Combination_Lock_Key",params);
+	buttonIds[10] = Commands->Get_ID(button);
+
+	sprintf(presetName,"%s_Key_Enter",realPresetName);
+	button = Commands->Create_Object(presetName,Commands->Get_Position(obj));
+	Commands->Attach_To_Object_Bone(button,obj,"Enter");
+	sprintf(params,"%d,4356342,0,null",Commands->Get_ID(obj));
+	Commands->Attach_Script(button,"JMG_Utility_Custom_Combination_Lock_Key",params);
+	buttonIds[11] = Commands->Get_ID(button);
+
+	int rand = Get_Int_Parameter("RandomCombinationDigits");
+	for (int x = 0;x < rand;x++)
+	{
+		combination[x] = (char)('0'+Commands->Get_Random_Int(0,10));
+		combination[x+1] = '\0';
+	}
+	sprintf(params,"%d,%s,4356341,4356342,4356340,%d,%d,%d,%d,%d,%d,%d,%d,%d,%s",Commands->Get_ID(obj),combination,Get_Int_Parameter("Success_Custom"),Get_Int_Parameter("Partial_Failure_Custom"),Get_Int_Parameter("Failure_Custom"),Get_Int_Parameter("Disable_On_Success"),Get_Int_Parameter("Disable_On_Failure"),Get_Int_Parameter("Starts_Enabled"),Get_Int_Parameter("Enable_Custom"),Get_Int_Parameter("Failure_Safty"),Get_Int_Parameter("Max_Failures"),Get_Parameter("Sound_Name_Base"));
+	Commands->Attach_Script(obj,"JMG_Utility_Custom_Combination_Lock",params);
+}
+void JMG_Utility_Security_System_Random_NumberPad_Control::Destroyed(GameObject *obj)
+{
+	for (int x = 0;x < 12;x++)
+	{
+		GameObject *button = Commands->Find_Object(buttonIds[x]);
+		if (button)
+			Commands->Destroy_Object(button);
+	}
+}
+void JMG_Utility_Custom_Combination_Lock::Created(GameObject *obj)
+{
+	id = Get_Int_Parameter("ID");
+	inputCustom = Get_Int_Parameter("Input_Number_Custom");
+	inputEnter = Get_Int_Parameter("Input_Enter_Custom");
+	inputClear = Get_Int_Parameter("Input_Clear_Custom");
+	enableCustom = Get_Int_Parameter("Enable_Custom");
+	sprintf(combination,"%s",Get_Parameter("Combination"));
+	successCustom = Get_Int_Parameter("Success_Custom");
+	failureSaftey = Get_Int_Parameter("Failure_Safty");
+	maxFailures = Get_Int_Parameter("Max_Failures");
+	partialFailCustom = Get_Int_Parameter("Partial_Failure_Custom");
+	failureCustom = Get_Int_Parameter("Failure_Custom");
+	disableOnFailure = Get_Int_Parameter("Disable_On_Failure") ? true : false;
+	disableOnSuccess = Get_Int_Parameter("Disable_On_Success") ? true : false;
+	ClearUserEntry();
+	failCount = 0;
+	Enable(Get_Int_Parameter("Starts_Enabled") == 0 ? false : true);
+	sprintf(soundNameBase,"%s",Get_Parameter("Sound_Name_Base"));
+}
+void JMG_Utility_Custom_Combination_Lock::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == inputCustom)
+	{
+		if (!enabled)
+			return;
+		if (depth >= 127)
+			return;
+		char sound[128];
+		sprintf(sound,"%s%c",soundNameBase,(char)(param+'0'));
+		Commands->Create_Sound(sound,Commands->Get_Position(obj),obj);
+		inputCode[depth] = (char)(param+'0');
+		inputCode[depth+1] = '\0';
+		depth++;
+	}
+	if (message == inputEnter)
+	{
+		if (!enabled)
+			return;
+		char sound[128];
+		sprintf(sound,"%sEnter",soundNameBase);
+		Commands->Create_Sound(sound,Commands->Get_Position(obj),obj);
+		if (_stricmp(inputCode,combination))
+		{
+			if (failureSaftey && depth)
+			{
+				failCount++;
+				if (failCount < maxFailures)
+					Send_Custom(sender,partialFailCustom,depth);
+				else
+				{
+					failCount = 0;
+					Send_Custom(sender,failureCustom,depth);
+					if (disableOnFailure)
+						enabled = false;
+				}
+			}
+			else if (!failureSaftey)
+			{
+				Send_Custom(sender,failureCustom,depth);
+				if (disableOnFailure)
+					enabled = false;
+			}
+			ClearUserEntry();
+		}
+		else
+		{
+			if (disableOnSuccess)
+				enabled = false;
+			ClearUserEntry();
+			failCount = 0;
+			Send_Custom(sender,successCustom,param);
+		}
+	}
+	if (message == inputClear)
+	{
+		if (!enabled)
+			return;
+		char sound[128];
+		sprintf(sound,"%sClear",soundNameBase);
+		Commands->Create_Sound(sound,Commands->Get_Position(obj),obj);
+		ClearUserEntry();
+	}
+	if (message == enableCustom)
+	{
+		if (enabled)
+			return;
+		Enable(true);
+	}
+}
+void JMG_Utility_Custom_Combination_Lock::Send_Custom(GameObject *obj,int custom,int param)
+{
+	GameObject *object = id ? Commands->Find_Object(id) : obj;
+	if (object)
+		Commands->Send_Custom_Event(obj,object,custom,param,0.0f);
+}
+void JMG_Utility_Custom_Combination_Lock::Enable(bool enableLock)
+{
+	ClearUserEntry();
+	failCount = 0;
+	enabled = enableLock;
+	for (unsigned int x = 0;x < strlen(combination);x++)
+		if (combination[x] < '0' || combination[x] > '9')
+		{
+			Console_Input("msg JMG_Custom_Combination_Lock ERROR: Combination must be a number 0 - 9!");
+			enabled = false;
+			break;
+		}
+}
+void JMG_Utility_Custom_Combination_Lock::ClearUserEntry()
+{
+	depth = 0;
+	sprintf(inputCode,"\0");
+}
+void JMG_Utility_Custom_Combination_Lock_Key::Created(GameObject *obj)
+{
+	id = Get_Int_Parameter("ID");
+	message = Get_Int_Parameter("Message");
+	param = Get_Int_Parameter("Param");
+	sprintf(soundName,"%s",Get_Parameter("Sound_Name"));
+	useAltAnimation = false;
+	sprintf(animation,"%s.%s",Get_Model(obj),Get_Model(obj));
+	sprintf(animation2,"%s.%sA",Get_Model(obj),Get_Model(obj));
+}
+void JMG_Utility_Custom_Combination_Lock_Key::Poked(GameObject *obj,GameObject *poker)
+{
+	if (Commands->Get_Player_Type(poker) == -4)
+		return;
+	if (id == 0)
+		Commands->Send_Custom_Event(poker,obj,message,param,0);
+	if (id != 0)
+		Commands->Send_Custom_Event(poker,Commands->Find_Object(id),message,param,0);
+	Commands->Create_Sound(soundName,Commands->Get_Position(obj),obj);
+	Commands->Set_Animation(obj,useAltAnimation ? animation2 : animation,false,0,1,5,false);
+	useAltAnimation = !useAltAnimation;
+}
+void JMG_Utility_Sync_String_With_Random_NumberPad_Control::Created(GameObject *obj)
+{
+	delim = Get_Parameter("Delim")[0];
+	stringId = Get_Int_Parameter("StringID");
+	Commands->Start_Timer(obj,this,0.1f,1);
+}
+void JMG_Utility_Sync_String_With_Random_NumberPad_Control::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1)
+	{
+		JMG_Utility_Security_System_Random_NumberPad_Control *script = (JMG_Utility_Security_System_Random_NumberPad_Control*)Find_Script_On_Object(obj,"JMG_Utility_Security_System_Random_NumberPad_Control");
+		if (!script)
+			return;
+		TDBObjClass *tdbobj = TranslateDBClass::Find_Object(stringId);
+		char newString[2048];
+		sprintf(newString,"%s",tdbobj->Get_English_String());
+		char tmpString[2048];
+		int offset = 0;
+		for (int x = 0;x < 2048;x++,offset++)
+		{
+			if (newString[x] == delim)
+				for (int y = 0;y < 25;y++)
+				{
+					if (script->combination[y] == '\0')
+					{
+						offset--;
+						break;
+					}
+					tmpString[offset] = script->combination[y];
+					offset++;
+				}
+			else
+				tmpString[offset] = newString[x];
+		}
+		if (tdbobj)
+		{
+			WideStringClass str(tmpString);
+			tdbobj->Set_String(TranslateDBClass::Get_Current_Language(),str);
+		}
+		for (int x = 1;x < 128;x++)
+		{
+			GameObject *player = Get_GameObj(x);
+			if (!player)
+				continue;
+			Change_String_Player(player,stringId,tmpString);
+		}
+	}
+}
+void JMG_Utility_Created_Animate_SubObject_On_Join::Created(GameObject *obj)
+{
+	sprintf(animation,"%s",Get_Parameter("Animation"));
+	sprintf(subobject,"%s",Get_Parameter("SubObject"));
+	for (int x = 1;x < 128;x++)
+		animating[x] = false;
+	Timer_Expired(obj,1);
+}
+void JMG_Utility_Created_Animate_SubObject_On_Join::Timer_Expired(GameObject *obj,int number)
+{
+	if (number == 1) 
+	{
+		for (int x = 1;x < 128;x++)
+		{
+			GameObject *player = Get_GameObj(x);
+			if (player && !animating[x])
+			{
+				animating[x] = true;
+				Set_Subobject_Animation_Player(player,obj,animation,true,subobject,0,-1,true);
+			}
+			if (!player && animating[x])
+				animating[x] = false;
+		}
+		Commands->Start_Timer(obj,this,0.1f,1);
+	}
+}
+void JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete::Created(GameObject *obj)
+{
+	receivedCustom = Get_Int_Parameter("ReceivedCustom");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("Custom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	sprintf(animation,"%s",Get_Parameter("Animation"));
+	startFrame = Get_Float_Parameter("StartFrame");
+	endFrame = Get_Float_Parameter("EndFrame");
+}
+void JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == receivedCustom)
+	{
+		if (obj->As_SoldierGameObj())
+		{
+			obj->As_SoldierGameObj()->Set_Override_Weapon_Hold_Style(14);
+			obj->As_SoldierGameObj()->Set_Can_Play_Damage_Animations(false);
+		}
+		Commands->Set_Animation(obj,animation,false,0,startFrame,endFrame,true);
+	}
+}
+void JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete::Animation_Complete(GameObject *obj,const char *anim)
+{
+	if (obj->As_SoldierGameObj())
+	{
+		obj->As_SoldierGameObj()->Set_Override_Weapon_Hold_Style(-1);
+		obj->As_SoldierGameObj()->Set_Can_Play_Damage_Animations(true);
+	}
+	GameObject *object = id ? Commands->Find_Object(id) : obj;
+	if (object)
+		Commands->Send_Custom_Event(obj,object,custom,Param,0.0f);
+}
+void JMG_Utility_Created_Play_Locked_Infantry_Animation::Created(GameObject *obj)
+{
+	if (!obj->As_SoldierGameObj())
+	{
+		Console_Input("msg JMG_Utility_Custom_Play_Locked_Infantry_Animation ERROR: Can only be used on infantry!");
+		return;
+	}
+	obj->As_SoldierGameObj()->Set_Override_Weapon_Hold_Style(14);
+	obj->As_SoldierGameObj()->Set_Can_Play_Damage_Animations(false);
+	Commands->Set_Animation(obj,Get_Parameter("Animation"),true,0,0.0f,-1.0,true);
+}
+void JMG_Utility_Custom_Send_Custom_To_Preset::Created(GameObject *obj)
+{
+	receivedCustom = Get_Int_Parameter("ReceivedCustom");
+	sprintf(preset,"%s",Get_Parameter("Preset"));
+	custom = Get_Int_Parameter("Custom");
+	Param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+}
+void JMG_Utility_Custom_Send_Custom_To_Preset::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == receivedCustom)
+	{
+		for (SLNode<BaseGameObj> *current = GameObjManager::GameObjList.Head();current;current = current->Next())
+		{
+			GameObject *target = current->Data()->As_ScriptableGameObj();
+			if (target && target->As_ScriptableGameObj() && !_stricmp(preset,Commands->Get_Preset_Name(target))) 
+			{
+				Commands->Send_Custom_Event(obj,target,custom,Param,0.0f);
+			}
+		}
+	}
+}
+void JMG_Utility_Custom_Talk::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	stringId = Get_Int_Parameter("StringID");
+}
+void JMG_Utility_Custom_Talk::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		Say_Dynamic_Dialogue(stringId,obj->As_SoldierGameObj(),&soundId);
+	}
+}
+void JMG_Utility_Custom_Talk::Killed(GameObject *obj,GameObject *killer)
+{
+	Stop_Sound_Team(soundId,true,2);
+}
+void JMG_Utility_Zone_Send_Custom_Enter_Preset::Created(GameObject *obj)
+{
+	sprintf(preset,"%s",Get_Parameter("Preset"));
+	playerType = Get_Int_Parameter("PlayerType");
+	id = Get_Int_Parameter("ID");
+	custom = Get_Int_Parameter("Custom");
+	param = Get_Int_Parameter("Param");
+	delay = Get_Float_Parameter("Delay");
+	triggerOnce = Get_Int_Parameter("TriggerOnce") ? true : false;
+}
+void JMG_Utility_Zone_Send_Custom_Enter_Preset::Entered(GameObject *obj,GameObject *enter)
+{
+	if (CheckPlayerType(enter,playerType) || Commands->Get_Player_Type(enter) == -4 || _stricmp(preset,Commands->Get_Preset_Name(enter)))
+		return;
+	GameObject *object = id ? (id == -1 ? enter : Commands->Find_Object(id)) : obj;
+	if (object)
+		Commands->Send_Custom_Event(obj,object,custom,param,delay);
+	if (triggerOnce)
+		Destroy_Script();
+}
+float JMG_Utility_Global_Armor_Controller::maxArmor = 0.0f;
+float JMG_Utility_Global_Armor_Controller::armor = 0.0f;
+void JMG_Utility_Global_Armor_Controller::Created(GameObject *obj)
+{
+	maxArmor = Get_Float_Parameter("DefaultMaxArmor");
+	armor = Get_Float_Parameter("DefaultArmor");
+}
+void JMG_Utility_Global_Armor_Custom::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	maxArmor = Get_Float_Parameter("MaxArmor");
+	armor = Get_Float_Parameter("Armor");
+	updateAllObjects = Get_Int_Parameter("UpdateAllObjects") ? true : false;
+}
+void JMG_Utility_Global_Armor_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		JMG_Utility_Global_Armor_Controller::maxArmor = maxArmor == -1.0f ? param : maxArmor;
+		JMG_Utility_Global_Armor_Controller::armor = armor == -1.0f ? param : armor;
+		if (!updateAllObjects)
+			return;
+		for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+		{
+			SmartGameObj *o = current->Data();
+			if (!o)
+				continue;
+			if (!Is_Script_Attached(o,"JMG_Utility_Global_Armor_Object"))
+				continue;
+			Set_Max_Shield_Strength(o,JMG_Utility_Global_Armor_Controller::maxArmor);
+			Commands->Set_Shield_Strength(o,JMG_Utility_Global_Armor_Controller::armor);
+		}
+	}
+}
+void JMG_Utility_Global_Armor_Object::Created(GameObject *obj)
+{
+	Set_Max_Shield_Strength(obj,JMG_Utility_Global_Armor_Controller::maxArmor);
+	Commands->Set_Shield_Strength(obj,JMG_Utility_Global_Armor_Controller::armor);
+}
+float JMG_Utility_Global_Health_Controller::maxHealth = 0.0f;
+float JMG_Utility_Global_Health_Controller::health = 0.0f;
+void JMG_Utility_Global_Health_Controller::Created(GameObject *obj)
+{
+	maxHealth = Get_Float_Parameter("DefaultMaxHealth");
+	health = Get_Float_Parameter("DefaultHealth");
+}
+void JMG_Utility_Global_Health_Custom::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	maxHealth = Get_Float_Parameter("MaxHealth");
+	health = Get_Float_Parameter("Health");
+	updateAllObjects = Get_Int_Parameter("UpdateAllObjects") ? true : false;
+}
+void JMG_Utility_Global_Health_Custom::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		JMG_Utility_Global_Health_Controller::maxHealth = maxHealth == -1.0f ? param : maxHealth;
+		JMG_Utility_Global_Health_Controller::health = health == -1.0f ? param : health;
+		if (!updateAllObjects)
+			return;
+		for (SLNode<SmartGameObj> *current = GameObjManager::SmartGameObjList.Head();current;current = current->Next())
+		{
+			SmartGameObj *o = current->Data();
+			if (!o)
+				continue;
+			if (!Is_Script_Attached(o,"JMG_Utility_Global_Health_Object"))
+				continue;
+			Set_Max_Health(o,JMG_Utility_Global_Health_Controller::maxHealth);
+			Commands->Set_Health(o,JMG_Utility_Global_Health_Controller::health);
+		}
+	}
+}
+void JMG_Utility_Global_Health_Object::Created(GameObject *obj)
+{
+	Set_Max_Health(obj,JMG_Utility_Global_Health_Controller::maxHealth);
+	Commands->Set_Health(obj,JMG_Utility_Global_Health_Controller::health);
+}
+void JMG_Utility_Custom_Create_Random_Explosions::Created(GameObject *obj)
+{
+	custom = Get_Int_Parameter("Custom");
+	sprintf(explosionPreset,"%s",Get_Parameter("ExplosionPreset"));
+	count = Get_Int_Parameter("Count");
+	distance = Get_Float_Parameter("MaxDistance");
+	killerId = Get_Int_Parameter("KillerID");
+}
+void JMG_Utility_Custom_Create_Random_Explosions::Custom(GameObject *obj,int message,int param,GameObject *sender)
+{
+	if (message == custom)
+	{
+		GameObject *killer = killerId ? (killerId == -1 ? sender : Commands->Find_Object(killerId)) : obj;
+		Vector3 originalPos = Commands->Get_Position(obj);
+		for (int x = 0;x < count;x++)
+		{
+			Vector3 pos = originalPos;
+			float Distance = Commands->Get_Random(0.0f,distance);
+			float Rotation = Commands->Get_Random(-180.0,180);
+			pos.X += cos(Rotation*PI180)*Distance;
+			pos.Y += sin(Rotation*PI180)*Distance;
+			Commands->Create_Explosion(explosionPreset,pos,killer);
+		}
+	}
+}
 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");
@@ -12223,7 +14674,7 @@
 ScriptRegistrant<JMG_Utility_Destroyed_Apply_Damage> JMG_Utility_Destroyed_Apply_Damage_Registrant("JMG_Utility_Destroyed_Apply_Damage","ID:int,Damage:float,Warhead:string");
 ScriptRegistrant<JMG_Utility_Scale_Damage_By_Player_Count> JMG_Utility_Scale_Damage_By_Player_Count_Registrant("JMG_Utility_Scale_Damage_By_Player_Count","MaxPlayersToScaleFor=50:int,MaxScaleFactor=0.95:float");
 ScriptRegistrant<JMG_Utility_Scale_Damage_Square_By_Player_Count> JMG_Utility_Scale_Damage_Square_By_Player_Count_Registrant("JMG_Utility_Scale_Damage_Square_By_Player_Count","MaxPlayersToScaleFor=50:int,MaxScaleFactor=0.95:float");
-ScriptRegistrant<JMG_Utility_Regen_HitPoints> JMG_Utility_Regen_HitPoints_Registrant("JMG_Utility_Regen_HitPoints","RegenHealth=1:int,HealthAmount=1.0:float,HealthPerPlayer=0.0:float,RegenArmor=1:int,ArmorAmount=1.0:float,ArmorPerPlayer=0.0:float,Rate=1.0:float,DamageDelay=0.0:float");
+ScriptRegistrant<JMG_Utility_Regen_HitPoints> JMG_Utility_Regen_HitPoints_Registrant("JMG_Utility_Regen_HitPoints","RegenHealth=1:int,HealthAmount=1.0:float,HealthPerPlayer=0.0:float,RegenArmor=1:int,ArmorAmount=1.0:float,ArmorPerPlayer=0.0:float,Rate=1.0:float,DamageDelay=0.0:float,ScaleHealthPerHeal=0.0:float,ScaleArmorPerHeal=0.0:float");
 ScriptRegistrant<JMG_Utility_Toggle_Flight_On_Delay> JMG_Utility_Toggle_Flight_On_Delay_Registrant("JMG_Utility_Toggle_Flight_On_Delay","Delay:float");
 ScriptRegistrant<JMG_Utility_Fainting_Soldier> JMG_Utility_Fainting_Soldier_Registrant("JMG_Utility_Fainting_Soldier","FaintAnimation:string,LayAnimation:string,StandAnimation:string,FaintSound=null:string,StandSound=null:string,ChangeArmorTypeWhenKnockedOut=0:int,ArmorTypeWhileKnockedOut=Blamo:string,TeamWhileKnockedOut=-2:int");
 ScriptRegistrant<JMG_Utility_AI_Guardian_Infantry> JMG_Utility_AI_Guardian_Infantry_Registrant("JMG_Utility_AI_Guardian_Infantry","WanderingAIGroupID:int,WanderSpeed=1.0:float,FireRange=-1.0:float,FaceTarget=1:int,CheckBlocked=1:int,StealthModeOverride=0:int");
@@ -12435,4 +14886,76 @@
 ScriptRegistrant<JMG_Utility_AI_Lobbed_Vehicle_Projectile_Custom> JMG_Utility_AI_Lobbed_Vehicle_Projectile_Custom_Registrant("JMG_Utility_AI_Lobbed_Vehicle_Projectile_Custom","TargetCustom:int,MinDistance=0.0:float,MinAngle=-90.0:float,MaxAngle=90.0:float,UseLowAngleMaxDistance=0.0:float,UseLowAngleWhenAboveMinDistance=1:int,UseLowAngleTargetAboveHeight=9999.9:float,VehicleProjectilePreset:string,FireVelocity=1.0:float,GravityScale=1.0:float,MissAmountPerMeter=0.0:float,BaseMissAmount=0.0:float,ProjectedShotsChance=1.0:float,FireRate=1.0:float,ReloadTime=1.0:float,ReloadSound=null:string,ClipCount=1:int,FireSound=null:string,MuzzleFlashExplosion=null:string,ProjectileExplosion=null:string,CustomTimeoutTime=3:int,AimTurret=1:int");
 ScriptRegistrant<JMG_Utility_Vehicle_Enter_Send_Custom_From_Enterer> JMG_Utility_Vehicle_Enter_Send_Custom_From_Enterer_Registrant("JMG_Utility_Vehicle_Enter_Send_Custom_From_Enterer","PlayerType=2:int,ID:int,Custom:int,Param:int,Delay:float,TriggerOnce:int");
 ScriptRegistrant<JMG_Utility_Custom_Send_Custom_On_Count_From_Sender> JMG_Utility_Custom_Send_Custom_On_Count_From_Sender_Registrant("JMG_Utility_Custom_Send_Custom_On_Count_From_Sender","Custom:int,Count:int,ID=0:int,SendCustom:int,Param:int,Delay:float,ResetCustom:int");
-ScriptRegistrant<JMG_Utility_Player_Seen_Send_Custom> JMG_Utility_Player_Seen_Send_Custom_Registrant("JMG_Utility_Player_Seen_Send_Custom","ID:int,Custom:int,Param:int,Delay:float,TriggerOnce:int");
\ No newline at end of file
+ScriptRegistrant<JMG_Utility_Player_Seen_Send_Custom> JMG_Utility_Player_Seen_Send_Custom_Registrant("JMG_Utility_Player_Seen_Send_Custom","ID:int,Custom:int,Param:int,Delay:float,TriggerOnce:int");
+ScriptRegistrant<JMG_Utility_Enter_Play_Animation_And_Relocate> JMG_Utility_Enter_Play_Animation_And_Relocate_Registrant("JMG_Utility_Enter_Play_Animation_And_Relocate","PresetName:string,Animation:string,AttachedModel:string,AttachedAnimation:string,CenterToZone:int,Custom:int,Param:int");
+ScriptRegistrant<JMG_Utility_Enter_Play_Animation_And_Relocate_Attached> JMG_Utility_Enter_Play_Animation_And_Relocate_Attached_Registrant("JMG_Utility_Enter_Play_Animation_And_Relocate_Attached","AttachedModel:string,AttachedAnimation:string,Facing:float,Custom:int,Param:int");
+ScriptRegistrant<JMG_Utility_Custom_Destroy_Closest_Object_To_Self> JMG_Utility_Custom_Destroy_Closest_Object_To_Self_Registrant("JMG_Utility_Custom_Destroy_Closest_Object_To_Self","Custom:int,PresetName:string,MaxDistance=0.0:float,TriggerOnce=0:int");
+ScriptRegistrant<JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self> JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self_Registrant("JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self","Custom:int,PresetName:string,Warhead:string,DamageAmount:float,MaxDistance=0.0:float,TriggerOnce=0:int");
+ScriptRegistrant<JMG_Utility_Custom_Restore_Building> JMG_Utility_Custom_Restore_Building_Registrant("JMG_Utility_Custom_Restore_Building","Custom:int,ID:int,TriggerOnce:int");
+ScriptRegistrant<JMG_Utility_Timer_Custom_Random> JMG_Utility_Timer_Custom_Random_Registrant("JMG_Utility_Timer_Custom_Random","Time:float,Random:float,ID:int,Message:int,Param=0:int,Repeat=0:int");
+ScriptRegistrant<JMG_Utility_Dynamic_Script_Controller> JMG_Utility_Dynamic_Script_Controller_Registrant("JMG_Utility_Dynamic_Script_Controller","");
+ScriptRegistrant<JMG_Utility_Dynamic_Script_Definition> JMG_Utility_Dynamic_Script_Definition_Registrant("JMG_Utility_Dynamic_Script_Definition","ScriptID:int,ScriptName:string");
+ScriptRegistrant<JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter> JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter_Registrant("JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter","ScriptID:int,Index:int,Param:string,Delim:string");
+ScriptRegistrant<JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter> JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter_Registrant("JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter","Custom:int,ScriptID:int,Index:int,Param:string,Delim:string");
+ScriptRegistrant<JMG_Utility_Dynamic_Script_Custom_Attach> JMG_Utility_Dynamic_Script_Custom_Attach_Registrant("JMG_Utility_Dynamic_Script_Custom_Attach","Custom:int,ScriptID:int");
+ScriptRegistrant<JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom> JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom_Registrant("JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom","Custom:int,NewObjectiveID:int,NewObjectiveStringID:int,ObjectiveMarkerObjectID:int,CompleteObjectiveID:int,Delay:float,NewObjectivePriority=1:int,SendCustom:int,Param:int,CustomDelay:float");
+ScriptRegistrant<JMG_Utility_Objective_System_Custom_Send_Custom_Status> JMG_Utility_Objective_System_Custom_Send_Custom_Status_Registrant("JMG_Utility_Objective_System_Custom_Send_Custom_Status","Custom:int,ObjectiveID:int,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Objective_System_Custom_Send_Custom_Does_Not_Exist> JMG_Utility_Objective_System_Custom_Send_Custom_Does_Not_Exist_Registrant("JMG_Utility_Objective_System_Custom_Send_Custom_Does_Not_Exist","Custom:int,ObjectiveID:int,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Objective_System_Send_Random_Objective_As_Custom> JMG_Utility_Objective_System_Send_Random_Objective_As_Custom_Registrant("JMG_Utility_Objective_System_Send_Random_Objective_As_Custom","Custom:int,ObjectiveID0=-999:int,ObjectiveID1=-999:int,ObjectiveID2=-999:int,ObjectiveID3=-999:int,ObjectiveID4=-999:int,ObjectiveID5=-999:int,ObjectiveID6=-999:int,ObjectiveID7=-999:int,ObjectiveID8=-999:int,ObjectiveID9=-999:int,ID=0:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Objective_System_Custom_Send_Custom_Not_Status> JMG_Utility_Objective_System_Custom_Send_Custom_Not_Status_Registrant("JMG_Utility_Objective_System_Custom_Send_Custom_Not_Status","Custom:int,ObjectiveID:int,Status:int,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float");
+ScriptRegistrant<JMG_Utility_Global_Keycard_System_Controller> JMG_Utility_Global_Keycard_System_Controller_Registrant("JMG_Utility_Global_Keycard_System_Controller","");
+ScriptRegistrant<JMG_Utility_Global_Keycard_System_Created_Add_Keycard> JMG_Utility_Global_Keycard_System_Created_Add_Keycard_Registrant("JMG_Utility_Global_Keycard_System_Created_Add_Keycard","KeycardID:int,GroupID:int");
+ScriptRegistrant<JMG_Utility_Global_Keycard_System_Custom_Add_Keycard> JMG_Utility_Global_Keycard_System_Custom_Add_Keycard_Registrant("JMG_Utility_Global_Keycard_System_Custom_Add_Keycard","Custom:int,KeycardID:int,GroupID:int");
+ScriptRegistrant<JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard> JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard_Registrant("JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard","Custom:int,KeycardID:int,GroupID:int");
+ScriptRegistrant<JMG_Utility_Global_Keycard_System_Soldier> JMG_Utility_Global_Keycard_System_Soldier_Registrant("JMG_Utility_Global_Keycard_System_Soldier","GroupID:int");
+ScriptRegistrant<JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change> JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Registrant("JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change","OccupiedID:int,OccupiedCustom:int,OccupiedParam:int,OccupiedDelay:float,VacantID:int,VacantCustom:int,VacantParam:int,VacantDelay:float");
+ScriptRegistrant<JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached> JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached_Registrant("JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached","ID:int");
+ScriptRegistrant<JMG_Utility_Silent_Countdown_Controller> JMG_Utility_Silent_Countdown_Controller_Registrant("JMG_Utility_Silent_Countdown_Controller","");
+ScriptRegistrant<JMG_Utility_Silent_Countdown_Timer> JMG_Utility_Silent_Countdown_Timer_Registrant("JMG_Utility_Silent_Countdown_Timer","TimerID:int,Time:int,StartCustom:int,PausedCustom:int,CancelCustom:int");
+ScriptRegistrant<JMG_Utility_Silent_Countdown_Send_Custom> JMG_Utility_Silent_Countdown_Send_Custom_Registrant("JMG_Utility_Silent_Countdown_Send_Custom","TimerID:int,TriggerTime:int,ID:int,Custom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Zone_Teleport_To_Random_WP_Boss> JMG_Utility_Zone_Teleport_To_Random_WP_Boss_Registrant("JMG_Utility_Zone_Teleport_To_Random_WP_Boss","WanderingAIGroupID=-1:int,SafeTeleportDistance=1.5:float,ChangeGroupIDCustom=0:int,PlayerType=2:int,RetryOnFailure=0:int,AiOnly=0:int,FaceBoss=1:int");
+ScriptRegistrant<JMG_Utility_Zone_Teleport_To_Random_WP_Boss_Object> JMG_Utility_Zone_Teleport_To_Random_WP_Boss_Object_Registrant("JMG_Utility_Zone_Teleport_To_Random_WP_Boss_Object","");
+ScriptRegistrant<JMG_Utility_Custom_Set_Soldier_Speed> JMG_Utility_Custom_Set_Soldier_Speed_Registrant("JMG_Utility_Custom_Set_Soldier_Speed","Custom:int,Speed:float");
+ScriptRegistrant<JMG_Utility_Control_Point_Controller> JMG_Utiliwty_Control_Point_Controller_Registrant("JMG_Utility_Control_Point_Controller","NeutralTeamName=Neutral:string,NeutralPlayerType=-2:int,NeutralRadarBlipColor=2:int,NeutralDefaultPointModel:string,NeutralDefaultAnimation:string,NeutralAnimationLength:float,NeutralCaptureSound:string,NeutralLostSound:string,NeutralLockedModel:string,NeutralLockedAnim:string,NeutralLockedAnimLength:float,NeutralDefenseMultiplier=0:int,NeutralCaptureMultiplier=0:int,NeutralUnoccupiedMultiplier=-1:int");
+ScriptRegistrant<JMG_Utility_Control_Point_Team_Setting> JMG_Utility_Control_Point_Team_Setting_Registrant("JMG_Utility_Control_Point_Team_Setting","TeamID:int,TeamName:string,PlayerType:int,RadarBlipColor:int,PointModel:string,Animation:string,AnimationLength:float,CaptureSound:string,LostSound:string,LockedModel:string,LockedAnim:string,LockedAnimLength:float,DefenseMultiplier=1:int,CaptureMultiplier=1:int,UnoccupiedMultiplier=1:int");
+ScriptRegistrant<JMG_Utility_Control_Point> JMG_Utility_Control_Point_Registrant("JMG_Utility_Control_Point","ControlPointName:string,ControllingTeamID:int,MaxCapturePoints=100:int,ControlDistance=15.0:float,ControlHeight[Min|Max]=0.0 0.0 0.0:Vector3,ZoneID=0:int,CaptureScore=1000.0:float,NeutralizeScore=1000.0:float,ControlGroupID=-1:int,StartsLocked=0:int,LockCustom=0:int");
+ScriptRegistrant<JMG_Utility_Control_Point_Setting_Override> JMG_Utility_Control_Point_Setting_Override_Registrant("JMG_Utility_Control_Point_Setting_Override","TeamID:int,ID:int,CaptureCustom:int,LostCustom:int,PointModelOverride:string,AnimationOverride:string,AnimationLength:float,LockedPointModelOverride:string,LockedAnimationOverride:string,LockedAnimationLength:float");
+ScriptRegistrant<JMG_Utility_Control_Point_Team_Member> JMG_Utility_Control_Point_Team_Member_Registrant("JMG_Utility_Control_Point_Team_Member","TeamID:int,Multiplier=1:int");
+ScriptRegistrant<JMG_Utility_Control_Point_Assault_Mode> JMG_Utility_Control_Point_Assault_Mode_Registrant("JMG_Utility_Control_Point_Assault_Mode","TeamID:int,ID:int,AdvanceCustom:int,PushBackCustom:int,ControlAllCustom:int,LostAllCustom:int");
+ScriptRegistrant<JMG_Utility_Control_Point_Wander_Point> JMG_Utility_Control_Point_Wander_Point_Registrant("JMG_Utility_Control_Point_Wander_Point","ControlPointID:int,TeamID:int,SpawnableGroupID=-999:int,UnspawnableGroupId=-999:int");
+ScriptRegistrant<JMG_Utility_Control_Point_Select_Spawn_System> JMG_Utility_Control_Point_Select_Spawn_System_Registrant("JMG_Utility_Control_Point_Select_Spawn_System","SpawnPreset:string,TeamID:int,SpawnCustom:int,GroupChangeCustom:int,UngroupedChangeCustom:int,SafeTeleportDistance=1.5:float,SpawnTime=10:int,MaxWanderRange=60.0:float,StringID=12628:int");
+ScriptRegistrant<JMG_Utility_Custom_And_Param_Send_Custom> JMG_Utility_Custom_And_Param_Send_Custom_Registrant("JMG_Utility_Custom_And_Param_Send_Custom","Custom:int,Param:int,ID=0:int,SendCustom:int,SendParam:int,Delay=0.0:float,RandomDelay=0.0:float,RandomChance=0.0:float");
+ScriptRegistrant<JMG_Utility_Create_Move_To_Nearest_Pathfind> JMG_Utility_Create_Move_To_Nearest_Pathfind_Registrant("JMG_Utility_Create_Move_To_Nearest_Pathfind","");
+ScriptRegistrant<JMG_Utility_Objective_System_Objective_GameObject> JMG_Utility_Objective_System_Objective_GameObject_Registrant("JMG_Utility_Objective_System_Objective_GameObject","ObjectivePriority=-2:int,ObjectiveID=-2:int,Preset:string,Attach=1:int");
+ScriptRegistrant<JMG_Utility_Objective_System_Objective_GameObject_Tracker> JMG_Utility_Objective_System_Objective_GameObject_Tracker_Registrant("JMG_Utility_Objective_System_Objective_GameObject_Tracker","GameObjectID:int,MarkerID:int");
+ScriptRegistrant<JMG_Utility_Attach_Script_To_Object_With_Weapon> JMG_Utility_Attach_Script_To_Object_With_Weapon_Registrant("JMG_Utility_Attach_Script_To_Object_With_Weapon","WeaponName:string,Script:string,Params:string,Delim=@:string,Rate=0.1:float,PlayerType=2:int");
+ScriptRegistrant<JMG_Utility_Drop_Object_On_Death_And_Attach_Script> JMG_Utility_Drop_Object_On_Death_And_Attach_Script_Registrant("JMG_Utility_Drop_Object_On_Death_And_Attach_Script","Drop_Object:string,Drop_Height=0.25:float,Probability=100.0:float,Script:string,Params:string,Delim=@:string");
+ScriptRegistrant<JMG_SinglePlayer_M04_Modifier> JMG_SinglePlayer_M04_Modifier_Registrant("JMG_SinglePlayer_M04_Modifier","");
+ScriptRegistrant<JMG_Utility_Custom_Send_Delayed_Ordered_Customs> JMG_Utility_Custom_Send_Delayed_Ordered_Customs_Registrant("JMG_Utility_Custom_Send_Delayed_Ordered_Customs","ID:int,Custom0:int,Time0:float,Custom1:int,Time1:float,Custom2:int,Time2:float,Custom3:int,Time3:float,Custom4:int,Time4:float,Custom5:int,Time5:float,Custom6:int,Time6:float,Custom7:int,Time7:float,Custom8:int,Time8:float,Custom9:int,Time9:float");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_If_Model> JMG_Utility_Custom_Send_Custom_If_Model_Registrant("JMG_Utility_Custom_Send_Custom_If_Model","Model:string,Custom:int,ID=0:int,SendCustom:int,Param:int,Delay=0.0:float,RandomDelay=0.0:float,RandomChance=0.0:float");
+ScriptRegistrant<JMG_Utility_Poke_Grant_Weapon_Destroy_Self> JMG_Utility_Poke_Grant_Weapon_Destroy_Self_Registrant("JMG_Utility_Poke_Grant_Weapon_Destroy_Self","Weapon:string,Rounds:int,Backpack:int");
+ScriptRegistrant<JMG_Utility_Killed_Drop_Object_If_Weapon_Present> JMG_Utility_Killed_Drop_Object_If_Weapon_Present_Registrant("JMG_Utility_Killed_Drop_Object_If_Weapon_Present","Weapon:string,DropPreset:string,Height=0.25:float");
+ScriptRegistrant<JMG_Utility_Poke_Send_Custom_If_Has_Weapon> JMG_Utility_Poke_Send_Custom_If_Has_Weapon_Registrant("JMG_Utility_Poke_Send_Custom_If_Has_Weapon","Weapon:string,ID:int,Custom:int,Param:int,Delay:float,RemoveWeapon:int,PlayerType=2:int,TriggerOnce=1:int,MustBeHeld=0:int");
+ScriptRegistrant<JMG_Utility_Send_Custom_If_Weapon_Held_In_Range> JMG_Utility_Send_Custom_If_Weapon_Held_In_Range_Registrant("JMG_Utility_Send_Custom_If_Weapon_Held_In_Range","Weapon:string,Range:float,ID:int,Custom:int,Param:int,Delay:float,RemoveWeapon:int,PlayerType=2:int,TriggerOnce=1:int,MustBeHeld=1:int");
+ScriptRegistrant<JMG_Utility_Custom_Refect_Custom_If_Model> JMG_Utility_Custom_Refect_Custom_If_Model_Registrant("JMG_Utility_Custom_Refect_Custom_If_Model","Model:string,SendFromSelf:int");
+ScriptRegistrant<JMG_Utility_Custom_Set_Team> JMG_Utility_Custom_Set_Team_Registrant("JMG_Utility_Custom_Set_Team","Custom:int,Team:int");
+ScriptRegistrant<JMG_Utility_Custom_Create_Object_At_Random_Wander_Point> JMG_Utility_Custom_Create_Object_At_Random_Wander_Point_Registrant("JMG_Utility_Custom_Create_Object_At_Random_Wander_Point","Custom:int,Preset:string,WanderingAIGroupID:int");
+ScriptRegistrant<JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point> JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Registrant("JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point","WanderingAIGroupID=-1:int,SafeTeleportDistance=1.5:float,ChangeGroupIDCustom=0:int,PlayerType=2:int,RetryOnFailure=0:int,AiOnly=0:int");
+ScriptRegistrant<JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach> JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach_Registrant("JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach","WanderingAIGroupID=-1:int,SafeTeleportDistance=1.5:float");
+ScriptRegistrant<JMG_Utility_Security_System_Random_NumberPad_Control> JMG_Utility_Security_System_Random_NumberPad_Control_Registrant("JMG_Utility_Security_System_Random_NumberPad_Control","RandomCombinationDigits:int,ID:int,Success_Custom=0:int,Partial_Failure_Custom=0:int,Failure_Custom=0:int,Disable_On_Success=1:int,Disable_On_Failure=0:int,Starts_Enabled=1:int,Enable_Custom=0:int,Failure_Safty=1:int,Max_Failures=1:int,Sound_Name_Base:string");
+ScriptRegistrant<JMG_Utility_Custom_Combination_Lock> JMG_Utility_Custom_Combination_Lock_Registrant("JMG_Utility_Custom_Combination_Lock","ID:int,Combination:string,Input_Number_Custom:int,Input_Enter_Custom:int,Input_Clear_Custom:int,Success_Custom=0:int,Partial_Failure_Custom=0:int,Failure_Custom=0:int,Disable_On_Success=1:int,Disable_On_Failure=0:int,Starts_Enabled=1:int,Enable_Custom=0:int,Failure_Safty=1:int,Max_Failures=1:int,Sound_Name_Base:string");
+ScriptRegistrant<JMG_Utility_Custom_Combination_Lock_Key> JMG_Utility_Custom_Combination_Lock_Key_Registrant("JMG_Utility_Custom_Combination_Lock_Key","ID=0:int,Message:int,Param=0:int,Sound_Name=null:string");
+ScriptRegistrant<JMG_Utility_Sync_String_With_Random_NumberPad_Control> JMG_Utility_Sync_String_With_Random_NumberPad_Control_Registrant("JMG_Utility_Sync_String_With_Random_NumberPad_Control","StringID:int,Delim=@:string");
+ScriptRegistrant<JMG_Utility_Created_Animate_SubObject_On_Join> JMG_Utility_Created_Animate_SubObject_On_Join_Registrant("JMG_Utility_Created_Animate_SubObject_On_Join","SubObject:string,Animation:string");
+ScriptRegistrant<JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete> JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete_Registrant("JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete","ReceivedCustom:int,Animation:string,StartFrame:float,EndFrame:float,ID:int,Custom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Created_Play_Locked_Infantry_Animation> JMG_Utility_Created_Play_Locked_Infantry_Animation_Registrant("JMG_Utility_Created_Play_Locked_Infantry_Animation","Animation:string");
+ScriptRegistrant<JMG_Utility_Custom_Send_Custom_To_Preset> JMG_Utility_Custom_Send_Custom_To_Preset_Registrant("JMG_Utility_Custom_Send_Custom_To_Preset","ReceivedCustom:int,Preset:string,Custom:int,Param:int,Delay:float");
+ScriptRegistrant<JMG_Utility_Custom_Talk> JMG_Utility_Custom_Talk_Registrant("JMG_Utility_Custom_Talk","Custom:int,StringID:int");
+ScriptRegistrant<JMG_Utility_Zone_Send_Custom_Enter_Preset> JMG_Utility_Zone_Send_Custom_Enter_Preset_Registrant("JMG_Utility_Zone_Send_Custom_Enter_Preset","Preset:string,ID=0:int,Custom=0:int,Param=0:int,Delay=0.0:float,PlayerType=2:int,TriggerOnce=1:int");
+ScriptRegistrant<JMG_Utility_Global_Armor_Controller> JMG_Utility_Global_Armor_Controller_Registrant("JMG_Utility_Global_Armor_Controller","DefaultMaxArmor:float,DefaultArmor:float");
+ScriptRegistrant<JMG_Utility_Global_Armor_Custom> JMG_Utility_Global_Armor_Custom_Registrant("JMG_Utility_Global_Armor_Custom","Custom:int,MaxArmor:float,Armor:float,UpdateAllObjects=1:int");
+ScriptRegistrant<JMG_Utility_Global_Armor_Object> JMG_Utility_Global_Armor_Object_Registrant("JMG_Utility_Global_Armor_Object","");
+ScriptRegistrant<JMG_Utility_Global_Health_Controller> JMG_Utility_Global_Health_Controller_Registrant("JMG_Utility_Global_Health_Controller","DefaultMaxHealth:float,DefaultHealth:float");
+ScriptRegistrant<JMG_Utility_Global_Health_Custom> JMG_Utility_Global_Health_Custom_Registrant("JMG_Utility_Global_Health_Custom","Custom:int,MaxHealth:float,Health:float,UpdateAllObjects=1:int");
+ScriptRegistrant<JMG_Utility_Global_Health_Object> JMG_Utility_Global_Health_Object_Registrant("JMG_Utility_Global_Health_Object","");
+ScriptRegistrant<JMG_Utility_Custom_Create_Random_Explosions> JMG_Utility_Custom_Create_Random_Explosions_Registrant("JMG_Utility_Custom_Create_Random_Explosions","Custom:int,ExplosionPreset:string,Count:int,MaxDistance:float,KillerID:int");
diff -urN sourceold/scripts/jmgUtility.h source/scripts/jmgUtility.h
--- sourceold/scripts/jmgUtility.h	2019-08-20 10:11:06.817000100 +1000
+++ source/scripts/jmgUtility.h	2020-09-10 09:08:27.972483200 +1000
@@ -157,6 +157,7 @@
 	void Poked(GameObject *obj, GameObject *poker);
 	void Timer_Expired(GameObject *obj,int number);
 };
+
 /*!
 * \brief Basic turret attach script, turrets match team of vehicle attached to, turrets are destroyed by destroy event
 * \Turret_Preset - Preset of the turret
@@ -860,6 +861,37 @@
 	}
 };
 
+/*!
+* \brief Creates a game object at the location of the objective's objective marker while active
+* \ObjectivePriority - Priorities of the objectives that this will create gameobjects for (-2 means ignore requirement)
+* \ObjectiveID - ID of the objective that this will create gameobjects for (-2 means ignore requirement)
+* \Preset - Name of the preset to create
+* \Attach - Should this be attached to the radar marker
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Objective_System_Objective_GameObject : public ScriptImpClass {
+	void Created(GameObject *obj);
+public:
+	int objectiveId;
+	int objectivePriority;
+	char preset[128];
+	bool attach;
+};
+/*!
+* \brief Attached by the objective controller so the code can remove the object when needed
+* \GameObjectID - ID of the gameobject that was created by the controller
+* \MarkerID - ID of the radar marker that this object is tracking
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Objective_System_Objective_GameObject_Tracker : public ScriptImpClass {
+	void Created(GameObject *obj);
+public:
+	int gameObjectId;
+	int markerId;
+};
+
 class NewObjectiveSystem
 {
 	bool objectiveStringIdsLoaded;
@@ -880,6 +912,7 @@
 public:
 	enum Priority{Undefined=-1,Unknown,Primary,Secondary,Tertiary,Quaternary,Quinary,Senary,Septenary,Octonary,Nonary,Denary,Bonus};
 	enum Status{Removed=-2,NotDefined=-1,Pending,Accomplished,Failed,Hidden};
+	int controllerId;
 private:
 	struct ObjectiveNode
 	{
@@ -976,12 +1009,12 @@
 			if (current->id == id)
 				if (current->active)
 				{
-					Destroy_Radar_Marker(Commands->Find_Object(radarMarkerId));
+					Destroy_Radar_Marker(current->radarMarkerId);
 					return false;
 				}
 				else
 				{
-					Destroy_Radar_Marker(Commands->Find_Object(current->radarMarkerId));
+					Destroy_Radar_Marker(current->radarMarkerId);
 					current->id = id;
 					current->priority = priority;
 					current->status = status;
@@ -1011,13 +1044,15 @@
 		}
 		return true;
 	}
-	void Destroy_Radar_Marker(GameObject *marker)
+	void Destroy_Radar_Marker(int markerId)
 	{
+		Destroy_Objective_GameObject(markerId);
+		GameObject *marker = Commands->Find_Object(markerId);
 		if (!marker)
 			return;
 		Commands->Destroy_Object(marker);
 	}
-	GameObject *Create_Radar_Marker(Vector3 pos, Priority priority,const char *modelOverride)
+	GameObject *Create_Radar_Marker(Vector3 pos, Priority priority,int objectiveId,const char *modelOverride)
 	{
 		GameObject *radarMarker = Commands->Create_Object("Daves Arrow",pos);
 		Commands->Set_Player_Type(radarMarker,team);
@@ -1037,6 +1072,7 @@
 			Commands->Set_Obj_Radar_Blip_Shape(radarMarker,RADAR_BLIP_SHAPE_OBJECTIVE);
 			Commands->Set_Obj_Radar_Blip_Color(radarMarker,priority == Primary ? RADAR_BLIP_COLOR_PRIMARY_OBJECTIVE : priority == Secondary ? RADAR_BLIP_COLOR_SECONDARY_OBJECTIVE : RADAR_BLIP_COLOR_TERTIARY_OBJECTIVE);
 		}
+		Create_Objective_GameObject(radarMarker,objectiveId,priority);
 		return radarMarker;
 	}
 public:
@@ -1068,15 +1104,15 @@
 	{
 		if (!blipUnit)
 			return false;
-		GameObject *radarMarker = Create_Radar_Marker(Vector3(),priority,modelOverride);
+		GameObject *radarMarker = Create_Radar_Marker(Commands->Get_Position(blipUnit),priority,objectiveId,modelOverride);
 		if (!radarMarker)
 			return false;
-		Commands->Attach_To_Object_Bone(radarMarker,blipUnit,"origin");
+		Commands->Attach_To_Object_Bone(radarMarker,blipUnit,blipUnit->As_SoldierGameObj() ? "c pelvis" : "origin");
 		return addObjective(objectiveId,priority,status,nameId,soundFilename,descriptionId,Commands->Get_ID(radarMarker),objectiveNumber);
 	}
 	bool Add_Objective(int objectiveId, Priority priority, Status status, unsigned long nameId, char *soundFilename, unsigned long descriptionId,Vector3 blipPosition,const char *modelOverride = NULL,int objectiveNumber = 0)
 	{
-		GameObject *radarMarker = Create_Radar_Marker(blipPosition,priority,modelOverride);
+		GameObject *radarMarker = Create_Radar_Marker(blipPosition,priority,objectiveId,modelOverride);
 		if (!radarMarker)
 			return false;
 		return addObjective(objectiveId,priority,status,nameId,soundFilename,descriptionId,Commands->Get_ID(radarMarker),objectiveNumber);
@@ -1109,11 +1145,11 @@
 		{
 			if (current->id == objectiveId)
 			{
-				Destroy_Radar_Marker(Commands->Find_Object(current->radarMarkerId));
-				GameObject *radarMarker = Create_Radar_Marker(Vector3(),current->priority,modelOverride);
+				Destroy_Radar_Marker(current->radarMarkerId);
+				GameObject *radarMarker = Create_Radar_Marker(Commands->Get_Position(blipUnit),current->priority,objectiveId,modelOverride);
 				if (!radarMarker)
 					return;
-				Commands->Attach_To_Object_Bone(radarMarker,blipUnit,"origin");
+				Commands->Attach_To_Object_Bone(radarMarker,blipUnit,blipUnit->As_SoldierGameObj() ? "c pelvis" : "origin");
 				current->radarMarkerId = Commands->Get_ID(radarMarker);
 				return;
 			}
@@ -1127,7 +1163,7 @@
 		{
 			if (current->id == objectiveId)
 			{
-				Destroy_Radar_Marker(Commands->Find_Object(current->radarMarkerId));
+				Destroy_Radar_Marker(current->radarMarkerId);
 				return;
 			}
 			current = current->next;
@@ -1140,8 +1176,8 @@
 		{
 			if (current->id == objectiveId)
 			{
-				Destroy_Radar_Marker(Commands->Find_Object(current->radarMarkerId));
-				GameObject *radarMarker = Create_Radar_Marker(blipPosition,current->priority,modelOverride);
+				Destroy_Radar_Marker(current->radarMarkerId);
+				GameObject *radarMarker = Create_Radar_Marker(blipPosition,current->priority,objectiveId,modelOverride);
 				if (!radarMarker)
 					return;
 				current->radarMarkerId = Commands->Get_ID(radarMarker);
@@ -1179,7 +1215,7 @@
 							selectMessageAndColor(formatObjectiveString(objectiveCancelledStringNumbered,objectivePrioritieStrings[current->priority],current->objectiveNumber),current->priority);
 						else
 							selectMessageAndColor(formatObjectiveString(objectiveCancelledString,objectivePrioritieStrings[current->priority]),current->priority);
-					Destroy_Radar_Marker(Commands->Find_Object(current->radarMarkerId));
+					Destroy_Radar_Marker(current->radarMarkerId);
 					current->active = false;
 					return true;
 				}
@@ -1373,6 +1409,46 @@
 			return Vector3(1.0f,1.0f,1.0f);
 		}
 	}
+	void Create_Objective_GameObject(GameObject *radarMarker,int objectiveId,int objectivePriority)
+	{
+		GameObject *obj = Commands->Find_Object(controllerId);
+		if (!obj)
+			return;
+		const SimpleDynVecClass<GameObjObserverClass *> & observer_list = obj->Get_Observers();
+		for(int index = 0;index < observer_list.Count();index++)
+			if (!_stricmp(observer_list[index]->Get_Name(),"JMG_Utility_Objective_System_Objective_GameObject"))
+			{
+				JMG_Utility_Objective_System_Objective_GameObject *script = (JMG_Utility_Objective_System_Objective_GameObject*)observer_list[index];
+				if (script && (script->objectiveId == objectiveId || script->objectiveId == -2) && (script->objectivePriority == objectivePriority || script->objectivePriority == -2))
+				{
+					GameObject *object = Commands->Create_Object(script->preset,Commands->Get_Position(radarMarker));
+					if (script->attach)
+						Commands->Attach_To_Object_Bone(object,radarMarker,"origin");
+					char params[128];
+					sprintf(params,"%d,%d",Commands->Get_ID(object),Commands->Get_ID(radarMarker));
+					Commands->Attach_Script(obj,"JMG_Utility_Objective_System_Objective_GameObject_Tracker",params);
+				}
+			}
+	}
+	void Destroy_Objective_GameObject(int markerId)
+	{
+		GameObject *obj = Commands->Find_Object(controllerId);
+		if (!obj)
+			return;
+		const SimpleDynVecClass<GameObjObserverClass *> & observer_list = obj->Get_Observers();
+		for(int index = 0;index < observer_list.Count();index++)
+			if (!_stricmp(observer_list[index]->Get_Name(),"JMG_Utility_Objective_System_Objective_GameObject_Tracker"))
+			{
+				JMG_Utility_Objective_System_Objective_GameObject_Tracker *script = (JMG_Utility_Objective_System_Objective_GameObject_Tracker*)observer_list[index];
+				if (script && script->markerId == markerId)
+				{
+					GameObject *object = Commands->Find_Object(script->gameObjectId);
+					if (object)
+						Commands->Destroy_Object(object);
+					script->Destroy_Script();
+				}
+			}
+	}
 };
 
 class ClientNetworkObjectPositionSync
@@ -1494,6 +1570,393 @@
 	}
 };
 
+class Rp2SimplePositionSystem
+{
+public:
+	struct SimplePositionNode
+	{
+		int id;
+		float facing;
+		Vector3 position;
+		int value;
+		struct SimplePositionNode *next;
+		SimplePositionNode(GameObject *obj,int value = 0)
+		{
+			this->id = Commands->Get_ID(obj);
+			this->facing = Commands->Get_Facing(obj);
+			this->position = Commands->Get_Position(obj);
+			this->value = value;
+			this->next = NULL;
+		}
+	};
+	SimplePositionNode *SimplePositionNodeList;
+	int ObjectCount;
+	Rp2SimplePositionSystem()
+	{
+		ObjectCount = 0;
+		SimplePositionNodeList = NULL;
+	}
+	Rp2SimplePositionSystem &operator += (GameObject *obj)
+	{
+		int id = Commands->Get_ID(obj);
+		SimplePositionNode *Current = SimplePositionNodeList;
+		if (!SimplePositionNodeList)
+			SimplePositionNodeList = new SimplePositionNode(obj);
+		while (Current)
+		{
+			if (Current->id == id)
+				return *this;
+			if (!Current->next)
+			{
+				Current->next = new SimplePositionNode(obj);
+				break;
+			}
+			Current = Current->next;
+		}
+		ObjectCount++;
+		return *this;
+	};
+	Rp2SimplePositionSystem &operator += (SimplePositionNode *node)
+	{
+		SimplePositionNode *Current = SimplePositionNodeList;
+		if (!SimplePositionNodeList)
+			SimplePositionNodeList = node;
+		while (Current)
+		{
+			if (Current->id == node->id)
+				return *this;
+			if (!Current->next)
+			{
+				Current->next = node;
+				break;
+			}
+			Current = Current->next;
+		}
+		ObjectCount++;
+		return *this;
+	};
+	void Empty_List()
+	{
+		ObjectCount = 0;
+		SimplePositionNode *temp,*die;
+		temp = SimplePositionNodeList;
+		while (temp)
+		{
+			die = temp;
+			temp = temp->next;
+			delete die;
+		}
+		SimplePositionNodeList = NULL;
+	}
+	SimplePositionNode *GetRandomFromGroup(int value)
+	{
+		int random = Commands->Get_Random_Int(0,ObjectCount*2),original;
+		original = random;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (random && value == Current->value)
+				random--;
+			if (!random && value == Current->value)
+				return Current;
+			Current = Current->next;
+			if (!Current && original != random && original)
+				Current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetNextFromGroup(int value,SimplePositionNode *last)
+	{
+		bool found = false;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (found && value == Current->value)
+				return Current;
+			if (Current == last && value == Current->value)
+				found = true;
+			Current = Current->next;
+			if (!Current)
+				Current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetRandom()
+	{
+		int random = Commands->Get_Random_Int(0,ObjectCount*2);
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (random)
+				random--;
+			if (!random)
+				return Current;
+			Current = Current->next;
+			if (!Current)
+				Current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetNearest(Vector3 pos)
+	{
+		float LongestDistance = 0;
+		SimplePositionNode *TempObject = NULL;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			float Temp = JmgUtility::SimpleDistance(Current->position,pos);
+			if (!TempObject || Temp < LongestDistance)
+			{
+				TempObject = Current;
+				LongestDistance = Temp;
+			}
+			Current = Current->next;
+		}
+		return TempObject;
+	}
+	SimplePositionNode *GetNearestFromGroup(int groupId,Vector3 pos)
+	{
+		float LongestDistance = 0;
+		SimplePositionNode *TempObject = NULL;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (Current->value == groupId)
+			{
+				float Temp = JmgUtility::SimpleDistance(Current->position,pos);
+				if (!TempObject || Temp < LongestDistance)
+				{
+					TempObject = Current;
+					LongestDistance = Temp;
+				}
+			}
+			Current = Current->next;
+		}
+		return TempObject;
+	}
+	Vector3 GetNearestVector(Vector3 pos)
+	{
+		SimplePositionNode *TempObject = GetNearest(pos);
+		if (TempObject)
+			return TempObject->position;
+		return Vector3();
+	}
+	SimplePositionNode *GetNearestFlat(Vector3 pos)
+	{
+		float LongestDistance = 0;
+		SimplePositionNode *TempObject = NULL;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			float Temp = JmgUtility::SimpleFlatDistance(Current->position,pos);
+			if (!TempObject || Temp < LongestDistance)
+			{
+				TempObject = Current;
+				LongestDistance = Temp;
+			}
+			Current = Current->next;
+		}
+		return TempObject;
+	}
+	SimplePositionNode *GetRandom(int minVal)
+	{
+		int repeatLimit = ObjectCount;
+		int random = Commands->Get_Random_Int(0,ObjectCount*2);
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (random && Current->value <= minVal)
+				random--;
+			if (!random && Current->value <= minVal)
+				return Current;
+			Current = Current->next;
+			if (!Current && repeatLimit)
+			{
+				repeatLimit--;
+				Current = SimplePositionNodeList;
+			}
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetRandomExcluding(SimplePositionNode *node)
+	{
+		int repeatLimit = ObjectCount;
+		int random = Commands->Get_Random_Int(0,ObjectCount*2);
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (random)
+				random--;
+			if (!random && node != Current)
+				return Current;
+			Current = Current->next;
+			if (!Current && repeatLimit)
+			{
+				repeatLimit--;
+				Current = SimplePositionNodeList;
+			}
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetRandomLowestValue()
+	{
+		int lowest = -1;
+		SimplePositionNode *current = SimplePositionNodeList;
+		while (current)
+		{
+			if (lowest == -1 || current->value <= lowest)
+				lowest = current->value;
+			current = current->next;
+		}
+		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+		int originalRandom = random;
+		current = SimplePositionNodeList;
+		while (current)
+		{
+			if (random && current->value == lowest)
+				random--;
+			if (!random)
+				return current;
+			current = current->next;
+			if (!current && originalRandom != random)
+				current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetRandomOutsideOfRange(float range,Vector3 pos)
+	{
+		range *= range;
+		SimplePositionNode *current = SimplePositionNodeList;
+		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+		int originalRandom = random;
+		while (current)
+		{
+			if (JmgUtility::SimpleDistance(current->position,pos) > range && random)
+				random--;
+			if (!random)
+				return current;
+			current = current->next;
+			if (!current && originalRandom != random)
+				current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetRandomOutsideOfRangeInGroup(int value,float range,Vector3 pos)
+	{
+		range *= range;
+		SimplePositionNode *current = SimplePositionNodeList;
+		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+		int originalRandom = random;
+		while (current)
+		{
+			if (current->value == value && JmgUtility::SimpleDistance(current->position,pos) > range && random)
+				random--;
+			if (current->value == value && !random)
+				return current;
+			current = current->next;
+			if (!current && originalRandom != random)
+				current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetFurthestInGroup(int value,Vector3 pos)
+	{
+		float LongestDistance = 0;
+		SimplePositionNode *TempObject = NULL;
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (Current->value == value)
+			{
+				float Temp = JmgUtility::SimpleDistance(Current->position,pos);
+				if (!TempObject || Temp > LongestDistance)
+				{
+					TempObject = Current;
+					LongestDistance = Temp;
+				}
+			}
+			Current = Current->next;
+		}
+		return TempObject;
+	}
+	SimplePositionNode *GetRandomInsideOfRangeInGroup(int value,float range,Vector3 pos)
+	{
+		range *= range;
+		SimplePositionNode *current = SimplePositionNodeList;
+		int random = Commands->Get_Random_Int(0,ObjectCount*2)+1;
+		int originalRandom = random;
+		while (current)
+		{
+			if (current->value == value && JmgUtility::SimpleDistance(current->position,pos) <= range && random)
+				random--;
+			if (value && !random)
+				return current;
+			current = current->next;
+			if (!current && originalRandom != random)
+				current = SimplePositionNodeList;
+		}
+		return NULL;
+	}
+	SimplePositionNode *GetLowestValueFurthestFromSpot(Vector3 pos)
+	{
+		int lowest = -1;
+		SimplePositionNode *current = SimplePositionNodeList,*bestNode = NULL;
+		while (current)
+		{
+			if (lowest == -1 || current->value <= lowest)
+				lowest = current->value;
+			current = current->next;
+		}
+
+		current = SimplePositionNodeList;
+		float dist = 0;
+		while (current)
+		{
+			float tempDist = JmgUtility::SimpleDistance(pos,current->position);
+			if (!bestNode || tempDist > dist)
+			{
+				bestNode = current;
+				dist = tempDist;
+			}
+			current = current->next;
+		}
+		return bestNode;
+	}
+	SimplePositionNode *GetSpotNotVisibileFromSpot(Vector3 pos);
+	SimplePositionNode *GetNonVisibleSpotFromPlayers(int value);
+	SimplePositionNode *GetFurthestSpotFromPlayers(int value)
+	{
+		SimplePositionNode *current = SimplePositionNodeList,*bestNode = NULL;
+		float dist = 0;
+		while (current)
+		{
+			if (current->value == value)
+			{
+				GameObject *player = Commands->Get_A_Star(current->position);
+				if (!player)
+					continue;
+				float tempDist = JmgUtility::SimpleDistance(Commands->Get_Position(player),current->position);
+				if (!bestNode || tempDist > dist)
+				{
+					bestNode = current;
+					dist = tempDist;
+				}
+			}
+			current = current->next;
+		}
+		return bestNode;
+	}
+	void DecreaseValue()
+	{
+		SimplePositionNode *Current = SimplePositionNodeList;
+		while (Current)
+		{
+			if (Current->value)
+				Current->value--;
+			Current = Current->next;
+		}
+	}
+};
 /*!
 * \brief An object that will have its position synced by JMG_Utility_Sync_System_Controller
 * \author jgray
@@ -1811,7 +2274,9 @@
 };
 
 class JMG_Utility_Dynamic_Clock_Object : public ScriptImpClass {
+	int animSynced[128];
 	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
 	void Destroyed(GameObject *obj);
 };
 
@@ -2587,6 +3052,7 @@
 	AggressiveAttackSpotSystem::AggressiveAttackSpotNode *node;
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj,int number);
+	void Killed(GameObject *obj,GameObject *killer);
 	void Destroyed(GameObject *obj);
 public:
 	JMG_Utility_AI_Aggressive_Attack_Spot_Point()
@@ -3067,6 +3533,8 @@
 * \ArmorPerPlayer - Amount of armor per player to regen (amount*player count)
 * \Rate - Speed to regen
 * \DamageDelay - Amount of time regen must wait to start again after taking damage
+* \ScaleHealthPerHeal - With each heal this value is added to HealthAmount, if the value goes negative it stops healing (value is multiplied by 0.00001)
+* \ScaleArmorPerHeal - With each heal this value is added to ArmorAmount, if the value goes negative it stops healing (value is multiplied by 0.00001)
 * \author jgray
 * \ingroup JmgUtility
 */
@@ -3081,9 +3549,12 @@
 	float rate;
 	float damageDelay;
 	bool enabled;
+	float scaleHealthPerHeal;
+	float scaleArmorPerHeal;
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj,int number);
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
+	float ScaleValue(float value,float scale);
 };
 
 /*!
@@ -3495,7 +3966,6 @@
 	bool Grab_Teleport_Spot(GameObject *enter,int attempts);
 };
 
-
 /*!
 * \brief Used by JMG_Utility_Zone_Teleport_To_Random_Wander_Point
 * \author jgray
@@ -3551,6 +4021,7 @@
 * \PresetName - Name of the preset to hunt down and kill
 * \Position - Position to search near
 * \MaxDistance - Max distance to scan, 0 = infinite
+* \TriggerOnce - If non-zero the script can only fire once then removes itself
 * \author jgray
 * \ingroup JmgUtility
 */
@@ -4999,6 +5470,7 @@
 	void Damaged(GameObject *obj,GameObject *damager,float damage);
 	void Poked(GameObject *obj, GameObject *poker);
 	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason reason);
+	void Killed(GameObject *obj, GameObject *killer);
 	void Destroyed(GameObject *obj);
 	void Detach(GameObject *obj);
 	void AttackTarget(GameObject *obj,GameObject *follow,GameObject *target,Vector3 location,float speed,float distance);
@@ -8029,4 +8501,1828 @@
 	bool triggerOnce;
 	void Created(GameObject *obj);
 	void Timer_Expired(GameObject *obj,int number);
+};
+/*!
+* \brief Plays an animation on the object that enters the zone, the object is also attached to another object (on the "attach" bone) which can be animated to do relocations
+* \PresetName - Preset required to trigger the script
+* \Animation - Animation to play on the object that enters 
+* \AttachedModel - Model to use for the object the enterer is attached to
+* \AttachedAnimation - Animation to play to relocate the entering object
+* \Custom - Custom to send to self on animation completion
+* \Param - Param to send to self on animation completion 
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Enter_Play_Animation_And_Relocate : public ScriptImpClass {
+	char animation[32];
+	char attachedModel[16];
+	char attachedAnimation[32];
+	char presetName[128];
+	float zoneRotation;
+	bool centerToZone;
+	int custom;
+	int customParam;
+	void Created(GameObject *obj);
+	void Entered(GameObject *obj,GameObject *enterer);
+};
+
+/*!
+* \brief Attached by JMG_Utility_Enter_Play_Animation_And_Relocate, not designed to be used directly
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Enter_Play_Animation_And_Relocate_Attached : public ScriptImpClass {
+	int holderId;
+	Vector3 offset;
+	void Created(GameObject *obj);
+	void Animation_Complete(GameObject *obj,const char *anim);
+	void Destroyed(GameObject *obj);
+};
+
+/*!
+* \brief Destroys the closest preset to the attached object (works with powerups and other "dumb" objects)
+* \Custom - Custom to trigger the script
+* \PresetName - Name of the preset to hunt down and kill
+* \MaxDistance - Max distance to scan, 0 = infinite
+* \TriggerOnce - If non-zero the script can only fire once then removes itself
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Destroy_Closest_Object_To_Self : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Applys damage to the closest preset to the attached object (works with powerups and other "dumb" objects, sender is the damager)
+* \Custom - Custom to trigger the script
+* \PresetName - Name of the preset to hunt down and kill
+* \Warhead - Warhead to use
+* \DamageAmount - Amount of damage to apply
+* \MaxDistance - Max distance to scan, 0 = infinite
+* \TriggerOnce - If non-zero the script can only fire once then removes itself
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Apply_Damage_Closest_Object_To_Self : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+
+/*!
+* \brief Applys damage to the closest preset to the attached object (works with powerups and other "dumb" objects, sender is the damager)
+* \Custom - Custom to trigger the script
+* \ID - ID of the building to restore, 0 restores attached, -1 restores sender
+* \TriggerOnce - Only allows this script to fire once
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Restore_Building : public ScriptImpClass {
+	int id;
+	int custom;
+	bool triggerOnce;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Basically exactly the same as JMG_Utility_Timer_Custom but with a random delay
+* \Time - delay to send the custom
+* \Random - Amount to add or subtract on each run of the timer from the base time
+* \ID - Id of the object to send the custom to, 0 sends to itself
+* \Message - Custom to send
+* \Param - parameter to send with the custom
+* \Repeat - 1 to make it trigger again
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Timer_Custom_Random : public ScriptImpClass {
+	int id;
+	int message;
+	int param;
+	bool repeat;
+	float time;
+	float random;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+class DynamicAttachScript
+{
+public:
+	class DynamicAttachScriptParams
+	{
+	public:
+		struct DynamicAttachScriptParamNode
+		{
+			int index;
+			char param[512];
+			struct DynamicAttachScriptParamNode *next;
+			DynamicAttachScriptParamNode(int index,const char *param)
+			{
+				this->index = index;
+				sprintf(this->param,"%s",param);
+				next = NULL;
+			}
+		};
+	private:
+		DynamicAttachScriptParamNode *DynamicAttachScriptParamNodeList;
+	public:
+		int objectCount;
+		DynamicAttachScriptParams()
+		{
+			objectCount = 0;
+			DynamicAttachScriptParamNodeList = NULL;
+		}
+		DynamicAttachScriptParamNode *AddOrUpdateParam(int index,const char *param)
+		{
+			DynamicAttachScriptParamNode *current = DynamicAttachScriptParamNodeList;
+			if (!DynamicAttachScriptParamNodeList)
+			{
+				objectCount++;
+				return (DynamicAttachScriptParamNodeList = new DynamicAttachScriptParamNode(index,param));
+			}
+			while (current)
+			{
+				if (current->index == index)
+				{
+					sprintf(current->param,"%s",param);
+					return current;
+				}
+				if (!current->next)
+				{
+					objectCount++;
+					return (current->next = new DynamicAttachScriptParamNode(index,param));
+				}
+				current = current->next;
+			}
+			return NULL;
+		};
+		DynamicAttachScriptParamNode *FindParam(int index)
+		{
+			DynamicAttachScriptParamNode *current = DynamicAttachScriptParamNodeList;
+			while (current)
+			{
+				if (current->index == index)
+					return current;
+				current = current->next;
+			}
+			return NULL;
+		};
+		void EmptyList()
+		{
+			objectCount = 0;
+			DynamicAttachScriptParamNode *temp = DynamicAttachScriptParamNodeList,*die;
+			while (temp)
+			{
+				die = temp;
+				temp = temp->next;
+				delete die;
+			}
+			DynamicAttachScriptParamNodeList = NULL;
+		}
+	};
+	struct DynamicAttachScriptNode
+	{
+		int scriptId;
+		char scriptName[512];
+		DynamicAttachScriptParams dynamicAttachScriptParams;
+		struct DynamicAttachScriptNode *next;
+		DynamicAttachScriptNode(int scriptId,const char *scriptName)
+		{
+			this->scriptId = scriptId;
+			sprintf(this->scriptName,"%s",scriptName);
+			next = NULL;
+		}
+	};
+private:
+	DynamicAttachScriptNode *DynamicAttachScriptNodeList;
+public:
+	DynamicAttachScript()
+	{
+		DynamicAttachScriptNodeList = NULL;
+	}
+	DynamicAttachScriptNode *AddDynamicScript(int scriptId,const char *scriptName)
+	{
+		DynamicAttachScriptNode *current = DynamicAttachScriptNodeList;
+		if (!DynamicAttachScriptNodeList)
+		{
+			DynamicAttachScriptNodeList = new DynamicAttachScriptNode(scriptId,scriptName);
+			return DynamicAttachScriptNodeList;
+		}
+		while (current)
+		{
+			if (current->scriptId == scriptId)
+			{
+				char errorMsg[220];
+				sprintf(errorMsg,"msg JMG_Utility_Dynamic_Script_Add_Parameter ERROR: ScriptID %d already exists!",scriptId);
+				Console_Input(errorMsg);
+				return current;
+			}
+			if (!current->next)
+			{
+				current->next = new DynamicAttachScriptNode(scriptId,scriptName);
+				return current->next;
+			}
+			current = current->next;
+		}
+		return NULL;
+	};
+	DynamicAttachScriptNode *FindDynamicScript(int scriptId)
+	{
+		DynamicAttachScriptNode *current = DynamicAttachScriptNodeList;
+		while (current)
+		{
+			if (current->scriptId == scriptId)
+				return current;
+			current = current->next;
+		}
+		char errorMsg[220];
+		sprintf(errorMsg,"msg JMG_Utility_Dynamic_Script_Add_Parameter ERROR: ScriptID %d doesn't exist!",scriptId);
+		Console_Input(errorMsg);
+		return NULL;
+	};
+	void AddParamToScript(int scriptId,int index,const char *param)
+	{
+		DynamicAttachScriptNode *current = DynamicAttachScriptNodeList;
+		while (current)
+		{
+			if (current->scriptId == scriptId)
+			{
+				current->dynamicAttachScriptParams.AddOrUpdateParam(index,param);
+				return;
+			}
+		}
+		char errorMsg[220];
+		sprintf(errorMsg,"msg JMG_Utility_Dynamic_Script_Add_Parameter ERROR: ScriptID %d doesn't exist!",scriptId);
+		Console_Input(errorMsg);
+	}
+	void EmptyList()
+	{
+		DynamicAttachScriptNode *temp = DynamicAttachScriptNodeList,*die;
+		while (temp)
+		{
+			die = temp;
+			temp = temp->next;
+			die->dynamicAttachScriptParams.EmptyList();
+			delete die;
+		}
+		DynamicAttachScriptNodeList = NULL;
+	}
+};
+
+/*!
+* \brief Used for cleanup after the level completes, place on an object that will exist the whole level
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Dynamic_Script_Controller : public ScriptImpClass {
+	void Destroyed(GameObject *obj);
+public:
+	static DynamicAttachScript dynamicAttachScript;
+};
+
+/*!
+* \brief Setup a dynamic script identity
+* \ScriptID - ID that this script is stored under, used to look up the definition when attaching the script to an object or giving it params
+* \ScriptName - Name of the script that will be attached to the object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Dynamic_Script_Definition : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Adds (or updates) a parameter on the specified script ID on create (there is a 0.1 second delay after script has been created, this is to make sure it happens after the script definition is created)
+* \ScriptID - ID that this script is stored under, used to look up the definition when attaching the script to an object or giving it params
+* \Index - Index of the parameter, if the index matches the index of a parameter on the script the param will be updated
+* \Param - Parameter for the index
+* \Delim - Use this in place of ,'s (that way you can have multiple parameters under one parameter as a time saver)
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter : public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Same as JMG_Utility_Dynamic_Script_Created_Add_Update_Parameter but triggers on custom
+* \Custom - Custom to trigger the add/update on
+* \ScriptID - ID that this script is stored under, used to look up the definition when attaching the script to an object or giving it params
+* \Index - Index of the parameter, if the index matches the index of a parameter on the script the param will be updated
+* \Param - Parameter for the index
+* \Delim - Use this in place of ,'s (that way you can have multiple parameters under one parameter as a time saver)
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Dynamic_Script_Custom_Add_Update_Parameter : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Attaches the script specified by ID and the currently defined parameters
+* \Custom - Custom to trigger the add/update on
+* \ScriptID - ID that this script is stored under, used to look up the definition when attaching the script to an object or giving it params
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Dynamic_Script_Custom_Attach : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Works just like JMG_Utility_Objective_System_Objective_Update_Custom except sends a custom if the objective being added doesn't exist yet
+* \Custom - custom to trigger on
+* \NewObjectiveID - ID of the new objective to add
+* \NewObjectiveStringID - Strings.tbl id for the objective message
+* \ObjectiveMarkerObjectID - ID of the object where the marker should be displayed (-1 sets this object that picked it up as the marker)
+* \CompleteObjectiveID - Objective to complete when this is trigger
+* \Delay - Delay for Objective before adding the new one
+* \NewObjectivePriority - Priority of the new objective {Undefined=-1,Unknown = 0,Primary = 1,Secondary = 2,Tertiary = 3,Quaternary = 4,Quinary = 5,Senary = 6,Septenary = 7,Octonary = 8,Nonary = 9,Denary = 10,Bonus = 11}
+* \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)
+* \CustomDelay - How long to delay the send
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Objective_System_Custom_Add_Objective_Send_Custom : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom on a custom if the specified objective is matching the defined status
+* \Custom - Custom to watch for
+* \ObjectiveID - Objective that must be pending in order to send the custom
+* \Status - Status objective must be in (Removed=-2 NotDefined=-1 Pending=0 Accomplished=1 Failed=2 Hidden=3
+* \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_Objective_System_Custom_Send_Custom_Status : public ScriptImpClass {
+	int objectiveId;
+	int status;
+	int recieveMessage;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Same as JMG_Utility_Objective_System_Custom_Send_Custom_Pending but only sends if the objective is removed or not defined
+* \Custom - Custom to watch for
+* \ObjectiveID - Objective that must be removed or not defined in order to send the custom
+* \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_Objective_System_Custom_Send_Custom_Does_Not_Exist : public ScriptImpClass {
+	int objectiveId;
+	int recieveMessage;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Chooses an non-existant or removed objective from the provided list to send as a custom
+* \Custom - Custom to watch for
+* \ObjectiveID0-9 - Objective choices 0 - 9, -999 means not a choice
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \Param - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Objective_System_Send_Random_Objective_As_Custom : public ScriptImpClass {
+	int objectiveId[10];
+	int recieveMessage;
+	int id;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom on a custom if the specified objective is not matching the defined status
+* \Custom - Custom to watch for
+* \ObjectiveID - Objective that must be pending in order to send the custom
+* \Status - Status objective must be in (Removed=-2 NotDefined=-1 Pending=0 Accomplished=1 Failed=2 Hidden=3
+* \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_Objective_System_Custom_Send_Custom_Not_Status : public ScriptImpClass {
+	int objectiveId;
+	int status;
+	int recieveMessage;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+class GlobalKeycardSystem
+{
+private:
+	struct GlobalKeycardNode
+	{
+		int keycardId;
+		int groupId;
+		struct GlobalKeycardNode *next;
+		GlobalKeycardNode(int keycardId,int groupId)
+		{
+			this->keycardId = keycardId;
+			this->groupId = groupId;
+			this->next = NULL;
+		}
+	};
+	GlobalKeycardNode *globalKeycardNodeList;
+	void AddKeycardToPlayerObjects(int keycardId,int groupId);
+	void RemoveKeycardFromPlayerObjects(int keycardId,int groupId);
+public:
+	GlobalKeycardSystem()
+	{
+		globalKeycardNodeList = NULL;
+	}
+	GlobalKeycardNode *AddKeycard(int keycardId,int groupId)
+	{
+		GlobalKeycardNode *current = globalKeycardNodeList;
+		if (!globalKeycardNodeList)
+		{
+			AddKeycardToPlayerObjects(keycardId,groupId);
+			return (globalKeycardNodeList = new GlobalKeycardNode(keycardId,groupId));
+		}
+		while (current)
+		{
+			if (current->keycardId == keycardId && current->groupId == groupId)
+				return current;
+			if (!current->keycardId)
+			{
+				AddKeycardToPlayerObjects(keycardId,groupId);
+				current->keycardId = keycardId;
+				return current;
+			}
+			if (!current->next)
+			{
+				AddKeycardToPlayerObjects(keycardId,groupId);
+				current->next = new GlobalKeycardNode(keycardId,groupId);
+				return current;
+			}
+			current = current->next;
+		}
+		return NULL;
+	}
+	void RemoveKeycard(int keycardId,int groupId)
+	{
+		if (!globalKeycardNodeList)
+			return;
+		GlobalKeycardNode *current = globalKeycardNodeList;
+		while (current)
+		{
+			if (current->keycardId == keycardId && current->groupId == groupId)
+			{
+				RemoveKeycardFromPlayerObjects(keycardId,groupId);
+				current->keycardId = 0;
+				break;
+			}
+			current = current->next;
+		}
+	}
+	void emptyList()
+	{
+		GlobalKeycardNode *temp = globalKeycardNodeList,*die;
+		while (temp)
+		{
+			RemoveKeycardFromPlayerObjects(temp->keycardId,temp->groupId);
+			die = temp;
+			temp = temp->next;
+			delete die;
+		}
+		globalKeycardNodeList = NULL;
+	}
+	void GrantKeycards(GameObject *obj,int groupId)
+	{
+		GlobalKeycardNode *current = globalKeycardNodeList;
+		while (current)
+		{
+			if (current->keycardId && current->groupId == groupId)
+				Commands->Grant_Key(obj,current->keycardId,true);
+			current = current->next;
+		}
+	}
+};
+
+/*!
+* \brief Controller for the keycard system (Grants Renegade keycards to soldiers that have the soldier scirpt on them (probably works with vehicles but havne't tested how keycards and vehicles interact)
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Keycard_System_Controller : public ScriptImpClass {
+	void Destroyed(GameObject *obj);
+};
+
+/*!
+* \brief Adds a keycard to the list of keycards to be granted to any global keycard soldier (also grants to any soldier already on the field)
+* \KeycardID - ID of the keycard to add
+* \GroupID - ID of the group this keycard is for
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Keycard_System_Created_Add_Keycard : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Adds a keycard to the list of keycards to be granted to any global keycard soldier (also grants to any soldier already on the field)
+* \Custom - Custom needed to trigger script
+* \KeycardID - ID of the keycard to add
+* \GroupID - ID of the group this keycard is for
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Keycard_System_Custom_Add_Keycard : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Removes a keycard to the list of keycards to be granted to any global keycard soldier (also removes from any soldier already on the field)
+* \Custom - Custom needed to trigger script
+* \KeycardID - ID of the keycard to removes
+* \GroupID - ID of the group this keycard is for
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Keycard_System_Custom_Remove_Keycard : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief The attached soldier will get and lose keycards whenever the global keycard system is updated or on spawn, only attach one of these per soldier
+* \GroupID - Group that this soldier belongs to for granting keycards
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Keycard_System_Soldier : public ScriptImpClass {
+	void Created(GameObject *obj);
+public:
+	int groupId;
+};
+
+/*!
+* \brief Sends a custom when the zone becomes occupied by a player and then sends a custom again when its vacant 
+* \OccupiedID - ID of the object to send to, -1 sends to the player that triggered, 0 sends to itself
+* \OccupiedCustom - Custom to send when occupied 
+* \OccupiedParam - Param to send when occupied
+* \OccupiedDelay - Delay for the occupied custom
+* \VacantID - ID of the object to send to, -1 sends to the player that triggered, 0 sends to itself
+* \VacantCustom - Custom to send when vacant
+* \VacantParam - Param to send when vacant
+* \VacantDelay - Delay for the vacant custom
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change : public ScriptImpClass {
+	int occupiedId;
+	int occupiedCustom;
+	int occupiedParam;
+	float occupiedDelay;
+	int vacantId;
+	int vacantCustom;
+	int vacantParam;
+	float vacantDelay;
+	bool inZone[128];
+	int count;
+	void Created(GameObject *obj);
+	void Entered(GameObject *obj,GameObject *enterer);
+public:
+	JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change()
+	{
+		for (int x = 0;x < 128;x++)
+			inZone[x] = false;
+		count = 0;
+	}
+	void Exited(GameObject *obj,GameObject *exiter);
+};
+
+/*!
+* \brief Attached by JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Send_Custom_On_Player_Occupation_Change_Attached : public ScriptImpClass {
+	void Destroyed(GameObject *obj);
+};
+
+/*!
+* \brief Controls setup and cleanup for the Silent countdown
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Silent_Countdown_Controller : public ScriptImpClass {
+public:
+	struct SendCustomOnSecondNode
+	{
+		int triggerSecond;
+		int id;
+		int custom;
+		int param;
+		float delay;
+		struct SendCustomOnSecondNode *next;
+		SendCustomOnSecondNode(int triggerSecond,int id,int custom,int param,float delay)
+		{
+			this->triggerSecond = triggerSecond;
+			this->id = id;
+			this->custom = custom;
+			this->param = param;
+			this->delay = delay;
+			this->next = NULL;
+		}
+	};
+	struct SendCustomIdNode
+	{
+		int countdownId;
+		struct SendCustomOnSecondNode *nodes;
+		struct SendCustomIdNode *next;
+		SendCustomIdNode(int countdownId)
+		{
+			this->countdownId = countdownId;
+			this->nodes = NULL;
+			this->next = NULL;
+		}
+		void Empty()
+		{
+			SendCustomOnSecondNode *temp = nodes,*die;
+			while (temp)
+			{
+				die = temp;
+				temp = temp->next;
+				delete die;
+			}
+			nodes = NULL;
+		}
+	};
+	static SendCustomIdNode *sendCustomIdNodeList;
+	static SendCustomIdNode *FindOrCreateCountdownId(int countdownId)
+	{
+		SendCustomIdNode *current = sendCustomIdNodeList;
+		if (!sendCustomIdNodeList)
+			return (sendCustomIdNodeList = new SendCustomIdNode(countdownId));
+		while (current)
+		{
+			if (current->countdownId == countdownId)
+				return current;
+			if (!current->next)
+				return (current->next = new SendCustomIdNode(countdownId));
+			current = current->next;
+		}
+		return NULL;
+	}
+private:
+	void Created(GameObject *obj);
+	void Destroyed(GameObject *obj);
+public:
+	static bool controllerPlaced;
+	JMG_Utility_Silent_Countdown_Controller()
+	{
+		controllerPlaced = false;
+	}
+	static void AddSecondNode(int countdownId,int triggerSecond,int id,int custom,int param,float delay)
+	{
+		SendCustomIdNode *baseNode = FindOrCreateCountdownId(countdownId);
+		SendCustomOnSecondNode *current = baseNode->nodes;
+		if (!baseNode->nodes)
+			baseNode->nodes = new SendCustomOnSecondNode(triggerSecond,id,custom,param,delay);
+		while (current)
+		{
+			if (triggerSecond == current->triggerSecond && id == current->id && custom == current->custom && param == current->param)
+			{
+				Console_Input("msg ERROR: A custom for this trigger second already exists!");
+				return;
+			}
+			if (!current->next)
+			{
+				current->next = new SendCustomOnSecondNode(triggerSecond,id,custom,param,delay);
+				return;
+			}
+			current = current->next;
+		}
+	}
+	static void NodeSendCustom(GameObject *obj,SendCustomIdNode *countdownNode,int second)
+	{
+		if (!countdownNode)
+			return;
+		SendCustomOnSecondNode *current = countdownNode->nodes;
+		while (current)
+		{
+			if (current->triggerSecond == second)
+				Commands->Send_Custom_Event(obj,Commands->Find_Object(current->id),current->custom,current->param,current->delay);
+			current = current->next;
+		}
+	}
+	void EmptyList()
+	{
+		controllerPlaced = false;
+		SendCustomIdNode *temp = sendCustomIdNodeList,*die;
+		while (temp)
+		{
+			temp->Empty();
+			die = temp;
+			temp = temp->next;
+			delete die;
+		}
+		sendCustomIdNodeList = NULL;
+	}
+};
+/*!
+* \brief Sends a custom when the time on JMG_Utility_Silent_Countdown matches the defined seconds on this script
+* \TimerID - The ID of the timer
+* \Time - How long in seconds the timer counts down
+* \StartCustom - Custom to start or resume the countdown
+* \PauseCustom - Custom to pause the countdown
+* \CancelCustom - Resets the countdown back to its original time and pauses it
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Silent_Countdown_Timer : public ScriptImpClass {
+	bool paused;
+	int startCustom;
+	int pausedCustom;
+	int cancelCustom;
+	int time;
+	JMG_Utility_Silent_Countdown_Controller::SendCustomIdNode *sendCustomIdNode;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sends a custom when the time on JMG_Utility_Silent_Countdown matches the defined seconds on this script
+* \TimerID - ID of the timer this belongs to
+* \TriggerTime - Time in which to send the custom
+* \ID - ID to send the custom to, if 0 it sends it to the object this script is attached too
+* \Custom - Custom to send
+* \Param - Param to send
+* \Delay - Delay to wait before sending the message
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Silent_Countdown_Send_Custom : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Teleports the object that enters the zone to a random wander point furthest from a boss object
+* \WanderingAIGroupID - Group of points to teleport to
+* \SafeTeleportDistance - How far can infantry be moved if the spot is blocked
+* \ChangeGroupIDCustom - Changes the wander point group id to a new one, uses the parameter on the custom sent in
+* \PlayerType - Playertype the zone triggers for
+* \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)
+* \AiOnly - Only trigger for AI
+* \FaceBoss - Should the enter face the boss object after passing through the teleport?
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Teleport_To_Random_WP_Boss : public ScriptImpClass {
+	bool faceBoss;
+	bool retryOnFailure;
+	int playerType;
+	float safeTeleportDistance;
+	int wanderPointGroup;
+	int changeGroupIDCustom;
+	bool aiOnly;
+	void Created(GameObject *obj);
+	void Entered(GameObject *obj,GameObject *enterer);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	bool Get_A_Defense_Point(Vector3 *position,float *facing,GameObject *boss);
+	bool Grab_Teleport_Spot(GameObject *enter,int attempts);
+public:
+	static int BossObjectId;
+};
+
+/*!
+* \brief Marks this object as a boss object for the JMG_Utility_Zone_Teleport_To_Random_WP_Boss script, last object made is the one tracked
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Teleport_To_Random_WP_Boss_Object : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Sets the speed of the attached soldier on Custom
+* \Custom - Custom needed to trigger script
+* \Speed - Movement speed of the soldier
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Set_Soldier_Speed : public ScriptImpClass {
+	int custom;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Controller for the control point system, needed if control points are on the map (NeutralPlayer is -2)
+* \NeutralTeamName - Name of the neutral state of the CP's (normally you'll never see this)
+* \NeutralRadarBlipColor - This sets the color of the control point's radar marker (NOD = 0,GDI = 1,NEUTRAL = 2,MUTANT = 3,RENEGADE = 4,PRIMARY_OBJECTIVE = 5,SECONDARY_OBJECTIVE = 6,TERTIARY_OBJECTIVE = 7)
+* \NeutralPlayerType - Player type to change the CP's to when owned by neutral
+* \NeutralDefaultPointModel - W3D to use for the CP when neutral owns a CP
+* \NeutralDefaultAnimation - Animation to use when neutral owns a CP
+* \NeutralAnimationLength - Length of the animation
+* \NeutralCaptureSound - Capture sound to play when neutral captures it (normally won't ever see this)
+* \NeutralLostSound - Sound to play when neutral loses a CP
+* \NeutralLockedModel - Model to display if the CP is locked while controlled by neutral
+* \NeutralLockedAnim - Animation for the locked state
+* \NeutralLockedAnimLength - Length of the animation
+* \NeutralDefenseMultiplier - Effect a neutral soldier has on an owned CP
+* \NeutralCaptureMultiplier - Effect a neutral solider has on an enemy CP
+* \NeutralUnoccupiedMultiplier - How neutral is modified when no soldiers are guarding it
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Controller : public ScriptImpClass {
+public:
+	struct ControlPointTeamData
+	{
+		int teamId;
+		char teamName[128];
+		int playerType;
+		int radarBlipColor;
+		char pointModel[16];
+		char defaultAnimation[32];
+		float animationLength;
+		int defenseMultiplier;
+		int captureMultiplier;
+		int unoccupiedMultiplier;
+		char lostSound[128];
+		char captureSound[128];
+		char lockedModel[16];
+		char lockedAnim[32];
+		float lockedAnimLen;
+		ControlPointTeamData(int teamId,const char *teamName,int playerType,int radarBlipColor,const char *pointModel,const char *defaultAnimation,float animationLength,const char *captureSound,const char *lostSound,const char *lockedModel,const char *lockedAnim,float lockedAnimLen,int defenseMultiplier,int captureMultiplier,int unoccupiedMultiplier)
+		{
+			this->teamId = teamId;
+			sprintf(this->teamName,"%s",teamName);
+			this->playerType = playerType;
+			this->radarBlipColor = radarBlipColor;
+			sprintf(this->pointModel,"%s",pointModel);
+			sprintf(this->defaultAnimation,"%s",defaultAnimation);
+			this->animationLength = animationLength;
+			this->defenseMultiplier = defenseMultiplier;
+			this->captureMultiplier = captureMultiplier;
+			this->unoccupiedMultiplier = unoccupiedMultiplier;
+			sprintf(this->captureSound,"%s",captureSound);
+			sprintf(this->lostSound,"%s",lostSound);
+			sprintf(this->lockedModel,"%s",lockedModel);
+			sprintf(this->lockedAnim,"%s",lockedAnim);
+			this->lockedAnimLen = lockedAnimLen;
+		}
+	};
+	static SList<ControlPointTeamData> controlPointTeamData;
+	static bool controllerPlaced;
+private:
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Destroyed(GameObject *obj);
+public:
+	JMG_Utility_Control_Point_Controller()
+	{
+		controllerPlaced = true;
+		allSetupComplete = false;
+	}
+	static SList<GameObject> controlPoints;
+	static SList<GameObject> wanderPoints;
+	static bool allSetupComplete;
+};
+
+/*!
+* \brief Adds a team's settings to the game, needs one per team
+* \TeamID - ID of the team, this is used to index the units on the map
+* \TeamName - Name of the team, this is used for capture messages and loss messages
+* \PlayerType - This is the team the CP will be set to when its owned by this team
+* \RadarBlipColor - This sets the color of the control point's radar marker (NOD = 0,GDI = 1,NEUTRAL = 2,MUTANT = 3,RENEGADE = 4,PRIMARY_OBJECTIVE = 5,SECONDARY_OBJECTIVE = 6,TERTIARY_OBJECTIVE = 7)
+* \PointModel - W3D to use for the CP when owned by this team
+* \Animation - Animation to use when owned by this team
+* \AnimationLength - Length of the animation
+* \CaptureSound - Capture sound to play when a CP is captured by this team
+* \LostSound - Sound to play when this team loses a CP
+* \LockedModel - Model to display if the CP is locked
+* \LockedAnim - Animation for the locked state
+* \LockedAnimLength - Length of the animation
+* \DefenseMultiplier - Effect this team's soldiers has on controlled CP's
+* \CaptureMultiplier - Effect this team's soldiers has on an enemy CP
+* \UnoccupiedMultiplier - Rate at which owned CP's regenerate when no one is around
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Team_Setting : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Sets this control point's default settings
+* \ControlPointName - Name displayed when CP is lost or captured
+* \ControllingTeamID - Owning team when game starts
+* \MaxCapturePoints - How much 'health' the control point has, a normal unit will take 4 points a second when in range
+* \ControlDistance - Max distance units can control this point
+* \ControlHeight[Min|Max] - If non-zero it will limit the vertical scan of the sphere used for detecting units
+* \ZoneID - If specified a script zone will be used as the zone instead of distance from the control point
+* \CaptureScore - Score to grant units in range when captured (divided amongst all units in range)
+* \NeutralizeScore - Score to grant units in range when lost (divided amongst all units in range)
+* \ControlGroupID - Used for organization but can be accessed by external scripts
+* \StartsLocked - Whether it should start with the locked state (no one can capture it)
+* \LockCustom - Custom Message that will lock the CP if the param is 1, or unlock if 0
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point : public ScriptImpClass {
+public:
+	struct ControlPointSettingOverride
+	{
+		int teamId;
+		int id;
+		int captureCustom;
+		int lostCustom;
+		char pointModelOverride[16];
+		char animOverride[32];
+		float animationLength;
+		char lockedPointModelOverride[16];
+		char lockedAnimOverride[32];
+		float lockedAnimationLength;
+		ControlPointSettingOverride(int teamId,int id,int captureCustom,int lostCustom,const char *pointModelOverride,const char *animOverride,float animationLength,const char *lockedPointModelOverride,const char *lockedAnimOverride,float lockedAnimationLength)
+		{
+			this->teamId = teamId;
+			this->id = id;
+			this->captureCustom = captureCustom;
+			this->lostCustom = lostCustom;
+			sprintf(this->pointModelOverride,"%s",pointModelOverride);
+			sprintf(this->animOverride,"%s",animOverride);
+			this->animationLength = animationLength;
+			sprintf(this->lockedPointModelOverride,"%s",lockedPointModelOverride);
+			sprintf(this->lockedAnimOverride,"%s",lockedAnimOverride);
+			this->lockedAnimationLength = lockedAnimationLength;
+		}
+	};
+	SList<ControlPointSettingOverride> controlPointSettingOverride;
+private:
+	float captureScore;
+	float neutralizeScore;
+	int currerntCapturePoints;
+	int maxCapturePoints;
+	float currentFrame;
+	float controlDistance;
+	bool occupied;
+	bool specialLocked;
+	int lockCustom;
+	int radarBipType;
+	bool wasLocked;
+	Vector3 controlHeightMinMax;
+	int zoneId;
+	ControlPointSettingOverride *controllingTeamOverride;
+	ControlPointSettingOverride *lastTeamOverride;
+	JMG_Utility_Control_Point_Controller::ControlPointTeamData *controllingTeam;
+	JMG_Utility_Control_Point_Controller::ControlPointTeamData *neutralTeam;
+	JMG_Utility_Control_Point_Controller::ControlPointTeamData *lastTeam;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Timer_Expired(GameObject *obj,int number);
+	void Destroyed(GameObject *obj);
+	void SetAnimation(GameObject *obj,const char *anim,float length);
+	void AddUpCpPoints(GameObject *obj,JMG_Utility_Control_Point_Controller::ControlPointTeamData *unitTeamData,int multiplier);
+	void JMG_Utility_Control_Point::Final_CP_Calculation(GameObject *obj);
+	void LostControlPoint(GameObject *obj);
+	void CaptureControlPoint(GameObject *obj);
+	void GrantPointsToTeamMembersInRange(GameObject *obj,float points,bool matchTeam);
+	void UpdateTeamOverride(int teamId);
+	void UpdateTeam(int teamId);
+	void TriggerAssaultLinesUpdate();
+	void UpdateControllerWanderPoints();
+public:
+	char controlPointName[128];
+	int cpGroupId;
+	bool locked;
+	bool captured;
+	bool setupComplete;
+	int controllingTeamId;
+	void ChangeModelAndTeam(GameObject *obj);
+	void UpdateAnimation(GameObject *obj);
+	SList<GameObject> controlPointWanderPoints;
+	JMG_Utility_Control_Point()
+	{
+		setupComplete = false;
+	}
+};
+
+/*!
+* \brief Allows team specific overrides for a control point, script must be on the control point itself
+* \TeamID - Team this effects
+* \ID - ID to send customs to
+* \CaptureCustom - Custom to send when captured by this team
+* \LostCustom - Custom to send when this team loses this CP
+* \PointModelOverride - W3D model to use when this CP is captured by this team (leave it blank if you want to use the default settings)
+* \AnimationOverride - Animation to use when this CP is captured, only used if PointModelOverride is used
+* \AnimationLength - How many frames is the override animation
+* \LockedPointModelOverride - W3D model to use when this CP is captured by this team and its locked (leave it blank if you want to use the default settings)
+* \LockedAnimationOverride - Animation to use when this CP is captured and locked, only used if LockedPointModelOverride is used
+* \LockedAnimationLength - How many frames is the override animation
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Setting_Override : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Needs to be on objects that can affect the control point scripts
+* \TeamID - Team this belongs to
+* \Multiplier - Make this unit weigh more for defnese and capture, 1 is the default
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Team_Member : public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+public:
+	int teamId;
+	JMG_Utility_Control_Point_Controller::ControlPointTeamData *teamData;
+	bool setupComplete;
+	int multiplier;
+	JMG_Utility_Control_Point_Team_Member()
+	{
+		teamData = NULL;
+		setupComplete = false;
+	}
+};
+
+/*!
+* \brief Turns the control point system into something resembling Mutant Assault in ECW, it uses the groupId on the control points to decide which to enable in disable
+* \ In general capture direction is from 0, to 1, to 2, etc.  GroupId's below 0 are ignored in the system
+* \TeamID - Team that is on the defensive
+* \ID - ID of the object to send customs to, 0 sends to self.
+* \AdvanceCustom - Custom to send if Team manages to grab a group number that is lower than the current, param is the groupId that was gained
+* \PushBackCustom - Custom to send if Team is pushed back, param is the groupId that was lost
+* \ControlAllCustom - Custom to send if Team has all CPs in the positive groupId range, param is the groupId that was gained
+* \LostAllCustom - Custom to send if Team has none of the CPs in the positive groupId range, param is the groupId that was lost
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Assault_Mode : public ScriptImpClass {
+	int id;
+	int advanceCustom;
+	int pushedBackCustom;
+	int controlAllCustom;
+	int lostAllCustom;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Destroyed(GameObject *obj);
+	void SendCustom(GameObject* obj,int custom,int frontLineGroup);
+public:
+	static int teamId;
+	static int frontLineGroup;
+	static int controllerId;
+	static int spawnGroup;
+	static int enemySpawnGroup;
+	void UpdateAssaultLine(GameObject *obj,bool initialSetup);
+};
+
+/*!
+* \brief Updates a wander point's group ID depending on if the attached control point can be spawned at by the specified team
+* \ControlPointID - ID of the control point this wander point is connected to
+* \TeamID - Team that this wander point updates for
+* \SpawnableGroupID - GroupId the wander point should change to if control point is spawnable, -999 doesn't update
+* \UnspawnableGroupId - GroupId the wander point should change to if the team can't spawn at the control point, -999 doesn't update
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Wander_Point : public ScriptImpClass {
+	int spawnableGroupId;
+	int unspawnableGroupId;
+	JMG_Utility_Control_Point *cpScript;
+	Rp2SimplePositionSystem::SimplePositionNode *wanderPoint;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	Rp2SimplePositionSystem::SimplePositionNode *AddAndReturnWanderpoint(GameObject *obj);
+public:
+	JMG_Utility_Control_Point_Wander_Point()
+	{
+		cpScript = NULL;
+	}
+	int teamId;
+	int controlPointId;
+	void ControlPointChanged();
+	void UpdateWanderpointSettings();
+};
+	
+/*!
+* \brief Allows the player to select their spawn location before entering the map much like Mutant Assault in ECW.
+* \SpawnPreset - Preset the player becomes after spawning
+* \TeamID - Team that the player is on, used for selecting controlled control points
+* \SpawnCustom - custom to trigger spawning, spawn time must be 0
+* \GroupChangeCustom - This custom causes the player to cycle between available control points on the front lines
+* \UngroupedChangeCustom - This custom causes the player to cycle through control points with groups less than 0
+* \SafeTeleportDistance - the maximum range you will be randomly placed into the map if your spawn location is occupied
+* \SpawnTime - Defines how long in seconds the player must be in spawn mode before being able to spawn
+* \MaxWanderRange - Maximum distance in meters the player can move from a spawn point before being pulled back
+* \StringID - String ID that is used for displaying HUD messages, this value gets updated in the strings table
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Control_Point_Select_Spawn_System : public ScriptImpClass {
+	int stringId;
+	int teamId;
+	int controlPointId;
+	int ungroupedControlPointId;
+	int selectedCpId;
+	int groupChangeCustom;
+	int ungroupedChangeCustom;
+	int spawnCustom;
+	float safeTeleportDistance;
+	int lastSpawnGroup;
+	int maxSpawnTime;
+	int spawnTime;
+	float maxWanderRange;
+	float startFadeRange;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	int SelectCpToSpawnFrom(GameObject *obj,int cpId,bool assaultLines);
+	bool MoveToControlledWanderPointForCp(GameObject *obj,int cpId);
+};
+
+/*!
+* \brief Sends a custom on a custom with matching param
+* \Custom - Custom to watch for
+* \Param - Param to watch for
+* \ID - ID to send to, 0 sends to self, -1 sends to sender
+* \SendCustom - custom to send
+* \SendParam - param to send (-1 sends the param that was received)
+* \Delay - delay to add
+* \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_And_Param_Send_Custom : public ScriptImpClass {
+	int recieveMessage;
+	int recieveParam;
+	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);
+};
+
+/*!
+* \brief Moves object to nearest pathfind location on create
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Create_Move_To_Nearest_Pathfind : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Scans the map and attaches the specifed script to all objects that have the specified weapon
+* \WeaponName - Name of the weapon to check for
+* \Script - Name of the script to attach
+* \Params - The parameters to use for the script
+* \Delim - The character to use in place of a ',' swapped at script creation
+* \Rate - Rate at which the script scans
+* \PlayerType - Required team to attach to
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Attach_Script_To_Object_With_Weapon : public ScriptImpClass {
+	char weaponName[128];
+	char *params;
+	char script[128];
+	float rate;
+	int playerType;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Basically GTH_Drop_Object_On_Death but attaches a script to the dropped object
+* \Drop_Object - Object to drop
+* \Drop_Height - Height to add to dropped object
+* \Probability - Chance to drop
+* \Script - Name of the script to attach to dropped object
+* \Params - The parameters to use for the script
+* \Delim - The character to use in place of a ',' swapped at script creation
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Drop_Object_On_Death_And_Attach_Script : public ScriptImpClass {
+	void Killed(GameObject *obj,GameObject *killer);
+};
+
+/*!
+* \brief Tweaks M04 so the bubbles work right and infantry carrying the shotgun drop it
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_SinglePlayer_M04_Modifier : public ScriptImpClass {
+	bool setupComplete;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+public: 
+	void Register_Auto_Save_Variables();
+	JMG_SinglePlayer_M04_Modifier()
+	{
+		setupComplete = false;
+	}
+};
+
+/*!
+* \brief Watches for specific custom and sends them to the specified id, after each send starts a timer, if another watched custom is received in this time it adds to a list
+* \ The list is processed one by one until empty, using the delays to delay each custom.
+* \ID - ID to send to, 0 = self (not recommended, will start a loop) -1 is sender, everything else is the id
+* \Custom0-9 - Customs to watch for
+* \Time0-9 - Amount of time after sending each custom in which the next custom must be delayed before sending
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Delayed_Ordered_Customs : public ScriptImpClass {
+	struct TimedOrderedCustom
+	{
+	public:
+		int senderId;
+		int custom;
+		int param;
+		float time;
+		TimedOrderedCustom(int senderid,int custom,int param,float time)
+		{
+			this->senderId = senderId;
+			this->custom = custom;
+			this->param = param;
+			this->time = time;
+		}
+	};
+	SList<TimedOrderedCustom> timedOrderedCustom;
+	int id;
+	int customs[10];
+	float times[10];
+	bool delay;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Destroyed(GameObject *obj);
+	void Detach(GameObject *obj);
+public:
+	JMG_Utility_Custom_Send_Delayed_Ordered_Customs()
+	{
+		timedOrderedCustom.Remove_All();
+	}
+};
+
+/*!
+* \brief Sends a custom on a custom if the model matches
+* \Model - Model this object must be for the script to trigger
+* \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 add
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_If_Model : public ScriptImpClass {
+	int recieveMessage;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	char model[16];
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Grants a weapon and then destroys self on poke
+* \Weapon - Weapon to grant
+* \Rounds - Rounds in the gun, -1 = infinite
+* \Backpack - Should it fill the backpack
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Poke_Grant_Weapon_Destroy_Self : public ScriptImpClass {
+	void Created(GameObject *obj);
+	void Poked(GameObject *obj, GameObject *poker);
+};
+
+/*!
+* \brief Drops an object on destroyed/killed/Detached (only drops once, whichever comes first)
+* \Weapon - Weapon the character must have to drop the object
+* \DropPreset - Preset that is dropped
+* \Height - Height to add to the drop preset
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Killed_Drop_Object_If_Weapon_Present : public ScriptImpClass
+{
+	bool dropped;
+	void Killed(GameObject *obj,GameObject *killer);
+	void Destroyed(GameObject *obj);
+	void Detach(GameObject *obj);
+public:
+	JMG_Utility_Killed_Drop_Object_If_Weapon_Present()
+	{
+		dropped = false;
+	}
+};
+
+/*!
+* \brief Sends a custom message on poke if the poker has a specified weapon, the message is sent from the poker
+* \Weapon - Weapon the poker must have
+* \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
+* \RemoveWeapon - Whether to remove the poker's weapon on poke
+* \PlayerType - Required player type
+* \TriggerOnce - Allows the script only to trigger the first time all customs are received
+* \MustBeHeld - Player must be holding the weapon out when poking
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Poke_Send_Custom_If_Has_Weapon : public ScriptImpClass {
+	int id;
+	int playerType;
+	int custom;
+	int param;
+	float delay;
+	int triggerOnce;
+	char weapon[256];
+	int removeWeapon;
+	int mustBeHeld;
+	void Created(GameObject *obj);
+	void Poked(GameObject *obj, GameObject *poker);
+};
+
+/*!
+* \brief Sends a custom message on if a player holds a specified weapon within a range of the attached object
+* \Weapon - Weapon the poker must have
+* \Range - detection range
+* \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
+* \RemoveWeapon - Whether to remove the poker's weapon on poke
+* \PlayerType - Required player type
+* \TriggerOnce - Allows the script only to trigger the first time all customs are received
+* \MustBeHeld - Player must be holding the weapon out when poking
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Send_Custom_If_Weapon_Held_In_Range : public ScriptImpClass {
+	int id;
+	int playerType;
+	int custom;
+	int param;
+	float delay;
+	int triggerOnce;
+	char weapon[256];
+	int removeWeapon;
+	int mustBeHeld;
+	float range;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Sends any custom message caught back to the sender
+* \Model - Model this object must be for the script to trigger
+* \SendFromSelf - If one the reflection is sent from the object that originally sent, otherwise sent from the reflector
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Refect_Custom_If_Model : public ScriptImpClass {
+	int sendFromSelf;
+	char model[16];
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Sets the attached object's team when a custom is received
+* \Custom - Custom to trigger on
+* \Team - Team to set
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Set_Team : public ScriptImpClass {
+	int custom;
+	int team;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief Creates a specified preset at a random wander point
+* \Custom - Custom to trigger on
+* \Preset - Preset to create
+* \WanderingAIGroupID - Id of the wandering point group to use to place the object
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Create_Object_At_Random_Wander_Point : public ScriptImpClass {
+	int custom;
+	char preset[128];
+	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);
+};
+
+/*!
+* \brief Teleports the object that enters the zone to a wander point that isn't visible to players
+* \WanderingAIGroupID - Group of points to teleport to
+* \SafeTeleportDistance - How far can infantry be moved if the spot is blocked
+* \ChangeGroupIDCustom - Changes the wander point group id to a new one, uses the parameter on the custom sent in
+* \PlayerType - Playertype the zone triggers for
+* \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)
+* \AiOnly - Only trigger for AI
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point : public ScriptImpClass {
+	bool retryOnFailure;
+	int playerType;
+	float safeTeleportDistance;
+	int wanderPointGroup;
+	int changeGroupIDCustom;
+	bool aiOnly;
+	void Created(GameObject *obj);
+	void Entered(GameObject *obj,GameObject *enterer);
+	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 Used by JMG_Utility_Zone_Teleport_To_Random_Wander_Point
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Teleport_To_Non_Visible_Wander_Point_Attach : public ScriptImpClass {
+	float safeTeleportDistance;
+	int wanderPointGroup;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+	bool Get_A_Defense_Point(Vector3 *position,float *facing);
+};
+
+/*!
+* \brief Builds a keypad using a model with bones named Number(0-9), Clear, and Enter, the buttons it creates to attach to the bones are named PRESETNAME_Key_(0-9), PRESETNAME_Key_Enter, or PRESETNAME_Key_Clear
+* \RandomCombinationDigits - How many digits this combination can be
+* \ID - ID to send the messages to
+* \Success_Custom - Message to send when the combination is matched
+* \Partial_Failure_Custom - Sent until max_failures is meet
+* \Failure_Custom - Message to send when max failures is meet, counter for partial fails is reset upon this
+* \Disable_On_Success - Disable the keypad on a valid entry
+* \Disable_On_Failure - If you completely fail should the keypad disable
+* \Starts_Enabled - Whether the pad can be used from the get-go
+* \Enable_Custom - Turn the number pad on/off
+* \Failure_Safty - Allow partial fail customs to send
+* \Max_Failures - As long as this value is less than the current failed attempt count a partial fail message is sent, once it matches a complete fail message is sent
+* \Sound_Name_Base - Sound that should be played when buttons are pressed, a number 0-9 or ENTER or CLEAR is tacked on to the end
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Security_System_Random_NumberPad_Control : public ScriptImpClass {
+	int buttonIds[12];
+	void Created(GameObject *obj);
+	void Destroyed(GameObject *obj);
+public:
+	char combination[25];
+};
+
+/*!
+* \brief Used by to make a param send when customs are sent in in the correct order
+* \ID - ID to send the messages to
+* \Combination - Combination that must be entered (25 char max)
+* \Input_Number_Custom - Number from the keypad
+* \Input_Enter_Custom - Enter key pressed
+* \Input_Clear_Custom - Clear key pressed
+* \Success_Custom - Message to send when the combination is matched
+* \Partial_Failure_Custom - Sent until max_failures is meet
+* \Failure_Custom - Message to send when max failures is meet, counter for partial fails is reset upon this
+* \Disable_On_Success - Disable the keypad on a valid entry
+* \Disable_On_Failure - If you completely fail should the keypad disable
+* \Starts_Enabled - Whether the pad can be used from the get-go
+* \Enable_Custom - Turn the number pad on/off
+* \Failure_Safty - Allow partial fail customs to send
+* \Max_Failures - As long as this value is less than the current failed attempt count a partial fail message is sent, once it matches a complete fail message is sent
+* \Sound_Name_Base - Sound that should be played when buttons are pressed, a number 0-9 or ENTER or CLEAR is tacked on to the end
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Combination_Lock : public ScriptImpClass {
+	char inputCode[128];
+	unsigned int depth;
+	int failCount;
+	bool enabled;
+	int inputCustom;
+	int inputEnter;
+	int inputClear;
+	int enableCustom;
+	char combination[25];
+	int successCustom;
+	int failureSaftey;
+	int maxFailures;
+	int partialFailCustom;
+	int failureCustom;
+	bool disableOnFailure;
+	bool disableOnSuccess;
+	int id;
+	char soundNameBase[128];
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Send_Custom(GameObject *obj,int custom,int param);
+	void Enable(bool enableLock);
+	void ClearUserEntry();
+};
+/*!
+* \brief Used for pressing keys on the combination lock, script is attached by the code
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Combination_Lock_Key : public ScriptImpClass {
+	int id;
+	int message;
+	int param;
+	bool useAltAnimation;
+	char animation[32];
+	char animation2[32];
+	char soundName[128];
+	void Created(GameObject *obj);
+	void Poked(GameObject *obj,GameObject *poker);
+};
+
+/*!
+* \brief Takes a string and replaces the delim with the code generated by JMG_Utility_Security_System_Random_NumberPad_Control
+* \StringID - ID of the string to update
+* \Delim - Character to replace in the string with the code
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Sync_String_With_Random_NumberPad_Control : public ScriptImpClass {
+	char delim;
+	int stringId;
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief Starts a looping animation on a sub object when a client joins a game 
+* \SubObject - Subobject to play on
+* \Animation - Animation to play
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Created_Animate_SubObject_On_Join : public ScriptImpClass {
+	char animation[32];
+	char subobject[16];
+	bool animating[128];
+	void Created(GameObject *obj);
+	void Timer_Expired(GameObject *obj,int number);
+};
+
+/*!
+* \brief When a specified custom is received it plays an animation, when the animation complete it sends a custom, if applied to a soldier damage animations and other animations are disabled.
+* \ReceivedCustom - Message needed to trigger the script
+* \Animation - Animation to play 
+* \StartFrame - First frame of the animation
+* \EndFrame - Last frame of the animation
+* \ID - Id of the object to send the custom to, 0 sends to itself
+* \Message - Custom to send
+* \Param - parameter to send with the custom
+* \Delay - Time amount to wait before sending the custom
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Play_Animation_Send_Custom_When_Complete : public ScriptImpClass {
+	int receivedCustom;
+	int id;
+	int custom;
+	int Param;
+	float delay;
+	char animation[32];
+	float startFrame;
+	float endFrame;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Animation_Complete(GameObject *obj,const char *anim);
+};
+
+/*!
+* \brief Plays a looped animation on the attached infantry, disables all animations
+* \Animation - Animation to play 
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Created_Play_Locked_Infantry_Animation : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Sends a custom to all matching presets on the map on custom
+* \ReceivedCustom - Message needed to trigger the script
+* \Preset - Presets to send to
+* \Message - Custom to send
+* \Param - parameter to send with the custom
+* \Delay - Time amount to wait before sending the custom
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Send_Custom_To_Preset : public ScriptImpClass {
+	int receivedCustom;
+	char preset[128];
+	int custom;
+	int Param;
+	float delay;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+
+/*!
+* \brief Plays a sound from the position of the attached soldier, also animates their face dynamically
+* \Custom - Message needed to trigger the script
+* \StringID - ID of the string in strings.tbl to play, must have the sound attached to it. (Use // at the front of the string to make it not show up in chat).
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Talk : public ScriptImpClass {
+	int custom;
+	int soundId;
+	int stringId;
+	char soundName[128];
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+	void Killed(GameObject *obj,GameObject *killer);
+};
+
+/*!
+* \brief Sends a custom to an object on entry if the preset matches
+* \Preset - Preset required
+* \PlayerType - Player type the zone triggers for
+* \ID - ID to send the custom to, 0 sends to self, -1 sends to enter
+* \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 the zone is entered
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Zone_Send_Custom_Enter_Preset : public ScriptImpClass {
+	char preset[128];
+	int playerType;
+	int custom;
+	int param;
+	float delay;
+	int id;
+	bool triggerOnce;
+	void Created(GameObject *obj);
+	void Entered(GameObject *obj,GameObject *enterer);
+};
+
+/*!
+* \brief Controller is required for the global armor system for all objects on the map that have the object script
+* \DefaultMaxArmor - Default max armor when the object spawns into the game 
+* \DefaultArmor - Default armor when the object spawns into the game 
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Armor_Controller : public ScriptImpClass {
+	void Created(GameObject *obj);
+public:
+	static float maxArmor;
+	static float armor;
+};
+
+/*!
+* \brief When a custom is received it will change the armor of all objects on the map that have the object script
+* \Custom - Custom needed to trigger the update
+* \MaxArmor - New max armor when the object spawns into the game, -1 uses the param
+* \Armor - New armor when the object spawns into the game, -1 uses the param
+* \UpdateAllObjects - Whether the script should update objects that are already spawned
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Armor_Custom : public ScriptImpClass {
+	int custom;
+	float maxArmor;
+	float armor;
+	bool updateAllObjects;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief When the object is created it will use the armor specified by the controller
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Armor_Object : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Controller is required for the global health system for all objects on the map that have the object script
+* \DefaultMaxHealth - Default max health when the object spawns into the game 
+* \DefaultHealth - Default health when the object spawns into the game 
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Health_Controller : public ScriptImpClass {
+	void Created(GameObject *obj);
+public:
+	static float maxHealth;
+	static float health;
+};
+
+/*!
+* \brief When a custom is received it will change the health of all objects on the map that have the object script
+* \Custom - Custom needed to trigger the update
+* \MaxHealth - New max health when the object spawns into the game, -1 uses the param
+* \Health - New health when the object spawns into the game, -1 uses the param
+* \UpdateAllObjects - Whether the script should update objects that are already spawned
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Health_Custom : public ScriptImpClass {
+	int custom;
+	float maxHealth;
+	float health;
+	bool updateAllObjects;
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
+};
+
+/*!
+* \brief When the object is created it will use the health specified by the controller
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Global_Health_Object : public ScriptImpClass {
+	void Created(GameObject *obj);
+};
+
+/*!
+* \brief Creates random explosions around the attached object within the specified paramaters
+* \Custom - Custom needed to trigger the update
+* \ExplosionPreset - Explosion(s) to create
+* \Count - How many explosions to make
+* \MaxDistance - Maximum distance from the attached object to create explosons 
+* \KillerID - Who gets the kill, -1 = sender, 0 = attached, everything else is a lookup ID
+* \author jgray
+* \ingroup JmgUtility
+*/
+class JMG_Utility_Custom_Create_Random_Explosions : public ScriptImpClass {
+	int custom;
+	int count;
+	float distance;
+	int killerId;
+	char explosionPreset[256];
+	void Created(GameObject *obj);
+	void Custom(GameObject *obj,int message,int param,GameObject *sender);
 };
\ No newline at end of file
diff -urN sourceold/scripts/mdb.cpp source/scripts/mdb.cpp
--- sourceold/scripts/mdb.cpp	2018-10-27 22:04:56.512400000 +1000
+++ source/scripts/mdb.cpp	2020-09-10 09:08:27.972483200 +1000
@@ -292,7 +292,7 @@
 	}
 }
 
-void MDB_Unit_Limit::Destroyed(GameObject *obj)
+void MDB_Unit_Limit::Killed(GameObject *obj,GameObject *killer)
 {
 	ReEnable();
 }
diff -urN sourceold/scripts/mdb.h source/scripts/mdb.h
--- sourceold/scripts/mdb.h	2018-10-27 22:04:56.513400000 +1000
+++ source/scripts/mdb.h	2020-09-10 09:08:27.973761100 +1000
@@ -74,7 +74,7 @@
 	PurchaseSettingsDefClass::TEAM Team;
 	unsigned int Definitions[4];
 	void Created(GameObject *obj);
-	void Destroyed(GameObject *obj);
+	void Killed(GameObject *obj,GameObject *killer);
 	public: void Register_Auto_Save_Variables();
 	void ReEnable();
 };
diff -urN sourceold/scripts/mdbdef.cpp source/scripts/mdbdef.cpp
--- sourceold/scripts/mdbdef.cpp	2018-10-27 22:04:56.514400000 +1000
+++ source/scripts/mdbdef.cpp	2020-09-10 09:08:27.973761100 +1000
@@ -1,5 +1,5 @@
 /*	Renegade Scripts.dll
-	Copyright 2013 Tiberian Technologies
+	Copyright 2015 Tiberian Technologies
 
 	This file is part of the Renegade scripts.dll
 	The Renegade scripts.dll is free software; you can redistribute it and/or modify it under
@@ -112,11 +112,6 @@
 	}
 }
 
-void MDB_Base_Defense_Popup_No_VTOL::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void MDB_Base_Defense_Popup_No_VTOL::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -289,11 +284,6 @@
 	}
 }
 
-void MDB_Base_Defense_Popup_VTOL_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void MDB_Base_Defense_Popup_VTOL_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -429,11 +419,6 @@
 	}
 }
 
-void MDB_Base_Defense_Popup::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	Commands->Action_Reset(obj,100);
-}
-
 void MDB_Base_Defense_Popup::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -485,17 +470,17 @@
 	Commands->Enable_Enemy_Seen(obj,true);
 	Vector3 pos = Commands->Get_Position(obj);
 	V[0].X = pos.X - 10;
-	V[0].Y = pos.Y - 10;
+	V[0].Y = pos.Y;
 	V[0].Z = pos.Z + 2;
 	V[1].X = pos.X + 10;
 	V[1].Y = pos.Y;
 	V[1].Z = pos.Z + 2;
-	V[2].X = pos.X + 10;
+	V[2].X = pos.X;
 	V[2].Y = pos.Y - 10;
 	V[2].Z = pos.Z + 2;
 	V[3].X = pos.X;
 	V[3].Y = pos.Y + 10;
-	V[3].Z = pos.Z + 4;
+	V[3].Z = pos.Z + 2;
 	ActionParamsStruct var;
 	var.Set_Basic(this,1,2);
 	var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
@@ -525,18 +510,10 @@
 		params.AttackCheckBlocked = true;
 	}
 	LastSeen = Commands->Get_ID(enemy);
-	Commands->Start_Timer(obj,this,5.0f,3);
+	Commands->Start_Timer(obj,this,1.0f,2);
 	Commands->Action_Attack(obj,params);
 }
 
-void MDB_Base_Defense::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 1)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void MDB_Base_Defense::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -544,6 +521,7 @@
 		ActionParamsStruct var;
 		var.Set_Basic(this,1,2);
 		var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
+		Commands->Action_Attack(obj,var);
 		Commands->Start_Timer(obj,this,Commands->Get_Random(5.0f,15.0f),1);
 	}
 	else if (number == 2)
@@ -581,17 +559,17 @@
 	Commands->Enable_Enemy_Seen(obj,true);
 	Vector3 pos = Commands->Get_Position(obj);
 	V[0].X = pos.X - 10;
-	V[0].Y = pos.Y - 10;
+	V[0].Y = pos.Y;
 	V[0].Z = pos.Z + 2;
 	V[1].X = pos.X + 10;
 	V[1].Y = pos.Y;
 	V[1].Z = pos.Z + 2;
-	V[2].X = pos.X + 10;
+	V[2].X = pos.X;
 	V[2].Y = pos.Y - 10;
 	V[2].Z = pos.Z + 2;
 	V[3].X = pos.X;
 	V[3].Y = pos.Y + 10;
-	V[3].Z = pos.Z + 4;
+	V[3].Z = pos.Z + 2;
 	ActionParamsStruct var;
 	var.Set_Basic(this,1,2);
 	var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
@@ -623,19 +601,11 @@
 			params.AttackCheckBlocked = true;
 		}
 		LastSeen = Commands->Get_ID(enemy);
-		Commands->Start_Timer(obj,this,5.0f,3);
+		Commands->Start_Timer(obj,this,1.0f,2);
 		Commands->Action_Attack(obj,params);
 	}
 }
 
-void MDB_Base_Defense_No_VTOL::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 1)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void MDB_Base_Defense_No_VTOL::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -643,6 +613,7 @@
 		ActionParamsStruct var;
 		var.Set_Basic(this,1,2);
 		var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
+		Commands->Action_Attack(obj,var);
 		Commands->Start_Timer(obj,this,Commands->Get_Random(5.0f,15.0f),1);
 	}
 	else if (number == 2)
@@ -680,17 +651,17 @@
 	Commands->Enable_Enemy_Seen(obj,true);
 	Vector3 pos = Commands->Get_Position(obj);
 	V[0].X = pos.X - 10;
-	V[0].Y = pos.Y - 10;
-	V[0].Z = pos.Z + 2;
+	V[0].Y = pos.Y;
+	V[0].Z = pos.Z + 5;
 	V[1].X = pos.X + 10;
 	V[1].Y = pos.Y;
-	V[1].Z = pos.Z + 2;
-	V[2].X = pos.X + 10;
+	V[1].Z = pos.Z + 5;
+	V[2].X = pos.X;
 	V[2].Y = pos.Y - 10;
-	V[2].Z = pos.Z + 2;
+	V[2].Z = pos.Z + 5;
 	V[3].X = pos.X;
 	V[3].Y = pos.Y + 10;
-	V[3].Z = pos.Z + 4;
+	V[3].Z = pos.Z + 5;
 	ActionParamsStruct var;
 	var.Set_Basic(this,1,2);
 	var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
@@ -717,14 +688,6 @@
 	}
 }
 
-void MDB_Base_Defense_VTOL_Only::Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason)
-{
-	if (action_id == 1)
-	{
-		Commands->Action_Reset(obj,100);
-	}
-}
-
 void MDB_Base_Defense_VTOL_Only::Timer_Expired(GameObject *obj,int number)
 {
 	if (number == 1)
@@ -732,6 +695,7 @@
 		ActionParamsStruct var;
 		var.Set_Basic(this,1,2);
 		var.Set_Attack(V[Commands->Get_Random_Int(0,4)],0.0,0.0,true);
+		Commands->Action_Attack(obj,var);
 		Commands->Start_Timer(obj,this,Commands->Get_Random(5.0f,15.0f),1);
 	}
 	else if (number == 2)
diff -urN sourceold/scripts/mdbdef.h source/scripts/mdbdef.h
--- sourceold/scripts/mdbdef.h	2018-10-27 22:04:56.515400000 +1000
+++ source/scripts/mdbdef.h	2020-09-10 09:08:27.974759900 +1000
@@ -1,5 +1,5 @@
 /*	Renegade Scripts.dll
-	Copyright 2013 Tiberian Technologies
+	Copyright 2015 Tiberian Technologies
 
 	This file is part of the Renegade scripts.dll
 	The Renegade scripts.dll is free software; you can redistribute it and/or modify it under
@@ -20,7 +20,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj, int type, int param, GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	void Animation_Complete(GameObject *obj,const char *animation_name);
 	public: void Register_Auto_Save_Variables();
@@ -37,7 +36,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj, int type, int param, GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	void Animation_Complete(GameObject *obj,const char *animation_name);
 	public: void Register_Auto_Save_Variables();
@@ -51,7 +49,6 @@
 	void Created(GameObject *obj);
 	void Custom(GameObject *obj, int type, int param, GameObject *sender);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	void Animation_Complete(GameObject *obj,const char *animation_name);
 	public: void Register_Auto_Save_Variables();
@@ -64,7 +61,6 @@
 	int LastSeen;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -76,7 +72,6 @@
 	int LastSeen;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
@@ -87,7 +82,6 @@
 	float MaxDis;
 	void Created(GameObject *obj);
 	void Enemy_Seen(GameObject *obj,GameObject *enemy);
-	void Action_Complete(GameObject *obj,int action_id,ActionCompleteReason complete_reason);
 	void Timer_Expired(GameObject *obj,int number);
 	public: void Register_Auto_Save_Variables();
 };
diff -urN sourceold/scripts/ms.cpp source/scripts/ms.cpp
--- sourceold/scripts/ms.cpp	1970-01-01 10:00:00.000000000 +1000
+++ source/scripts/ms.cpp	2020-03-30 20:42:37.444271100 +1000
@@ -0,0 +1,1232 @@
+#include "general.h"
+#include "ms.h"
+#include "VehicleGameObj.h"
+#include "dp88_custom_timer_defines.h"
+#include "engine_game.h"
+#include "engine_script.h"
+#include "engine_obj.h"
+#include "engine_obj2.h"
+#include "engine_player.h"
+#include "engine_def.h"
+#include "engine_dmg.h"
+#include "engine_weap.h"
+#include "TwiddlerClass.h"
+#include "DefinitionMgrClass.h"
+#include "random2class.h"
+#include "TranslateDBClass.h"
+#include "engine_tt.h"
+
+#define REGISTER_SCRIPT(name, params) ScriptRegistrant<name> name##Registrant(#name, params);
+
+/*------------------------
+Generic / Utility scripts 
+--------------------------*/
+
+
+/*!
+* \brief Send custom based on health
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* When attached to an object, this script can make that object send customs around when its health
+* goes under or above a given value.
+
+* The custom is sent only once until the health goes to the other side of the treshold.
+* This means if the Health parameter is set to 100 and the object's health is 99 then the custom is sent once.
+* Then the health needs to reach 100 and then 99 once again to send the custom once more.
+*
+* \param Health
+*   Current HP will be always checked against this value if it is under or above it.
+* \param UnderID
+*   The ID of the object to send the custom message to when the health is under the treshold.
+*	When set to -1, nothing will get a message.
+*	0 sends to self.
+* \param UnderCustom
+*   The under custom message that will be sent to the object defined in the ID.
+* \param UnderParam
+*   The parameter of the under custom message that will be sent to the object defined in the ID.
+* \param AboveID
+*   The ID of the object to send the custom message to when the health is above the treshold.
+*	When set to -1, nothing will get a message.
+*	0 sends to self.
+* \param AboveCustom
+*   The above custom message that will be sent to the object defined in the ID.
+* \param AboveParam
+*   The parameter of the above custom message that will be sent to the object defined in the ID.
+*/
+class MS_Health_Send_Custom : public ScriptImpClass
+{
+	float tresholdHP;
+	float currentHP;
+	bool underIgnored;
+	bool aboveIgnored;
+	bool underTriggered;
+	bool aboveTriggered;
+	bool triggerOnInstantDeath;
+	bool isPreventingSpam;
+	int underID;
+	int aboveID;
+
+	void Created(GameObject* obj) override
+	{
+		underIgnored = false;
+		aboveIgnored = false;
+		underTriggered = false;
+		isPreventingSpam = false;
+
+		aboveTriggered = true;
+		tresholdHP = Get_Float_Parameter("Health");
+		underID = Get_Int_Parameter("UnderID");
+		aboveID = Get_Int_Parameter("AboveID");
+		triggerOnInstantDeath = Get_Int_Parameter("TriggerOnInstantDeath") == 1;
+
+		if (underID == -1)
+		{
+			underIgnored = true;
+		}
+
+		if (aboveID == -1)
+		{
+			aboveIgnored = true;
+		}
+		Do_Checks(obj);
+	}
+
+	void Do_Checks(GameObject *obj)
+	{
+		currentHP = Commands->Get_Health(obj);
+		if (currentHP <= 0 && !triggerOnInstantDeath)
+		{
+			return;
+		}
+
+		// Under
+		if (!underTriggered && currentHP < tresholdHP)
+		{
+			underTriggered = true;
+			aboveTriggered = false;
+			if (!underIgnored)
+			{
+				GameObject *targetUnder = underID ? Commands->Find_Object(underID) : obj;
+				Commands->Send_Custom_Event(obj, targetUnder, Get_Int_Parameter("UnderCustom"), Get_Int_Parameter("UnderParam"), 0);
+			}
+		}
+		// Above
+		else if (!aboveTriggered && currentHP >= tresholdHP)
+		{
+			aboveTriggered = true;
+			underTriggered = false;
+			if (!aboveIgnored)
+			{
+				GameObject *targetAbove = aboveID ? Commands->Find_Object(aboveID) : obj;
+				Commands->Send_Custom_Event(obj, targetAbove, Get_Int_Parameter("AboveCustom"), Get_Int_Parameter("AboveParam"), 0);
+			}
+		}
+	}
+
+	void Damaged(GameObject *obj, GameObject *damager, float amount) override
+	{
+		if (!isPreventingSpam)
+		{
+			Do_Checks(obj);
+			isPreventingSpam = true;
+			Commands->Start_Timer(obj, this, 1.0f, 0);
+
+		}
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		Do_Checks(obj);
+		isPreventingSpam = false;
+	}
+};
+REGISTER_SCRIPT(MS_Health_Send_Custom, "Health=0.0:float,UnderID=-1:int,UnderCustom=0:int,UnderParam=0:int,AboveID=-1:int,AboveCustom=0:int,AboveParam=0:int,TriggerOnInstantDeath=0:int");
+
+
+/*!
+* \brief Send custom when fully healed
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script lets you send a custom when the object it is attached to reaches full health/armor after being damaged.
+*
+* \param ID
+*   The ID of the object to send the custom message to.
+*   0 sends to self.
+* \param Custom
+*   The custom message that will be sent to the object defined in the ID.
+* \param Param
+*   The parameter of the custom message that will be sent to the object defined in the ID.
+* \param Delay
+*   How many seconds to wait before the custom is sent to the object defined in the ID.
+*/
+class MS_Healed_Send_Custom : public ScriptImpClass
+{
+	bool damaged;
+
+	void Created(GameObject* obj) override
+	{
+		damaged = false;
+	}
+
+	void Damaged(GameObject *obj, GameObject *damager, float amount) override
+	{
+		if (Commands->Get_Health(obj) < Commands->Get_Max_Health(obj) || Commands->Get_Shield_Strength(obj) < Commands->Get_Max_Shield_Strength(obj))
+		{
+			damaged = true;
+		}
+		if (Commands->Get_Health(obj) == Commands->Get_Max_Health(obj) && Commands->Get_Shield_Strength(obj) == Commands->Get_Max_Shield_Strength(obj) && damaged)
+		{
+			damaged = false;
+			int id = Get_Int_Parameter("ID");
+			GameObject *receiver = id ? Commands->Find_Object(id) : obj;
+			if (receiver)
+				Commands->Send_Custom_Event(obj, receiver, Get_Int_Parameter("Custom"), Get_Int_Parameter("Param"), Get_Float_Parameter("Delay"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Healed_Send_Custom, "ID=0:int,Custom=0:int,Param=0:int,Delay=0.0:float");
+
+
+/*!
+* \brief Send custom when receive custom conditionally
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script lets you send a custom when the object it is attached receives a custom,
+* with an addition of a switch/gate that enables/disables the processing.
+*
+* \param ReceiveCustom
+*   The custom message to listen for. Ignored until EnableCustom is received first.
+* \param SendCustom
+*   The custom message that will be sent to the object defined in the ID.
+* \param Param
+*   The parameter of the custom message that will be sent to the object defined in the ID.
+* \param Delay
+*   How many seconds to wait before the custom is sent to the object defined in the ID.
+* \param ID
+*   The ID of the object to send the custom message to.
+*   0 sends to self.
+* \param EnableCustom
+*   The custom message that enables sending the custom.
+* \param DisableCustom
+*   The custom message that disables sending the custom.
+*/
+class MS_Custom_Send_Custom_Switch : public ScriptImpClass
+{
+	bool enabled;
+
+	void Created(GameObject* obj) override
+	{
+		enabled = false;
+	}
+
+	void Custom(GameObject *obj,int type,int param,GameObject *sender) override
+	{
+		if (type == Get_Int_Parameter("EnableCustom"))
+		{
+			enabled = true;
+		}
+		else if (type == Get_Int_Parameter("DisableCustom"))
+		{
+			enabled = false;
+		}
+		else if (enabled && type == Get_Int_Parameter("ReceiveCustom"))
+		{
+			int id = Get_Int_Parameter("ID");
+			GameObject *receiver = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+			if (receiver)
+				Commands->Send_Custom_Event(obj, receiver, Get_Int_Parameter("SendCustom"), Get_Int_Parameter("Param"), Get_Float_Parameter("Delay"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Custom_Send_Custom_Switch, "ReceiveCustom=0:int,SendCustom=0:int,Param=0:int,Delay=0.0:float,ID=0:int,EnableCustom=0:int,DisableCustom=0:int");
+
+
+/*!
+* \brief Set max health on custom
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script lets you set the maximum health on the object it's attached to when it receives a custom.
+* It does not heal the object after doing so.
+*
+* \param Custom
+*   The custom message to listen for.
+* \param Value
+*   The value to update the maximum health to.
+*/
+class MS_Custom_Set_Max_Health : public ScriptImpClass
+{
+	void Custom(GameObject *obj,int type,int param,GameObject *sender) override
+	{
+		if (type == Get_Int_Parameter("Custom"))
+		{
+			Set_Max_Health_Without_Healing(obj,Get_Float_Parameter("Value"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Custom_Set_Max_Health, "Custom=0:int,Value=0.0:float");
+
+
+/*!
+* \brief Set max shield on custom
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script lets you set the maximum shield on the object it's attached to when it receives a custom.
+* It does not heal the object after doing so.
+*
+* \param Custom
+*   The custom message to listen for.
+* \param Value
+*   The value to update the maximum shield to.
+*/
+class MS_Custom_Set_Max_Shield : public ScriptImpClass
+{
+	void Custom(GameObject *obj,int type,int param,GameObject *sender) override
+	{
+		if (type == Get_Int_Parameter("Custom"))
+		{
+			Set_Max_Shield_Strength_Without_Healing(obj,Get_Float_Parameter("Value"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Custom_Set_Max_Shield, "Custom=0:int,Value=0.0:float");
+
+
+/*!
+* \brief Grant credits to an entire team when custom is received
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script grants credits to all players of a team when it receives a custom.
+*
+* \param Custom
+*   The custom message to listen for. When it arrives, the credits will be distributed.
+* \param Team
+*   Which team to give credits to. Every player on that team will get the credits.
+* \param Amount
+*   The amount of credits to grant.
+*/
+/*class MS_Custom_Grant_Credits_Team : public ScriptImpClass
+{
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == Get_Int_Parameter("Custom"))
+		{
+			Commands->Give_Money_To_Team(Get_Int_Parameter("Team"), (float)Get_Int_Parameter("Amount"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Custom_Grant_Credits_Team, "Custom=0:int,Team=0:int,Amount=0:int");*/
+
+
+/*!
+* \brief Enable or disable specifically named spawners when custom is received
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script lets you enable or disable one or more spawners when it receives a custom.
+*
+* \param Custom
+*   The custom message to listen for.
+* \param SpawnerName
+*   The name of the spawner preset(s) that will be affected.
+* \param Enable
+*   Enable or disable the spawner(s).
+*/
+class MS_Custom_Toggle_Spawners : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!Is_Valid_Preset(Get_Parameter("SpawnerName")))
+		{
+			Console_Output("[%d:%hs:%hs] Critical Error: '%hs' is not a valid preset. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), Get_Parameter("SpawnerName"));
+			Destroy_Script();
+			return;
+		}
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == Get_Int_Parameter("Custom"))
+		{
+			Enable_Spawners_By_Name(Get_Parameter("SpawnerName"), Get_Int_Parameter("Enable") == 1);
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Custom_Toggle_Spawners, "Custom=0:int,SpawnerName:string,Enable=1:int");
+
+
+/*!
+* \brief Paradrop script
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Make a character fall slowly with an optional parachute model attached until the character lands.
+* Landing is detected by the first falling (Earth warhead) damage received.
+*
+* \param Parachute_Model
+*   The name of the parachute W3D model that will be attached. 
+*   The extension .w3d can be omitted, enter the name only.
+*   If left empty, no model will be attached.
+*/
+/*class MS_Paradrop : public ScriptImpClass
+{
+	int parachuteObjID = 0;
+
+	int fallFailCount = 0;
+	int groundCloseCount = 0;
+
+	unsigned int earthWarhead = 0;
+	float cachedHP = 0.0f;
+	float cachedShield = 0.0f;
+
+	bool isArmorChanged = false;
+	bool isCleanedUp = false;
+
+	void Created(GameObject* obj) override
+	{
+		earthWarhead = ArmorWarheadManager::Get_Warhead_Type("Earth");
+		cachedHP = Commands->Get_Health(obj);
+		cachedShield = Commands->Get_Shield_Strength(obj);
+
+		if (auto soldier = obj->As_SoldierGameObj())
+		{
+			soldier->Set_Grav_Scale(0);
+		}
+
+		Set_Velocity(obj, Vector3(0, 0, -5));
+		const char* parachuteModel = Get_Parameter("Parachute_Model");
+		if (parachuteModel[0] != '\0' && strcmp(parachuteModel, "null") != 0 && strcmp(parachuteModel, "NULL") != 0)
+		{
+			auto parachute = Commands->Create_Object("Invisible_Object", Commands->Get_Position(obj));
+			Commands->Set_Model(parachute, parachuteModel);
+			Commands->Attach_To_Object_Bone(parachute, obj, "ROOTTRANSFORM");
+			parachuteObjID = parachute->Get_ID();
+		}
+
+		const char* newSkin = Get_Parameter("Parachuting_Skin");
+		const char* newShield = Get_Parameter("Parachuting_Shield");
+		if (strcmp(newSkin, "null") != 0 || strcmp(newShield, "null") != 0)
+		{
+			isArmorChanged = true;
+			char scriptParams[256];
+			snprintf(scriptParams, sizeof(scriptParams), "%s,%s,0", newSkin, newShield);
+			Commands->Attach_Script(obj, "MS_Switch_Skin_And_Shield_Timer", scriptParams);
+		}
+
+		Commands->Start_Timer(obj, this, 0, 0);
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		// Timed destroy, started by cleanup
+		if (number == -1)
+		{
+			Destroy_Script();
+			return;
+		}
+
+		// No more timers after cleanup
+		if (isCleanedUp)
+			return;
+
+		// Check for being in a vehicle, ladder or elevator for crazy situations
+		if (Get_Vehicle(obj) || obj->As_SoldierGameObj()->Is_On_Ladder() || obj->As_SoldierGameObj()->Is_In_Elevator())
+		{
+			Clean_Up();
+			return;
+		}
+
+		Commands->Start_Timer(obj, this, 0.5f, 0);
+
+		// Keep checking ground distance
+		if (Commands->Is_Something_Below(obj, "ROOTTRANSFORM", 1.5f, false))
+			groundCloseCount++;
+		else
+			groundCloseCount = 0;
+
+		// Set velocity to falling if the paratrooper was blocked for a moment in mid-air
+		auto vel = Get_Velocity(obj);
+		if (vel.Z > -5)
+		{
+			fallFailCount++;
+			Set_Velocity(obj, Vector3(vel.X, vel.Y, -5));
+		}
+		else
+			fallFailCount = 0;
+
+		// Falling is blocked or is standing on the ground, standstill can have -5 velocity. Clean up
+		if (fallFailCount > 4 || groundCloseCount > 4)
+		{
+			Clean_Up();
+		}
+	}
+
+	void Damaged(GameObject *obj, GameObject *damager, float amount) override
+	{
+		if (Commands->Get_Damage_Warhead() == earthWarhead)
+		{
+			Commands->Set_Health(obj, cachedHP);
+			Commands->Set_Shield_Strength(obj, cachedShield);
+			Clean_Up();
+		}
+		else
+		{
+			cachedHP = Commands->Get_Health(obj);
+			cachedShield = Commands->Get_Shield_Strength(obj);
+		}
+	}
+
+	void Clean_Up()
+	{
+		if (!isCleanedUp)
+		{
+			isCleanedUp = true;
+			if (auto soldier = Owner()->As_SoldierGameObj())
+			{
+				soldier->Reset_Grav_Scale();
+			}
+			if (isArmorChanged)
+			{
+				Remove_Script(Owner(), "MS_Switch_Skin_And_Shield_Timer");
+			}
+			Commands->Destroy_Object(Commands->Find_Object(parachuteObjID));
+			Commands->Start_Timer(Owner(), this, 1.0f, -1);
+		}
+	}
+
+	void Killed(GameObject *obj, GameObject *killer) override
+	{
+		Clean_Up();
+	}
+
+	void Destroyed(GameObject *obj) override
+	{
+		Clean_Up();
+	}
+
+	void Detach(GameObject* obj) override
+	{
+		Clean_Up();
+		ScriptImpClass::Detach(obj);
+	}
+};
+REGISTER_SCRIPT(MS_Paradrop,
+	"Parachute_Model:string,"
+	"Parachuting_Skin=null:string,"
+	"Parachuting_Shield=null:string"
+);*/
+
+
+/*!
+* \brief Switch skin and shield
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* When attached to an object, this script will change that object's skin and/or shield type to a new one
+* for a set amount of time. After the time runs out, the original skin and shield types are set on the object again.
+* If the Time is set to 0, the newly set skin and armor types will stay on the object permanently.
+*
+* \param Skin
+*   The skin type to switch to.
+*	When left as "null", the skin type will not be changed.
+* \param Shield
+*   The skin type to switch to.
+*	When left as "null", the shield type will not be changed.
+* \param Time
+*   The time duration for how long the object should wield the new skin and shield types.
+*	When set to 0, the new skin and shield types stay permanently, or until the script is removed from the object.
+*/
+class MS_Switch_Skin_And_Shield_Timer : public ScriptImpClass
+{
+	const char *OriginalSkin;
+	const char *OriginalShield;
+
+	void Created(GameObject* obj) override
+	{
+		OriginalSkin = Get_Skin(obj);
+		OriginalShield = Get_Shield_Type(obj);
+
+		const char *ParamSkin = Get_Parameter("Skin");
+		const char *ParamShield = Get_Parameter("Shield");
+
+		if (strcmp(ParamSkin, "null") != 0)
+		{
+			Set_Skin(obj, ParamSkin);
+		}
+		if (strcmp(ParamShield, "null") != 0)
+		{
+			Commands->Set_Shield_Type(obj, ParamShield);
+		}
+
+		float time = Get_Float_Parameter("Time");
+		if (time != 0)
+		{
+			Commands->Start_Timer(obj, this, time, 1);
+		}
+
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == 1)
+		{
+			Destroy_Script();
+		}
+	}
+
+	void Detach(GameObject* obj)
+	{
+		Set_Skin(obj, OriginalSkin);
+		Commands->Set_Shield_Type(obj, OriginalShield);
+		ScriptImpClass::Detach(obj);
+	}
+};
+REGISTER_SCRIPT(MS_Switch_Skin_And_Shield_Timer, "Skin=null:string,Shield=null:string,Time=0.0:float");
+
+
+/*!
+* \brief Kill passengers on vehicle death
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* When attached to a vehicle, this script will kill the passengers when the vehicle dies.
+* This includes the driver, gunner and all other passengers as well.
+* The passengers can stay visible or become invisible when the vehicle blows up.
+*
+* \param PassengersVisible
+*   The visibility of passengers when the vehicle dies. Set to 0 to make them invisible.
+* \param RemoveScriptsFromPassengers
+*   Should all scripts be removed from passengers or not. Can be useful e.g. if you don't want scripts to play death sounds.
+*/
+class MS_Vehicle_Death_Kill_Passengers : public ScriptImpClass
+{
+	bool PassengersVisible;
+	bool RemoveScriptsFromPassengers;
+
+	void Created(GameObject* obj) override
+	{
+		PassengersVisible = true;
+		if (!obj->As_VehicleGameObj())
+		{
+			Console_Output("[%d:%hs:%hs] Critical Error: This script is only compatible with vehicle game objects. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			Destroy_Script();
+			return;
+		}
+		PassengersVisible = Get_Int_Parameter("PassengersVisible") == 1;
+		RemoveScriptsFromPassengers = Get_Int_Parameter("RemoveScriptsFromPassengers") == 1;
+	}
+
+	void Killed(GameObject *obj, GameObject *killer) override
+	{
+		VehicleGameObj *vObj = obj->As_VehicleGameObj();
+		// Kill occupants
+		for (int i = 0; i < vObj->Get_Occupant_Count(); i++)
+		{
+			SoldierGameObj *passenger = vObj->Get_Occupant(i);
+			if (passenger)
+			{
+				if (!PassengersVisible)
+					Commands->Set_Model(passenger, "S_A_HUMAN");
+				if (RemoveScriptsFromPassengers)
+					Remove_All_Scripts(passenger);
+
+				Toggle_Fly_Mode(passenger);
+				Commands->Apply_Damage(passenger, 99999, "Death", killer);
+			}
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Vehicle_Death_Kill_Passengers, "PassengersVisible=1:int,RemoveScriptsFromPassengers=0:int");
+
+
+
+/*!
+* \brief Send custom when powerup is collected by a member of set team
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* When attached to a powerup object, this script can send a custom if collected by a
+* member of the team specified in the Team parameter.
+* Credits go to Jerad2142 for the base of the script.
+*
+* \param ID
+*   The ID of the object to send the custom message to.
+*   0 sends to self.
+*   -1 sends back to the sender.
+* \param Custom
+*   The custom message that will be sent to the object defined in the ID.
+* \param Param
+*   The parameter of the custom message that will be sent to the object defined in the ID.
+* \param Delay
+*   How many seconds to wait before the custom is sent to the object defined in the ID.
+* \param Team
+*   Which team should the custom sending trigger on. This is the team of the powerup collector.
+*/
+class MS_Powerup_Send_Custom_Team : public ScriptImpClass
+{
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (Get_Object_Type(sender) != Get_Int_Parameter("Team"))
+			return;
+
+		if (type == CUSTOM_EVENT_POWERUP_GRANTED)
+		{
+			int id = Get_Int_Parameter("ID");
+			GameObject *receiver = id ? (id == -1 ? sender : Commands->Find_Object(id)) : obj;
+			if (receiver)
+				Commands->Send_Custom_Event(obj, receiver, Get_Int_Parameter("Custom"), Get_Int_Parameter("Param"), Get_Float_Parameter("Delay"));
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Powerup_Send_Custom_Team, "ID=0:int,Custom=0:int,Param=0:int,Delay=0.0:float,Team=0:int");
+
+
+
+/*------------------------
+Building Scripts
+--------------------------*/
+
+
+/*!
+* \brief BuildingScripts Weakpoint Objective Marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Attach this script to minor weakpoints as an extension script to create different
+* objective marker objects depending on the building's state. 
+* The objects are attached to a bone named "objective".
+* All objects are deleted when the building is destroyed.
+*
+* \param OnlinePreset
+*   The preset to attach when the building is online.
+* \param OfflinePreset
+*   The preset to attach when the building is offline.
+*/
+/*class MS_BuildingScripts_Weakpoint_ObjMarker : public ScriptImpClass
+{
+	int ObjectID = 0;
+
+	void Created(GameObject* obj) override
+	{
+		Create_Objective_Marker(obj, Get_Parameter("OnlinePreset"));
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_BUILDINGSCRIPTS_BUILDINGONLINE)
+		{
+			Create_Objective_Marker(obj, Get_Parameter("OnlinePreset"));
+		}
+		else if (type == CUSTOM_BUILDINGSCRIPTS_BUILDINGOFFLINE)
+		{
+			Create_Objective_Marker(obj, Get_Parameter("OfflinePreset"));
+		}
+		else if (type == CUSTOM_BUILDINGSCRIPTS_BUILDINGDESTROYED)
+		{
+			Create_Objective_Marker(obj, nullptr);
+		}
+	}
+
+	void Create_Objective_Marker(GameObject *obj, const char *presetName)
+	{
+		// Destroy previous object if exists
+		if (ObjectID)
+		{
+			Commands->Destroy_Object(Commands->Find_Object(ObjectID));
+			ObjectID = 0;
+		}
+
+		if (presetName == nullptr || !Is_Valid_Preset(presetName))
+			return;
+
+		GameObject *object = Commands->Create_Object(presetName, Commands->Get_Position(obj));
+		Commands->Set_Facing(object, Commands->Get_Facing(obj));
+		Commands->Attach_To_Object_Bone(object, obj, "objective");
+		ObjectID = Commands->Get_ID(object);
+	}
+};
+REGISTER_SCRIPT(MS_BuildingScripts_Weakpoint_ObjMarker, "OnlinePreset=null:string,OfflinePreset=null:string");*/
+
+
+
+/*------------------------
+Anti Refill Script
+--------------------------*/
+
+
+/*!
+* \brief Disable refilling after damage
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Place this script on infantry presets that should not be able to refill for a few
+* seconds after taking damage.
+* Setting the time to 0 means no refill until the script is removed somehow.
+*
+* \param NoRefillTime
+*   For how many seconds should the soldier not be able to refill after taking damage.
+*/
+/*class MS_Damaged_No_Refill : public ScriptImpClass
+{
+	int time = 0;
+	bool originalCanRefill = true;
+	bool checktimerRunning = false;
+	int lastAttackerID = 0;
+
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_SoldierGameObj())
+		{
+			Commands->Console_Output(L"[%d:%hs:%hs] Critical Error: Only objects of type SoldierGameObj can use the MS_Damaged_No_Refill script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			Destroy_Script();
+			return;
+		}
+
+		time = Get_Int_Parameter("NoRefillTime");
+		originalCanRefill = obj->As_SoldierGameObj()->Can_Refill();
+	}
+
+	void Damaged(GameObject *obj, GameObject *damager, float amount) override
+	{
+		// Only accept positive damage from soldiers
+		if (amount <= 0 || !damager || !damager->As_SoldierGameObj() || Get_Vehicle(damager))
+		{
+			return;
+		}
+
+		lastAttackerID = damager->Get_ID();
+		if (!checktimerRunning)
+		{
+			checktimerRunning = true;
+			Commands->Start_Timer(obj, this, 1.0f, 2);
+		}
+
+		// Restart current timer if damage arrives during countdown
+		if (Commands->Has_Timer(obj, this, 1))
+		{
+			Commands->Stop_Timer2(obj, this, 1);
+		}
+		else
+		{
+			obj->As_SoldierGameObj()->Set_Can_Refill(false);
+		}
+
+		if (time != 0)
+		{
+			Commands->Start_Timer(obj, this, (float)time, 1);
+		}
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == 1 || !Commands->Find_Object(lastAttackerID))
+		{
+			obj->As_SoldierGameObj()->Set_Can_Refill(originalCanRefill);
+			checktimerRunning = false;
+		}
+		else
+		{
+			Commands->Start_Timer(obj, this, 1.0f, 2);
+		}
+	}
+
+	void Detach(GameObject* obj)
+	{
+		if (obj->As_SoldierGameObj())
+			obj->As_SoldierGameObj()->Set_Can_Refill(originalCanRefill);
+
+		ScriptImpClass::Detach(obj);
+	}
+};
+REGISTER_SCRIPT(MS_Damaged_No_Refill,
+	"NoRefillTime=5:int"
+);*/
+
+
+/*------------------------
+Target Painter Scripts
+--------------------------*/
+
+
+/*!
+* \brief Target Painter Host
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Place this script on objects that can "place" target markers on anything on keypress.
+*
+* \param None
+*   None
+*/
+/*class MS_Target_Painter_Host : public JFW_Key_Hook_Base
+{
+	int targetID = 0;
+	int playerID = 0;
+	bool canPaint = true;
+
+	void Created(GameObject *obj) override
+	{
+		if (!obj->As_SmartGameObj()->Is_Human_Controlled())
+		{
+			Destroy_Script();
+			return;
+		}
+
+		playerID = Get_Player_ID(obj);
+		InstallHook("MarkTarget", obj);
+
+		// Make sure radio bot commands are initiated
+		MS_AccessHelper::Init_AI_Global_Controller();
+	}
+
+	void KeyHook()
+	{
+		if (canPaint)
+		{
+			// This makes sure the painter cannot spam markers in the 1.5 second before the created object reports back
+			canPaint = false;
+			Commands->Start_Timer(Owner(), this, 4.0f, 0);
+		}
+
+		// Do not spam markers, also makes sure the painter object only exists once
+		if (auto targetObj = Commands->Find_Object(targetID))
+		{
+			Commands->Set_Model(targetObj, "NULL");
+			Commands->Send_Custom_Event(Owner(), targetObj, CUSTOM_TARGETPAINTER_TARGET_OBJECT_DESTROY_SELF, 0, 3.0f);
+		}
+
+		SmartGameObj *muzzle_obj = Owner()->As_SmartGameObj();
+		auto createPos = muzzle_obj->As_ArmedGameObj()->Get_Muzzle();
+
+		Commands->Fire_C4("Experimental_Ammo_Target_Marker", Owner()->As_SoldierGameObj(), 1, createPos);
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		canPaint = true;
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_TARGETPAINTER_TARGET_OBJECT_CREATED)
+		{
+			targetID = sender->Get_ID();
+
+			int radio_index = Radio_Message_Type::TAKE_THE_POINT;
+			int custom = CUSTOM_AI_ORDER_MOVE;
+			int orderObjID = sender->Get_ID();
+			int team = Get_Object_Type(obj);
+
+			GameObject *target = Commands->Find_Object(param);
+			if (target)
+			{
+
+				int targetTeam = Get_Object_Type(target);
+
+
+				custom = targetTeam == team ? CUSTOM_AI_ORDER_DEFEND : CUSTOM_AI_ORDER_ATTACK;
+				radio_index = targetTeam == team ? Radio_Message_Type::DEFEND_THAT_STRUCTURE : Radio_Message_Type::ATTACK_THAT_STRUCTURE;
+
+				// Vehicle
+				if (auto targetVeh = target->As_VehicleGameObj())
+				{
+					orderObjID = targetVeh->Get_ID();
+					// Not turret
+					if (!targetVeh->Is_Turret())
+					{
+						// TODO: what if it's friendly?!
+						radio_index = Radio_Message_Type::DESTROY_THAT_VEHICLE;
+
+						if (Is_Harvester_Preset(targetVeh))
+							radio_index = targetTeam == team ? Radio_Message_Type::DEFEND_THE_HARVESTER : Radio_Message_Type::ATTACK_THE_HARVESTER;
+					}
+				}
+				else if (target->As_SoldierGameObj())
+				{
+					radio_index = Radio_Message_Type::DESTROY_IT_NOW;
+					orderObjID = target->Get_ID();
+				}
+			}
+
+			char params[256];
+			snprintf(params, sizeof(params), "%d,%d,%d,%d,%d,%d,%d", team, orderObjID, 10, 1000, 1000, 1000, 1000);
+			Commands->Attach_Script((target ? target : sender), "dp88_AI_Objective", params);
+
+			int stringID = Commands->Get_Radio_Command_String(radio_index, team);
+			Commands->Send_Client_Announcement(team, playerID, stringID, ANNOUNCE_TEAM, radio_index, true, true, true);
+
+			// Work with bots
+			MS_AccessHelper::Receive_Radio_Command(obj, stringID, custom, orderObjID);
+		}
+	}
+
+};
+REGISTER_SCRIPT(MS_Target_Painter_Host, "");*/
+
+
+/*!
+* \brief Target Painted Object
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Do not place this on anything manually!
+*
+* \param None
+*   None
+*/
+/*class MS_Target_Painted_Object : public ScriptImpClass
+{
+	void Created(GameObject *obj) override
+	{
+		Commands->Start_Timer(obj, this, 1.5f, 0);
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_TARGETPAINTER_TARGET_OBJECT_DESTROY_SELF)
+			Commands->Destroy_Object(obj);
+
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (auto owner = Get_C4_Planter(obj))
+		{
+			Commands->Send_Custom_Event(obj, owner, CUSTOM_TARGETPAINTER_TARGET_OBJECT_CREATED, Commands->Get_ID(Get_C4_Attached(obj)), 0.0f);
+		}
+		else
+			Commands->Destroy_Object(obj);
+	}
+};
+REGISTER_SCRIPT(MS_Target_Painted_Object, "");*/
+
+/*!
+* \brief A jukebox script that can play a music list, shuffling it.
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Attach this script to a Sound Obj preset (inside Object in the preset list).
+* Then set up its parameters to have a shuffling music player on your map.
+*
+* \param MusicTwiddler
+*   The preset name of a sound twiddler that contains non-repeating music tracks (Loop Count must be 1, Is 3D Sound OFF).
+* \param NextMusicAnnouncePrefix
+*   If set, this text will be displayed in front of the music preset name (E.g. *Now playing:* -> "Now playing: Hell March")
+* \param Red/Blue/Green
+*   The color of the message set in NextMusicAnnouncePrefix.
+* \param ScoreScreenMusicPreset
+*   The preset name of the music that will be played when the game ends and the score screen is shown.
+*/
+/*class MS_Jukebox : public ScriptImpClass
+{
+	int currentMusic = 0;
+	int prevMusic = 0;
+	bool firstTime = true;
+	static MS_Jukebox *current;
+	static const char *scoreMusic;
+	DynamicVectorClass<int> fullMusicList;
+
+	void Created(GameObject *obj) override
+	{
+	    if (!Is_Valid_Preset(Get_Parameter("MusicTwiddler")))
+		{
+			Commands->Console_Output(L"[%d:%hs:%hs] Critical Error: '%hs' is not a valid preset. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), Get_Parameter("MusicTwiddler"));
+			Destroy_Script();
+			return;
+		}
+
+		auto fullList = ((TwiddlerClass*)DefinitionMgrClass::Find_Named_Definition(Get_Parameter("MusicTwiddler"), false))->Get_Definition_List();
+		for (int i = 0; i < fullList.Count(); i++)
+		{
+			int musicID = DefinitionMgrClass::Find_Definition(fullList[i], false)->Get_ID();
+			fullMusicList.Add(musicID);
+		}
+
+		// Handle empty list case
+		if (fullMusicList.Count() > 0)
+		{
+			Commands->Start_Timer(obj, this, Get_Float_Parameter("Delay"), 1);
+		}
+		current = this;
+		scoreMusic = Get_Parameter("ScoreScreenMusicPreset");
+		Commands->AddGameOverHook(GameOverHook);
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_EVENT_SOUND_ENDED)
+		{
+			if (!Get_Int_Parameter("NoLoop"))
+			{
+				PlayNext();
+			}
+		}
+	}
+
+	void PlayNext()
+	{
+		// Remove current and previous music from list
+		DynamicVectorClass<int> musicList;
+		for (int i = 0; i < fullMusicList.Count(); i++)
+		{
+			if (fullMusicList.Count() == 1 ||
+				((fullMusicList.Count() == 2 || fullMusicList.Count() == 3) && fullMusicList[i] != currentMusic) ||
+				(fullMusicList.Count() > 3 && fullMusicList[i] != currentMusic && fullMusicList[i] != prevMusic))
+			{
+				musicList.Add(fullMusicList[i]);
+			}
+		}
+
+		prevMusic = currentMusic;
+		if (!firstTime || !Get_Int_Parameter("FirstSong"))
+		{
+			currentMusic = musicList[FreeRandom.Get_Int(musicList.Count())];
+		}
+		else
+		{
+			firstTime = false;
+			currentMusic = musicList[0];
+		}
+		SoundGameObj *s = Owner()->As_SoundGameObj();
+		if (s)
+		{
+			s->Set_Sound(currentMusic);
+
+			// Display text if prefix is set
+			if (strlen(Get_Parameter("NextMusicAnnouncePrefix")) != 0)
+			{
+				DynamicVectorClass<WideStringClass> parameters;
+				parameters.Add(Get_Parameter("NextMusicAnnouncePrefix"));
+				parameters.Add(Get_Definition_Name(currentMusic));
+				Send_Message(Get_Int_Parameter("Red"), Get_Int_Parameter("Green"), Get_Int_Parameter("Blue"), "%s %s", parameters);
+			}
+		}
+	}
+
+	void Detach(GameObject* obj) override
+	{
+		ScriptImpClass::Detach(obj);
+		current = nullptr;
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		PlayNext();
+	}
+
+	static void GameOverHook()
+	{
+		if (current)
+		{
+			SoundGameObj *s = current->Owner()->As_SoundGameObj();
+			if (s)
+			{
+				s->Set_Sound(0);
+				if (strcmp(scoreMusic, "") != 0)
+				{
+					Commands->Create_2D_Sound(scoreMusic);
+				}
+			}
+		}
+	}
+};
+MS_Jukebox *MS_Jukebox::current = nullptr;
+const char *MS_Jukebox::scoreMusic = "";
+REGISTER_SCRIPT(MS_Jukebox, "MusicTwiddler:string,NextMusicAnnouncePrefix:string,Red=255:int,Blue=255:int,Green=255:int,ScoreScreenMusicPreset:string,NoLoop:int,Delay:float,FirstSong:int");*/
+
+
+/*------------------------
+Damage by poke scripts
+--------------------------*/
+
+
+/*!
+* \brief Damage or heal MS_Damage_By_Poke_Target targets by poking
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Place this script on soldier objects to instantly damage/heal objects
+* that have MS_Damage_By_Poke_Target attached.
+*
+* \param Cooldown
+*   How many seconds must pass between the poking damage/heal ability usages
+* \param Warhead
+*   What warhead to use for damaging/healing
+* \param Damage
+*   How much damage to do to the poked object
+*/
+/*class MS_Damage_By_Poke_Poker : public ScriptImpClass
+{
+	bool canRepair = true;
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == 0)
+			canRepair = true;
+	}
+
+public:
+	bool Poke()
+	{
+		if (canRepair)
+		{
+			if (Get_Int_Parameter("Cooldown") > 0)
+			{
+				canRepair = false;
+				Commands->Start_Timer(Owner(), this, (float)Get_Int_Parameter("Cooldown"), 0);
+			}
+			return true;
+		}
+		else
+		{
+			StringClass str = "IDS_ABILITYCOOLDOWN";
+			Send_Message_Player(Owner(), DP88_RGB_GENERAL_MSG, str);
+			return false;
+		}
+	}
+
+	const char *Get_Warhead()
+	{
+		return Get_Parameter("Warhead");
+	}
+
+	int Get_Damage()
+	{
+		return Get_Int_Parameter("Damage");
+	}
+};
+REGISTER_SCRIPT(MS_Damage_By_Poke_Poker, "Cooldown=60:int,Warhead=None:string,Damage=5000:int");*/
+
+
+/*!
+* \brief Get damaged or healed if poked by MS_Damage_By_Poke_Poker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* Place this script on objects that can be instantly damaged/healed by a 
+* soldier object with MS_Damage_By_Poke_Poker attached to it.
+*
+* \param None
+*   None
+*/
+/*class MS_Damage_By_Poke_Target : public ScriptImpClass
+{
+	void Poked(GameObject *obj, GameObject *poker) override
+	{
+		if (auto script = (MS_Damage_By_Poke_Poker *)(Find_Script_On_Object(poker, "MS_Damage_By_Poke_Poker")))
+		{
+			if (script->Poke())
+			{
+				// Instant damage/repair!
+				Commands->Apply_Damage(obj, (float)script->Get_Damage(), script->Get_Warhead(), poker);
+			}
+		}
+	}
+};
+REGISTER_SCRIPT(MS_Damage_By_Poke_Target, "");*/
+
+
+/*!
+* \brief Remove key on character change
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+*
+* This script removes a key from the soldier it's attached to when the player changes character.
+*
+* \param Key
+*   The key code to remove.
+*/
+/*class MS_Destroyed_Remove_Key : public ScriptImpClass
+{
+	void Destroyed(GameObject *obj) override
+	{
+		Commands->Grant_Key(obj, Get_Int_Parameter("Key"), false);
+	}
+};
+REGISTER_SCRIPT(MS_Destroyed_Remove_Key, "Key=0:int");*/
diff -urN sourceold/scripts/ms.h source/scripts/ms.h
--- sourceold/scripts/ms.h	1970-01-01 10:00:00.000000000 +1000
+++ source/scripts/ms.h	2020-04-03 08:27:17.955761900 +1000
@@ -0,0 +1,153 @@
+#pragma once
+#include "scripts.h"
+
+
+#define MS_BASE                                          0xDA000000
+
+#define CUSTOM                                           0x00010000
+#define TIMER                                            0x00020000
+#define ACTION                                           0x00030000
+
+#define CUSTOM_TARGETPAINTER_TARGET_OBJECT_CREATED       (MS_BASE|CUSTOM|100201)
+#define CUSTOM_TARGETPAINTER_TARGET_OBJECT_DESTROY_SELF  (MS_BASE|CUSTOM|100202)
+
+
+#define CUSTOM_AI_SOLDIER_DIED                           (MS_BASE|CUSTOM|100501)
+#define CUSTOM_AI_VEHICLE_DIED                           (MS_BASE|CUSTOM|100502)
+#define CUSTOM_AI_GO_TO_VEHICLE                          (MS_BASE|CUSTOM|100503)
+#define CUSTOM_AI_ARRIVED_TO_VEHICLE                     (MS_BASE|CUSTOM|100504)
+#define CUSTOM_AI_VEHICLE_REQUEST_ENTER                  (MS_BASE|CUSTOM|100505)
+#define CUSTOM_AI_VEHICLE_EXITED                         (MS_BASE|CUSTOM|100506)
+#define CUSTOM_AI_REQUEST_REFUND                         (MS_BASE|CUSTOM|100507)
+#define CUSTOM_AI_TRANSPORT_ARRIVED                      (MS_BASE|CUSTOM|100508)
+#define CUSTOM_AI_VEHICLE_AMMO_EMPTY                     (MS_BASE|CUSTOM|100509)
+#define CUSTOM_AI_CHECK_REPAIR_NEEDED                    (MS_BASE|CUSTOM|100510)
+#define CUSTOM_AI_GO_TO_REFILL                           (MS_BASE|CUSTOM|100511)
+#define CUSTOM_AI_GO_TO_RELOAD                           (MS_BASE|CUSTOM|100512)
+#define CUSTOM_AI_GO_TO_EXIT_VEHICLE                     (MS_BASE|CUSTOM|100513)
+#define CUSTOM_AI_ARRIVED_TO_REFILL                      (MS_BASE|CUSTOM|100514)
+#define CUSTOM_AI_ARRIVED_TO_EXIT_VEHICLE                (MS_BASE|CUSTOM|100515)// TODO: use generic ARRIVED_TO_SECONDARY_TASK custom.
+#define CUSTOM_AI_REFILL_COMPLETE                        (MS_BASE|CUSTOM|100516)
+#define CUSTOM_AI_SEARCH_FOR_VEHICLE                     (MS_BASE|CUSTOM|100517)
+#define CUSTOM_AI_DEACTIVATE_AI                          (MS_BASE|CUSTOM|100518)
+#define CUSTOM_AI_GO_SWITCH_OBJECTIVE                    (MS_BASE|CUSTOM|100519)
+#define CUSTOM_AI_REQUEST_PATROL_POINT                   (MS_BASE|CUSTOM|100520)
+#define CUSTOM_AI_GO_TO_PATROL_POINT                     (MS_BASE|CUSTOM|100521)
+#define CUSTOM_AI_CHECK_GO_HOME                          (MS_BASE|CUSTOM|100522)
+#define CUSTOM_AI_SEND_PUBLIC_MESSAGE                    (MS_BASE|CUSTOM|100523)
+#define CUSTOM_AI_ADD_RADAR_TARGET                       (MS_BASE|CUSTOM|100524)
+#define CUSTOM_AI_CHECK_RADAR_TARGETS                    (MS_BASE|CUSTOM|100525)
+#define CUSTOM_AI_ENEMY_KILLED                           (MS_BASE|CUSTOM|100526)
+#define CUSTOM_AI_ADD_ALLY_TARGET                        (MS_BASE|CUSTOM|100527)
+#define CUSTOM_AI_REPAIR_NEEDED                          (MS_BASE|CUSTOM|100528)
+#define CUSTOM_AI_REPAIR_COMPLETE                        (MS_BASE|CUSTOM|100529)
+
+#define CUSTOM_AI_COOP_NEXT_RALLYPOINT                   (MS_BASE|CUSTOM|100601)
+#define CUSTOM_AI_COOP_ARRIVED_TO_RALLYPOINT             (MS_BASE|CUSTOM|100602)
+#define CUSTOM_AI_COOP_GROUPRUSH_COMPLETE                (MS_BASE|CUSTOM|100603)
+#define CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE          (MS_BASE|CUSTOM|100604)
+
+#define CUSTOM_AI_COOP_PLAYER_RADIO_MESSAGE              (MS_BASE|CUSTOM|100701)
+#define CUSTOM_AI_COOP_PLAYER_FOLLOW                     (MS_BASE|CUSTOM|100702)
+#define CUSTOM_AI_COOP_PLAYER_FOLLOW_COVER               (MS_BASE|CUSTOM|100703)
+#define CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP                (MS_BASE|CUSTOM|100704)
+#define CUSTOM_AI_COOP_PLAYER_DEFEND_BASE                (MS_BASE|CUSTOM|100705)
+#define CUSTOM_AI_COOP_PLAYER_CREATE_OBJECTIVE           (MS_BASE|CUSTOM|100706)
+#define CUSTOM_AI_COOP_PLAYER_DESTROY_OBJECTIVE          (MS_BASE|CUSTOM|100707)
+
+#define CUSTOM_AI_ORDER_MOVE                             (MS_BASE|CUSTOM|100801)
+#define CUSTOM_AI_ORDER_ATTACK                           (MS_BASE|CUSTOM|100802)
+#define CUSTOM_AI_ORDER_DEFEND                           (MS_BASE|CUSTOM|100803)
+
+#define TIMER_AI_CONTROLLER_PLAYERCHECK                  (MS_BASE|TIMER|100501)
+#define TIMER_AI_OBJECT_SPAWN_COUNTDOWN                  (MS_BASE|TIMER|100502)
+#define TIMER_AI_OBJECT_SPAWN_GHOSTING_COUNTDOWN         (MS_BASE|TIMER|100503)
+#define TIMER_AI_OBJECT_ACTIVATE_COUNTDOWN               (MS_BASE|TIMER|100504)
+#define TIMER_AI_WAIT_FOR_VEHICLE_CONSTRUCT              (MS_BASE|TIMER|100505)
+#define TIMER_AI_GO_TO_VEHICLE_TIMEOUT                   (MS_BASE|TIMER|100506)
+#define TIMER_AI_UNDER_ATTACK                            (MS_BASE|TIMER|100507)
+#define TIMER_AI_REQUEST_DEPLOY                          (MS_BASE|TIMER|100508)
+#define TIMER_AI_POST_CREATED                            (MS_BASE|TIMER|100509)
+#define TIMER_AI_SEARCH_FOR_VEHICLE                      (MS_BASE|TIMER|100510)
+#define TIMER_AI_REFILL_COOLDOWN                         (MS_BASE|TIMER|100511)
+#define TIMER_AI_DEFEND                                  (MS_BASE|TIMER|100512)
+
+#define TIMER_AI_CHECK_FOR_BLOCK                         (MS_BASE|TIMER|100551)
+#define TIMER_AI_CHECK_GET_AROUND_TIMEOUT                (MS_BASE|TIMER|100552)
+#define TIMER_AI_CHECK_UNSTUCK_TIMEOUT                   (MS_BASE|TIMER|100553)
+#define TIMER_AI_CHECK_DISTANCE                          (MS_BASE|TIMER|100554)
+#define TIMER_AI_CHECK_RADAR                             (MS_BASE|TIMER|100555)
+#define TIMER_AI_CHECK_ALLIES                            (MS_BASE|TIMER|100556)
+
+#define TIMER_AI_COOP_RETRY_REQUEST                      (MS_BASE|TIMER|100601)
+#define TIMER_AI_COOP_GROUP_WAIT_TEAM_0                  (MS_BASE|TIMER|100602)
+#define TIMER_AI_COOP_GROUP_WAIT_TEAM_1                  (MS_BASE|TIMER|100603)
+#define TIMER_AI_COOP_PAUSE_INITIATION                   (MS_BASE|TIMER|100604)
+
+#define ACTION_AI_GO_TO_VEHICLE                          (MS_BASE|ACTION|100001)
+#define ACTION_AI_GET_AROUND_REVERSE                     (MS_BASE|ACTION|100002)
+#define ACTION_AI_GET_AROUND_FORWARD                     (MS_BASE|ACTION|100003)
+#define ACTION_AI_RANDOM_MOVING                          (MS_BASE|ACTION|100004)
+
+#define DEFAULT_BOT_NAME                                 "Bot"
+
+#define MS_RGB_JOINLEFT_MSG                              178,178,178     // grey
+#define MS_RGB_DEBUG                                     255,255,255
+
+class Radio_Message_Type
+{
+public:
+	static int BUILDING_NEEDS_REPAIRS;
+	static int GET_IN_THE_VEHICLE;
+	static int GET_OUT_OF_THE_VEHICLE;
+	static int DESTROY_THAT_VEHICLE;
+	static int DONT_GET_IN_MY_WAY;
+	static int AFFIRMATIVE;
+	static int NEGATIVE;
+
+	static int TAKE_THE_POINT;
+	static int MOVE_OUT;
+	static int FOLLOW_ME;
+	static int COVER_ME;
+	static int DESTROY_IT_NOW;
+
+	static int ATTACK_THE_HARVESTER;
+	static int ATTACK_THAT_STRUCTURE;
+	static int DEFEND_THE_BASE;
+	static int DEFEND_THE_HARVESTER;
+	static int DEFEND_THAT_STRUCTURE;
+};
+
+
+class MS_AccessHelper
+{
+public:
+	static void Init_AI_Global_Controller();
+	static int Get_AI_Player_Controllers_Count(int team);
+	static void Give_Bot_Credits(int team, float amount);
+	static float Steal_Bot_Credits(int team, float percentage);
+	static void Receive_Radio_Command(GameObject *sender, int AnnouncementID, int orderType = 0, int orderObjID = 0);
+	static void Send_Radio_Order_To_Bot(GameObject *sender, GameObject *receiver, int radioIndex, int orderType = 0, int orderObjID = 0);
+	static float Get_Money(GameObject *object);
+	static void Give_Money(GameObject *object, float money);
+	static void Activate_Defense_Protocol(int team);
+};
+
+
+class MS_AI_Rally_Point : public ScriptImpClass
+{
+	void Created(GameObject *obj) override;
+	void Destroyed(GameObject *obj) override;
+public:
+	int Get_Team();
+	int Get_NodeGroup();
+	int Get_NodeNum();
+	bool Get_IsInfantryOnly();
+	GameObject *Get_Object();
+};
+
+class MS_AI_Patrol_Point : public MS_AI_Rally_Point
+{
+	void Created(GameObject *obj) override;
+	void Destroyed(GameObject *obj) override;
+};
diff -urN sourceold/scripts/ms_ai.cpp source/scripts/ms_ai.cpp
--- sourceold/scripts/ms_ai.cpp	1970-01-01 10:00:00.000000000 +1000
+++ source/scripts/ms_ai.cpp	2020-09-26 08:59:57.401120700 +1000
@@ -0,0 +1,8339 @@
+#include "general.h"
+#include "ms.h"
+#include "PurchaseSettingsDefClass.h"
+#include "VehicleGameObj.h"
+#include "BeaconGameObj.h"
+#include "BeaconGameObjDef.h"
+#include "C4GameObj.h"
+#include "DefinitionMgrClass.h"
+#include "GameObjManager.h"
+#include "MoveablePhysClass.h"
+#include "objectlibrarymanager.h"
+#include "PowerUpGameObjDef.h"
+#include "Random2Class.h"
+#include "RefineryGameObj.h"
+#include "SoldierGameObjDef.h"
+#include "TwiddlerClass.h"
+#include "WeaponClass.h"
+#include "WeaponBagClass.h"
+#include "dp88_ar.h"
+#include "dp88_custom_timer_defines.h"
+#include "engine_game.h"
+#include "engine_script.h"
+#include "engine_obj.h"
+#include "engine_obj2.h"
+#include "engine_player.h"
+#include "engine_def.h"
+#include "engine_tdb.h"
+#include "engine_pt.h"
+#include "engine_tt.h"
+#include <unordered_map>
+//#include "cgamedata.h"
+//#include "iniclass.h"
+#include "cPlayer.h"
+#define REGISTER_SCRIPT(name, params) ScriptRegistrant<name> name##Registrant(#name, params);
+
+
+int Radio_Message_Type::BUILDING_NEEDS_REPAIRS;
+int Radio_Message_Type::GET_IN_THE_VEHICLE;
+int Radio_Message_Type::GET_OUT_OF_THE_VEHICLE;
+int Radio_Message_Type::DESTROY_THAT_VEHICLE;
+int Radio_Message_Type::DONT_GET_IN_MY_WAY;
+int Radio_Message_Type::AFFIRMATIVE;
+int Radio_Message_Type::NEGATIVE;
+
+int Radio_Message_Type::TAKE_THE_POINT;
+int Radio_Message_Type::MOVE_OUT;
+int Radio_Message_Type::FOLLOW_ME;
+int Radio_Message_Type::COVER_ME;
+int Radio_Message_Type::DESTROY_IT_NOW;
+
+int Radio_Message_Type::ATTACK_THE_HARVESTER;
+int Radio_Message_Type::ATTACK_THAT_STRUCTURE;
+int Radio_Message_Type::DEFEND_THE_BASE;
+int Radio_Message_Type::DEFEND_THE_HARVESTER;
+int Radio_Message_Type::DEFEND_THAT_STRUCTURE;
+
+
+
+/*------------------------
+Branch differences to eliminate false diff positives
+--------------------------*/
+
+
+inline void M_Console_Input(const char *Input)
+{
+	Console_Input(Input);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+inline void M_Console_Output(const char *Output)
+{
+	Console_Output(Output);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+inline bool M_Has_Timer(GameObject* obj, ScriptClass* script, int number)
+{
+	return Has_Timer(obj, script, number);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+inline void M_Stop_Timer2(GameObject* obj, ScriptClass* script, int number)
+{
+	Stop_Timer2(obj, script, number);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+inline bool M_Test_Raycast_Collision(const Vector3 &pos1, const Vector3 &pos2, const bool checkDynamicObjects, Vector3 *contactPoint = nullptr, GameObject *compareObj = nullptr)
+{
+	Vector3 newPoint;
+	if (!contactPoint)
+		contactPoint = &newPoint;
+
+	return Test_Raycast_Collision(pos1, pos2, checkDynamicObjects, contactPoint, compareObj);
+}
+
+
+/*------------------------
+Non script-specific functions
+--------------------------*/
+
+static bool DebugPathfindFailMessagesLiveEnabled = false;
+static bool DebugPathfindFailMessagesFileEnabled = false;
+
+void Print_Debug(const StringClass& text)
+{
+	if (DebugPathfindFailMessagesFileEnabled) {
+		Write_File_Async("_debug_bots.txt", text.Peek_Buffer());
+		Write_File_Async("_debug_bots.txt", "\n");
+	}
+
+	if (DebugPathfindFailMessagesLiveEnabled) {
+		M_Console_Input("msg " + text);
+	}
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool Can_Move_To(GameObject *obj, Vector3 position)
+{
+	MoveablePhysClass* phys = obj->As_PhysicalGameObj() ? obj->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : nullptr;
+	if (phys && phys->Can_Teleport(Matrix3D(position)))
+		return true;
+	return false;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+// param cos_cone_behind - 0 is everything behind the middle line. Set this higher (in rad) to restrict "behind" more.
+bool Is_Behind_Cone(const Matrix3D &transform, const Vector3 &target_location, float cos_cone_behind = 0)
+{
+	auto to_target = Normalize(target_location - transform.Get_Translation());
+	float dot = transform.Get_X_Vector() * to_target;
+	return dot < -cos_cone_behind;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+float Calculate_Relative_Facing_Diff(GameObject *obj, Vector3 targetLocation)
+{
+	Matrix3D m1 = obj->As_SmartGameObj()->Get_Transform();
+	Matrix3D currentTM = Matrix3D::Identity;
+	currentTM.Rotate_Z(m1.Get_Z_Rotation());
+	currentTM.Set_Translation(m1.Get_Translation());
+
+	Vector3 out;
+	Matrix3D::Inverse_Transform_Vector(currentTM, targetLocation, &out);
+	float x = out.X;
+	float y = out.Y;
+	float f = atan2(y, x);
+	float result = WWMath::Wrap(f, 0, 6.2831855f);
+	result = WWMath::Wrap(result, -3.1415927f, 3.1415927f);
+
+	return result;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool Get_Random_Pathfind_Spot_Around_Position(GameObject *obj, const Vector3 &centerPos, float minDist, float maxDist, Vector3 *returnPos, GameObject *target = nullptr)
+{
+	// Default to centerpos if all else fails
+	*returnPos = centerPos;
+
+	Vector3 pathfindPos;
+	// Check if position is in a sector
+	if (!Get_Random_Pathfind_Spot(centerPos, maxDist, &pathfindPos))
+	{
+		// Try to get the closest sector mid
+		if (!Get_Closest_Pathfind_Spot(centerPos, 15.0f, &pathfindPos))
+			return false;
+	}
+	else
+		pathfindPos = centerPos;
+
+	int count = 0;
+	while (count < 50)
+	{
+		bool success = Get_Random_Pathfind_Spot(pathfindPos, maxDist, returnPos);
+		if (!success)
+			return false;
+
+		float dist_sq = Vector3::Distance_Squared(pathfindPos, *returnPos);
+		if (dist_sq >= minDist * minDist)
+		{
+			if (obj->As_VehicleGameObj())
+				(*returnPos).Z += 1.5f;
+
+			if (Can_Move_To(obj, *returnPos) && (!target || !M_Test_Raycast_Collision(Commands->Get_Position(obj) + Vector3(0, 0, 1.5f), Commands->Get_Position(target) + Vector3(0, 0, 1.5f), false)))
+			{
+				if (obj->As_VehicleGameObj())
+					(*returnPos).Z -= 1.5f;
+
+				return true;
+			}
+		}
+		count++;
+	}
+	return false;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool Get_Random_Pathfind_Spot_Around_Self(GameObject *obj, float minDist, float maxDist, Vector3 *returnPosition, GameObject* target = nullptr)
+{
+	Vector3 pos = Commands->Get_Position(obj);
+	return Get_Random_Pathfind_Spot_Around_Position(obj, pos, minDist, maxDist, returnPosition, target);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+template <class T>
+DynamicVectorClass<T> Combine_Lists(const DynamicVectorClass<T>& list1, const DynamicVectorClass<T>& list2)
+{
+	DynamicVectorClass<T> result;
+	result.Grow(list1.Count() + list2.Count());
+	result.Add_Multiple(list1);
+	result.Add_Multiple(list2);
+	return result;
+}
+template <class T>
+SimpleDynVecClass<T> Combine_Lists(const SimpleDynVecClass<T>& list1, const SimpleDynVecClass<T>& list2)
+{
+	SimpleDynVecClass<T> result;
+	result.Grow(list1.Count() + list2.Count());
+	result.Add_Multiple(list1);
+	result.Add_Multiple(list2);
+	return result;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+DynamicVectorClass<Vector3> Order_Points_By_Distance(const DynamicVectorClass<Vector3> &inArray, const Vector3 &compareToPoint, float maxDistance)
+{
+	DynamicVectorClass<Vector3> outArray;
+	float maxDistance_sq = maxDistance * maxDistance;
+
+	for (int i = 0; i < inArray.Count(); i++)
+	{
+		const Vector3 &newPoint = inArray[i];
+		const float newDist_sq = Vector3::Distance_Squared(compareToPoint, newPoint);
+
+		// Skip out of range points
+		if (newDist_sq > maxDistance_sq)
+			continue;
+
+		// First element can just go in
+		if (!outArray.Count())
+		{
+			outArray.Add(newPoint);
+			continue;
+		}
+
+		// Search where to insert new point
+		for (int j = 0; j < outArray.Count(); j++)
+		{
+			// Visited point is bigger: insert before it (works with any 1+ sized array)
+			if (newDist_sq < Vector3::Distance_Squared(compareToPoint, outArray[j]))
+			{
+				outArray.Insert(j, newPoint);
+				break;
+			}
+			// No bigger points found, reached the end of the array: add to end of array
+			else if (j == outArray.Count() - 1)
+			{
+				outArray.Add(newPoint);
+				break;
+			}
+		}
+	}
+	return outArray;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void Remove_Stock_AI_Behaviour(GameObject *obj)
+{
+	if (auto soldier = obj->As_SoldierGameObj())
+	{
+		GameObjObserverClass *InnateObserver = obj->As_SoldierGameObj()->Find_Observer("Innate Soldier");
+		if(InnateObserver)
+		{
+			soldier->Remove_Observer(InnateObserver);
+			soldier->Set_Innate_Observer(NULL);
+		}
+	}
+}
+
+// -------------------------------------------------------------------------------------------------
+
+/* Damage of one shot from this weapon against the target, including any explosion component, scaled against target's skin/shield, average between the two */
+float Calculate_Average_Scaled_Damage(WeaponClass* weapon, GameObject* target)
+{
+	ArmorType skin = target->As_DamageableGameObj()->Get_Defense_Object()->Get_Skin();
+	ArmorType shield = target->As_DamageableGameObj()->Get_Defense_Object()->Get_Shield_Type();
+
+	WarheadType awarhead = weapon->PrimaryAmmoDefinition->Warhead();
+	float adamage = weapon->PrimaryAmmoDefinition->Damage();
+	float askinMultiplier = ArmorWarheadManager::Get_Damage_Multiplier(skin, awarhead);
+	float ashieldMultiplier = ArmorWarheadManager::Get_Damage_Multiplier(shield, awarhead);
+
+	float scaledDamage = (askinMultiplier * adamage + ashieldMultiplier * adamage) / 2;
+
+	// Add explosion component damage
+	if (auto expDef = (ExplosionDefinitionClass*)Find_Definition(weapon->PrimaryAmmoDefinition->ExplosionDefID))
+	{
+		WarheadType ewarhead = expDef->Warhead;
+		float edamage = expDef->DamageStrength;
+		float eskinMultiplier = ArmorWarheadManager::Get_Damage_Multiplier(skin, ewarhead);
+		float eshieldMultiplier = ArmorWarheadManager::Get_Damage_Multiplier(shield, ewarhead);
+		scaledDamage += (eskinMultiplier * edamage + eshieldMultiplier * edamage) / 2;
+	}
+
+	// Add spraycount
+	scaledDamage *= weapon->PrimaryAmmoDefinition->SprayCount();
+
+	return scaledDamage;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+/* Calculate the raw Damage Per Second of this weapon. The passed damage parameter can be the scaled damage against a particular target for a more specific DPS calculation. */
+float Calculate_Weapon_DPS(WeaponClass* weapon, float damage)
+{
+	int clipSize = weapon->Get_Definition()->ClipSize();
+	// If clip size is infinite (-1), don't consider reload times into calculation
+	return (clipSize * damage) / ((clipSize / weapon->PrimaryAmmoDefinition->RateOfFire) + (clipSize != -1 ? weapon->Get_Definition()->ReloadTime() + weapon->PrimaryAmmoDefinition->ChargeTime() : 0));
+}
+
+// -------------------------------------------------------------------------------------------------
+
+int Create_Timed_Objective(int team, float seconds, Vector3 position, int range, int type = 0, int attachObjID = 0)
+{
+	auto timedObjective = Commands->Create_Object("Invisible_Object", position);
+	int id = timedObjective->Get_ID();
+
+	int objectiveType = type == 0 ? id : type;
+	StringClass params;
+	params.Format("%d,%d,%d,%d,%d,%d,%d", team, objectiveType, range, 1000, 1000, 1000, 1000);
+	Commands->Attach_Script(timedObjective, "dp88_AI_Objective", params.Peek_Buffer());
+
+	// 0 seconds is forever
+	if (seconds > 0)
+	{
+		params.Format("%.2f,0", seconds);
+		Commands->Attach_Script(timedObjective, "JFW_Destroy_Self_Timer", params.Peek_Buffer());
+	}
+
+	if (auto hostObj = Commands->Find_Object(attachObjID))
+	{
+		Attach_Script_V(hostObj, "JFW_Death_Destroy_Object", "%d", id);
+		Commands->Attach_To_Object_Bone(timedObjective, hostObj, "ROOTTRANSFORM");
+	}
+
+	return id;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class BotPersonality : public RefCountClass
+{
+	float spawnTimeMin, spawnTimeMax;
+	float radioTimeMin, radioTimeMax;
+	float thinkTimeMin, thinkTimeMax;
+	float attackErrorMultiplyMin, attackErrorMultiplyMax;
+	float buyinfChance;
+	float buyvehChance;
+	float teamworkChance;
+	float sprintChance;
+	float jumpChanceRegular;
+	float jumpChanceCombat;
+	float crouchChanceCalm;
+
+	// Skill-independent personality traits
+	float investigateChance;
+	float chatChance;
+	float sympathyVarMin, sympathyVarMax;
+
+public:
+	int personalityType;
+
+	enum INTERVAL {
+		SPAWN_TIME,
+		RADIO_TIME,
+		THINK_TIME,
+		ATTACK_ERROR_MULTIPLIER,
+		SYMPATHY_VARIABLE,
+		BUYINF_CHANCE,
+		BUYVEH_CHANCE,
+		TEAMWORK_CHANCE,
+		SPRINT_CHANCE,
+		JUMP_CHANCE_REGULAR,
+		JUMP_CHANCE_COMBAT,
+		CROUCH_CHANCE_CALM,
+		INVESTIGATE_CHANCE,
+		CHAT_CHANCE,
+	};
+
+	float Get_Random(BotPersonality::INTERVAL intervalType)
+	{
+		switch (intervalType)
+		{
+		case SPAWN_TIME:
+			return FreeRandom.Get_Float(spawnTimeMin, spawnTimeMax);
+		case RADIO_TIME:
+			return FreeRandom.Get_Float(radioTimeMin, radioTimeMax);
+		case THINK_TIME:
+			return FreeRandom.Get_Float(thinkTimeMin, thinkTimeMax);
+		case ATTACK_ERROR_MULTIPLIER:
+			return FreeRandom.Get_Float(attackErrorMultiplyMin, attackErrorMultiplyMax) + 1;
+		case SYMPATHY_VARIABLE:
+			return FreeRandom.Get_Float(sympathyVarMin, sympathyVarMax);
+		case BUYINF_CHANCE:
+			return FreeRandom.Get_Float() < buyinfChance ? 1.0f : 0.0f;
+		case BUYVEH_CHANCE:
+			return FreeRandom.Get_Float() < buyvehChance ? 1.0f : 0.0f;
+		case TEAMWORK_CHANCE:
+			return FreeRandom.Get_Float() < teamworkChance ? 1.0f : 0.0f;
+		case SPRINT_CHANCE:
+			return FreeRandom.Get_Float() < sprintChance ? 1.0f : 0.0f;
+		case JUMP_CHANCE_REGULAR:
+			return FreeRandom.Get_Float() < jumpChanceRegular ? 1.0f : 0.0f;
+		case JUMP_CHANCE_COMBAT:
+			return FreeRandom.Get_Float() < jumpChanceCombat ? 1.0f : 0.0f;
+		case CROUCH_CHANCE_CALM:
+			return FreeRandom.Get_Float() < crouchChanceCalm ? 1.0f : 0.0f;
+		case INVESTIGATE_CHANCE:
+			return FreeRandom.Get_Float() < investigateChance ? 1.0f : 0.0f;
+		case CHAT_CHANCE:
+			return FreeRandom.Get_Float() < chatChance ? 1.0f : 0.0f;
+		}
+		return 0.0f;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	bool Wants_To_Repair(GameObject *obj, float distance)
+	{
+		float currentHitp = Commands->Get_Health(obj) + Commands->Get_Shield_Strength(obj);
+		float maxHitp = Commands->Get_Max_Health(obj) + Commands->Get_Max_Shield_Strength(obj);
+
+		float ratio = 1 - (currentHitp / maxHitp);
+
+		float chance = (ratio*ratio) * 4;
+
+		float correctedChance = chance - (distance*distance / 40000);
+
+		/*StringClass buff;
+		buff.Format("msg hp: %.2f/%.2f, ratio: %.2f, chance: %.2f, dist: %.2f, correctedChance: %.2f", currentHitp, maxHitp, ratio, chance, distance, correctedChance);
+		M_Console_Input(buff.Peek_Buffer());*/
+
+		if (currentHitp == maxHitp)
+			return false;
+
+		// TODO: personality randoms should affect this
+		return correctedChance >= 1.0f;
+	}
+
+	BotPersonality(int type)
+	{
+		personalityType = type;
+		// Temporary? template personalities
+		switch (type)
+		{
+		// Newbie
+		case 0:
+			spawnTimeMin = 2.5f;
+			spawnTimeMax = 7.5f;
+			radioTimeMin = 2.0f;
+			radioTimeMax = 3.5f;
+			thinkTimeMin = FreeRandom.Get_Float(0.95f, 1.05f);
+			thinkTimeMax = FreeRandom.Get_Float(1.15f, 1.25f);
+			attackErrorMultiplyMin = +0.0f;
+			attackErrorMultiplyMax = +0.4f;
+			buyinfChance = FreeRandom.Get_Float(0.15f, 0.60f);
+			buyvehChance = FreeRandom.Get_Float(0.20f, 0.65f);
+			teamworkChance = FreeRandom.Get_Float(0.15f, 0.35f);
+			sprintChance = FreeRandom.Get_Float(0.00f, 0.10f);
+			jumpChanceRegular = FreeRandom.Get_Float(0.00f, 0.02f);
+			jumpChanceCombat = FreeRandom.Get_Float(0.04f, 0.12f);
+			crouchChanceCalm = FreeRandom.Get_Float(0.10f, 0.20f);
+			break;
+		// Regular
+		case 1:
+			spawnTimeMin = 1.5f;
+			spawnTimeMax = 5.5f;
+			radioTimeMin = 1.5f;
+			radioTimeMax = 3.0f;
+			thinkTimeMin = FreeRandom.Get_Float(0.85f, 0.95f);
+			thinkTimeMax = FreeRandom.Get_Float(1.05f, 1.15f);
+			attackErrorMultiplyMin = -0.1f;
+			attackErrorMultiplyMax = +0.2f;
+			buyinfChance = FreeRandom.Get_Float(0.40f, 0.80f);
+			buyvehChance = FreeRandom.Get_Float(0.30f, 0.75f);
+			teamworkChance = FreeRandom.Get_Float(0.35f, 0.55f);
+			sprintChance = FreeRandom.Get_Float(0.10f, 0.30f);
+			jumpChanceRegular = FreeRandom.Get_Float(0.03f, 0.06f);
+			jumpChanceCombat = FreeRandom.Get_Float(0.20f, 0.40f);
+			crouchChanceCalm = FreeRandom.Get_Float(0.20f, 0.40f);
+			break;
+		// Experienced
+		case 2:
+			spawnTimeMin = 0.5f;
+			spawnTimeMax = 2.0f;
+			radioTimeMin = 1.0f;
+			radioTimeMax = 2.0f;
+			thinkTimeMin = FreeRandom.Get_Float(0.75f, 0.85f);
+			thinkTimeMax = FreeRandom.Get_Float(0.95f, 1.05f);
+			attackErrorMultiplyMin = -0.4f;
+			attackErrorMultiplyMax = -0.1f;
+			buyinfChance = FreeRandom.Get_Float(0.25f, 0.70f);
+			buyvehChance = FreeRandom.Get_Float(0.40f, 0.85f);
+			teamworkChance = FreeRandom.Get_Float(0.55f, 0.75f);
+			sprintChance = FreeRandom.Get_Float(0.60f, 0.90f);
+			jumpChanceRegular = FreeRandom.Get_Float(0.08f, 0.16f);
+			jumpChanceCombat = FreeRandom.Get_Float(0.60f, 0.80f);
+			crouchChanceCalm = FreeRandom.Get_Float(0.70f, 1.00f);
+			break;
+		}
+		// Untied to fixed personalities
+		investigateChance = FreeRandom.Get_Float(0.1f, 0.9f);
+		chatChance = FreeRandom.Get_Float(0.04f, 0.09f);
+		sympathyVarMin = FreeRandom.Get_Float(0.004f, 0.01f);
+		sympathyVarMax = FreeRandom.Get_Float(0.011f, 0.018f);
+	}
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class PlayerRelationData
+{
+	// Player sympathy stuff
+	struct PlayerSympathy
+	{
+		float sympathy;
+		PlayerSympathy(float sympathy) : sympathy(sympathy) {}
+	};
+
+	std::unordered_map<StringClass, PlayerSympathy, hash_string, equals_string> sympathyList;
+
+public:
+
+	PlayerSympathy* GetPlayerData(const StringClass &name)
+	{
+		if (name.Is_Empty() || strcmp(name.Peek_Buffer(), "None") == 0)
+			return nullptr;
+
+		auto it = sympathyList.find(name);
+		if (it == sympathyList.end())
+			it = sympathyList.insert(std::make_pair(name, PlayerSympathy(0.15f))).first;
+	
+		return &it->second;
+	}
+};
+
+// -------------------------------------------------------------------------------------------------
+
+
+
+/*------------------------
+AI Helper / Marker Scripts
+--------------------------*/
+
+
+/*!
+* \brief AI Bomber Aircraft marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on aircraft presets that attack by ejecting bombs downwards from the sky.
+*
+* This is a logic marker script for presets that have MS_AI_Behaviour on them, or if they have
+* the MS_AI_Preferences script attached and bots are created by one MS_AI_Player_Count_Controller
+* or individual MS_AI_Player_Controller scripts.
+*
+* \param None
+*   None
+*/
+class MS_AI_Marker_Bomber_Aircraft : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_VehicleGameObj() || Get_Vehicle_Mode(obj) != VEHICLE_TYPE_FLYING)
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only flying vehicles can use the MS_AI_Bomber_Aircraft script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_Bomber_Aircraft, "");
+
+
+/*!
+* \brief AI Deployable marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on unit presets that can use a deploy script to change behaviour in combat.
+*
+* This is a logic marker script for presets that have MS_AI_Behaviour on them, or if they have
+* the MS_AI_Preferences script attached and bots are created by one MS_AI_Player_Count_Controller
+* or individual MS_AI_Player_Controller scripts.
+*
+* \param Deploy_Script_Name
+*   This parameter should match the name of the deploy script.
+* \param Deploy_VS_Infantry:
+*   Should the unit use its deploy ability against infantry: 1 to deploy, 0 to not deploy.
+* \param Deploy_VS_Light_Vehicle:
+*   Should the unit use its deploy ability against light vehicles: 1 to deploy, 0 to not deploy.
+* \param Deploy_VS_Heavy_Vehicle:
+*   Should the unit use its deploy ability against heavy vehicles: 1 to deploy, 0 to not deploy.
+* \param Deploy_VS_VTOL:
+*   Should the unit use its deploy ability against flying/VTOL units: 1 to deploy, 0 to not deploy.
+* \param Deploy_VS_Building:
+*   Should the unit use its deploy ability against buildings: 1 to deploy, 0 to not deploy.
+* \param Deploy_Is_Stationary:
+*   Setting this parameter to 1 is telling the script this unit becomes stationary when deployed. 0 means it can move after deploy.
+*/
+class MS_AI_Marker_Deployable : public ScriptImpClass
+{
+	void Created(GameObject *obj) override
+	{
+		if (!Is_Script_Attached(obj, Get_Deploy_Script_Name()))
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: The script '%hs' does not exist on this object. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), Get_Deploy_Script_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+
+public:
+	const char *Get_Deploy_Script_Name() { return Get_Parameter("Deploy_Script_Name"); }
+
+	bool Get_Deploy_VS_Infantry() { return Get_Int_Parameter("Deploy_VS_Infantry") == 1; }
+
+	bool Get_Deploy_VS_Light_Vehicle() { return Get_Int_Parameter("Deploy_VS_Light_Vehicle") == 1; }
+
+	bool Get_Deploy_VS_Heavy_Vehicle() { return Get_Int_Parameter("Deploy_VS_Heavy_Vehicle") == 1; }
+
+	bool Get_Deploy_VS_VTOL() { return Get_Int_Parameter("Deploy_VS_VTOL") == 1; }
+
+	bool Get_Deploy_VS_Building() { return Get_Int_Parameter("Deploy_VS_Building") == 1; }
+
+	bool Get_Deploy_Is_Stationary() { return Get_Int_Parameter("Deploy_Is_Stationary") == 1; }
+};
+REGISTER_SCRIPT(MS_AI_Marker_Deployable,
+	"Deploy_Script_Name=null:string,"
+	"Deploy_VS_Infantry=1:int,"
+	"Deploy_VS_Light_Vehicle=1:int,"
+	"Deploy_VS_Heavy_Vehicle=1:int,"
+	"Deploy_VS_VTOL=1:int,"
+	"Deploy_VS_Building=1:int,"
+	"Deploy_Is_Stationary=1:int"
+);
+
+
+/*!
+* \brief AI No Jump marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on soldier presets to explicitly tell them to never jump.
+*
+* This is a logic marker script for presets that have MS_AI_Behaviour on them, or if they have
+* the MS_AI_Preferences script attached and bots are created by one MS_AI_Player_Count_Controller
+* or individual MS_AI_Player_Controller scripts.
+*
+* \param None
+*   None
+*/
+class MS_AI_Marker_No_Jump : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_SoldierGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only soldiers can use the MS_AI_Marker_No_Jump script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_No_Jump, "");
+
+
+/*!
+* \brief AI Refiller Vehicle marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on vehicle presets that need to refill their ammo in a script zone.
+*
+* This is a logic marker script for presets that have MS_AI_Behaviour on them, or if they have
+* the MS_AI_Preferences script attached and bots are created by one MS_AI_Player_Count_Controller
+* or individual MS_AI_Player_Controller scripts.
+*
+* \param RefillZonePreset
+*   Name of the zone preset where the vehicle can refill
+*/
+class MS_AI_Marker_Refiller_Vehicle : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_VehicleGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only vehicles can use the MS_AI_Marker_Refiller_Vehicle script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+		if (!Is_Valid_Preset(Get_Zone_Preset_Name()))
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: '%hs' is not a valid preset. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), Get_Zone_Preset_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+
+public:
+	const char *Get_Zone_Preset_Name()
+	{
+		return Get_Parameter("RefillZonePreset");
+	}
+
+	const char *Get_Exit_Zone_Preset_Name()
+	{
+		return Get_Parameter("ExitZonePreset");
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_Refiller_Vehicle, "RefillZonePreset=null:string,ExitZonePreset=null:string");
+
+
+/*!
+* \brief AI Vehicle Repair Zone marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on script zone presets where vehicles can get repaired.
+*
+* This is a logic marker script for presets that have MS_AI_Behaviour on them, or if they have
+* the MS_AI_Preferences script attached and bots are created by one MS_AI_Player_Count_Controller
+* or individual MS_AI_Player_Controller scripts.
+*
+* \param Team
+*   The team this repair zone belongs to.
+*/
+class MS_AI_Marker_Vehicle_Repair_Zone : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_ScriptZoneGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only script zones can use the MS_AI_Marker_Vehicle_Repair_Zone script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+
+public:
+	int Get_Team()
+	{
+		return Get_Int_Parameter("Team");
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_Vehicle_Repair_Zone, "Team=0:int");
+
+
+/*!
+* \brief AI Vehicle ignore marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on vehicle presets to explicitly mark them as vehicles bots should not search for.
+*
+* This is a logic marker script for vehicle presets that MS_AI_Behaviour bots don't want to use.
+*
+* \param IgnorerTeam
+*   The team that will ignore this vehicle. 2 is all teams.
+*/
+class MS_AI_Marker_Vehicle_Search_Ignore : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_VehicleGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only vehicles can use the MS_AI_Marker_Vehicle_Search_Ignore script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+
+public:
+	int Get_Ignorer_Team()
+	{
+		return Get_Int_Parameter("IgnorerTeam");
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_Vehicle_Search_Ignore, "IgnorerTeam=2:int");
+
+
+/*!
+* \brief AI Transport vehicle override marker
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on vehicle presets to explicitly mark them as transport vehicles. 
+* It makes sure bots use it as a transport, even if the seat count is less than the one set
+* TransportVehicleMinimumSeatCount in the bot config file.
+*
+* This is a logic marker script for vehicle presets that MS_AI_Behaviour bots want to use.
+*
+* \param None
+*   None
+*/
+class MS_AI_Marker_Transport_Vehicle_Override : public ScriptImpClass
+{
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_VehicleGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only vehicles can use the MS_AI_Marker_Transport_Vehicle_Override script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Marker_Transport_Vehicle_Override, "");
+
+
+
+// -------------------------------------------------------------------------------------------------
+
+static DynamicVectorClass<GameObject *> AIBuildings;
+
+/* Building debug stuff stored outside the globalcontroller because this script can exist without palyer logic bots  */
+static bool BuildingDebugInited = false;
+static bool BuildingDebugLocationsDisplayEnabled = false;
+StringClass BuildingDebugTargetModel;
+StringClass BuildingDebugObjectiveModel;
+
+void Init_Building_Debug()
+{
+	if (BuildingDebugInited)
+		return;
+
+	INIClass* config = Get_INI("bot_config.cfg");
+	if (config)
+	{
+		BuildingDebugLocationsDisplayEnabled = config->Get_Bool("Debug", "EnableBuildingTargetsDisplay", false);
+		config->Get_String(BuildingDebugTargetModel, "Debug", "BuildingTargetModel", "o_davesarrow");
+		config->Get_String(BuildingDebugObjectiveModel, "Debug", "BuildingObjectiveModel", "o_davesarrow");
+		Release_INI(config);
+		BuildingDebugInited = true;
+	}
+}
+
+/*!
+* \brief AI Building behaviour controller
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change.
+*
+* \param None
+*   None
+*/
+class MS_AI_Building : public ScriptImpClass
+{
+	const char* customWeakpointPresetName;
+	Vector3 mctTargetLocation;
+	ReferencerClass objectiveMCTAttackLocationObj;
+	ReferencerClass objectiveRepairLocationObj;
+	DynamicVectorClass<ReferencerClass> objectiveVehicleAttackLocations;
+	float averageGroundHeight;
+	bool isDeleted;
+	bool isReachableOnGround;
+
+public:
+	Vector3 mctObjectiveLocation;
+	ReferencerClass objectiveMCTAttackTargetObj;
+	ReferencerClass objectiveRepairTargetObj;
+	ReferencerClass centralTarget;
+	ReferencerClass centralTargetGround;
+	DynamicVectorClass<ReferencerClass> exteriorTargets;
+	DynamicVectorClass<Vector3> exteriorTargetWatchpoints;
+	
+private:
+	// TODO: optimize reviving procedure, do not recalculate the target positions, only 1x at created OR if there are 0 targets (e.g. script was attached first when building was already dead or started dead)
+	void Created(GameObject* obj) override
+	{
+		Init_Building_Debug();
+		isReachableOnGround = true;
+
+		objectiveMCTAttackLocationObj = nullptr;
+		objectiveMCTAttackTargetObj = nullptr;
+		objectiveRepairLocationObj = nullptr;
+		objectiveRepairTargetObj = nullptr;
+		centralTarget = nullptr;
+		centralTargetGround = nullptr;
+		customWeakpointPresetName = Get_Parameter("CustomWeakpointPresetName");
+		if ((!obj->As_BuildingGameObj() && !obj->As_PhysicalGameObj()) || obj->As_SoldierGameObj() || (obj->As_VehicleGameObj() && !obj->As_VehicleGameObj()->Is_Turret()))
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only BuildingGameObjects, turret type Vehicles and Objects can use the MS_AI_Building script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+		else if (strcmp(customWeakpointPresetName, "") != 0 && !Is_Valid_Preset(customWeakpointPresetName))
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: '%hs' is not a valid preset. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), customWeakpointPresetName);
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+		else
+		{
+			Init(obj);
+
+			// Setup automatic repair needed triggers
+			StringClass params;
+			params.Format("%d,%d,%d,%d,%d,%d,%d,%s", (int)(Commands->Get_Max_Health(obj) * ((float)(100-(Get_Int_Parameter("DamagePercentToRepair"))) / 100)), 0, CUSTOM_AI_REPAIR_NEEDED, 0, -1, 0, 0, "false");
+			Commands->Attach_Script(obj, "MS_Health_Send_Custom", params.Peek_Buffer());
+			params.Format("%d,%d,%d,%d,%d,%d,%d,%s", (int)(Commands->Get_Max_Health(obj)), -1, 0, 0, 0, CUSTOM_AI_REPAIR_COMPLETE, 0, "false");
+			Commands->Attach_Script(obj, "MS_Health_Send_Custom", params.Peek_Buffer());
+		}
+	}
+
+	void Init(GameObject* obj)
+	{
+		// Check if building has any polygons at all, don't compute unnecessarily in cases where there is only a building controller but no mesh belonging to it (e.g. C&C_MiniFrost)
+		if (obj->As_BuildingGameObj())
+		{
+			Vector3 closestPoly;
+			Vector3 bldCenter = Commands->Get_Position(obj);
+			Find_Closest_Poly_Position(obj->As_BuildingGameObj(), bldCenter, &closestPoly);
+			// If this building has no polygons at all, then bots cannot attack it or repair it, destroy script
+			if (closestPoly == Vector3())
+			{
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: ATTENTION: This building has no polygons (only a building controller)", Commands->Get_Preset_Name(obj));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+				Destroy_Script();
+				return;
+			}
+		}
+
+		auto &boundingBox = obj->As_BuildingGameObj() ? obj->As_BuildingGameObj()->Get_Bounds() : obj->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+		const int bldRadius = (int)(max(boundingBox.Extent.X, boundingBox.Extent.Y));
+		mctObjectiveLocation = Vector3();
+		objectiveRepairLocationObj = nullptr;
+		objectiveRepairTargetObj = nullptr;
+
+		Vector3 MCTPos = Vector3();
+		float facing = 0;
+		bool hasWeakpoint = true;
+
+		if (customWeakpointPresetName[0] != '\0')
+		{
+			if (auto customMct = Find_Nearest_Preset(Commands->Get_Position(obj), customWeakpointPresetName))
+			{
+				Matrix3D transform = Matrix3D::Identity;
+				// Check if it has a target1, target2, etc. bone for precision
+				for (int i = 1; i < 4; i++) {
+					StringClass boneName;
+					boneName.Format("target%d", i);
+
+					if (int boneIdx = customMct->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index(boneName.Peek_Buffer()))
+					{
+						// M_Console_Input("msg Target bone found!");
+						transform = customMct->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+						break;
+					}
+				}
+
+				if (transform == Matrix3D::Identity)
+				{
+					customMct->Get_Position(&MCTPos);
+					MCTPos.Z += 1.2f;
+					facing = DEG2RAD(Commands->Get_Facing(customMct));
+					// M_Console_Input("msg Target bone NOT found!");
+				}
+				else
+				{
+					MCTPos = transform.Get_Translation();
+					facing = transform.Get_Z_Rotation();
+				}
+			}
+			else
+			{
+				StringClass msg;
+				msg.Format("[%d:%hs:%hs] Critical Error: custom weakpoint not found. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+				M_Console_Output(msg.Peek_Buffer());
+				Destroy_Script();
+				return;
+			}
+		}
+		else if (obj->As_BuildingGameObj() && obj->As_BuildingGameObj()->Find_MCT())
+		{
+			BuildingAggregateClass* MCT = obj->As_BuildingGameObj()->Find_MCT();
+			auto &mctBoundingBox = MCT->Get_Bounding_Box();
+			const Vector3 mctBoundingBoxCenter = mctBoundingBox.Center;
+
+			//MCT->Get_Position(&MCTPos);
+
+			// MCT location correction needed
+			// if (Vector3::Distance(MCTPos, mctBoundingBoxCenter) > 1.0f)
+			MCTPos = mctBoundingBoxCenter;
+			facing = MCT->Get_Facing();
+
+			// MCT mesh is somewhere inside the big aggregate... 
+			if (mctBoundingBox.Extent.X > 2 || mctBoundingBox.Extent.Y > 2 || mctBoundingBox.Extent.Z > 2)
+			{
+				// M_Console_Input("msg Big MCT aggregate.");
+
+				// Check for help from config
+				auto bldName = Commands->Get_Preset_Name(obj);
+				INIClass* config = Get_INI("bot_config.cfg");
+
+				if (config)
+				{
+					if (auto bldSection = config->Find_Section(bldName))
+					{
+						Vector3 offset;
+						offset.X += config->Get_Float(bldName, "X", 0) * cos(facing);
+						offset.Y += config->Get_Float(bldName, "X", 0) * sin(facing);
+
+						offset.Y += config->Get_Float(bldName, "Y", 0) * cos(facing);
+						offset.X -= config->Get_Float(bldName, "Y", 0) * sin(facing);
+
+						offset.Z += config->Get_Float(bldName, "Z", 0);
+
+						MCTPos += offset;
+					}
+					Release_INI(config);
+				}
+			}
+		}
+		else
+		{
+			// No MCT, no custom weakpoint defined, find a proper goto/repair location still (e.g. APB Helipads)
+			hasWeakpoint = false;
+			const Vector3 bldCenter = obj->As_BuildingGameObj() ? boundingBox.Center : Commands->Get_Position(obj);
+
+			bool isWithinPathfindGrid = false;
+			for (int i = 1; i < bldRadius + 5; i += 1)
+			{
+				isWithinPathfindGrid = Get_Closest_Pathfind_Spot_Size(bldCenter, (float)i, &mctObjectiveLocation, 0.5f);
+				if (isWithinPathfindGrid)
+				{
+					if (obj->As_BuildingGameObj())
+					{
+						Find_Closest_Poly_Position(obj->As_BuildingGameObj(), mctObjectiveLocation, &mctTargetLocation);
+					}
+					else
+					{
+						Matrix3D transform = Matrix3D::Identity;
+
+						int boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("repair");
+						if (!boneIdx)
+						{
+							boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("target");
+						}
+						if (boneIdx)
+						{
+							// M_Console_Input("msg Target bone found!");
+							transform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+						}
+
+						if (transform == Matrix3D::Identity)
+						{
+							mctTargetLocation = bldCenter;
+							mctTargetLocation.Z += 0.2f;
+							facing = DEG2RAD(Commands->Get_Facing(obj));
+							// M_Console_Input("msg Target bone NOT found!");
+						}
+						else
+						{
+							mctTargetLocation = transform.Get_Translation();
+							facing = transform.Get_Z_Rotation();
+						}
+					}
+					break;
+				}
+			}
+
+			if (!isWithinPathfindGrid)
+			{
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: WARNING: Unable to find a suitable MCT/weakpoint objective location for bots", Commands->Get_Preset_Name(obj));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+				isReachableOnGround = false;
+			}
+		}
+
+		if (hasWeakpoint)
+		{
+			Vector3 testPos;
+			facing -= WWMATH_PI / 2;
+
+			const float mctTargetForwardOffset = 0.3f;
+
+
+			bool isSuitable = false;
+			// Find a good position
+			for (int i = 0; i < 7; i++)
+			{
+				facing = facing + WWMATH_PI / 2;
+				testPos = MCTPos;
+				testPos.X += 1.3f * cos(facing);
+				testPos.Y += 1.3f * sin(facing);
+				testPos.Z -= 0.3f;
+
+				Vector3 helpPos = Vector3();
+				bool isWithinPathfindGrid = Get_Closest_Pathfind_Spot_Size(testPos, 0.25f, &helpPos, 0.5f);
+				helpPos = MCTPos;
+				helpPos.X += mctTargetForwardOffset * cos(facing);
+				helpPos.Y += mctTargetForwardOffset * sin(facing);
+				if (isWithinPathfindGrid && Can_Generic_Soldier_Teleport(testPos) && !M_Test_Raycast_Collision(helpPos, testPos, false))
+				{
+					isSuitable = true;
+					break;
+				}
+				// else Wrong MCT objective pos, trying a different one
+				/*else
+				{
+					if (!isWithinPathfindGrid)
+						M_Console_Input("msg Wrong MCT objective pos Reason: not in pathfind grid.");
+					else if (!Can_Generic_Soldier_Teleport(testPos))
+						M_Console_Input("msg Wrong MCT objective pos Reason: Can_Generic_Soldier_Teleport NOT");
+					else if (M_Test_Raycast_Collision(helpPos, testPos, false))
+						M_Console_Input("msg Wrong MCT objective pos Reason: M_Test_Raycast_Collision problems.");
+				}*/
+			}
+
+			if (!isSuitable)
+			{
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: WARNING: Unable to find a suitable MCT/weakpoint objective location for bots", Commands->Get_Preset_Name(obj));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+				isReachableOnGround = false;
+			}
+
+			mctObjectiveLocation = testPos;
+			mctTargetLocation = MCTPos;
+		}
+
+
+		// Generate building exterior target markers, better perf than calling find closest poly/ lots of raycasts on every bot constantly
+		Generate_Targets(obj, boundingBox);
+
+		StringClass params;
+		// Add unique central targets which will be tested first always by bots, it reduces the number of raycasts in a lot of cases
+		centralTarget = Commands->Create_Object("Invisible_Object", boundingBox.Center);
+		auto skinName = ArmorWarheadManager::Get_Armor_Name(obj->As_DamageableGameObj()->Get_Defense_Object()->Get_Skin());
+		params.Format("Blamo,%s,0", skinName);
+		Commands->Attach_Script(centralTarget, "MS_Switch_Skin_And_Shield_Timer", params.Peek_Buffer());
+		Commands->Attach_Script(centralTarget, "dp88_AI_Marker_Building", "");
+		Set_Object_Type(centralTarget, Get_Object_Type(obj));
+		Set_Max_Shield_Strength(centralTarget, Commands->Get_Max_Health(obj));
+
+		centralTargetGround = Commands->Create_Object("Invisible_Object", Vector3(boundingBox.Center.X, boundingBox.Center.Y, averageGroundHeight));
+		Commands->Attach_Script(centralTargetGround, "MS_Switch_Skin_And_Shield_Timer", params.Peek_Buffer());
+		Commands->Attach_Script(centralTargetGround, "dp88_AI_Marker_Building", "");
+		Set_Object_Type(centralTargetGround, Get_Object_Type(obj));
+		Set_Max_Shield_Strength(centralTargetGround, Commands->Get_Max_Health(obj));
+
+		// Also create target marker at the weakpoint for soldiers
+		if (isReachableOnGround)
+		{
+			auto targetObj = Commands->Create_Object("Invisible_Object", mctTargetLocation);
+			if (BuildingDebugLocationsDisplayEnabled)
+				Commands->Set_Model(targetObj, BuildingDebugTargetModel.Peek_Buffer());
+			Commands->Attach_Script(targetObj, "MS_Switch_Skin_And_Shield_Timer", params.Peek_Buffer());
+			Commands->Attach_Script(targetObj, "dp88_AI_Marker_Building", "");
+			Set_Object_Type(targetObj, Get_Object_Type(obj));
+			Set_Max_Shield_Strength(targetObj, Commands->Get_Max_Health(obj));
+			objectiveMCTAttackTargetObj = targetObj;
+		}
+
+		// Next, generate go-to locations
+		// If weakpoint is reachable, just use its already calculated location
+		Vector3 soldierAttackObjectiveLocation = mctObjectiveLocation;
+		// Otherwise, more calculations are needed to make sure the building is attackable (closest pathfind spot is not guaranteed to hit the building from, eg. RA_Fissure top silos, rocks cover it)
+		if (!isReachableOnGround)
+		{
+			Get_Closest_Pathfind_Spot_Size(boundingBox.Center, bldRadius + 50.0f, &soldierAttackObjectiveLocation, 0.5f);
+			Vector3 helper;
+			int count = 0;
+			while (count < 50 && 
+				!M_Test_Raycast_Collision(soldierAttackObjectiveLocation, boundingBox.Center, false, &helper, obj) && 
+				!M_Test_Raycast_Collision(soldierAttackObjectiveLocation, boundingBox.Center + Vector3(0, 0, boundingBox.Extent.Z), false, &helper, obj))
+			{
+				count++;
+				helper = soldierAttackObjectiveLocation - (boundingBox.Center - soldierAttackObjectiveLocation).Normalized() * (float)count;
+				Get_Closest_Pathfind_Spot_Size(helper, 5.0f, &soldierAttackObjectiveLocation, 0.5f);
+			}
+			// Place a target here to make sure the soldiers actually see a target to shoot from here (is not always certain, e.g. on RA_Fissure Soviet Silo objective)
+			Validate_New_Target(obj, helper, soldierAttackObjectiveLocation);
+			//StringClass msg;
+			//msg.Format("msg %s: Recalced a proper position %d times", Commands->Get_Preset_Name(obj), count);
+			//M_Console_Input(msg);
+		}
+
+		// One objective for soldiers to run to the MCT/attack location when no weakpoint present
+		auto attackObjectiveObj = Commands->Create_Object("Invisible_Object", soldierAttackObjectiveLocation);
+		if (BuildingDebugLocationsDisplayEnabled)
+			Commands->Set_Model(attackObjectiveObj, BuildingDebugObjectiveModel.Peek_Buffer());
+		Commands->Attach_Script(attackObjectiveObj, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+		params.Format("%d,%d,%d,%d,%d,%d,%d,%s", Get_Object_Type(obj) ? 0 : 1, dp88_AI_Objective::TYPE_OFFENSIVE, 1, 5, 0, 0, 0, Commands->Get_Preset_Name(obj));
+		Commands->Attach_Script(attackObjectiveObj, "dp88_AI_Objective", params.Peek_Buffer());
+		objectiveMCTAttackLocationObj = attackObjectiveObj;
+
+		// Objectives for vehicles to go to the building
+		const float offsetAmount = 5.0f;
+		Vector3 offsetVectors[4];
+		offsetVectors[0] = Vector3(+boundingBox.Extent.X + offsetAmount, 0, 0);
+		offsetVectors[1] = Vector3(-boundingBox.Extent.X - offsetAmount, 0, 0);
+		offsetVectors[2] = Vector3(0, +boundingBox.Extent.Y + offsetAmount, 0);
+		offsetVectors[3] = Vector3(0, -boundingBox.Extent.Y - offsetAmount, 0);
+		Vector3 objectivePos;
+		int vehObjectivesCount = 0;
+		for (int i = 0; i < 4; i++)
+		{
+			if (Get_Closest_Pathfind_Spot_Size(Vector3(boundingBox.Center.X, boundingBox.Center.Y, averageGroundHeight) + offsetVectors[i], offsetAmount - 1.0f, &objectivePos, 2.5f))
+			{
+				// Check for weird situations where pathfind sectors are epic tall and points would not be anywhere near the ground
+				Vector3 checkPoint = objectivePos + Vector3(0, 0, 2 * -boundingBox.Extent.Z - 5.0f);
+				Vector3 helper;
+				if (M_Test_Raycast_Collision(objectivePos, checkPoint, false, &helper) && abs(helper.Z - objectivePos.Z) > 2.0f)
+					objectivePos.Z = helper.Z + 2.0f;
+
+				auto vehAttackObjectiveObj = Commands->Create_Object("Invisible_Object", objectivePos);
+				if (BuildingDebugLocationsDisplayEnabled)
+					Commands->Set_Model(vehAttackObjectiveObj, BuildingDebugObjectiveModel.Peek_Buffer());
+				Commands->Attach_Script(vehAttackObjectiveObj, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+				params.Format("%d,%d,%d,%d,%d,%d,%d,%s", Get_Object_Type(obj) ? 0 : 1, dp88_AI_Objective::TYPE_OFFENSIVE, 10, 0, 5, 5, 5, Commands->Get_Preset_Name(obj));
+				Commands->Attach_Script(vehAttackObjectiveObj, "dp88_AI_Objective", params.Peek_Buffer());
+				objectiveVehicleAttackLocations.Add(vehAttackObjectiveObj);
+				vehObjectivesCount++;
+			}
+		}
+
+		// Fallback when no locations found, uncomment when needed
+		/*if (!count && Get_Closest_Pathfind_Spot_Size(Vector3(boundingBox.Center.X, boundingBox.Center.Y, averageGroundHeight), bldRadius + 50.0f, &objectivePos, 2.0f))
+		{
+			auto vehAttackObjectiveObj = Commands->Create_Object("Invisible_Object", objectivePos); Commands->Set_Model(vehAttackObjectiveObj, "o_davesarrow");
+			Commands->Attach_Script(vehAttackObjectiveObj, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+			params.Format("%d,%d,%d,%d,%d,%d,%d", Get_Object_Type(obj) ? 0 : 1, dp88_AI_Objective::TYPE_OFFENSIVE, (2 * bldRadius), 0, 5, 5, 5);
+			Commands->Attach_Script(vehAttackObjectiveObj, "dp88_AI_Objective", params.Peek_Buffer());
+			objectiveVehicleAttackLocations.Add(vehAttackObjectiveObj);
+			vehObjectivesCount++;
+		}*/
+
+		// Finally, add a marker if it's not a building logic type, otherwise they will be treated as light vehicles
+		if (!obj->As_BuildingGameObj())
+			Commands->Attach_Script(obj, "dp88_AI_Marker_Building", "");
+
+		
+		if (BuildingDebugLocationsDisplayEnabled)
+		{
+			StringClass debug;
+			debug.Format("msg %s targets: %d, veh objectives: %d (bounding box: %.2f/%.2f/%.2f)", Commands->Get_Preset_Name(obj), exteriorTargets.Count(), vehObjectivesCount, boundingBox.Extent.X, boundingBox.Extent.Y, boundingBox.Extent.Z);
+			M_Console_Input(debug);
+		}
+
+		Commands->Start_Timer(obj, this, 1.0f, 1);
+
+		isDeleted = false;
+		AIBuildings.Add(obj);
+
+		/*const char* presetName = "";
+		auto list = Combine_Lists(Get_Enlisted_Purchase_Items(1), Get_Purchase_Items(1, PurchaseSettingsDefClass::TYPE_CLASSES));
+		for (int i = 0; i < list.Count(); i++)
+		{
+			presetName = DefinitionMgrClass::Find_Definition(list[i], false)->Get_Name();
+			if (Is_Valid_Preset(presetName))
+				break;
+		}
+
+		auto testSold = ObjectLibraryManager::Create_Object(presetName);
+		Set_Object_Type(testSold, 2);
+		testSold->As_SoldierGameObj()->Set_Grav_Scale(0);
+		Commands->Set_Position(testSold, mctObjectiveLocation);
+		Commands->Set_Facing(testSold, (float)RAD2DEG(facing));
+
+		auto testSold2 = ObjectLibraryManager::Create_Object(presetName);
+		Set_Object_Type(testSold2, 2);
+		testSold2->As_SoldierGameObj()->Set_Grav_Scale(0);
+		Commands->Set_Position(testSold2, mctTargetLocation);
+		Commands->Set_Facing(testSold2, (float)RAD2DEG(facing));*/
+
+	}
+
+	void Generate_Targets(GameObject *obj, const AABoxClass &boundingBox)
+	{
+		// Add full Z extent to make sure we start checking above the ground, even if the building's underground interior is bigger than above ground
+		const float heightStart = obj->As_BuildingGameObj() ? (boundingBox.Extent.Z + 2.0f) : 0;
+		const float sideOffset = 2.0f;
+		Vector3 sg1 = boundingBox.Center + Vector3(boundingBox.Extent.X + sideOffset, 0, heightStart);
+		Vector3 sg2 = boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, 0, heightStart);
+		Vector3 sg3 = boundingBox.Center + Vector3(0, boundingBox.Extent.Y + sideOffset, heightStart);
+		Vector3 sg4 = boundingBox.Center + Vector3(0, -boundingBox.Extent.Y - sideOffset, heightStart);
+
+		// Corners next
+		Vector3 sg5 = boundingBox.Center + Vector3( boundingBox.Extent.X + sideOffset,  boundingBox.Extent.Y + sideOffset, heightStart);
+		Vector3 sg6 = boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, -boundingBox.Extent.Y - sideOffset, heightStart);
+		Vector3 sg7 = boundingBox.Center + Vector3( boundingBox.Extent.X + sideOffset, -boundingBox.Extent.Y - sideOffset, heightStart);
+		Vector3 sg8 = boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset,  boundingBox.Extent.Y + sideOffset, heightStart);
+
+		Place_Target_Pair(obj, sg1, sg2, boundingBox);
+		Place_Target_Pair(obj, sg3, sg4, boundingBox);
+		Place_Target_Pair(obj, sg5, sg6, boundingBox);
+		Place_Target_Pair(obj, sg7, sg8, boundingBox);
+
+		averageGroundHeight = (sg1.Z + sg2.Z + sg3.Z + sg4.Z + sg5.Z + sg6.Z + sg7.Z + sg8.Z) / 8;
+
+		// Place more targets if the building is wide 
+		float step = boundingBox.Extent.Y > 28 ? 14.0f : 8.0f;
+		for (float i = step; i < boundingBox.Extent.Y - step; i += step)
+		{
+			Vector3 p1 = boundingBox.Center + Vector3(boundingBox.Extent.X + sideOffset, i, heightStart);
+			Vector3 p2 = boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, i, heightStart);
+			Vector3 p3 = boundingBox.Center + Vector3(boundingBox.Extent.X + sideOffset, -i, heightStart);
+			Vector3 p4 = boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, -i, heightStart);
+			Place_Target_Pair(obj, p1, p2, boundingBox);
+			Place_Target_Pair(obj, p3, p4, boundingBox);
+		}
+
+		step = boundingBox.Extent.X > 28 ? 14.0f : 8.0f;
+		for (float i = step; i < boundingBox.Extent.X - step; i += step)
+		{
+			Vector3 p1 = boundingBox.Center + Vector3(i, boundingBox.Extent.Y + sideOffset, heightStart);
+			Vector3 p2 = boundingBox.Center + Vector3(i, -boundingBox.Extent.Y - sideOffset, heightStart);
+			Vector3 p3 = boundingBox.Center + Vector3(-i, boundingBox.Extent.Y + sideOffset, heightStart);
+			Vector3 p4 = boundingBox.Center + Vector3(-i, -boundingBox.Extent.Y - sideOffset, heightStart);
+			Place_Target_Pair(obj, p1, p2, boundingBox);
+			Place_Target_Pair(obj, p3, p4, boundingBox);
+		}
+
+		// Place targets for bombers / units that are looking at the building from up high
+		const float stepX = boundingBox.Extent.X > 14 ? 12.0f : 6.0f;
+		const float stepY = boundingBox.Extent.Y > 14 ? 12.0f : 6.0f;
+		for (float i = -boundingBox.Extent.X; i < boundingBox.Extent.X; i += stepX)
+		{
+			for (float j = -boundingBox.Extent.Y; j < boundingBox.Extent.Y; j += stepY)
+			{
+				Vector3 p1 = boundingBox.Center + Vector3(i, j, +(boundingBox.Extent.Z + 2.0f));
+				Vector3 p2 = boundingBox.Center + Vector3(i, j, -(boundingBox.Extent.Z + 2.0f));
+				Vector3 contactPoint;
+				if (M_Test_Raycast_Collision(p1, p2, false, &contactPoint, obj))
+				{
+					Validate_New_Target(obj, contactPoint, p1);
+				}
+			}
+		}
+
+		// Place one extra topdown center point
+		Vector3 contactPoint;
+		Vector3 p1 = boundingBox.Center + Vector3(0, 0, +(boundingBox.Extent.Z + 2.0f));
+		Vector3 p2 = boundingBox.Center + Vector3(0, 0, -(boundingBox.Extent.Z + 2.0f));
+
+		if (!obj->As_BuildingGameObj())
+		{
+			int boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("target");
+			if (!boneIdx)
+				boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("turret");
+			if (!boneIdx)
+				boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("ROOTTRANSFORM");
+			if (boneIdx)
+			{
+				auto transform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+				p2 = transform.Get_Translation();
+				p2 = p2 - (p1 - p2).Normalized() * Vector3::Distance(p1, p2);
+			}
+		}
+		if (M_Test_Raycast_Collision(p1, p2, !obj->As_BuildingGameObj(), &contactPoint, obj))
+		{
+			Validate_New_Target(obj, contactPoint, p1);
+		}
+	}
+
+	// TODO: rename
+	void Place_Target_Pair(GameObject *obj, Vector3 &sg1, Vector3 &sg2, const AABoxClass &boundingBox)
+	{
+		Vector3 closestPoly1;
+		Vector3 closestPoly2;
+		for (int i = 0; i < 2; i++)
+		{
+			Vector3 &point = i == 0 ? sg1 : sg2;
+			Vector3 originalPoint = point;
+			Vector3 helper;
+			int limit = 100;
+			while (limit && !Get_Closest_Pathfind_Spot(point, 1.7f, &helper))
+			{
+				point.Z -= 1.5f;
+				limit--;
+				//auto g1 = Commands->Create_Object("Invisible_Object", point); Commands->Set_Model(g1, "o_davesarrow");
+			}
+
+			// Check for weird situations where pathfind sectors are epic tall and points would not be anywhere near the ground (e.g. APB Beta Bonzai, Allied Ref)
+			// Also, buildings offmap with no pathfind right next to them need extra help raycast downwards to detect the ground location before placing targets
+			Vector3 &startingPoint = limit ? point : originalPoint;
+			Vector3 checkPoint = Vector3(point.X, point.Y, boundingBox.Center.Z) + Vector3(0, 0, 2 * -boundingBox.Extent.Z + 2.0f);
+
+			if (M_Test_Raycast_Collision(startingPoint, checkPoint, false, &helper) && abs(helper.Z - startingPoint.Z) > 3.0f)
+			{
+				point.Z = helper.Z + 3.0f;
+			}
+			else if (!limit)
+			{
+				//StringClass msg;
+				//msg.Format("msg %s: point failed to find a proper spot", Commands->Get_Preset_Name(obj));
+				//M_Console_Input(msg);
+				// No ground around the building?! Oookay, then allow to shoot its underground interior parts too.
+				point.Z = boundingBox.Center.Z + (boundingBox.Extent.Z > 6.0f ? -boundingBox.Extent.Z + 1.0f : 0);
+				// Return, otherwise this point can end up deep underground firing rays upwards, creating underground interior targets, which is not wanted
+				// return;
+			}
+
+			//auto g1 = Commands->Create_Object("Invisible_Object", point); Commands->Set_Model(g1, "o_davesarrow");
+
+			Vector3 &closestPoly = i == 0 ? closestPoly1 : closestPoly2;
+			if (obj->As_BuildingGameObj())
+			{
+				Find_Closest_Poly_Position(obj->As_BuildingGameObj(), point, &closestPoly);
+				// Do not place point to closest poly, this is unreliable and a bad source of bad target locations (in the interior)
+			}
+			else
+			{
+				int boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("target");
+				if (!boneIdx)
+					boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("turret");
+				if (!boneIdx)
+					boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("ROOTTRANSFORM");
+				if (boneIdx)
+				{
+					auto transform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+					closestPoly = transform.Get_Translation();
+				}
+			}
+		}
+
+		Raycast_Target_Pair(obj, sg1, sg2, closestPoly1, closestPoly2); 
+
+		// Tall buildings
+		const float heightAvg = (sg1.Z + sg2.Z) / 2;
+		int i = 1;
+		while (boundingBox.Center.Z + boundingBox.Extent.Z > heightAvg + 10 * i)
+		{
+			Vector3 higherPoint1 = sg1 + Vector3(0, 0, 10.0f * i);
+			Vector3 higherPoint2 = sg2 + Vector3(0, 0, 10.0f * i);
+			if (obj->As_BuildingGameObj())
+			{
+				Find_Closest_Poly_Position(obj->As_BuildingGameObj(), higherPoint1, &closestPoly1);
+				Find_Closest_Poly_Position(obj->As_BuildingGameObj(), higherPoint2, &closestPoly2);
+			}
+			Raycast_Target_Pair(obj, higherPoint1, higherPoint2, closestPoly1, closestPoly2);
+			i++;
+		}
+	}
+
+	// TODO: rename
+	void Raycast_Target_Pair(GameObject *obj, Vector3 &sg1, Vector3 &sg2, const Vector3 &closestPoly1, const Vector3 &closestPoly2)
+	{
+		for (int i = 0; i < 2; i++)
+		{
+			Vector3 &p1 = i == 0 ? sg1 : sg2;
+			Vector3 &p2 = i == 0 ? sg2 : sg1;
+			Vector3 contactPoint;
+			if (M_Test_Raycast_Collision(p1, p2, !obj->As_BuildingGameObj(), &contactPoint, obj))
+			{
+				Validate_New_Target(obj, contactPoint, p1);
+			}
+			else
+			{
+				Vector3 closestPoly = i == 0 ? closestPoly1 : closestPoly2;
+				Vector3 buildingCtrlPos = Commands->Get_Position(obj);
+				Vector3 otherPoint1 = closestPoly - (p1 - closestPoly).Normalized() * Vector3::Distance(p1, closestPoly);
+				Vector3 otherPoint2 = buildingCtrlPos - (p1 - buildingCtrlPos).Normalized() * Vector3::Distance(p1, buildingCtrlPos);
+
+				// Closest poly is unreliable, proven can be on an invisible physical only blocker, do not use it for building target position
+				// Closest poly, ESPECIALLY stepped in any direction, is unreliable to start raycasting from. Only raycasting towards it should be used.
+				// Vector3 steppedClosestPoly = closestPoly + (p1 - closestPoly).Normalized() * 1.5f;
+				if (M_Test_Raycast_Collision(p1, otherPoint1, !obj->As_BuildingGameObj(), &contactPoint, obj))
+				{
+					Validate_New_Target(obj, contactPoint, p1);
+				}
+				else if (M_Test_Raycast_Collision(p1, otherPoint2, !obj->As_BuildingGameObj(), &contactPoint, obj))
+				{
+					Validate_New_Target(obj, contactPoint, p1);
+				}
+			}
+		}
+	}
+
+	void Validate_New_Target(GameObject *obj, const Vector3& newTargetLocation, const Vector3& raycastedFromLocation)
+	{
+		float min_dist = (obj->As_BuildingGameObj() ? (exteriorTargets.Count() >= 8 ? 3.0f : 1.0f) : (exteriorTargets.Count() >= 4 ? 1.0f : 0.5f));
+		float min_dist_sq = min_dist * min_dist;
+		for (int i = 0; i < exteriorTargets.Count(); i++)
+		{
+			auto target = exteriorTargets[i];
+			if (Vector3::Distance_Squared(Commands->Get_Position(target), newTargetLocation) < min_dist_sq)
+				return;
+		}
+		auto extTarget = Commands->Create_Object("Invisible_Object", newTargetLocation);
+		if (BuildingDebugLocationsDisplayEnabled)
+			Commands->Set_Model(extTarget, BuildingDebugTargetModel.Peek_Buffer());
+		auto skinName = ArmorWarheadManager::Get_Armor_Name(obj->As_DamageableGameObj()->Get_Defense_Object()->Get_Skin());
+		StringClass params;
+		params.Format("Blamo,%s,0", skinName);
+		Commands->Attach_Script(extTarget, "MS_Switch_Skin_And_Shield_Timer", params.Peek_Buffer());
+		Commands->Attach_Script(extTarget, "dp88_AI_Marker_Building", "");
+		Set_Object_Type(extTarget, Get_Object_Type(obj));
+		Set_Max_Shield_Strength(extTarget, Commands->Get_Max_Health(obj));
+		Vector3 exteriorTargetLocation = Commands->Get_Position(extTarget);
+		Vector3 targetWatchLocation = exteriorTargetLocation + (raycastedFromLocation - exteriorTargetLocation).Normalized() * 0.2f;
+		exteriorTargets.Add(extTarget);
+		exteriorTargetWatchpoints.Add(targetWatchLocation);
+	}
+
+	void Timer_Expired(GameObject *obj, int number) override
+	{
+		if (number == 1)
+		{
+			// Sync health of targets with building hp so bots prioritize between buildings/other units correctly
+			if (centralTarget && Commands->Get_Shield_Strength(centralTarget) != Commands->Get_Health(obj))
+			{
+				const float newHealth = Commands->Get_Health(obj);
+				Commands->Set_Shield_Strength(centralTarget, newHealth);
+				Commands->Set_Shield_Strength(centralTargetGround, newHealth);
+				if (objectiveMCTAttackTargetObj)
+					Commands->Set_Shield_Strength(objectiveMCTAttackTargetObj, newHealth);
+
+				for (int i = 0; i < exteriorTargets.Count(); i++)
+				{
+					auto target = exteriorTargets[i];
+					Commands->Set_Shield_Strength(target, newHealth);
+				}
+			}
+			Commands->Start_Timer(obj, this, 4.0f, 1);
+		}
+	}
+
+	void Custom(GameObject* obj, int type, int param, GameObject* sender) override
+	{
+		if (type == CUSTOM_AI_REPAIR_NEEDED)
+		{
+			if (!objectiveRepairLocationObj && isReachableOnGround)
+			{
+				auto objectiveObj = Commands->Create_Object("Invisible_Object", mctObjectiveLocation);
+				Commands->Attach_Script(objectiveObj, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+				objectiveRepairLocationObj = objectiveObj;
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d,%s", Get_Object_Type(obj), dp88_AI_Objective::TYPE_ENGINEERING, 1, 5, 0, 0, 0, Commands->Get_Preset_Name(obj));
+				Commands->Attach_Script(objectiveObj, "dp88_AI_Objective", params.Peek_Buffer());
+
+				// Also create target marker at the mct
+				auto targetObj = Commands->Create_Object("Invisible_Object", mctTargetLocation);
+				auto skinName = ArmorWarheadManager::Get_Armor_Name(obj->As_DamageableGameObj()->Get_Defense_Object()->Get_Skin());
+				params.Format("Blamo,%s,0", skinName);
+				Commands->Attach_Script(targetObj, "MS_Switch_Skin_And_Shield_Timer", params.Peek_Buffer());
+				objectiveRepairTargetObj = targetObj;
+				// Make it teamed so engineers will "shoot" it with the updated Enemy_Seen calls
+				Set_Object_Type(targetObj, Get_Object_Type(obj));
+				StringClass repParams;
+				repParams.Format("%.2f", dp88_customAI::getDistance(objectiveObj, targetObj));
+				Commands->Attach_Script(targetObj, "dp88_AI_Marker_Repairable", repParams.Peek_Buffer());
+
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: repair needed!", Commands->Get_Preset_Name(obj));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_REPAIR_COMPLETE)
+		{
+			if (objectiveRepairLocationObj && isReachableOnGround)
+			{
+				Commands->Destroy_Object(objectiveRepairLocationObj);
+				Commands->Destroy_Object(objectiveRepairTargetObj);
+				objectiveRepairLocationObj = nullptr;
+				objectiveRepairTargetObj = nullptr;
+
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: repair complete!", Commands->Get_Preset_Name(obj));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+			}
+		}
+		else if (type == CUSTOM_EVENT_BUILDING_REVIVED)
+		{
+			Init(obj);
+		}
+	}
+
+	void Damaged(GameObject* obj, GameObject* damager, float amount) override
+	{
+		// Suppress damage "spam"
+		if (M_Has_Timer(obj, this, 2) || amount == 0)
+			return;
+
+		Commands->Start_Timer(obj, this, 5.0f, 2);
+		if (!damager || (!damager->As_SoldierGameObj() && !damager->As_VehicleGameObj()))
+			return;
+
+		Vector3 targetPos = Vector3();
+		bool isWithinPathfindGrid = Get_Closest_Pathfind_Spot_Size(Commands->Get_Position(damager), 2.0f, &targetPos, 2.0f);
+		if (!isWithinPathfindGrid)
+		{
+			dp88_customAI::TargetType targetType = dp88_customAI::GetTargetType(damager);
+			if (targetType != dp88_customAI::FLYING && !Get_Closest_Pathfind_Spot_Size(Commands->Get_Position(damager), 20.0f, &targetPos, 2.0f))
+			{
+				return;
+			}
+			// If attacker is flying, find ground location under it
+			else if (targetType == dp88_customAI::FLYING)
+			{
+				Vector3 enemyLocation = Commands->Get_Position(damager);
+				Vector3 helper;
+				if (!M_Test_Raycast_Collision(enemyLocation, enemyLocation + Vector3(0, 0, -100), false, &helper) ||
+					// Assume the raycast landed within a pathfind grid, run a smaller radius check, and a bigger one only if that fails
+					(!Get_Closest_Pathfind_Spot_Size(helper, 10.0f, &targetPos, 2.0f) && !Get_Closest_Pathfind_Spot_Size(helper, 50.0f, &targetPos, 2.0f)))
+				{
+					return;
+				}
+			}
+			else
+			{
+				// Failed to find suitable location to go closer to target enemy, better not place than cause massive pathfind fail suiciding
+				return;
+			}
+		}
+		else
+		{
+			// Place exactly at enemy if within pathfind grid, not around in the pathfind sector
+			targetPos = Commands->Get_Position(damager);
+		}
+		Create_Timed_Objective(Get_Object_Type(obj), 8.0f, targetPos, 2, dp88_AI_Objective::TYPE_DEFENSIVE);
+
+	}
+
+	void Cleanup(GameObject* obj)
+	{
+		if (!isDeleted)
+		{
+			Commands->Destroy_Object(objectiveMCTAttackLocationObj);
+			Commands->Destroy_Object(objectiveMCTAttackTargetObj);
+			Commands->Destroy_Object(objectiveRepairLocationObj);
+			Commands->Destroy_Object(objectiveRepairTargetObj);
+			Commands->Destroy_Object(centralTarget);
+			Commands->Destroy_Object(centralTargetGround);
+			for (int i = 0; i < exteriorTargets.Count(); i++) 
+				Commands->Destroy_Object(exteriorTargets[i]);
+
+			for (int i = 0; i < objectiveVehicleAttackLocations.Count(); i++) 
+				Commands->Destroy_Object(objectiveVehicleAttackLocations[i]);
+
+			// TODO: keep the locations saved so no need to recalculate after reviving, just place them
+			exteriorTargets.Clear();
+			exteriorTargetWatchpoints.Clear();
+			if (M_Has_Timer(obj, this, 1))
+				M_Stop_Timer2(obj, this, 1);
+
+			AIBuildings.DeleteObj(obj);
+			isDeleted = true;
+		}
+	}
+
+	void Killed(GameObject* obj, GameObject* killer) override
+	{
+		Cleanup(obj);
+	}
+
+	void Detach(GameObject* obj) override
+	{
+		if (Exe != EXE_LEVELEDIT)
+		{
+			Cleanup(obj);
+		}
+		ScriptImpClass::Detach(obj);
+	}
+};
+REGISTER_SCRIPT(MS_AI_Building, "DamagePercentToRepair=10:int,CustomWeakpointPresetName:string");
+
+
+/*------------------------
+AI Scripts
+--------------------------*/
+
+static bool DebugPathfindFailInited = false;
+
+void Init_Pathfind_Debug()
+{
+	if (DebugPathfindFailInited)
+		return;
+
+	INIClass* config = Get_INI("bot_config.cfg");
+	if (config)
+	{
+		DebugPathfindFailMessagesLiveEnabled = config->Get_Bool("Debug", "EnablePathfindFailLiveMessages", false);
+		DebugPathfindFailMessagesFileEnabled = config->Get_Bool("Debug", "EnablePathfindFailFileMessages", false);
+		Release_INI(config);
+		DebugPathfindFailInited = true;
+	}
+}
+
+/*!
+* \brief Extended AI Behaviour
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Copy of Danpaul88's Offensive AI, but it knows if it can deploy and can be defensive/engineer too.
+* These AI units will strafe around in battle, trying to evade enemy shots.
+* They can also properly use flying units and vehicles with limited turret rotation.
+* The script also helps the AI a bit when colliding into objects they cannot see in the pathfind grid.
+*/
+class MS_AI_Behaviour : public dp88_AI_Tank_Offensive, public Pathfind_Help_Implementer
+{
+	template<typename> 
+	friend class ScriptRegistrant;
+
+	enum State {
+		UNKNOWN,
+		GOING_TO_VEHICLE,
+		GOING_TO_REFILL,
+		GOING_TO_SWITCH_OBJECTIVE,
+		GOING_TO_RELOAD,
+		GOING_TO_EXIT_VEHICLE_POINT,
+		GOING_TO_RALLY_POINT,
+		PATROLLING,
+	};
+
+	State state;
+
+	unsigned int objectiveType;
+	JFW_Key_Hook_Base *deployScript;
+	bool isDeployable;
+	bool isDeployed;
+	bool isDeployedStationary;
+	bool isVTOL;
+	bool isBomber;
+	bool isJumpAllowed;
+	bool isLimitedAmmoUnit;
+	bool isAmmoEmptyNoteSent;
+	// Prevent damage detection event spam
+	bool isDamageDetectionEnabled;
+	int totalAmmo;
+
+	bool deploy_vs_infantry;
+	bool deploy_vs_lightVehicle;
+	bool deploy_vs_heavyVehicle;
+	bool deploy_vs_VTOL;
+	bool deploy_vs_building;
+
+	float attackError;
+	float flyingHeight;
+
+	// Data from pathfind helper
+	bool gettingAround;
+	int pathfindFailCount;
+	int objectiveNotFoundCount;
+
+	int thinkCount;
+
+	// Limiting wandering
+	int wanderThinkCount;
+
+	// Limiting evading
+	int evadeThinkCount;
+	Vector3 evadePosition;
+
+	// Squishing!
+	bool isSquisherUnit;
+	bool isSquishableUnit;
+
+	// Helping limited turret rotation vehicles
+	bool isPrimaryAmmoTurning;
+	bool isSecondaryAmmoTurning;
+	bool isPrimaryAmmoGravityAffected;
+	bool isSecondaryAmmoGravityAffected;
+	bool isTurretRotationLimited;
+	float turretTiltMin;
+	float turretTiltMax;
+	float turretTurnMin;
+	float turretTurnMax;
+
+	// Ignoring primary objectives when doing other stuff
+	ReferencerClass overridingTask;
+	//int overridingTaskID;
+	bool arrivedToOverridingTask;
+	// Types: 0 = stationary | 1 = follow | 2 = cover | 7 = blindless attack
+	int overridingTaskType;
+
+	ReferencerClass patrolTask;
+	//int patrolTaskID;
+
+	BotPersonality *personality;
+
+	DynamicVectorClass<int> ignoredObjectives;
+
+	MS_AI_Behaviour()
+		: personality(nullptr)
+	{
+
+	}
+
+	~MS_AI_Behaviour()
+	{
+		REF_PTR_RELEASE(personality);
+	}
+
+	void Created(GameObject *obj) override
+	{
+		state = UNKNOWN;
+
+		objectiveType = 1;
+		deployScript = nullptr;
+		isDeployable = false;
+		isDeployed = false;
+		isDeployedStationary = false;
+		isVTOL = false;
+		isBomber = false;
+		isJumpAllowed = false;
+		isLimitedAmmoUnit = false;
+		isAmmoEmptyNoteSent = false;
+		isDamageDetectionEnabled = true;
+		totalAmmo = 0;
+
+		deploy_vs_infantry = false;
+		deploy_vs_lightVehicle = false;
+		deploy_vs_heavyVehicle = false;
+		deploy_vs_VTOL = false;
+		deploy_vs_building = false;
+
+		attackError = 0;
+		flyingHeight = 25.0f;	
+
+		gettingAround = false;
+		pathfindFailCount = 0;
+		objectiveNotFoundCount = 0;
+
+		thinkCount = 0;
+
+		wanderThinkCount = 0;
+
+		evadeThinkCount = 0;
+		evadePosition = Vector3();
+
+		isSquisherUnit = false;
+		isSquishableUnit = false;
+
+		isPrimaryAmmoTurning = false;
+		isSecondaryAmmoTurning = false;
+		isPrimaryAmmoGravityAffected = false;
+		isSecondaryAmmoGravityAffected = false;
+		isTurretRotationLimited = false;
+		turretTiltMin = 0.0f;
+		turretTiltMax = 0.0f;
+		turretTurnMin = 0.0f;
+		turretTurnMax = 0.0f;
+
+		overridingTask = nullptr;
+		arrivedToOverridingTask = true;
+		overridingTaskType = 0;
+
+		patrolTask = nullptr;
+
+		personality = nullptr;
+
+		Remove_Stock_AI_Behaviour(obj);
+		Init_Pathfind_Debug();
+
+		objectiveType = (unsigned int)Get_Int_Parameter("ObjectiveType");
+		if (objectiveType != dp88_AI_Objective::TYPE_OFFENSIVE && objectiveType != dp88_AI_Objective::TYPE_DEFENSIVE &&
+			objectiveType != dp88_AI_Objective::TYPE_ENGINEERING)
+		{
+			objectiveType = dp88_AI_Objective::TYPE_OFFENSIVE;
+		}
+
+		if (GetTargetType(obj) == FLYING)
+			isVTOL = true;
+
+		attackError = obj->As_SmartGameObj()->Get_Weapon_Error();
+
+		if (obj->As_VehicleGameObj())
+		{
+			isSquisherUnit = obj->As_VehicleGameObj()->Get_Squish_Velocity() < 10;
+			turretTurnMin = obj->As_VehicleGameObj()->As_ArmedGameObj()->Get_Weapon_Turn_Min();
+			turretTurnMax = obj->As_VehicleGameObj()->As_ArmedGameObj()->Get_Weapon_Turn_Max();
+
+			if (turretTurnMin > DEG2RAD(-180) || turretTurnMax < DEG2RAD(180))
+				isTurretRotationLimited = true;
+
+			turretTiltMin = obj->As_VehicleGameObj()->As_ArmedGameObj()->Get_Weapon_Tilt_Min();
+			turretTiltMax = obj->As_VehicleGameObj()->As_ArmedGameObj()->Get_Weapon_Tilt_Max();
+
+			WeaponClass* weapon = obj->As_VehicleGameObj()->Get_Weapon();
+			if (weapon)
+			{
+				if (auto ammoDef = weapon->PrimaryAmmoDefinition)
+				{
+					isPrimaryAmmoTurning = ammoDef->TurnRate > 0;
+					isPrimaryAmmoGravityAffected = ammoDef->Gravity != 0;
+				}
+				if (auto ammoDef = weapon->SecondaryAmmoDefinition)
+				{
+					isSecondaryAmmoTurning = ammoDef->TurnRate > 0;
+					isSecondaryAmmoGravityAffected = ammoDef->Gravity != 0;
+				}
+			}
+		}
+		else if (obj->As_SoldierGameObj()) {
+			isSquishableUnit = !Is_Unsquishable(obj);
+			obj->As_SoldierGameObj()->Set_Override_Muzzle_Direction(true);
+		}
+
+		// Overridden by MS_AI_Player_Controller logic if present
+		personality = new BotPersonality(FreeRandom.Get_Int(3)); // Ref count 1
+
+		Commands->Start_Timer(obj, this, 0.1f, TIMER_AI_POST_CREATED);
+		dp88_AI_Tank_Offensive::Created(obj);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Post_Created(GameObject *obj)
+	{
+		// Enable transitions for player-logic bot driven vehicles (but not for vehicle bots with no drivers inside)
+		if (obj->As_VehicleGameObj() && obj->As_VehicleGameObj()->Get_Occupant_Count())
+		{
+			// Fix disabled transition set in dp88_customAI::Init 
+			Commands->Enable_Vehicle_Transitions(obj, true);
+		}
+		if (auto deployParamsScript = (MS_AI_Marker_Deployable *)(Find_Script_On_Object(obj, "MS_AI_Marker_Deployable")))
+		{
+			deployScript = (JFW_Key_Hook_Base *)(Find_Script_On_Object(obj, deployParamsScript->Get_Deploy_Script_Name()));
+			isDeployable = deployScript ? true : false;
+			if (isDeployable)
+			{
+				// Init should deploy booleans
+				deploy_vs_infantry = deployParamsScript->Get_Deploy_VS_Infantry();
+				deploy_vs_lightVehicle = deployParamsScript->Get_Deploy_VS_Light_Vehicle();
+				deploy_vs_heavyVehicle = deployParamsScript->Get_Deploy_VS_Heavy_Vehicle();
+				deploy_vs_VTOL = deployParamsScript->Get_Deploy_VS_VTOL();
+				deploy_vs_building = deployParamsScript->Get_Deploy_VS_Building();
+				isDeployedStationary = deployParamsScript->Get_Deploy_Is_Stationary();
+			}
+		}
+
+		isBomber = Is_Script_Attached(obj, "MS_AI_Marker_Bomber_Aircraft");
+		isJumpAllowed = !Is_Script_Attached(obj, "MS_AI_Marker_No_Jump");
+		isLimitedAmmoUnit = Is_Script_Attached(obj, "MS_AI_Marker_Refiller_Vehicle");
+
+		// Fix bad AI definition settings
+		int weaponMaxRange = max(primary_maxRange, secondary_maxRange);
+		float defSightRange = obj->As_SmartGameObj()->Get_Definition().Get_Sight_Range();
+		float neededSightRange = max( max(weaponMaxRange, defSightRange), 100 ) + 5; // +5: Sight range needs to be larger than the weapon max range
+		if (defSightRange < neededSightRange)
+			const_cast<SmartGameObjDef&>(obj->As_SmartGameObj()->Get_Definition()).Set_Sight_Range(neededSightRange);
+
+		float neededArc = obj->As_SoldierGameObj() ? 135.0f : 360.0f;
+		if (obj->As_SmartGameObj()->Get_Definition().Get_Sight_Arc() < neededArc)
+			const_cast<SmartGameObjDef&>(obj->As_SmartGameObj()->Get_Definition()).Set_Sight_Arc(DEG2RAD(neededArc));
+
+		Commands->Attach_Script(obj, "MS_AI_Pathfind_Helper", "");
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	// Engineers do react and shoot repairable target types, while others don't.
+	float getBasePriority(GameObject *target) override
+	{
+		if (GetTargetType(target) == REPAIRABLE && objectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+			return 1000000.0;
+		else
+			return dp88_customAI::getBasePriority(target);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	float getPriority(GameObject *obj, GameObject* target) override
+	{
+		// Very high priority if blindless attack mode
+		if (overridingTaskType == 7 && target && target == overridingTask)
+			return 99999;
+
+		return dp88_customAI::getPriority(obj, target);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	// Return "should deploy" setting for current target type
+	bool Should_Deploy(GameObject *target)
+	{
+		if (!isDeployable)
+			return false;
+
+		switch (GetTargetType(target))
+		{
+		case SOLDIER:
+			return deploy_vs_infantry;
+		case LIGHT_VEHICLE:
+			return deploy_vs_lightVehicle;
+		case HEAVY_VEHICLE:
+			return deploy_vs_heavyVehicle;
+		case FLYING:
+			return deploy_vs_VTOL;
+		case BUILDING:
+			return deploy_vs_building;
+		}
+		return false;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	bool Can_Tilt_Towards_Target(const bool isAimFree, const Matrix3D &turretTransform, GameObject* target)
+	{
+		if (isAimFree)
+			return true;
+
+		Vector3 targetPos = Commands->Get_Position(target);
+		Vector3 adjustedTargetPos;
+		Matrix3D::Inverse_Transform_Vector(turretTransform, targetPos, &adjustedTargetPos);
+		adjustedTargetPos.X = Vector2(adjustedTargetPos.X, adjustedTargetPos.Y).Length();
+		adjustedTargetPos.Y = 0;
+		adjustedTargetPos.Normalize();
+		const float angle = acos(adjustedTargetPos.X);
+		const float signedAngle = adjustedTargetPos.Z >= 0 ? angle : -angle;
+		const float finalAngle = signedAngle;
+
+		return finalAngle < turretTiltMax && finalAngle > turretTiltMin;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	// Air units: params.usePathfind must be false on VTOL units (or they use ground) + generate air pathfind on all maps! - Jerad
+	void Enemy_Seen(GameObject *obj, GameObject *enemy) override
+	{
+		// Ignore all enemies if out of ammo or while going through doors
+		if ((isLimitedAmmoUnit && !totalAmmo) || (obj->As_SoldierGameObj() && Is_Soldier_Busy(obj->As_SoldierGameObj())))
+		{
+			return;
+		}
+
+		// Attack immediately if this is the objective but not yet the target
+		/*
+		if (overridingTaskType == 7 && Commands->Get_ID(enemy) == overridingTaskID && Commands->Get_ID(enemy) != targetID)
+		{
+			targetID = Commands->Get_ID(enemy);
+			AttackTarget(obj, enemy);
+		}
+		else
+		*/
+			dp88_AI_Tank_Offensive::Enemy_Seen(obj, enemy);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	bool IsValidTarget(GameObject* obj, GameObject *target) override
+	{
+		if (obj->As_VehicleGameObj())
+		{
+			// Check if needed tilting angle is still available
+			Matrix3D turretTransform;
+			int boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("turret");
+			if (boneIdx)
+				turretTransform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+			else
+				turretTransform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Transform();
+
+			const bool isAimFree = getPrimary(target) ? (isPrimaryAmmoTurning || isPrimaryAmmoGravityAffected) : (isSecondaryAmmoTurning || isSecondaryAmmoGravityAffected);
+			if (!Can_Tilt_Towards_Target(isAimFree, turretTransform, target))
+				return false;
+		}
+
+		return dp88_AI_Tank_Offensive::IsValidTarget(obj, target);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	// Air units: params.usePathfind must be FALSE on VTOL units (or they use ground) + generate air pathfind on all maps! - Jerad
+	void AttackTarget(GameObject *obj, GameObject *target) override
+	{
+		if (state == PATROLLING)
+		{
+			state = UNKNOWN;
+		}
+
+		ActionParamsStruct params;
+		bool bPersueTarget = ShouldPursueTarget(obj, target);
+		bool bModifyAction = false;
+
+		// If we are going to persue the target then clear any existing actions
+		if (bPersueTarget || !obj->As_SmartGameObj()->Get_Action()->Is_Acting() || 
+			evadePosition != Vector3() || Commands->Get_Action_ID(obj) != ACTION_ID_ATTACK_TARGET)
+		{
+			// COPIED ORIGINAL FUNCTION, ONLY RESET IF ACTION DOESNT MATCH
+			if (Commands->Get_Action_ID(obj) != ACTION_ID_ATTACK_TARGET)
+				ResetAllActions(obj);
+			params.Set_Basic(this, 90, ACTION_ID_ATTACK_TARGET);
+		}
+		// Modify only works for changing target while already attacking something.
+		// Does not work for: 1. Modify movement and keep current target OR 2. Keep moving and ADD target while had none before
+		else
+		{
+			Commands->Get_Action_Params(obj, params);
+			bModifyAction = true;
+		}
+
+		// Setup parameters to track state
+		// COPIED ORIGINAL FUNCTION, REMOVED VALUE SETTINGS HERE, Enemy_Seen HANDLES THEM
+		//targetID = Commands->Get_ID(target);
+		m_bTargetPrimaryFire = getPrimary(target);
+		targetPriority = getPriority(obj, target);
+		//targetLastSeen = (int)time(0);
+
+		TargetType targetType = GetTargetType(target);
+
+		// Setup the attack parameters
+		// COPIED ORIGINAL FUNCTION, MODIFIED ATTACK ERROR FROM 0.0
+		int maxRange = m_bTargetPrimaryFire ? primary_maxRange : secondary_maxRange;
+		WeaponClass* weapon = obj->As_SmartGameObj()->Get_Weapon();
+		// Use a worse attack error when throwing C4 so they dont end up all on top of eachother, better attack error vs building exteriors (helps longrange a lot)
+		const float tempAttackError = (targetType == BUILDING && weapon) ? (weapon->Get_Style() == 0 ? DEG2RAD(200) : attackError / 5) : (attackError * personality->Get_Random(BotPersonality::ATTACK_ERROR_MULTIPLIER));
+		// If C4 type, attack center of the c4, because the origin point is the lowlow part and shooting it would miss
+		if (target->As_PhysicalGameObj() && (target->As_PhysicalGameObj()->As_C4GameObj() || target->As_PhysicalGameObj()->As_BeaconGameObj()))
+		{
+			auto &boundingBox = target->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+			params.Set_Attack(boundingBox.Center, (float)(maxRange), tempAttackError, m_bTargetPrimaryFire);
+		}
+		else
+			params.Set_Attack(target, (float)(maxRange), tempAttackError, m_bTargetPrimaryFire);
+		params.AttackCheckBlocked = false;
+		// This makes units like Renegade MRLS/Stealth Tank work, their rockets turn after firing but the barrel cant point at their target
+		params.AttackForceFire = m_bTargetPrimaryFire ? isPrimaryAmmoTurning : isSecondaryAmmoTurning;
+
+		// COPIED ORIGINAL FUNCTION, MODIFIED FROM HERE
+		// Soldiers crouch with some probability
+		bool moveCrouched = false;
+		if (bPersueTarget && obj->As_SoldierGameObj())
+		{
+			if (FreeRandom.Get_Float() < 0.1f && !isDeployable)
+				moveCrouched = true;
+
+			if (FreeRandom.Get_Float() < 0.5f)
+				params.AttackCrouched = true;
+		}
+
+		int preferredRange = GetPreferredAttackRange(obj, target);
+		float currentDistance2 = Vector3::Distance(Commands->Get_Position(obj), Commands->Get_Position(target));
+
+		// Evade-moving around
+		if (evadePosition != Vector3())
+		{
+			params.Set_Movement(evadePosition, 1.0f, obj->As_SoldierGameObj() ? 0.5f : 2.0f, moveCrouched);
+			if (obj->As_VehicleGameObj())
+			{
+				params.MoveBackup = Is_Behind_Cone(Get_Transform(obj), evadePosition, 0);
+			}
+		}
+		// COPIED ORIGINAL FUNCTION, MODIFIED UNTIL HERE
+		// Setup the movement parameters if we want to persue the target
+		else if (bPersueTarget)
+		{
+			bool canSquishTarget = isSquisherUnit && target->As_SoldierGameObj() && !Is_Unsquishable(target);
+			m_bMovingToTarget = (preferredRange < getDistance(obj, target) || canSquishTarget) && (!isDeployed || !isDeployedStationary);
+			// Check if it's even possible to go there before pursuing, units might not be able to pathfind to target, and be stuck
+			Vector3 targetPos = Commands->Get_Position(target);
+			bool isTargetFlying = targetType == FLYING;
+			// No corrections when target is flying or repairable
+			if (!isTargetFlying)
+			{
+				Vector3 originalTargetPos = targetPos;
+				bool isWithinPathfindGrid = Get_Closest_Pathfind_Spot_Size(originalTargetPos, 1.0f, &targetPos, 2.0f);
+				if (!isWithinPathfindGrid)
+				{
+					if (!Get_Closest_Pathfind_Spot_Size(originalTargetPos, 15.0f, &targetPos, 2.0f))
+					{
+						// Default to not trying to move
+						m_bMovingToTarget = false;
+					}
+					// Try to reach it without pathfind if out of range
+					else if (targetType == REPAIRABLE && getDistance(obj, target) >= maxRange)
+					{
+						params.MovePathfind = false;
+					}
+				}
+				else
+				{
+					// If pathfind sector is big, targetPos can be in a bad location, e.g. huge sector's center, further than the obj
+					if (getDistance(obj, target) < Vector3::Distance(originalTargetPos, targetPos))
+					{
+						// Stupid result, defaulting to target location
+						targetPos = originalTargetPos;
+					}
+				}
+			}
+
+			bool needsRotating = false;
+			if (isTurretRotationLimited)
+			{
+				const float angle = Calculate_Relative_Facing_Diff(obj, targetPos);
+				// Only need correction if the turret cannot rotate there, unless the ammo is turning, in which case a little offness is acceptable since the ammo turn corrects it after some distance
+				auto &boundingBox = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+				const int radius = (int)(max(boundingBox.Extent.X, boundingBox.Extent.Y));
+				if ((angle < turretTurnMin || angle > turretTurnMax) && (!(m_bTargetPrimaryFire ? isPrimaryAmmoTurning : isSecondaryAmmoTurning) || (abs(angle) > DEG2RAD(20) && Vector3::Distance(Commands->Get_Position(obj), targetPos) > radius + 10.0f)))
+					needsRotating = true;
+			}
+
+			if (targetType == REPAIRABLE)
+			{
+				params.AttackForceFire = true;
+				if (auto objectiveObj = m_pCurrentObjective ? m_pCurrentObjective->GetGameObject() : nullptr)
+					params.Set_Movement(Commands->Get_Position(objectiveObj), 1.0f, 1.25f);
+
+				if (getDistance(obj, target) >= maxRange)
+				{
+					// targetPos = Commands->Get_Position(target);
+					params.AttackRange = (float)(getDistance(obj, target) + 1);
+				}
+			}
+			// COPIED ORIGINAL FUNCTION, ADDED REACHABLE CHECK
+			else if (!needsRotating && (m_bMovingToTarget || pathfindFailCount > 4) && !isTargetFlying)
+			{
+				// COPIED ORIGINAL FUNCTION, MODIFIED FROM HERE
+				// Choose random position around the position between me and target within preferred range
+				// Get current distance and wanted distance to get the required ratio
+				float ratio = preferredRange / currentDistance2;
+				Vector3 pos = Lerp(targetPos, Commands->Get_Position(obj), ratio);
+				// If building target type, make sure this pos is not something stupid unreachable (BAD_DEST when big vehicles get a position inside a building interior for example)
+				if (targetType == BUILDING)
+				{
+					Get_Closest_Pathfind_Spot_Size(pos, min(20.0f, preferredRange), &pos, obj->As_SoldierGameObj() ? 0.5f : 5.0f);
+					params.Set_Movement(pos, 1.0f, 1.0f, moveCrouched);
+				}
+				else 
+				{
+					if (canSquishTarget) {
+						params.Set_Movement(target, 1.0f, 0);
+					}
+					else if (!Get_Random_Pathfind_Spot(pos, (preferredRange / 2 - 2.0f), &pos) || !Can_Move_To(obj, pos) || preferredRange == 0 || isTurretRotationLimited || pathfindFailCount > 4)
+					{
+						params.Set_Movement(targetPos, 1.0f, abs((float)preferredRange - 1.5f), moveCrouched);
+					}
+					else
+						params.Set_Movement(pos, 1.0f, 1.0f, moveCrouched);
+				}
+				// COPIED ORIGINAL FUNCTION, MODIFIED UNTIL HERE
+			}
+			// COPIED ORIGINAL FUNCTION, MODIFIED FROM HERE
+			// "Turn towards" enemy if turret cannot turn on the enemy by moving closer to target
+			else if (needsRotating && !isTargetFlying)
+			{
+				Vector3 objPos = Commands->Get_Position(obj);
+				targetPos = Commands->Get_Position(target);
+
+				// "Turn" towards target by actually going towards it
+				const float currentDistance = Vector3::Distance(objPos, targetPos);
+				float direction;
+				if (currentDistance > preferredRange)
+				{
+					direction = 1.0f;
+				}
+				else
+				{
+					direction = -1.0f;
+					params.MoveBackup = true;
+				}
+				Vector3 pos = objPos + (targetPos - objPos).Normalized() * direction * 1.0f;
+				params.Set_Movement(pos, 1.0f, 0.5f);
+			}
+			// Change position around target randomly
+			else if (obj->As_SoldierGameObj() && (!isDeployable || !isDeployedStationary))
+			{
+				Vector3 pos = Commands->Get_Position(target);
+				if (Get_Random_Pathfind_Spot_Around_Position(obj, pos, 0, (float)preferredRange / 2, &pos))
+					params.Set_Movement(pos, 1.0f, 5.0f);
+				// Can't reach: continue to move towards objective (if one exists and not there yet)
+				else if (nullptr != m_pCurrentObjective && m_pCurrentObjective->GetRange() < getDistance(obj, m_pCurrentObjective->GetGameObject()))
+					params.Set_Movement(Commands->Get_Position(m_pCurrentObjective->GetGameObject()), 1.0f, (float)(m_pCurrentObjective->GetRange()));
+			}
+			// COPIED ORIGINAL FUNCTION, MODIFIED UNTIL HERE
+		}
+		// COPIED ORIGINAL FUNCTION, MODIFIED FROM HERE
+		// Continue towards current objective, but shoot target
+		else if (nullptr != m_pCurrentObjective)
+		{
+			// Setup the movement parameters
+			int arrivalRange = m_pCurrentObjective->GetRange();
+			m_bMovingToObjective = (arrivalRange + 1) < getDistance(obj, m_pCurrentObjective->GetGameObject());
+			if (m_bMovingToObjective)
+				params.Set_Movement(Commands->Get_Position(m_pCurrentObjective->GetGameObject()), 1.0f, (float)arrivalRange);
+		}
+
+		// VTOLs should never use ground pathfind
+		if (isVTOL)
+		{
+			params.MovePathfind = false;
+			params.MoveLocation.Z += flyingHeight;
+			auto helperPos = Commands->Get_Position(obj);
+			helperPos.Z -= 15;
+			if (isBomber && Vector3::Distance(helperPos, Commands->Get_Position(target)) <= maxRange)
+			{
+				params.AttackForceFire = true;
+			}
+		}
+		// COPIED ORIGINAL FUNCTION, MODIFIED UNTIL HERE
+
+		// Commence the action
+		if (bModifyAction)
+			Commands->Modify_Action(obj, params.ActionID, params, m_bMovingToTarget, true);
+		else
+		{
+			/*
+			if (m_bSplashInfantry && targetType == SOLDIER && target->As_SoldierGameObj()->Get_State() != HumanStateClass::SWIM)
+			{
+				Trace(TT::Tracing::TRACE_INFO, "Using splash damage against this target");
+				Commands->Action_Attack_Splash(obj, params);
+			}
+			else
+			*/
+				Commands->Action_Attack(obj, params);
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	dp88_AI_Objective* ChooseNewObjective(GameObject* obj) override
+	{
+		unsigned int objType = overridingTask ? (unsigned int)overridingTask->Get_ID() : objectiveType;
+		dp88_AI_Objective* pObjective = dp88_AI_Objective::GetBestObjective(obj, objType, modifier_distance, ignoredObjectives);
+
+		// Override patrolling with defensive objective
+		if (pObjective && state == PATROLLING)
+			state = UNKNOWN;
+
+		return pObjective;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void GoToObjective(GameObject *obj, float speed = 1.0f) override
+	{
+		if (nullptr == m_pCurrentObjective)
+			return;
+
+		if (Go_To_Near_Objective(obj, m_pCurrentObjective))
+			m_bMovingToObjective = true;
+
+		// Override
+		if (!m_bMovingToObjective || !overridingTask || overridingTaskType == 1 || arrivedToOverridingTask)
+		{
+			dp88_AI_Unit::GoToObjective(obj, Get_Movement_Speed(obj));
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void GoToPatrolPoint(GameObject *obj)
+	{
+		if (auto patrolObj = patrolTask.Get_Ptr())
+		{
+			auto objective = (dp88_AI_Objective *)(Find_Script_On_Object(patrolObj, "dp88_AI_Objective"));
+			if (!objective)
+				return;
+
+			if (Go_To_Near_Objective(obj, objective))
+				state = PATROLLING;
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	/* Used by GoToObjective and GoToPatrolPoint */
+	bool Go_To_Near_Objective(GameObject *obj, dp88_AI_Objective *objective)
+	{
+		if (isDeployable)
+		{
+			if (isDeployed && isDeployedStationary)
+			{
+				deployScript->KeyHook();
+				//M_Console_Input("msg GoToObjective, undeploying");
+			}
+		}
+
+		Vector3 pos;
+		Vector3 objectivePos = Commands->Get_Position(objective->GetGameObject());
+		float minDist = obj->As_SoldierGameObj() ? 1.0f : 2.0f;
+		if (minDist > objective->GetRange())
+			minDist = (float)objective->GetRange();
+
+		if (Get_Random_Pathfind_Spot_Around_Position(obj, objectivePos, minDist, (float)objective->GetRange(), &pos))
+		{
+			ActionParamsStruct params;
+			Commands->Get_Action_Params(obj, params);
+			// Stop normal behaviour
+			params.MoveObject = nullptr;
+			params.WaypathID = 0;
+			Commands->Action_Reset(obj, 101.0f);
+
+			params.Set_Basic(this, 80, ACTION_ID_MOVE_TO_OBJECTIVE);
+			if (isVTOL)
+			{
+				params.MovePathfind = false;
+				pos.Z += flyingHeight;
+			}
+			params.Set_Movement(pos, Get_Movement_Speed(obj), 1.0f);
+			Commands->Action_Goto(obj, params);
+			return true;
+		}
+		return false;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	/* Returns the speed that should be used while going to an objective */
+	float Get_Movement_Speed(GameObject *obj)
+	{
+		float speed = 1.0f;
+		// Fixes 4.x vehicle bots not driving at full speed potential
+		if (obj->As_VehicleGameObj())
+			speed = 100.0f;
+		// Sprinting possibility for soldiers
+		/*if (obj->As_SoldierGameObj() && personality->Get_Random(BotPersonality::SPRINT_CHANCE))
+			speed = Commands->Get_Soldier_Sprint_Speed(obj);*/
+		return speed;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	bool ShouldPursueTarget(GameObject *obj, GameObject *target) override
+	{
+		// Don't chase if there's an objective AND objective is not this target
+		if (overridingTask)
+			return overridingTask == target;
+
+		return dp88_AI_Unit::ShouldPursueTarget(obj, target);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_DEPLOY_STATE_CHANGED && isDeployable)
+		{
+			isDeployed = param == 1;
+		}
+		else if (type == CUSTOM_AI_GO_TO_PATROL_POINT)
+		{
+			patrolTask = Commands->Find_Object(param);
+			GoToPatrolPoint(obj);
+		}
+		else if (type == CUSTOM_AI_COOP_NEXT_RALLYPOINT || type == CUSTOM_AI_COOP_PLAYER_FOLLOW ||
+			type == CUSTOM_AI_COOP_PLAYER_FOLLOW_COVER || type == CUSTOM_AI_COOP_PLAYER_DEFEND_BASE ||
+			type == CUSTOM_AI_ORDER_MOVE || type == CUSTOM_AI_ORDER_ATTACK ||
+			type == CUSTOM_AI_ORDER_DEFEND || type == CUSTOM_AI_GO_TO_REFILL || type == CUSTOM_AI_GO_SWITCH_OBJECTIVE ||
+			type == CUSTOM_AI_GO_TO_RELOAD || type == CUSTOM_AI_GO_TO_EXIT_VEHICLE ||
+			type == CUSTOM_AI_GO_TO_VEHICLE)
+		{
+			overridingTask = Commands->Find_Object(param);
+			arrivedToOverridingTask = param == 0;
+
+			if (type == CUSTOM_AI_GO_TO_VEHICLE)
+				state = GOING_TO_VEHICLE;
+
+			else if (type == CUSTOM_AI_GO_TO_REFILL)
+				state = GOING_TO_REFILL;
+
+			else if (type == CUSTOM_AI_GO_SWITCH_OBJECTIVE)
+				state = GOING_TO_SWITCH_OBJECTIVE;
+
+			else if (type == CUSTOM_AI_GO_TO_RELOAD)
+				state = GOING_TO_RELOAD;
+
+			else if (type == CUSTOM_AI_GO_TO_EXIT_VEHICLE)
+				state = GOING_TO_EXIT_VEHICLE_POINT;
+
+			// If task is 0, timer will catch the proper objective?
+			else if (type == CUSTOM_AI_COOP_NEXT_RALLYPOINT && overridingTask)
+				state = GOING_TO_RALLY_POINT;
+			
+			switch (type) {
+			case CUSTOM_AI_COOP_NEXT_RALLYPOINT:
+			case CUSTOM_AI_ORDER_MOVE:
+			{
+				//M_Console_Input("msg AI Beh: MOVE rcvd");
+				overridingTaskType = 0;
+				break;
+			}
+			case CUSTOM_AI_COOP_PLAYER_FOLLOW:
+			case CUSTOM_AI_ORDER_DEFEND:
+			case CUSTOM_AI_GO_TO_REFILL:
+			case CUSTOM_AI_GO_SWITCH_OBJECTIVE:
+			case CUSTOM_AI_GO_TO_RELOAD:
+			case CUSTOM_AI_GO_TO_VEHICLE:
+			case CUSTOM_AI_GO_TO_EXIT_VEHICLE:
+			{
+				//M_Console_Input("msg AI Beh: DEFEND rcvd");
+				overridingTaskType = 1;
+				break;
+			}
+			case CUSTOM_AI_COOP_PLAYER_FOLLOW_COVER:
+			case CUSTOM_AI_COOP_PLAYER_DEFEND_BASE:
+			{
+				overridingTaskType = 2;
+				break;
+			}
+			case CUSTOM_AI_ORDER_ATTACK:
+			{
+				if (auto taskTarget = overridingTask.Get_Ptr())
+				{
+					overridingTaskType = 7;
+					state = UNKNOWN;
+					// This makes sure infantry turn towards and attack the target if it's close by
+					if (!M_Test_Raycast_Collision(Commands->Get_Position(obj) + Vector3(0, 0, 1.5f), Commands->Get_Position(taskTarget) + Vector3(0, 0, 1.5f), false))
+						Enemy_Seen(obj, taskTarget);
+				}
+
+				break;
+			}
+			}
+			ResetAllActions(obj);
+			// Faster acting
+			m_pCurrentObjective = ChooseNewObjective(obj);
+			if (nullptr != m_pCurrentObjective)
+				GoToObjective(obj);
+		}
+		else if (type == CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP || type == CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE)
+		{
+			overridingTask = nullptr;
+			m_target = nullptr;
+			ResetAllActions(obj);
+			m_pCurrentObjective = nullptr;
+			state = UNKNOWN;
+		}
+		else
+			dp88_AI_Tank_Offensive::Custom(obj, type, param, sender);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == TIMER_AI_POST_CREATED)
+		{
+			Post_Created(obj);
+		}
+		else if (number == TIMER_AI_THINK)
+		{
+			if (Commands->Get_Health(obj) < 1.0f)
+			{
+				return;
+			}
+
+			thinkTime = personality->Get_Random(BotPersonality::THINK_TIME);
+			dp88_AI_Tank_Offensive::Timer_Expired(obj, number);
+
+			thinkCount++;
+			if (thinkCount > 9999)
+				thinkCount = 0;
+			else if (thinkCount % 30 == 0) {
+				ignoredObjectives.Clear();
+			}
+
+			isDamageDetectionEnabled = true;
+
+			if (state != GOING_TO_RELOAD && state != GOING_TO_REFILL)
+			{
+				Check_Repairables(obj);
+				if (thinkCount % 2 == 0)
+					Check_Enemy_Buildings(obj);
+			}
+			Handle_Range_Checks(obj);
+			Handle_Refill_Checks(obj);
+
+			if (!obj->As_SmartGameObj()->Get_Action()->Is_Acting() && state != GOING_TO_RELOAD && state != GOING_TO_REFILL && state != GOING_TO_SWITCH_OBJECTIVE)
+				Handle_Idle_State(obj);
+			else
+				wanderThinkCount = 0;
+
+			// Send check for alternate objectives request
+			if (objectiveType != dp88_AI_Objective::TYPE_OFFENSIVE && !m_pCurrentObjective && thinkCount % 20 == 0)
+			{
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_CHECK_GO_HOME, 0, 0.0f);
+				return;
+			}
+
+			/*if (obj->As_VehicleGameObj() && m_bMovingToObjective)
+			{
+			// TODOx: random side movements to break up the congaline
+			}*/
+
+			if (m_target)
+				Handle_Target_Reaction_Think(obj, m_target);
+
+			// Jumping
+			/*if (Should_Jump(obj))
+				Commands->Action_Jump(obj);*/
+		}
+		else
+		{
+			dp88_AI_Tank_Offensive::Timer_Expired(obj, number);
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Damaged(GameObject *obj, GameObject *damager, float amount) override
+	{
+		// Trigger enemy seen if there is no blocking inbetween
+		if (damager && isDamageDetectionEnabled && 
+			!M_Test_Raycast_Collision(Commands->Get_Position(obj) + Vector3(0,0,1.5f), Commands->Get_Position(damager) + Vector3(0, 0, 1.5f), false))
+		{ 
+			Enemy_Seen(obj, damager);
+			Notify_Neighbors_Enemy_Seen(obj, damager);
+			isDamageDetectionEnabled = false;
+		}
+
+		// Trigger defensive evasion/jumping
+		if (amount > 0 && !M_Has_Timer(obj, this, TIMER_AI_UNDER_ATTACK))
+			Commands->Start_Timer(obj, this, 5.0f, TIMER_AI_UNDER_ATTACK);
+
+		dp88_AI_Tank_Offensive::Damaged(obj, damager, amount);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Handle_Idle_State(GameObject *obj)
+	{
+		// Patrolling for defensive bots: if nothing to do and not yet patrolling, do patrolling!
+		if (nullptr == m_pCurrentObjective && (objectiveType == dp88_AI_Objective::TYPE_DEFENSIVE || objectiveType == dp88_AI_Objective::TYPE_OFFENSIVE) && 
+			state != PATROLLING && wanderThinkCount > 0)
+		{
+			if (patrolTask)
+				GoToPatrolPoint(obj);
+			else
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REQUEST_PATROL_POINT, 0, 0.0f);
+		}
+		else if (state == PATROLLING)
+			state = UNKNOWN;
+
+		wanderThinkCount++;
+
+		// Nothing happening? Do random walking/driving!
+		if (wanderThinkCount > 5)
+		{
+			if (isDeployed && isDeployedStationary)
+			{
+				deployScript->KeyHook();
+				//M_Console_Input("msg Wandering time, undeploying");
+			}
+
+			wanderThinkCount = 0;
+			Vector3 pos = Commands->Get_Position(obj);
+			float minDist = obj->As_SoldierGameObj() ? 3.0f : 8.0f;
+			float maxDist = obj->As_SoldierGameObj() ? 9.0f : 35.0f;
+			if (Get_Random_Pathfind_Spot_Around_Self(obj, minDist, maxDist, &pos))
+			{
+				ActionParamsStruct params;
+				params.Set_Basic(this, 1, ACTION_AI_RANDOM_MOVING);
+				if (isVTOL)
+				{
+					params.MovePathfind = false;
+					pos.Z += flyingHeight;
+				}
+
+				bool moveCrouched = false;
+				float speed = 0.3f;
+				if (obj->As_SoldierGameObj() && personality->Get_Random(BotPersonality::CROUCH_CHANCE_CALM))
+				{
+					moveCrouched = true;
+					speed = 1.0f;
+				}
+				params.Set_Movement(pos, speed, 1.0f, moveCrouched);
+				Commands->Action_Goto(obj, params);
+			}
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Handle_Target_Reaction_Think(GameObject *obj, GameObject *target)
+	{
+		// Deal with deployment activation and for stationary deployers, moving closer if needed
+		bool deployOrderOverride = false;
+		if (isDeployable)
+		{
+			int preferredRange = GetPreferredAttackRange(obj, target);
+			float currentDistance = getDistance(obj, target);
+			float maxRange = (float)((m_bTargetPrimaryFire) ? primary_maxRange : secondary_maxRange);
+
+			// Deploy if deploy vs target is true, AND (deploy doesn't make it unmovable OR when it does, it must be within preferred range)
+			bool shouldDeploy = Should_Deploy(target);
+			bool isInPreferredDeployRange = !isDeployedStationary || currentDistance <= preferredRange;
+			bool inRange = maxRange > currentDistance;
+
+			if (!isDeployed && shouldDeploy)
+			{
+				if (isInPreferredDeployRange)
+				{
+					// Default
+					evadePosition = Vector3();
+					// Don't deploy when busy, retry later
+					if (!(obj->As_SoldierGameObj()&& Is_Soldier_Busy(obj->As_SoldierGameObj())))
+						deployScript->KeyHook();
+					// Commands->Start_Timer(obj, this, 0.8f, TIMER_AI_REQUEST_DEPLOY);
+				}
+				else
+				{
+					// Go closer, randomize location so it's not an easy botkill due to straight line movement
+					Vector3 pos;
+					float minDist = 0.5f;
+					float maxDist = 10.0f;
+					auto targetPos = Commands->Get_Position(target);
+					if (Get_Random_Pathfind_Spot_Around_Position(obj, targetPos, minDist, maxDist, &pos))
+						evadePosition = pos;
+					else
+						evadePosition = targetPos;
+				}
+				deployOrderOverride = true;
+				AttackTarget(obj, target);
+			}
+			else if (isDeployed && (!shouldDeploy || !inRange))
+			{
+				deployScript->KeyHook();
+				//M_Console_Input("msg Target not vs deployed mode, undeploying");
+			}
+		}
+
+		TargetType targetType = GetTargetType(target);
+
+		// Check weapon effectiveness/out of ammo
+		if (obj->As_SoldierGameObj() && obj->As_SoldierGameObj()->Get_Weapon() && obj->As_SoldierGameObj()->Get_Weapon_Bag()->Get_Count() > 2)
+		{
+			WeaponClass* weapon = obj->As_SoldierGameObj()->Get_Weapon();
+			if (!weapon->Get_Total_Rounds() || objectiveType == dp88_AI_Objective::TYPE_ENGINEERING || thinkCount % 2 == 0)
+			{
+				float scaledDamage = Calculate_Average_Scaled_Damage(weapon, target);
+
+				bool shouldChangeWeapon = !weapon->Get_Total_Rounds() || (targetType != REPAIRABLE && scaledDamage <= 0) || (targetType == REPAIRABLE && (scaledDamage) >= 0);
+				// Forced/occasional best DPS vs current target weapon checks
+				if (shouldChangeWeapon || thinkCount % 4 == 0)
+				{
+					float distFromTarget = getDistance(obj, target);
+					// Pure DPS of this weapon's primary ammo vs the target
+					float damagePerSec = weapon->Get_Total_Rounds() ? Calculate_Weapon_DPS(weapon, scaledDamage) : 0;
+
+					// Make the dps invalid to ensure a weapon switch when currently carrying C4 but target is not fitting
+					// TODO: do not allow proximity type c4!
+					bool isC4UsageAllowed = (targetType == BUILDING && m_pCurrentObjective && (m_pCurrentObjective->GetRange() + 1) > getDistance(obj, m_pCurrentObjective->GetGameObject()) && distFromTarget < 4) ||
+											(targetType != BUILDING && targetType != SOLDIER && distFromTarget < 12);
+					if (weapon->Get_Style() == 0 && !isC4UsageAllowed)
+						damagePerSec = 0;
+
+					WeaponClass* originalWeapon = weapon;
+					WeaponClass* nextWeapon = weapon;
+
+					auto weapon_bag = obj->As_SoldierGameObj()->Get_Weapon_Bag();
+					int weapon_count = weapon_bag->Get_Count();
+					for (int index = 0; index < weapon_count; index++)
+					{
+						weapon = weapon_bag->Peek_Weapon(index);
+						if (!weapon || !weapon->Get_Total_Rounds() || (weapon->Get_Style() == 0 && !isC4UsageAllowed) || (weapon->PrimaryAmmoDefinition->Range <= 15 && distFromTarget >= 20))
+							continue;
+
+						scaledDamage = Calculate_Average_Scaled_Damage(weapon, target);
+
+						float tempDamagePerSec = Calculate_Weapon_DPS(weapon, scaledDamage);
+						if (targetType != REPAIRABLE || Get_Object_Type(target) != Get_Object_Type(obj) ? (tempDamagePerSec > damagePerSec) : (tempDamagePerSec < damagePerSec))
+						{
+							damagePerSec = tempDamagePerSec;
+							nextWeapon = weapon;
+						}
+					}
+
+					if (originalWeapon != nextWeapon)
+					{
+						Commands->Select_Weapon(obj, nextWeapon->Get_Name());
+						// Recalculate ranges
+						primary_maxRange = (int)nextWeapon->PrimaryAmmoDefinition->Range();
+						if (nextWeapon->Get_Style() == 0 && target->As_VehicleGameObj())
+						{
+							auto model = target->As_VehicleGameObj()->Peek_Model();
+							auto &obj_box = model->Get_Bounding_Box();
+							// Expecting most vehicles to be square-ish sized, epic long vehicles will be problematic here.
+							primary_maxRange += (int)(obj_box.Extent.X / 2 + obj_box.Extent.Y / 2) / 2;
+						}
+						m_primary_prefRange = primary_maxRange < 120 ? (int)(primary_maxRange / 2) : primary_maxRange - 10;
+						if (primary_minRange > m_primary_prefRange)
+							primary_minRange = m_primary_prefRange - 1;
+						if (primary_minRange < 0)
+							primary_minRange = 0;
+
+						AttackTarget(obj, target);
+						return;
+					}
+				}
+			}
+		}
+
+		// Move around randomly every second (x second for vehs) during battle
+		if (!gettingAround && !deployOrderOverride)
+		{
+			float distFromTarget = getDistance(obj, target);
+			float maxRange = (float)((m_bTargetPrimaryFire) ? primary_maxRange : secondary_maxRange);
+			bool inRange = maxRange > distFromTarget;
+			bool inPrefRange = GetPreferredAttackRange(obj, target) >= distFromTarget;
+			bool hasWeapon = false;
+
+			if (target->As_SmartGameObj())
+				hasWeapon = target->As_SmartGameObj()->Get_Weapon() ? true : false;
+
+			// Set moving to false so the parent timer catches it and moves deploying units closer
+			if (!inRange)
+				m_bMovingToTarget = false;
+
+			// Reset
+			evadePosition = Vector3();
+
+			bool canSquishTarget = isSquisherUnit && target->As_SoldierGameObj() && !Is_Unsquishable(target);
+
+			// Cancel evading in these cases
+			if (!IsValidTarget(obj, target) || 
+				// When not having to pursue the target, the evasion movement will not return but move the bot towards the objective.
+				(ShouldPursueTarget(obj, target) && 
+					((isDeployed && isDeployedStationary) || Should_Deploy(target) || canSquishTarget || 
+						// Must check turret limit, otherwise e.g. the Renegade GDI MRLS does not rotate towards the target building and can keep hitting the wall/other dead buildings in the way
+						(!M_Has_Timer(obj, this, TIMER_AI_UNDER_ATTACK) && !isTurretRotationLimited && (!hasWeapon || targetType == BUILDING || targetType == REPAIRABLE))
+					)
+				)
+			)
+				return;
+
+			evadeThinkCount++;
+			// Vehicles evade slower, limited turret vehicles correct their aim-position a bit faster
+			if (ShouldPursueTarget(obj, target) &&
+				(obj->As_VehicleGameObj() && evadeThinkCount < 5 && !(isTurretRotationLimited && evadeThinkCount > 2)))
+				return;
+
+			evadeThinkCount = 0;
+			Vector3 pos;
+			float minDist = obj->As_SoldierGameObj() ? 3.0f : 8.0f;
+			float maxDist = obj->As_SoldierGameObj() ? 9.0f : 20.0f;
+
+			// Limited turret rotation vehicles will calculate their next goto position inside AttackTarget instead of here, unless they have an overriding task (e.g. Follow a player, grouprush)
+			if (obj->As_SoldierGameObj() || (state != GOING_TO_REFILL && state != GOING_TO_SWITCH_OBJECTIVE && (!isTurretRotationLimited || overridingTask))) {
+				GameObject* objectiveObj = nullptr;
+				if (m_pCurrentObjective != nullptr)
+					objectiveObj = m_pCurrentObjective->GetGameObject();
+
+				if ((ShouldPursueTarget(obj, target) || M_Has_Timer(obj, this, TIMER_AI_UNDER_ATTACK)) && (targetType != BUILDING ? inPrefRange : inRange) && Get_Random_Pathfind_Spot_Around_Self(obj, minDist, maxDist, &pos, targetType != BUILDING ? target: nullptr))
+					evadePosition = pos;
+				else if (objectiveObj && (m_pCurrentObjective->GetRange() + 1) < getDistance(obj, m_pCurrentObjective->GetGameObject()))
+				{
+					minDist = 0.5f;
+					maxDist = (float)m_pCurrentObjective->GetRange();
+					auto objectivePos = Commands->Get_Position(objectiveObj);
+					if (Get_Random_Pathfind_Spot_Around_Position(obj, objectivePos, minDist, maxDist, &pos))
+						evadePosition = pos;
+				}
+			}
+
+			AttackTarget(obj, target);
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Handle_Range_Checks(GameObject *obj)
+	{
+		if (overridingTask)
+		{
+			auto taskObj = overridingTask.Get_Ptr();
+			// Check the current objective, if any, is still valid
+			if (nullptr == m_pCurrentObjective || (overridingTaskType == 7 && taskObj && (
+					// Target soldier entered a vehicle
+					(taskObj->As_SoldierGameObj() && Get_Vehicle(taskObj)) ||
+					// OR attacking a vehicle but its driver has left it and it's not the harvester!
+					(taskObj->As_VehicleGameObj() && !taskObj->As_VehicleGameObj()->Get_Occupant_Count() && !Is_Harvester(taskObj)))
+			))
+			{
+				objectiveNotFoundCount++;
+				//M_Console_Input("msg Objective not found!");
+				if (objectiveNotFoundCount >= FreeRandom.Get_Int(1,3))
+				{
+					//M_Console_Input("msg Objective is invalid!");
+					Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE, 0, 0.0f);
+				}
+				return;
+			}
+			else
+				objectiveNotFoundCount = 0;
+
+			if (state == GOING_TO_VEHICLE)
+			{
+				if (auto objectiveGO = m_pCurrentObjective->GetGameObject())
+				{
+					if (auto vehicle = objectiveGO->As_VehicleGameObj())
+					{
+						auto model = vehicle->Peek_Model();
+						auto &obj_box = model->Get_Bounding_Box();
+						// Expecting most vehicles to be square-ish sized, epic long ships will be problematic here.
+						float distance = (obj_box.Extent.X / 2 + obj_box.Extent.Y / 2) / 2 + 5;
+
+						if (distance > getDistance(obj, objectiveGO))
+						{
+							Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_ARRIVED_TO_VEHICLE, 1, 0.0f);
+						}
+					}
+				}
+			}
+			else if (state == GOING_TO_REFILL || state == GOING_TO_SWITCH_OBJECTIVE || state == GOING_TO_EXIT_VEHICLE_POINT)
+			{
+				if (auto objectiveGO = m_pCurrentObjective->GetGameObject())
+				{
+					if (m_pCurrentObjective->GetRange() + 1 >= getDistance(obj, objectiveGO))
+					{
+						auto custom = state == GOING_TO_REFILL || state == GOING_TO_SWITCH_OBJECTIVE ? CUSTOM_AI_ARRIVED_TO_REFILL : CUSTOM_AI_ARRIVED_TO_EXIT_VEHICLE;
+						Commands->Send_Custom_Event(obj, obj, custom, 0, 0.0f);
+					}
+				}
+			}
+			else if (state == GOING_TO_RALLY_POINT && !arrivedToOverridingTask)
+			{
+				if (auto objectiveGO = m_pCurrentObjective->GetGameObject())
+				{
+					if (m_pCurrentObjective->GetRange() + 10 + (isVTOL ? flyingHeight : 0) >= getDistance(obj, objectiveGO))
+					{
+						arrivedToOverridingTask = true;
+						Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_ARRIVED_TO_RALLYPOINT, 0, 0.0f);
+					}
+				}
+			}
+		}
+		// Check if arrived to next patrol point and ask for next
+		else if (state == PATROLLING)
+		{
+			if (auto patrolObj = patrolTask.Get_Ptr())
+			{
+				auto objective = (dp88_AI_Objective *)(Find_Script_On_Object(patrolObj, "dp88_AI_Objective"));
+				if (objective && objective->GetRange() + 4 + (isVTOL ? flyingHeight : 0) >= getDistance(obj, patrolObj))
+				{
+					Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REQUEST_PATROL_POINT, patrolTask->Get_ID(), 0.0f);
+				}
+			}
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Handle_Refill_Checks(GameObject *obj)
+	{
+		if (thinkCount % 4 == 0)
+		{
+			if (!overridingTask)
+			{
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_CHECK_REPAIR_NEEDED, 0, 0.0f);
+			}
+			else if (state == GOING_TO_REFILL)
+			{
+				// No money
+				if (obj->As_VehicleGameObj() &&
+					MS_AccessHelper::Get_Money(obj) < Get_Team_Cost(Commands->Get_Preset_ID(obj), Get_Object_Type(obj)) * 0.25f)
+				{
+					Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE, 0, 0.0f);
+				}
+				else
+				{
+					const float currentHitp = Commands->Get_Health(obj) + Commands->Get_Shield_Strength(obj);
+					const float maxHitp = Commands->Get_Max_Health(obj) + Commands->Get_Max_Shield_Strength(obj);
+					if (currentHitp == maxHitp)
+					{
+						Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REFILL_COMPLETE, 0, 0.0f);
+						Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE, 0, 0.0f);
+					}
+				}
+			}
+		}
+
+		// GOING_TO_REFILL is checked here so it doesnt override a refilling order! +Check vehicle/weapon, empty falling longbows can crash the game
+		if (isLimitedAmmoUnit && state != GOING_TO_REFILL && obj->As_VehicleGameObj() && obj->As_VehicleGameObj()->Get_Weapon())
+		{
+			totalAmmo = obj->As_VehicleGameObj()->Get_Weapon()->Get_Total_Rounds();
+			bool isAmmoFull = obj->As_VehicleGameObj()->Get_Weapon()->Is_Ammo_Maxed();
+
+			if (!isAmmoEmptyNoteSent && totalAmmo == 0)
+			{
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_VEHICLE_AMMO_EMPTY, obj->Get_ID(), 0.0f);
+				isAmmoEmptyNoteSent = true;
+			}
+			else if (isAmmoEmptyNoteSent && isAmmoFull)
+			{
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REFILL_COMPLETE, 0, 0.0f);
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE, 0, 0.0f);
+				isAmmoEmptyNoteSent = false;
+			}
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Check_Repairables(GameObject* obj)
+	{
+		if (objectiveType != dp88_AI_Objective::TYPE_ENGINEERING || !obj->As_SoldierGameObj())
+			return;
+
+		// M_Console_Input("msg Checking repairables...");
+
+		// Find Beacons
+		for (auto node = GameObjManager::BeaconGameObjList.Head(); node; node = node->Next())
+		{
+			auto target = node->Data();
+			// Skip friendly beacons and ones that don't have an objective on them (not a threat)
+			if (Get_Object_Type(obj) == Get_Object_Type(target) || !Is_Script_Attached(target, "dp88_AI_Marker_Repairable"))
+				continue;
+			if (obj->As_SmartGameObj()->Is_Obj_Visible(target))
+			{
+				Enemy_Seen(obj, target);
+				// Ignore all other targets
+				return;
+			}
+		}
+
+		// Find C4s
+		for (auto node = GameObjManager::C4GameObjList.Head(); node; node = node->Next())
+		{
+			auto target = node->Data();
+			// Skip friendly C4s and ones that don't have an objective on them (not a threat)
+			if (Get_Object_Type(obj) == Get_Object_Type(target) || !Is_Script_Attached(target, "dp88_AI_Marker_Repairable"))
+				continue;
+			if (obj->As_SmartGameObj()->Is_Obj_Visible(target))
+				Enemy_Seen(obj, target);
+		}
+
+		for (int index = 0; index < AIBuildings.Count(); index++)
+		{
+			auto building = AIBuildings[index];
+			auto bldScript = (MS_AI_Building *)(Find_Script_On_Object(building, "MS_AI_Building"));
+
+			if (!bldScript || (building->As_BuildingGameObj() && building->As_BuildingGameObj()->Is_Destroyed()))
+				continue;
+
+			auto target = bldScript->objectiveRepairTargetObj;
+
+			if (target && GetTargetType(target) == REPAIRABLE && Get_Object_Type(obj) == Get_Object_Type(target))
+			{
+				// Only check friendly repairables
+				/*if (strcmp("Invisible_Object", Commands->Get_Preset_Name(target)) == 0)
+					M_Console_Input("msg Found my boy");*/
+
+				auto script = (dp88_AI_Marker_Repairable*)(Find_Script_On_Object(target, "dp88_AI_Marker_Repairable"));
+				float extraRange = GetPreferredAttackRange(obj, target) > 2.0f ? -1.0f : 1.0f;
+				/*if (script)
+				{
+					StringClass buff;
+					buff.Format("me from target: %.2f, must be smallerthan: %.2f, target from pathfind: %.2f, prefrange: %d, and +/- 1.0 by moonsense", getDistance(obj, target), script->Get_Distance_From_Pathfind() + GetPreferredAttackRange(obj, target) + extraRange,
+						script->Get_Distance_From_Pathfind(), GetPreferredAttackRange(obj, target));
+					Send_Message(MS_RGB_DEBUG, buff.Peek_Buffer());
+				}*/
+
+				if (script && getDistance(obj, target) < script->Get_Distance_From_Pathfind() + GetPreferredAttackRange(obj, target) + extraRange)
+				{
+					// M_Console_Input("msg Found one, calling enemy seen");
+					Enemy_Seen(obj, target);
+				}
+			}
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Check_Enemy_Buildings(GameObject* obj)
+	{
+		if (objectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+			return;
+
+		// M_Console_Input("msg Checking enemy buildings...");
+
+		auto weapon = obj->As_SmartGameObj()->Get_Weapon();
+		if (!weapon)
+			return;
+
+		bool isLimitedAmmoInfantry = obj->As_SoldierGameObj() && weapon->Get_Total_Rounds() != -1;
+		Vector3 objPos = obj->As_SmartGameObj()->Get_Bullseye_Position() + Vector3(0, 0, 0.3f);
+		Vector3 muzzlePos = objPos;
+		Matrix3D turretTransform;
+		// Raycast from the muzzlebone if this is a vehicle to make sure raycasting and actual firing matches better
+		if (obj->As_VehicleGameObj())
+		{
+			int boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("muzzlea0");
+			if (boneIdx)
+				muzzlePos = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx).Get_Translation();
+
+			boneIdx = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Index("turret");
+			if (boneIdx)
+			{
+				turretTransform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Bone_Transform(boneIdx);
+				objPos = turretTransform.Get_Translation();
+			}
+			else
+			{
+				turretTransform = obj->As_PhysicalGameObj()->Peek_Model()->Get_Transform();
+				objPos = obj->As_SmartGameObj()->Get_Bullseye_Position();
+			}
+		}
+
+		for (int index = 0; index < AIBuildings.Count(); index++)
+		{
+			auto target = AIBuildings[index];
+			if (target && Get_Object_Type(obj) != Get_Object_Type(target) && GetTargetType(target) == BUILDING)
+			{
+				auto bldScript = (MS_AI_Building *)(Find_Script_On_Object(target, "MS_AI_Building"));
+
+				// Buildings with no mesh, only bld controller and destroyed buildings: should be skipped
+				if (!bldScript || (target->As_BuildingGameObj() && target->As_BuildingGameObj()->Is_Destroyed()))
+					continue;
+
+				// StringClass msg;
+
+				auto &boundingBox = target->As_BuildingGameObj() ? target->As_BuildingGameObj()->Get_Bounds() : target->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+				const int radius = (int)(max(boundingBox.Extent.X, boundingBox.Extent.Y));
+				const float weaponRange = float(getPrimary(target) ? primary_maxRange : secondary_maxRange);
+
+				// Buildings too far away: should be skipped
+				float distFromBuildingEdge = Vector3::Distance(objPos, boundingBox.Center) - radius + 2.0f;
+				if (distFromBuildingEdge > weaponRange)
+					continue;
+
+				// Don't do tilting calculations for infantry and vehicles that use ammo with gravity or turning
+				const bool isAimFree = obj->As_VehicleGameObj() ? (getPrimary(target) ? (isPrimaryAmmoTurning || isPrimaryAmmoGravityAffected) : (isSecondaryAmmoTurning || isSecondaryAmmoGravityAffected)) : true;
+
+				if (!target->As_BuildingGameObj() && obj->As_SmartGameObj()->Is_Obj_Visible(target->As_PhysicalGameObj()) && Can_Tilt_Towards_Target(isAimFree, turretTransform, target))
+				{
+					// msg.Format("msg %s: Can naturally be seen, no extra checks.", Commands->Get_Preset_Name(target));
+					// M_Console_Input(msg);
+					Enemy_Seen(obj, target);
+					continue;
+				}
+
+				// We are in range, check the targets
+
+				// Check if MCT is in sight range (if exists) and stop any further checks if yes as it's the best target to attack
+				if (obj->As_SoldierGameObj() && bldScript->objectiveMCTAttackTargetObj)
+				{
+					if (!M_Test_Raycast_Collision(objPos, bldScript->mctObjectiveLocation, false) && (
+						(isLimitedAmmoInfantry && m_pCurrentObjective && getDistance(obj, m_pCurrentObjective->GetGameObject()) < 3) ||
+						(!isLimitedAmmoInfantry && getDistance(obj, target) < weaponRange)
+					))
+					{
+						// msg.Format("msg %s: Found weakpoint, calling enemy seen", Commands->Get_Preset_Name(target), bldScript->exteriorTargetWatchpoints.Count());
+						// M_Console_Input(msg);
+						Enemy_Seen(obj, bldScript->objectiveMCTAttackTargetObj);
+						return;
+					}
+				}
+
+				// Limited ammo infantry should only shoot the MCT, others are fine to attack from outside too
+				if (isLimitedAmmoInfantry)
+					continue;
+
+				// Do 1 simple raycast first towards the building boundary center on same Z level as the bot first, and stop any further checks if it succeeds as attacking here should be safe (reduce the number of raycasts)
+				Vector3 centralTargetPos = Commands->Get_Position(bldScript->centralTargetGround);
+				Vector3 checkHitPos = (Vector3::Distance_Squared(objPos, centralTargetPos) < weaponRange * weaponRange) ? centralTargetPos : (objPos + (centralTargetPos - objPos).Normalized() * (float)weaponRange);
+				if (M_Test_Raycast_Collision(objPos, checkHitPos, false, &checkHitPos, target) && Can_Tilt_Towards_Target(isAimFree, turretTransform, bldScript->centralTargetGround))
+				{
+					// msg.Format("msg %s: Raycast found lower center, calling enemy seen", Commands->Get_Preset_Name(target), bldScript->exteriorTargetWatchpoints.Count());
+					// M_Console_Input(msg);
+					Enemy_Seen(obj, bldScript->centralTargetGround);
+					continue;
+				}
+				centralTargetPos = Commands->Get_Position(bldScript->centralTarget);
+				// Do 1 simple raycast then towards the building boundary center, and stop any further checks if it succeeds as attacking here should be safe (reduce the number of raycasts)
+				checkHitPos = (Vector3::Distance_Squared(objPos, centralTargetPos) < weaponRange * weaponRange) ? centralTargetPos : (objPos + (centralTargetPos - objPos).Normalized() * (float)weaponRange);
+				if (M_Test_Raycast_Collision(objPos, checkHitPos, false, &checkHitPos, target) && Can_Tilt_Towards_Target(isAimFree, turretTransform, bldScript->centralTarget))
+				{
+					// msg.Format("msg %s: Raycast found center, calling enemy seen", Commands->Get_Preset_Name(target), bldScript->exteriorTargetWatchpoints.Count());
+					// M_Console_Input(msg);
+					Enemy_Seen(obj, bldScript->centralTarget);
+					continue;
+				}
+
+				// Long range units: do 8 raycasts and stop trying if all failed, prevents epic amount of unnecessary raycastings
+				if (distFromBuildingEdge > 100 && bldScript->exteriorTargetWatchpoints.Count() > 12)
+				{
+					const float sideOffset = 2.0f;
+					DynamicVectorClass<Vector3> bldCorners;
+					bldCorners.Add(boundingBox.Center + Vector3( boundingBox.Extent.X + sideOffset, 0, 0));
+					bldCorners.Add(boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, 0, 0));
+					bldCorners.Add(boundingBox.Center + Vector3( 0, -boundingBox.Extent.Y - sideOffset, 0));
+					bldCorners.Add(boundingBox.Center + Vector3( 0,  boundingBox.Extent.Y + sideOffset, 0));
+					bldCorners.Add(boundingBox.Center + Vector3( boundingBox.Extent.X + sideOffset,  boundingBox.Extent.Y + sideOffset, 0));
+					bldCorners.Add(boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset, -boundingBox.Extent.Y - sideOffset, 0));
+					bldCorners.Add(boundingBox.Center + Vector3( boundingBox.Extent.X + sideOffset, -boundingBox.Extent.Y - sideOffset, 0));
+					bldCorners.Add(boundingBox.Center + Vector3(-boundingBox.Extent.X - sideOffset,  boundingBox.Extent.Y + sideOffset, 0));
+					bool edgesVisible = false;
+					for (int i = 0; i < bldCorners.Count(); i++)
+					{
+						Vector3 &point = bldCorners[i];
+						if (!M_Test_Raycast_Collision(objPos, point, false))
+						{
+							edgesVisible = true;
+							// msg.Format("msg %s: Long range: a corner is visible, checking targets...", Commands->Get_Preset_Name(target), bldScript->exteriorTargetWatchpoints.Count());
+							// M_Console_Input(msg);
+							break;
+						}
+					}
+					if (!edgesVisible)
+					{
+						// msg.Format("msg %s: Long range: NO corner is visible, aborting checks...", Commands->Get_Preset_Name(target), bldScript->exteriorTargetWatchpoints.Count());
+						// M_Console_Input(msg);
+						continue;
+					}
+				}
+
+				// Order targets by distance first to prevent a lot of unnecessary raycasts
+				DynamicVectorClass<Vector3> watchpointDistanceOrder = Order_Points_By_Distance(bldScript->exteriorTargetWatchpoints, objPos, weaponRange - 1);
+
+				// int raycastCount = 0;
+				// bool found = false;
+				for (int i = 0; i < watchpointDistanceOrder.Count(); i++)
+				{
+					// raycastCount++;
+					Vector3 &point = watchpointDistanceOrder[i];
+					if (!M_Test_Raycast_Collision(muzzlePos, point, false))
+					{
+						const int targetArrayIndex = bldScript->exteriorTargetWatchpoints.ID(point);
+						ReferencerClass &extTarget = bldScript->exteriorTargets[targetArrayIndex];
+						if (!Can_Tilt_Towards_Target(isAimFree, turretTransform, extTarget))
+							continue;
+						// msg.Format("msg %s: Cheap attempts failed, FOUND a target, calling enemy seen (raycasts: %d / %d) (autoindex: %d)", Commands->Get_Preset_Name(target), raycastCount, bldScript->exteriorTargetWatchpoints.Count(), targetArrayIndex);
+						// M_Console_Input(msg);
+						Enemy_Seen(obj, extTarget);
+						// found = true;
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	bool Should_Jump(GameObject *obj)
+	{
+		if (!obj->As_SoldierGameObj() || !isJumpAllowed || Is_Soldier_Busy(obj->As_SoldierGameObj()))
+		{
+			return false;
+		}
+
+		if (m_target && personality->Get_Random(BotPersonality::JUMP_CHANCE_COMBAT))
+		{
+			// Check target type here, engineers should not jump while "battling" repair targets...
+			if (m_target && GetTargetType(m_target) != REPAIRABLE)
+			{
+				return true;
+			}
+		}
+		else if (!m_target && m_bMovingToObjective && personality->Get_Random(BotPersonality::JUMP_CHANCE_REGULAR))
+		{
+			return true;
+		}
+		
+		return false;
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Notify_Neighbors_Enemy_Seen(GameObject *obj, GameObject *enemy)
+	{
+		SList<GameObject> list;
+		Find_All_Objects_With_Script_By_Distance("MS_AI_Behaviour", list, Commands->Get_Position(obj));
+		for (auto x = list.Head(); x != nullptr; x = x->Next())
+		{
+			GameObject* neighbor = x->Data();
+			// Neighbors already ordered by distance, can stop the cycle here
+			if (getDistance(obj, neighbor) > 20)
+				return;
+
+			// Notify friends only, skip self
+			if (Get_Object_Type(obj) != Get_Object_Type(neighbor) || Commands->Get_ID(obj) == Commands->Get_ID(neighbor))
+				continue;
+
+			auto behaviourScript = (MS_AI_Behaviour *)(Find_Script_On_Object(neighbor, "MS_AI_Behaviour"));
+			if (behaviourScript)
+				behaviourScript->On_Notification_Enemy_Seen(neighbor, enemy);
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void On_Notification_Enemy_Seen(GameObject *obj, GameObject *enemy)
+	{
+		// Trigger enemy seen if there is no blocking inbetween
+		if (enemy && 
+			!M_Test_Raycast_Collision(Commands->Get_Position(obj) + Vector3(0, 0, 1.5f), Commands->Get_Position(enemy) + Vector3(0, 0, 1.5f), false))
+		{
+			Enemy_Seen(obj, enemy);
+		}
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Detach(GameObject* obj) override
+	{
+		if (Exe != EXE_LEVELEDIT)
+		{
+			// This should help make it possible to activate/deactivate bot behaviour whenever wanted
+			ResetAllActions(obj);
+			Remove_Script(obj, "MS_AI_Pathfind_Helper");
+		}
+		dp88_AI_Tank_Offensive::Detach(obj);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+	void Action_Complete(GameObject *obj, int action_id, ActionCompleteReason complete_reason) override
+	{
+		if (action_id == ACTION_ID_MOVE_TO_OBJECTIVE)
+		{
+			if (obj->As_SoldierGameObj() && state != GOING_TO_VEHICLE && 
+				(complete_reason == ACTION_COMPLETE_PATH_BAD_DEST || complete_reason == ACTION_COMPLETE_PATH_BAD_START) && m_pCurrentObjective && m_pCurrentObjective->GetGameObject())
+			{
+				GameObject *objectiveObj = m_pCurrentObjective->GetGameObject();
+				Vector3 objectiveLocation = Commands->Get_Position(objectiveObj);
+				StringClass botName = obj->As_SoldierGameObj()->Get_Bot_Tag();
+
+				if (overridingTask) {
+					overridingTask = nullptr;
+					if (DebugPathfindFailMessagesLiveEnabled || DebugPathfindFailMessagesFileEnabled) {
+						StringClass debug;
+						debug.Format("%s(%d): cannot reach overriding task: %s (%s-%d) at location: %.2f / %.2f / %.2f (%.2f dist, %.5f prio)", botName.Peek_Buffer(), Get_Object_Type(obj), m_pCurrentObjective->GetDebugTag(), Commands->Get_Preset_Name(objectiveObj), objectiveObj->Get_ID(), objectiveLocation.X, objectiveLocation.Y, objectiveLocation.Z, getDistance(obj, objectiveObj), m_pCurrentObjective->GetPriority(obj, modifier_distance));
+						Print_Debug(debug);
+					}
+				}
+				else {
+					ignoredObjectives.Add(objectiveObj->Get_ID());
+					auto objectiveCount = dp88_AI_Objective::CountUnitObjectives(Get_Object_Type(obj), objectiveType, obj, modifier_distance);
+					if (DebugPathfindFailMessagesLiveEnabled || DebugPathfindFailMessagesFileEnabled) {
+						StringClass debug;
+						debug.Format("%s(%d): cannot reach: %s (%s-%d) at location: %.2f / %.2f / %.2f  (%.2f dist, %.5f prio) (%d/%d)", botName.Peek_Buffer(), Get_Object_Type(obj), m_pCurrentObjective->GetDebugTag(), Commands->Get_Preset_Name(objectiveObj), objectiveObj->Get_ID(), objectiveLocation.X, objectiveLocation.Y, objectiveLocation.Z, getDistance(obj, objectiveObj), m_pCurrentObjective->GetPriority(obj, modifier_distance), ignoredObjectives.Count(), objectiveCount);
+						Print_Debug(debug);
+					}
+
+					// Out of objectives, could not reach any, suicide now and hope for a better spawn location next time.
+					if (objectiveCount && objectiveCount == ignoredObjectives.Count()) {
+						Vector3 botLocation = Commands->Get_Position(obj);
+						if (DebugPathfindFailMessagesLiveEnabled || DebugPathfindFailMessagesFileEnabled) {
+							StringClass debug;
+							debug.Format("%s: out of objectives (%d), suiciding now at location: %.2f / %.2f / %.2f ", botName.Peek_Buffer(), objectiveCount, botLocation.X, botLocation.Y, botLocation.Z);
+							Print_Debug(debug);
+						}
+
+						Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REQUEST_REFUND, 0, 0.0f);
+						Commands->Apply_Damage(obj, 9999.0f, "Death", nullptr);
+						return;
+					}
+				}
+				m_target = nullptr;
+				ResetAllActions(obj);
+				state = UNKNOWN;
+				// Faster acting
+				m_pCurrentObjective = ChooseNewObjective(obj);
+				if (nullptr != m_pCurrentObjective) {
+					GoToObjective(obj);
+					return;
+				}
+			}
+			// Transport arrived to actual objective
+			else if (obj->As_VehicleGameObj() && obj->As_VehicleGameObj()->Get_Occupant_Count() > 1
+				&& m_pCurrentObjective && m_pCurrentObjective->GetGameObject()
+				&& getDistance(obj, m_pCurrentObjective->GetGameObject()) < m_pCurrentObjective->GetRange()
+				)
+			{
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_TRANSPORT_ARRIVED, 0, 0.0f);
+			}
+			// This action needs to call the parent
+			dp88_AI_Tank_Offensive::Action_Complete(obj, action_id, complete_reason);
+		}
+		else if (action_id == ACTION_ID_ATTACK_TARGET)
+		{
+			if (complete_reason == ACTION_COMPLETE_PATH_BAD_DEST || complete_reason == ACTION_COMPLETE_PATH_BAD_START)
+			{
+				pathfindFailCount++;
+			}
+			else
+			{
+				if (pathfindFailCount > 0)
+					pathfindFailCount--;
+				// Do not reset actions when move-location is changed!
+				if (complete_reason == ACTION_COMPLETE_LOW_PRIORITY)
+					return;
+				else
+					dp88_AI_Tank_Offensive::Action_Complete(obj, action_id, complete_reason);
+			}
+		}
+		else if (action_id == ACTION_AI_GET_AROUND_REVERSE || action_id == ACTION_AI_GET_AROUND_FORWARD)
+			return;
+		else
+			dp88_AI_Tank_Offensive::Action_Complete(obj, action_id, complete_reason);
+	}
+
+	// -------------------------------------------------------------------------------------------------
+
+public:
+	int Get_Objective_Type()
+	{
+		return (int)objectiveType;
+	}
+
+	int Get_State()
+	{
+		return state;
+	}
+
+	GameObject *Get_Override_Task()
+	{
+		return overridingTask;
+	}
+
+	int Get_Override_Task_Type()
+	{
+		return overridingTaskType;
+	}
+
+	GameObject *Get_Current_Objective()
+	{
+		return m_pCurrentObjective ? m_pCurrentObjective->GetGameObject() : nullptr;
+	}
+
+	void Set_Personality(BotPersonality *newPersonality)
+	{
+		REF_PTR_SET(personality, newPersonality);
+	}
+
+	bool Should_Chase_Radar_Target(GameObject *enemy)
+	{
+		if (!Owner() || objectiveType == dp88_AI_Objective::TYPE_ENGINEERING || m_target)
+			return false;
+
+		return ShouldPursueTarget(Owner(), enemy);
+	}
+
+	bool Should_Ignore_Blocked_Checking(GameObject *obj)
+	{
+		if (isDeployed)
+			return true;
+
+		if (m_target)
+		{
+			TargetType targetType = GetTargetType(m_target);
+
+			// Is Repairing
+			if (targetType == REPAIRABLE && getDistance(obj, m_target) <= GetPreferredAttackRange(obj, m_target))
+				return true;
+			//M_Console_Input("msg Target is repairable and within range, I don't care about 'being stuck'");
+
+			bool hasWeapon = m_target->As_SmartGameObj() && m_target->As_SmartGameObj()->Get_Weapon() != nullptr;
+			// DO check for stuck if moving, even if not evade-moving!
+			if (!m_bMovingToObjective && 
+				// Bot will not keep evading in these cases and would think it's stuck
+				(!IsValidTarget(obj, m_target) || !ShouldPursueTarget(obj, m_target) || !hasWeapon || targetType == BUILDING))
+				return true;
+		}
+		return false;
+	}
+
+	void Prepare_Getting_Around(GameObject *obj)
+	{
+		ResetAllActions(obj);
+		gettingAround = true;
+		m_bMovingToObjective = true;
+	}
+
+	void Stop_Getting_Around(GameObject *obj)
+	{
+		ResetAllActions(obj);
+		gettingAround = false;
+		m_target = nullptr;
+	}
+
+	void On_Unable_To_Get_Unstuck(GameObject *obj)
+	{
+		if (obj->As_SoldierGameObj())
+		{
+			Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_REQUEST_REFUND, 0, 0.0f);
+			Commands->Apply_Damage(obj, 9999.0f, "Death", nullptr);
+		}
+		else if (obj->As_VehicleGameObj())
+		{
+			Commands->Apply_Damage(obj, 9999.0f, "Death", nullptr);
+		}
+	}
+
+	ActionParamsStruct Get_Param_Attack_Modifications(ActionParamsStruct params) override
+	{
+		// Return attack params if there is a target
+		if (m_target)
+		{
+			// Setup the attack parameters
+			m_bTargetPrimaryFire = getPrimary(m_target);
+			params.Set_Attack(m_target, (float)((m_bTargetPrimaryFire) ? primary_maxRange : secondary_maxRange), attackError, m_bTargetPrimaryFire);
+		}
+
+		return params;
+	}
+};
+REGISTER_SCRIPT(MS_AI_Behaviour,
+	"Priority_Infantry=1.0:float,"
+	"Weapon_Infantry=1:int,"
+	"Splash_Infantry=0:int,"
+	"Priority_Light_Vehicle=5.0:float,"
+	"Weapon_Light_Vehicle=1:int,"
+	"Priority_Heavy_Vehicle=7.0:float,"
+	"Weapon_Heavy_Vehicle=1:int,"
+	"Priority_VTOL=5.0:float,"
+	"Weapon_VTOL=1:int,"
+	"Priority_Building=12.0:float,"
+	"Weapon_Building=1:int,"
+	"Max_Attack_Range=150:int,"
+	"Min_Attack_Range=0:int,"
+	"Preferred_Attack_Range=60:int,"
+	"Max_Attack_Range_Secondary=150:int,"
+	"Min_Attack_Range_Secondary=0:int,"
+	"Preferred_Attack_Range_Secondary=60:int,"
+	"Modifier_Distance=0.25:float,"
+	"Modifier_Target_Damage=0.1:float,"
+	"Modifier_Target_Value=0.05:float,"
+	"DebugDeprecated=0:int,"
+	"Detects_Stealth=0:int,"
+	"ObjectiveType=1:int"
+);
+
+
+/*!
+* \brief AI Preferences
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* AI Preferences, used by MS_AI_Player_Count_Controller. Attach this to all presets you want
+* to be able to become bots, without the need to duplicate a bot/player version of the preset.
+*/
+class MS_AI_Preferences : public ScriptImpClass
+{
+	void Created(GameObject *obj) override
+	{
+		/*if (obj->As_SmartGameObj()->Is_Human_Controlled())
+		{
+			Commands->Attach_Script(obj, "MS_Target_Painter_Host", "");
+		}*/
+	}
+
+	void Killed(GameObject *obj, GameObject *killer) override
+	{
+		if (obj && obj->As_SmartGameObj() && obj->As_SmartGameObj()->Is_Human_Controlled())
+		{
+			Commands->Send_Custom_Event(obj, killer, CUSTOM_AI_ENEMY_KILLED, 0, 0.0f);
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Preferences,
+	"Priority_Infantry=1.0:float,"
+	"Weapon_Infantry=1:int,"
+	"Splash_Infantry=0:int,"
+	"Priority_Light_Vehicle=5.0:float,"
+	"Weapon_Light_Vehicle=1:int,"
+	"Priority_Heavy_Vehicle=7.0:float,"
+	"Weapon_Heavy_Vehicle=1:int,"
+	"Priority_VTOL=5.0:float,"
+	"Weapon_VTOL=1:int,"
+	"Priority_Building=3.0:float,"
+	"Weapon_Building=1:int,"
+	"Max_Attack_Range=150:int,"
+	"Min_Attack_Range=0:int,"
+	"Preferred_Attack_Range=60:int,"
+	"Max_Attack_Range_Secondary=150:int,"
+	"Min_Attack_Range_Secondary=0:int,"
+	"Preferred_Attack_Range_Secondary=60:int,"
+	"Modifier_Distance=0.25:float,"
+	"Modifier_Target_Damage=0.1:float,"
+	"Modifier_Target_Value=0.05:float,"
+	"DebugDeprecated=0:int,"
+	"Detects_Stealth=0:int"
+);
+
+
+/*!
+* \brief AI Pathfind Helper
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* The script helps the AI a bit when colliding into objects they cannot see in the pathfind grid.
+*/
+class MS_AI_Pathfind_Helper : public ScriptImpClass
+{
+	// Improving pathfinding ability (getting stuck on objects/trees)
+	float lastRotation;
+	Vector3 lastPosition;
+	Vector3 getAroundReversePos;
+	Vector3 getAroundSidePos;
+	bool gettingAround;
+	int stuckCount;
+	int consecutiveStuckCount;
+	Pathfind_Help_Implementer *implScript;
+
+	void Created(GameObject *obj)
+	{
+		lastPosition = Vector3();
+		getAroundReversePos = Vector3();
+		getAroundSidePos = Vector3();
+		gettingAround = false;
+		stuckCount = 0;
+		consecutiveStuckCount = 0;
+		implScript = nullptr;
+
+		// Check for being blocked
+		lastRotation = Commands->Get_Facing(obj);
+		lastPosition = Commands->Get_Position(obj);
+		Commands->Start_Timer(obj, this, 5.0f, TIMER_AI_CHECK_FOR_BLOCK);
+
+		Commands->Start_Timer(obj, this, 0.1f, TIMER_AI_POST_CREATED);
+	}
+
+	void Post_Created(GameObject *obj)
+	{
+		if (Is_Script_Attached(obj, "MS_AI_Behaviour"))
+		{
+			implScript = (MS_AI_Behaviour *)(Find_Script_On_Object(obj, "MS_AI_Behaviour"));
+		}
+		/*else if (Is_Script_Attached(obj, "dp88_Ore_Miner"))
+		{
+			implScript = (dp88_Ore_Miner *)(Find_Script_On_Object(obj, "dp88_Ore_Miner"));
+		}
+		else if (Is_Script_Attached(obj, "dp88_AR_Chrono_Miner"))
+		{
+			implScript = (dp88_AR_Chrono_Miner *)(Find_Script_On_Object(obj, "dp88_AR_Chrono_Miner"));
+		}*/
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == TIMER_AI_POST_CREATED)
+		{
+			Post_Created(obj);
+		}
+		else if (number == TIMER_AI_CHECK_FOR_BLOCK)
+		{
+			Check_For_Block(obj);
+			// Keep running this check
+			Commands->Start_Timer(obj, this, 1.0f, TIMER_AI_CHECK_FOR_BLOCK);
+		}
+		else if (number == TIMER_AI_CHECK_UNSTUCK_TIMEOUT)
+		{
+			if (!stuckCount && !gettingAround)
+			{
+				consecutiveStuckCount = 0;
+			}
+		}
+		else if (number == TIMER_AI_CHECK_GET_AROUND_TIMEOUT)
+		{
+			if (!M_Has_Timer(obj, this, TIMER_AI_CHECK_UNSTUCK_TIMEOUT))
+				Commands->Start_Timer(obj, this, 12, TIMER_AI_CHECK_UNSTUCK_TIMEOUT);
+			
+			stuckCount = 1;
+			consecutiveStuckCount++;
+			if (consecutiveStuckCount >= 3 && implScript)
+			{
+				implScript->On_Unable_To_Get_Unstuck(obj);
+			}
+			gettingAround = false;
+			if (implScript)
+			{
+				implScript->Stop_Getting_Around(obj);
+			}
+		}
+		else if (number == TIMER_AI_CHECK_DISTANCE)
+		{
+			bool isReversing = Commands->Get_Action_ID(obj) == ACTION_AI_GET_AROUND_REVERSE;
+			auto checkPos = isReversing ? getAroundReversePos : getAroundSidePos;
+			auto isInRange = Vector3::Distance(Commands->Get_Position(obj), checkPos) < 1.5f;
+
+			if (isInRange)
+			{
+				if (isReversing)
+					Reverse_Movement_Complete(obj);
+				else if (!isReversing)
+					Forward_Movement_Complete(obj);
+			}
+			else if (gettingAround)
+			{
+				Commands->Start_Timer(obj, this, 1, TIMER_AI_CHECK_DISTANCE);
+			}
+		}
+	}
+
+	void Action_Complete(GameObject *obj, int action_id, ActionCompleteReason complete_reason) override
+	{
+		if (complete_reason == ACTION_COMPLETE_LOW_PRIORITY)
+		{
+			return;
+		}
+
+		if (action_id == ACTION_AI_GET_AROUND_REVERSE)
+		{
+			Reverse_Movement_Complete(obj);
+		}
+		else if (action_id == ACTION_AI_GET_AROUND_FORWARD)
+		{
+			Forward_Movement_Complete(obj);
+		}
+	}
+
+	void Check_For_Block(GameObject *obj)
+	{
+		if (Should_Ignore_Blocked_Checking(obj))
+		{
+			stuckCount = 0;
+			return;
+		}
+
+		Vector3 pos = Commands->Get_Position(obj);
+		float minDist = obj->As_SoldierGameObj() ? 1.5f : 3.0f;
+		float rot = Commands->Get_Facing(obj);
+
+		// This distance should depend on the speed... calculate average speed?
+		if (Vector3::Distance(pos, lastPosition) < minDist && (obj->As_SoldierGameObj() || abs(rot - lastRotation) < 5))
+			stuckCount++;
+		else if (stuckCount > 0) // Don't go into negative
+			stuckCount--;
+
+		if (stuckCount > 5)
+		{
+			SimpleDynVecClass<Vector3> availableListB;
+			SimpleDynVecClass<Vector3> availableListS;
+
+			int distance = 12;
+			// Right
+			Vector3 offsetVectorRight;
+			offsetVectorRight.X -= distance / 2;
+			offsetVectorRight.Y -= distance;
+			Vector3 backRight = Commands->Get_Position(obj) + Get_Transform(obj).Rotate_Vector(offsetVectorRight);
+			offsetVectorRight.X += 3 * distance / 2;
+			Vector3 right = Commands->Get_Position(obj) + Get_Transform(obj).Rotate_Vector(offsetVectorRight);
+
+			float zCorrection = 0.5f;
+			int count = 0;
+			// These positions could be underground, raise
+			while (!Get_Random_Pathfind_Spot_Around_Position(obj, backRight, 0, 5, &backRight))
+			{
+				count++;
+				if (count >= 5)
+				{
+					backRight.Z -= count * zCorrection;
+					break;
+				}
+				backRight.Z += zCorrection;
+			}
+			count = 0;
+			while (!Get_Random_Pathfind_Spot_Around_Position(obj, right, 0, 5, &right))
+			{
+				count++;
+				if (count >= 5)
+				{
+					right.Z -= count * zCorrection;
+					break;
+				}
+				right.Z += zCorrection;
+			}
+
+			// Left
+			Vector3 offsetVectorLeft;
+			offsetVectorLeft.X -= distance / 2;
+			offsetVectorLeft.Y += distance;
+			Vector3 backLeft = Commands->Get_Position(obj) + Get_Transform(obj).Rotate_Vector(offsetVectorLeft);
+			offsetVectorLeft.X += 3 * distance / 2;
+			Vector3 left = Commands->Get_Position(obj) + Get_Transform(obj).Rotate_Vector(offsetVectorLeft);
+
+			count = 0;
+			// These positions could be underground, raise
+			while (!Get_Random_Pathfind_Spot_Around_Position(obj, backLeft, 0, 5, &backLeft))
+			{
+				count++;
+				if (count >= 5)
+				{
+					backLeft.Z -= count * zCorrection;
+					break;
+				}
+				backLeft.Z += zCorrection;
+			}
+			count = 0;
+			while (!Get_Random_Pathfind_Spot_Around_Position(obj, left, 0, 5, &left))
+			{
+				count++;
+				if (count >= 5)
+				{
+					left.Z -= count * zCorrection;
+					break;
+				}
+				left.Z += zCorrection;
+			}
+
+			if (Get_Random_Pathfind_Spot_Around_Position(obj, backRight, 0, 6, &backRight) && Get_Random_Pathfind_Spot_Around_Position(obj, right, 0, 6, &right))
+			{
+				availableListB.Add(backRight);
+				availableListS.Add(right);
+			}
+			if (Get_Random_Pathfind_Spot_Around_Position(obj, backLeft, 0, 6, &backLeft) && Get_Random_Pathfind_Spot_Around_Position(obj, left, 0, 6, &left))
+			{
+				availableListB.Add(backLeft);
+				availableListS.Add(left);
+			}
+
+			// Choose one side randomly
+			if (availableListB.Count() > 0)
+			{
+				int random = FreeRandom.Get_Int(availableListB.Count());
+				getAroundReversePos = availableListB[random];
+				getAroundSidePos = availableListS[random];
+
+				//M_Console_Input("msg STUCK: Attempting getting unstuck...");
+			}
+			else
+			{
+				//M_Console_Input("msg STUCK: There is no escape!");
+				stuckCount = 0;
+				Commands->Start_Timer(obj, this, 0, TIMER_AI_CHECK_GET_AROUND_TIMEOUT);
+				return;
+			}
+			if (implScript)
+			{
+				implScript->Prepare_Getting_Around(obj);
+			}
+			else
+			{
+				Commands->Action_Reset(obj, 101.0f);
+			}
+			gettingAround = true;
+
+			ActionParamsStruct params;
+			params.Set_Basic(this, 95, ACTION_AI_GET_AROUND_REVERSE);
+			params.Set_Movement(getAroundReversePos, 1.0f, 1.0f);
+			params.MoveBackup = true;
+			Handle_Movement_Action(obj, params);
+		}
+
+		lastRotation = rot;
+		lastPosition = pos;
+	}
+
+	void Handle_Movement_Action(GameObject *obj, ActionParamsStruct params)
+	{
+		Commands->Start_Timer(obj, this, 9, TIMER_AI_CHECK_GET_AROUND_TIMEOUT);
+		if (implScript)
+		{
+			params = implScript->Get_Param_Attack_Modifications(params);
+			if (params.AttackObject)
+			{
+				Commands->Action_Attack(obj, params);
+				// Attack action is not caught by Action_Complete function like Action_Goto, so we must check if arrived manually
+				Commands->Start_Timer(obj, this, 1, TIMER_AI_CHECK_DISTANCE);
+				return;
+			}
+		}
+		Commands->Action_Goto(obj, params);
+	}
+
+	void Reverse_Movement_Complete(GameObject *obj)
+	{
+		if (M_Has_Timer(obj, this, TIMER_AI_CHECK_GET_AROUND_TIMEOUT))
+			M_Stop_Timer2(obj, this, TIMER_AI_CHECK_GET_AROUND_TIMEOUT);
+
+		ActionParamsStruct params;
+		params.Set_Basic(this, 95, ACTION_AI_GET_AROUND_FORWARD);
+		params.Set_Movement(getAroundSidePos, 1.0f, 1.0f);
+		params.MovePathfind = false;
+		params.MoveBackup = false;
+		Handle_Movement_Action(obj, params);
+	}
+
+	void Forward_Movement_Complete(GameObject *obj)
+	{
+		if (M_Has_Timer(obj, this, TIMER_AI_CHECK_GET_AROUND_TIMEOUT))
+			M_Stop_Timer2(obj, this, TIMER_AI_CHECK_GET_AROUND_TIMEOUT);
+
+		if (!M_Has_Timer(obj, this, TIMER_AI_CHECK_UNSTUCK_TIMEOUT))
+			Commands->Start_Timer(obj, this, 12, TIMER_AI_CHECK_UNSTUCK_TIMEOUT);
+
+		stuckCount = 0;
+		consecutiveStuckCount = 0;
+		gettingAround = false;
+		if (implScript)
+		{
+			implScript->Stop_Getting_Around(obj);
+		}
+	}
+
+	bool Should_Ignore_Blocked_Checking(GameObject *obj)
+	{
+		return gettingAround || !obj->As_SmartGameObj()->Get_Action()->Is_Acting() || (implScript && implScript->Should_Ignore_Blocked_Checking(obj));
+	}
+};
+REGISTER_SCRIPT(MS_AI_Pathfind_Helper, "");
+
+
+
+/*------------------------
+AI script helpers with player-logic elements
+--------------------------*/
+
+
+/* Bot control helper struct */
+struct MS_AI_Global_Controller
+{
+private:
+	static MS_AI_Global_Controller ms_instance;
+	DynamicVectorClass<StringClass> availableBotNames;
+	DynamicVectorClass<StringClass> killedResponses;
+	DynamicVectorClass<StringClass> killerResponses;
+	bool isInited;
+	bool isMapInited;
+
+	int rallyPointNodeGroupTeam[2];
+	int rallyPointNodeNumTeam[2];
+	int rallyPointChosenNodeIDTeam[2];
+	bool rallyPointRegistrationOpenForTeam[2];
+	DynamicVectorClass<int> rallyPointRegistrationsTeam[2];
+	DynamicVectorClass<int> rallyPointUnitsNotArrivedTeam[2];
+	int baseDefObjectiveIDTeam[2];
+
+public:
+	float radarRadius;
+	GameObject* teamControllers[2];
+	DynamicVectorClass<GameObject*> botControllersTeam[2];
+	DynamicVectorClass<MS_AI_Rally_Point*> rallyPointsTeam[2];
+	DynamicVectorClass<MS_AI_Patrol_Point*> patrolPointsTeam[2];
+	DynamicVectorClass<Vector3> spawnPositions[2];
+	DynamicVectorClass<int> playerList;
+	DynamicVectorClass<const char*> engineerUnitList;
+	int transportVehicleSeatCount;
+	float grantCreditsMultiplier;
+	bool killedRespEnabled;
+	bool killerRespEnabled;
+	bool debugBuyMessagesEnabled;
+	bool debugAcquireVehicleMessagesEnabled;
+	bool debugGrouprushMessagesEnabled;
+
+	MS_AI_Global_Controller() : isInited(false)
+	{
+		Reset();
+	}
+
+	~MS_AI_Global_Controller()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		isInited = false;
+		availableBotNames.Delete_All();
+		killedResponses.Delete_All();
+		killerResponses.Delete_All();
+	}
+
+	static MS_AI_Global_Controller& GetInstance()
+	{
+		if (!ms_instance.isInited)
+		{
+			ms_instance.Init();
+		}
+		if (!ms_instance.isMapInited)
+		{
+			ms_instance.Create_Helper();
+		}
+		return ms_instance;
+	}
+
+	static bool GetMapInited()
+	{
+		return ms_instance.isMapInited;
+	}
+
+	void Init()
+	{
+		rallyPointNodeGroupTeam[0] = -1;
+		rallyPointNodeGroupTeam[1] = -1;
+		rallyPointNodeNumTeam[0] = -1;
+		rallyPointNodeNumTeam[1] = -1;
+		rallyPointChosenNodeIDTeam[0] = 0;
+		rallyPointChosenNodeIDTeam[1] = 0;
+		rallyPointRegistrationOpenForTeam[0] = true;
+		rallyPointRegistrationOpenForTeam[1] = true;
+		teamControllers[0] = nullptr;
+		teamControllers[1] = nullptr;
+		baseDefObjectiveIDTeam[0] = 0;
+		baseDefObjectiveIDTeam[1] = 0;
+		radarRadius = 0;
+		transportVehicleSeatCount = 0;
+		killedRespEnabled = false;
+		killerRespEnabled = false;
+		debugBuyMessagesEnabled = false;
+		debugAcquireVehicleMessagesEnabled = false;
+		debugGrouprushMessagesEnabled = false;
+
+		Init_Bot_Strings();
+		Init_Bot_Config();
+		AddRadioHook(RadioHook);
+		AddPlayerJoinHook(PlayerJoinHook);
+		AddPlayerLeaveHook(PlayerLeaveHook);
+		isInited = true;
+	}
+
+	void Init_New_Map()
+	{
+		isMapInited = true;
+		Refresh_Player_List();
+		spawnPositions[0].Delete_All();
+		spawnPositions[1].Delete_All();
+		rallyPointsTeam[0].Delete_All();
+		rallyPointsTeam[1].Delete_All();
+		patrolPointsTeam[0].Delete_All();
+		patrolPointsTeam[1].Delete_All();
+		engineerUnitList.Delete_All();
+
+		// TODO: THIS IS A SMALL PROBLEM
+		radarRadius = 50.0f; //Commands->Get_Radar_Radius();
+
+		int radarMode = RADAR_NOBODY;
+		if (The_Game())
+			radarMode = The_Game()->Get_Radar_Mode();
+
+		if (radarMode == RADAR_ALL)
+			Start_Timer_Fake(5.0f, TIMER_AI_CHECK_RADAR);
+
+		Start_Timer_Fake(8.0f, TIMER_AI_CHECK_ALLIES);
+
+		// Automatic building setups for bots
+		// Get default parameters
+		StringClass bldParams = "";
+		DynamicVectorClass<ScriptParameter> parameters;
+		Get_Script_Parameters("MS_AI_Building", parameters);
+		for (int i = 0; i < parameters.Count(); i++)
+		{
+			auto parameter = parameters[i];
+			bldParams.Format((i == 0 ? "%s%s" : "%s,%s"), bldParams.Peek_Buffer(), parameter.value.Peek_Buffer());
+		}
+		for (auto x = GameObjManager::BuildingGameObjList.Head(); x != nullptr; x = x->Next())
+		{
+			BuildingGameObj* object = x->Data();
+			if (!Is_Script_Attached(object, "MS_AI_Building") && strcmp(Get_Skin(object), "Blamo") != 0)
+				Commands->Attach_Script(object, "MS_AI_Building", bldParams.Peek_Buffer());
+			// Find stock refineries, check their trickle/unload timers and hook into them to send money to bots
+			if (object->As_RefineryGameObj() && !Is_Script_Attached(object, "MS_AI_Refinery"))
+				Commands->Attach_Script(object, "MS_AI_Refinery", "");
+		}
+
+		DynamicVectorClass<StringClass> extraTargetList;
+		INIClass* config = Get_INI("bot_config.cfg");
+		if (config)
+		{
+			if (auto bldSection = config->Find_Section("BotTargets"))
+			{
+				int i = 0;
+				StringClass entry = "Target0";
+				while (config->Find_Entry("BotTargets", entry.Peek_Buffer()))
+				{
+					StringClass preset;
+					config->Get_String(preset, "BotTargets", entry.Peek_Buffer(), "");
+					extraTargetList.Add(preset);
+					i++;
+					entry.Format("Target%d", i);
+				}
+			}
+			Release_INI(config);
+		}
+
+		if (extraTargetList.Count() > 0)
+		{
+			for (auto x = GameObjManager::GameObjList.Head(); x != nullptr; x = x->Next())
+			{
+				auto data = x->Data();
+				if (!data->As_ScriptableGameObj() || extraTargetList.ID(Commands->Get_Preset_Name(data->As_ScriptableGameObj())) == -1)
+					continue;
+
+				auto object = data->As_ScriptableGameObj();
+				if (!Is_Script_Attached(object, "MS_AI_Building") && strcmp(Get_Skin(object), "Blamo") != 0)
+						Commands->Attach_Script(object, "MS_AI_Building", bldParams);
+			}
+		}
+
+		Create_Engineer_Unit_List();
+	}
+
+	void End_Map()
+	{
+		isMapInited = false;
+	}
+
+	void Create_Helper()
+	{
+		if (!Find_Object_With_Script("MS_AI_Global_Controller_Helper"))
+		{
+			auto obj = Commands->Create_Object("Invisible_Object", Vector3());
+			Commands->Attach_Script(obj, "MS_AI_Global_Controller_Helper", "");
+		}
+	}
+
+	void Refresh_Player_List()
+	{
+		playerList.Delete_All();
+		for (auto PlayerIter = Get_Player_List()->Head(); (PlayerIter != nullptr); PlayerIter = PlayerIter->Next())
+		{
+			cPlayer* p = PlayerIter->Data();
+			// Check if ID is in the list already
+			if (playerList.ID(p->Get_Id()) == -1)
+			{
+				playerList.Add(p->Get_Id());
+			}
+		}
+	}
+
+	void Init_Bot_Strings()
+	{
+		INIClass *config = Get_INI("bot_names.cfg");
+		if (config)
+		{
+			int i = 0;
+			StringClass entry = "BotName0";
+			while (config->Find_Entry("BotNames", entry.Peek_Buffer()))
+			{
+				StringClass name;
+				config->Get_String(name, "BotNames", entry.Peek_Buffer(), "");
+				availableBotNames.Add(name);
+				i++;
+				entry.Format("BotName%d", i);
+			}
+			Release_INI(config);
+		}
+	}
+
+	StringClass Get_Free_Bot_Name()
+	{
+		StringClass botName = DEFAULT_BOT_NAME;
+
+		int count = availableBotNames.Count();
+		// Get a random one
+		if (count)
+		{
+			StringClass bot = availableBotNames[FreeRandom.Get_Int(count)];
+			botName = bot;
+			availableBotNames.DeleteObj(bot);
+		}
+
+		return botName;
+	}
+
+	void Give_Back_Bot_Name(const StringClass& name)
+	{
+		availableBotNames.Add(name);
+	}
+
+	StringClass Get_Killed_Response()
+	{
+		return Get_Chat_Response(killedResponses);
+	}
+
+	StringClass Get_Killer_Response()
+	{
+		return Get_Chat_Response(killerResponses);
+	}
+
+	StringClass Get_Chat_Response(const DynamicVectorClass<StringClass>& strArray)
+	{
+		StringClass resp = ":o";
+
+		int count = strArray.Count();
+		// Get a random one
+		if (count)
+		{
+			resp = strArray[FreeRandom.Get_Int(count)];
+		}
+
+		if (FreeRandom.Get_Float() < 0.3f)
+			resp = resp.AsUpper();
+		else if (FreeRandom.Get_Float() < 0.3f)
+			resp = resp.AsLower();
+
+		if (FreeRandom.Get_Float() < 0.5f)
+			resp += "!";
+
+		return resp;
+	}
+
+	void Init_Bot_Config()
+	{
+		// 0 (building needs repair)
+		// 1 (get in the veh), 2 (get out of the veh)
+		// 6 (yes), 7 (no), 12 (move out), 13 (follow me), 14 (hold position), 15 (cover me)
+
+		Radio_Message_Type::BUILDING_NEEDS_REPAIRS = 0;
+		Radio_Message_Type::GET_IN_THE_VEHICLE = 1;
+		Radio_Message_Type::GET_OUT_OF_THE_VEHICLE = 2;
+		Radio_Message_Type::DESTROY_THAT_VEHICLE = 3;
+		Radio_Message_Type::DONT_GET_IN_MY_WAY = 5;
+		Radio_Message_Type::AFFIRMATIVE = 6;
+		Radio_Message_Type::NEGATIVE = 7;
+
+		Radio_Message_Type::TAKE_THE_POINT = 11;
+		Radio_Message_Type::MOVE_OUT = 12;
+		Radio_Message_Type::FOLLOW_ME = 13;
+		Radio_Message_Type::COVER_ME = 15;
+		Radio_Message_Type::DESTROY_IT_NOW = 19;
+
+		Radio_Message_Type::ATTACK_THE_HARVESTER = 21;
+		Radio_Message_Type::ATTACK_THAT_STRUCTURE = 22;
+		Radio_Message_Type::DEFEND_THE_BASE = 25;
+		Radio_Message_Type::DEFEND_THE_HARVESTER = 26;
+		Radio_Message_Type::DEFEND_THAT_STRUCTURE = 27;
+
+		// read from ini file?
+
+		INIClass* config = Get_INI("bot_config.cfg");
+
+		if (config)
+		{
+			killedRespEnabled = config->Get_Bool("EventKilled", "Enabled", false);
+			int i = 0;
+			StringClass entry = "Resp0";
+			while (config->Find_Entry("EventKilled", entry.Peek_Buffer()))
+			{
+				StringClass name;
+				config->Get_String(name, "EventKilled", entry.Peek_Buffer(), "");
+				killedResponses.Add(name);
+				i++;
+				entry.Format("Resp%d", i);
+			}
+
+			killerRespEnabled = config->Get_Bool("EventKiller", "Enabled", false);
+			i = 0;
+			entry.Format("Resp0");
+			while (config->Find_Entry("EventKiller", entry.Peek_Buffer()))
+			{
+				StringClass name;
+				config->Get_String(name, "EventKiller", entry.Peek_Buffer(), "");
+				killerResponses.Add(name);
+				i++;
+				entry.Format("Resp%d", i);
+			}
+
+			if (config->Get_Bool("RadioCommands", "Override", false))
+			{
+				Radio_Message_Type::BUILDING_NEEDS_REPAIRS = config->Get_Int("RadioCommands", "BUILDING_NEEDS_REPAIRS", Radio_Message_Type::BUILDING_NEEDS_REPAIRS);
+				Radio_Message_Type::GET_IN_THE_VEHICLE = config->Get_Int("RadioCommands", "GET_IN_THE_VEHICLE", Radio_Message_Type::GET_IN_THE_VEHICLE);
+				Radio_Message_Type::GET_OUT_OF_THE_VEHICLE = config->Get_Int("RadioCommands", "GET_OUT_OF_THE_VEHICLE", Radio_Message_Type::GET_OUT_OF_THE_VEHICLE);
+				Radio_Message_Type::DESTROY_THAT_VEHICLE = config->Get_Int("RadioCommands", "DESTROY_THAT_VEHICLE", Radio_Message_Type::DESTROY_THAT_VEHICLE);
+				Radio_Message_Type::DONT_GET_IN_MY_WAY = config->Get_Int("RadioCommands", "DONT_GET_IN_MY_WAY", Radio_Message_Type::DONT_GET_IN_MY_WAY);
+				Radio_Message_Type::AFFIRMATIVE = config->Get_Int("RadioCommands", "AFFIRMATIVE", Radio_Message_Type::AFFIRMATIVE);
+				Radio_Message_Type::NEGATIVE = config->Get_Int("RadioCommands", "NEGATIVE", Radio_Message_Type::NEGATIVE);
+
+				Radio_Message_Type::TAKE_THE_POINT = config->Get_Int("RadioCommands", "TAKE_THE_POINT", Radio_Message_Type::TAKE_THE_POINT);
+				Radio_Message_Type::MOVE_OUT = config->Get_Int("RadioCommands", "MOVE_OUT", Radio_Message_Type::MOVE_OUT);
+				Radio_Message_Type::FOLLOW_ME = config->Get_Int("RadioCommands", "FOLLOW_ME", Radio_Message_Type::FOLLOW_ME);
+				Radio_Message_Type::COVER_ME = config->Get_Int("RadioCommands", "COVER_ME", Radio_Message_Type::COVER_ME);
+				Radio_Message_Type::DESTROY_IT_NOW = config->Get_Int("RadioCommands", "DESTROY_IT_NOW", Radio_Message_Type::DESTROY_IT_NOW);
+
+				Radio_Message_Type::ATTACK_THE_HARVESTER = config->Get_Int("RadioCommands", "ATTACK_THE_HARVESTER", Radio_Message_Type::ATTACK_THE_HARVESTER);
+				Radio_Message_Type::ATTACK_THAT_STRUCTURE = config->Get_Int("RadioCommands", "ATTACK_THAT_STRUCTURE", Radio_Message_Type::ATTACK_THAT_STRUCTURE);
+				Radio_Message_Type::DEFEND_THE_BASE = config->Get_Int("RadioCommands", "DEFEND_THE_BASE", Radio_Message_Type::DEFEND_THE_BASE);
+				Radio_Message_Type::DEFEND_THE_HARVESTER = config->Get_Int("RadioCommands", "DEFEND_THE_HARVESTER", Radio_Message_Type::DEFEND_THE_HARVESTER);
+				Radio_Message_Type::DEFEND_THAT_STRUCTURE = config->Get_Int("RadioCommands", "DEFEND_THAT_STRUCTURE", Radio_Message_Type::DEFEND_THAT_STRUCTURE);
+			}
+
+			transportVehicleSeatCount = config->Get_Int("General", "TransportVehicleMinimumSeatCount", 3);
+			grantCreditsMultiplier = config->Get_Float("General", "GrantCreditsMultiplier", 1.0f);
+			debugBuyMessagesEnabled = config->Get_Bool("Debug", "EnableBuyMessages", false);
+			debugAcquireVehicleMessagesEnabled = config->Get_Bool("Debug", "EnableAcquireVehicleMessages", false);
+			debugGrouprushMessagesEnabled = config->Get_Bool("Debug", "EnableGrouprushMessages", false);
+
+			Release_INI(config);
+		}
+
+	}
+
+	static int Get_Radio_Command_Index(int stringID, int team)
+	{
+		for (int i = 0; i <= 29; i++)
+		{
+			if (Get_Radio_Command_String(i) == stringID)
+				return i;
+		}
+		return -1;
+	}
+
+	static void Receive_Radio_Command(GameObject *sender, int AnnouncementID, int orderType = 0, int orderObjID = 0)
+	{
+		int team = Get_Object_Type(sender);
+		if (team != 0 && team != 1)
+			return;
+
+		int radioIndex = Get_Radio_Command_Index(AnnouncementID, team);
+		if (radioIndex == Radio_Message_Type::DEFEND_THE_BASE)
+		{
+			MS_AI_Global_Controller::GetInstance().Create_Base_Defense_Objective(team);
+			MS_AccessHelper::Activate_Defense_Protocol(team);
+		}
+
+		else if (radioIndex == Radio_Message_Type::BUILDING_NEEDS_REPAIRS)
+			MS_AccessHelper::Activate_Defense_Protocol(team);
+
+		else if (radioIndex == Radio_Message_Type::ATTACK_THE_HARVESTER)
+		{
+			if (auto target = Find_Harvester(team == 1 ? 0 : 1))
+			{
+				const int targetID = target->Get_ID();
+				Create_Timed_Objective(team, 45.0f, Commands->Get_Position(target), 15, targetID, targetID);
+			}
+		}
+
+		auto &botControllers = MS_AI_Global_Controller::GetInstance().botControllersTeam[team];
+		// Send custom to all AI controllers
+		for (int i = 0; i < botControllers.Count(); i++)
+		{
+			MS_AccessHelper::Send_Radio_Order_To_Bot(sender, botControllers[i], radioIndex, orderType, orderObjID);
+		}
+	}
+
+	// Listen to Radio Commands
+	static bool RadioHook(int PlayerType, int PlayerID, int AnnouncementID, int IconID, AnnouncementEnum AnnouncementType)
+	{
+		if (!MS_AI_Global_Controller::GetMapInited())
+			return true;
+		Receive_Radio_Command(Get_GameObj(PlayerID), AnnouncementID);
+		return true;
+	}
+
+	static void PlayerJoinHook(int PlayerID, const char *PlayerName)
+	{
+		if (!MS_AI_Global_Controller::GetMapInited())
+			return;
+		// Check if ID is in the list already
+		if (MS_AI_Global_Controller::GetInstance().playerList.ID(PlayerID) == -1)
+			MS_AI_Global_Controller::GetInstance().playerList.Add(PlayerID);
+	}
+
+	static void PlayerLeaveHook(int PlayerID)
+	{
+		if (!MS_AI_Global_Controller::GetMapInited())
+			return;
+		MS_AI_Global_Controller::GetInstance().playerList.DeleteObj(PlayerID);
+	}
+
+	void Start_Timer_Fake(float duration, int timerNum, int param = -1)
+	{
+		if (auto helper = Find_Object_With_Script("MS_AI_Global_Controller_Helper"))
+		{
+			if (auto script = Find_Script_On_Object(helper, "MS_AI_Global_Controller_Helper"))
+			{
+				if (param == -1)
+					Commands->Start_Timer(helper, script, duration, timerNum);
+				else
+					Commands->Send_Custom_Event(nullptr, helper, timerNum, param, duration);
+			}
+		}
+	}
+
+	void Stop_Timer_Fake(int number)
+	{
+		if (auto helper = Find_Object_With_Script("MS_AI_Global_Controller_Helper"))
+		{
+			if (auto script = Find_Script_On_Object(helper, "MS_AI_Global_Controller_Helper"))
+			{
+				if (M_Has_Timer(helper, script, number))
+					M_Stop_Timer2(helper, script, number);
+			}
+		}
+	}
+
+	void Timer_Expired_Fake(int number, int param = -1)
+	{
+		if (number == TIMER_AI_COOP_GROUP_WAIT_TEAM_0 || number == TIMER_AI_COOP_GROUP_WAIT_TEAM_1)
+		{
+			int team = number == TIMER_AI_COOP_GROUP_WAIT_TEAM_0 ? 0 : 1;
+
+			// Close registrations if this was the wait timer for the first node
+			int &nodeNum = rallyPointNodeNumTeam[team];
+			if (nodeNum == 0)
+			{
+				bool &regsOpen = rallyPointRegistrationOpenForTeam[team];
+				regsOpen = false;
+			}
+			//M_Console_Input("msg Kicking unarrived registrants by timeout!");
+			Kick_Missing_GroupRush_Registrants(team);
+		}
+		else if (number == CUSTOM_AI_COOP_PLAYER_DESTROY_OBJECTIVE)
+		{
+			int &id = baseDefObjectiveIDTeam[param];
+			id = 0;
+		}
+		else if (number == TIMER_AI_CHECK_RADAR)
+		{
+			DynamicVectorClass<GameObject*> visibleObjects;
+			// Go through all objects, save their position, team and visibility for later checks.
+			for (auto x = GameObjManager::SmartGameObjList.Head(); x != nullptr; x = x->Next())
+			{
+				GameObject* object = x->Data();
+				PhysicalGameObj* phys = object->As_PhysicalGameObj();
+
+				// Check if radar blip is not None and make sure it's not a sneaking soldier
+				if (phys->Get_Radar_Blip_Shape_Type() && object->As_SmartGameObj()->Is_Visible() &&
+					(!object->As_SoldierGameObj() || (!Get_Vehicle(object)/* && (!Commands->Get_Soldiers_Can_Sneak() || !object->As_SoldierGameObj()->Is_Sneaking())*/)
+					)
+				)
+				{
+					visibleObjects.Add(object);
+				}
+			}
+
+			Start_Timer_Fake(5.0f, TIMER_AI_CHECK_RADAR);
+
+			bool team0RadarEnabled = Is_Radar_Enabled(0);
+			bool team1RadarEnabled = Is_Radar_Enabled(1);
+
+			auto botControllers = Combine_Lists(botControllersTeam[0], botControllersTeam[1]);
+			for (int i = 0; i < botControllers.Count(); i++)
+			{
+				auto botController = botControllers[i];
+
+				// Always reset first
+				Commands->Send_Custom_Event(nullptr, botController, CUSTOM_AI_ADD_RADAR_TARGET, 0, 0.0f);
+
+				// Radar off? Stop checking
+				if (!(Get_Object_Type(botController) ? team1RadarEnabled : team0RadarEnabled))
+					continue;
+
+				// Send all visible enemies
+				for (int j = 0; j < visibleObjects.Count(); j++)
+				{
+					auto visibleObject = visibleObjects[j];
+					if (Get_Object_Type(visibleObject) != Get_Object_Type(botController) && Get_Object_Type(visibleObject) != SCRIPT_PLAYERTYPE_NEUTRAL)
+					{
+						Commands->Send_Custom_Event(nullptr, botController, CUSTOM_AI_ADD_RADAR_TARGET, visibleObject->Get_ID(), 0.1f);
+					}
+				}
+			}
+		}
+		else if (number == TIMER_AI_CHECK_ALLIES)
+		{
+			DynamicVectorClass<GameObject*> playerObjects;
+			for (int i = 0; i < playerList.Count(); i++)
+			{
+				auto playerID = playerList[i];
+				if (auto playerObject = Get_GameObj(playerID))
+				{ 
+					playerObjects.Add(playerObject);
+				}
+			}
+
+			Start_Timer_Fake(8.0f, TIMER_AI_CHECK_ALLIES);
+
+			auto botControllers = Combine_Lists(botControllersTeam[0], botControllersTeam[1]);
+			for (int i = 0; i < botControllers.Count(); i++)
+			{
+				auto botController = botControllers[i];
+
+				Commands->Send_Custom_Event(nullptr, botController, CUSTOM_AI_ADD_ALLY_TARGET, 0, 0.0f);
+
+				// Send all friendly players
+				for (int j = 0; j < playerObjects.Count(); j++)
+				{
+					auto playerObject = playerObjects[j];
+					if (Get_Object_Type(playerObject) == Get_Object_Type(botController) && Get_Object_Type(playerObject) != -2)
+					{
+						Commands->Send_Custom_Event(nullptr, botController, CUSTOM_AI_ADD_ALLY_TARGET, playerObject->Get_ID(), 0.1f);
+					}
+				}
+			}
+		}
+	}
+
+	void Change_Bot_Count(int count)
+	{
+		if (auto countController = Find_Object_With_Script("MS_AI_Player_Count_Controller"))
+		{
+			Commands->Send_Custom_Event(nullptr, countController, CUSTOM_AI_CHANGE_BOTCOUNT, count, 0);
+		}
+		// Create a count controller otherwise!
+		else if (count != 0)
+		{
+			auto obj = Commands->Create_Object("Invisible_Object", Vector3());
+			StringClass params;
+			// Get default parameters
+			DynamicVectorClass<ScriptParameter> parameters;
+			Get_Script_Parameters("MS_AI_Player_Count_Controller", parameters);
+			for (int i = 0; i < parameters.Count(); i++)
+			{
+				auto parameter = parameters[i];
+				if (i == 0)
+					params.Format("%d", count);
+				else
+					params.Format("%s,%s", params.Peek_Buffer(), parameter.value.Peek_Buffer());
+			}
+			Commands->Attach_Script(obj, "MS_AI_Player_Count_Controller", params.Peek_Buffer());
+			M_Console_Input("msg WARNING: Bots are not configured for this map and may act strange!");
+			if (!Is_Pathfind_Generated())
+			{
+				return;
+			}
+
+			if (!Find_Object_With_Script("dp88_AI_Objective"))
+			{
+				Create_Base_Attack_Objective(0);
+				Create_Base_Attack_Objective(1);
+			}
+		}
+	}
+
+	void Change_Bot_Count_Team(int team)
+	{
+		if (auto countController = Find_Object_With_Script("MS_AI_Player_Count_Controller"))
+		{
+			Commands->Send_Custom_Event(nullptr, countController, CUSTOM_AI_CHANGE_BOTCOUNTTEAM, team, 0);
+		}
+	}
+
+	void Create_Engineer_Unit_List()
+	{
+		// Find character presets that can repair a building
+		if (auto sampleBuilding = Find_Building_By_Team(1) ? Find_Building_By_Team(1) : Find_Building_By_Team(0))
+		{
+			auto bldSkin = sampleBuilding->As_BuildingGameObj()->Get_Defense_Object()->Get_Skin();
+			// auto bldSkinName = ArmorWarheadManager::Get_Armor_Name(bldSkin);
+			/*StringClass params;
+			params.Format("msg Sample building (%s): %s", Get_Translated_Definition_Name(Commands->Get_Preset_Name(sampleBuilding)), bldSkinName);
+			M_Console_Input(params.Peek_Buffer());*/
+
+			DynamicVectorClass<int> characterList;
+			characterList = Combine_Lists(characterList, Get_Enlisted_Purchase_Items(1));
+			characterList = Combine_Lists(characterList, Get_Enlisted_Purchase_Items(0));
+			characterList = Combine_Lists(characterList, Get_Purchase_Items(1, PurchaseSettingsDefClass::TYPE_CLASSES));
+			characterList = Combine_Lists(characterList, Get_Purchase_Items(0, PurchaseSettingsDefClass::TYPE_CLASSES));
+			characterList = Combine_Lists(characterList, Get_Purchase_Items(1, PurchaseSettingsDefClass::TYPE_SECRET_CLASSES));
+			characterList = Combine_Lists(characterList, Get_Purchase_Items(0, PurchaseSettingsDefClass::TYPE_SECRET_CLASSES));
+			for (int i = 0; i < characterList.Count(); i++)
+			{
+				const char* presetName = DefinitionMgrClass::Find_Definition(characterList[i], false)->Get_Name();
+				if (auto soldierDef = (SoldierGameObjDef*)Find_Named_Definition(presetName))
+				{
+
+					DynamicVectorClass<int> weaponList;
+					weaponList.Add(soldierDef->Get_Weapon_Def_ID());
+					weaponList.Add(soldierDef->Get_Secondary_Weapon_Def_ID());
+					auto scriptList = soldierDef->ScriptNameList;
+					auto paramList = soldierDef->ScriptParameterList;
+					for (int j = 0; j < scriptList.Count(); j++)
+					{
+						// Check weapon granting scripts, there are multiple?
+						if (strcmp(scriptList[j], "M00_GrantPowerup_Created") == 0)
+						{
+							if (auto powerupDef = (PowerUpGameObjDef*)Find_Named_Definition(paramList[j].Peek_Buffer()))
+								weaponList.Add(powerupDef->Get_Grant_Weapon_ID());
+						}
+					}
+
+					for (int j = 0; j < weaponList.Count(); j++)
+					{
+						if (auto weaponDef = (WeaponDefinitionClass*)Find_Definition(weaponList[j]))
+						{
+							if (auto ammoDef = (AmmoDefinitionClass*)Find_Definition(weaponDef->PrimaryAmmoDefID))
+							{
+								//auto whName = ArmorWarheadManager::Get_Warhead_Name(ammoDef->Warhead());
+								auto multiplier = ArmorWarheadManager::Get_Damage_Multiplier(bldSkin, ammoDef->Warhead());
+								auto damage = ammoDef->Damage();
+								// Repairs!
+								if (multiplier * damage < 0)
+								{
+									/*StringClass params;
+									params.Format("msg Preset can rep: '%s' Weapon: %s, ammo warhead: %s, bld damage: %.2f / multiplier: %.2f, res: %.2f", presetName, weaponDef->Get_Name(), whName, damage, multiplier, multiplier*damage);// Get_Translated_Definition_Name(presetName));
+									M_Console_Input(params.Peek_Buffer());*/
+									engineerUnitList.Add(presetName);
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	int Get_Temporary_Objective_ID(int team)
+	{
+		return baseDefObjectiveIDTeam[team];
+	}
+
+	void Create_Base_Attack_Objective(int team)
+	{
+		// Create objective in enemy base
+		Vector3 center = Get_Base_Center((team == 1 ? 0 : 1));
+		Vector3 pos;
+		Get_Closest_Pathfind_Spot_Size(center, 50.0f, &pos, 3.0f);
+
+		Create_Timed_Objective(team, 0, pos, 40, dp88_AI_Objective::TYPE_OFFENSIVE);
+	}
+
+	void Create_Base_Defense_Objective(int team)
+	{
+		//M_Console_Input("msg Creating base defense objective...");
+		Vector3 center = Get_Base_Center(team);
+		Vector3 pos;
+		Get_Closest_Pathfind_Spot_Size(center, 50.0f, &pos, 3.0f);
+
+		int &id = baseDefObjectiveIDTeam[team];
+		if (!id)
+		{
+			id = Create_Timed_Objective(team, 60, pos, 40);
+			Start_Timer_Fake(60, CUSTOM_AI_COOP_PLAYER_DESTROY_OBJECTIVE, team);
+		}
+	}
+
+	Vector3 Get_Closest_Spawn_Point(GameObject *obj, int team)
+	{
+		Vector3 returnPos = Vector3();
+		float dist = FLT_MAX;
+		for (int i = 0; i < spawnPositions[team].Count(); i++)
+		{
+			auto position = spawnPositions[team][i];
+			float newDistance = Vector3::Distance_Squared(Commands->Get_Position(obj), position);
+			if (newDistance < dist)
+			{
+				returnPos = position;
+				dist = newDistance;
+			}
+		}
+		return returnPos;
+	}
+
+	int Get_New_Patrol_Point_ID(int team, int previousID, bool isForVehicle)
+	{
+		auto &patrolPoints = patrolPointsTeam[team];
+		if (patrolPoints.Count())
+		{
+			if (patrolPoints.Count() == 1)
+				return patrolPoints[0]->Get_Object()->Get_ID();
+			
+			auto copiedPoints = patrolPoints;
+			DynamicVectorClass<MS_AI_Patrol_Point*> nodes;
+			if (auto previousPoint = Commands->Find_Object(previousID))
+			{
+				auto patrolScript = (MS_AI_Patrol_Point *)(Find_Script_On_Object(previousPoint, "MS_AI_Patrol_Point"));
+				copiedPoints.DeleteObj(patrolScript);
+
+				// Collect all patrol points with the next num, return a random one
+				for (int i = 0; i < copiedPoints.Count(); i++)
+				{
+					// Skip other groups
+					if (copiedPoints[i]->Get_NodeGroup() != patrolScript->Get_NodeGroup())
+						continue;
+
+					if (copiedPoints[i]->Get_NodeNum() == patrolScript->Get_NodeNum() + 1)
+						nodes.Add(copiedPoints[i]);
+				}
+				// Choose one of the same nums randomly
+				if (nodes.Count() > 0)
+					return nodes[FreeRandom.Get_Int(nodes.Count())]->Get_Object()->Get_ID();
+				// None found, get num 0 of another group
+				else
+				{
+					for (int i = 0; i < copiedPoints.Count(); i++)
+					{
+						// Skip same group or if not fit (inf only group for a vehicle)
+						if (copiedPoints[i]->Get_NodeGroup() == patrolScript->Get_NodeGroup() || (copiedPoints[i]->Get_IsInfantryOnly() && isForVehicle))
+							continue;
+
+						if (copiedPoints[i]->Get_NodeNum() == 0)
+							nodes.Add(copiedPoints[i]);
+					}
+					// Choose one of the same nums randomly
+					if (nodes.Count() > 0)
+						return nodes[FreeRandom.Get_Int(nodes.Count())]->Get_Object()->Get_ID();
+				}
+			}
+			nodes.Delete_All();
+			// Get num 0 of any group
+			for (int i = 0; i < copiedPoints.Count(); i++)
+			{
+				// Skip if not fit (inf only group for a vehicle)
+				if (copiedPoints[i]->Get_IsInfantryOnly() && isForVehicle)
+					continue;
+
+				if (copiedPoints[i]->Get_NodeNum() == 0)
+					nodes.Add(copiedPoints[i]);
+			}
+			// Choose one of the same nums randomly
+			if (nodes.Count() > 0)
+				return nodes[FreeRandom.Get_Int(nodes.Count())]->Get_Object()->Get_ID();
+		}
+		return 0;
+	}
+
+	void Reset_GroupRush(int team)
+	{
+		//M_Console_Input("msg Reset_GroupRush.");
+
+		int &nodeGroup = rallyPointNodeGroupTeam[team];
+		nodeGroup = -1;
+
+		bool &regsOpen = rallyPointRegistrationOpenForTeam[team];
+		regsOpen = true;
+
+		DynamicVectorClass<int> &rallyPointUnitsNotArrived = rallyPointUnitsNotArrivedTeam[team];
+		DynamicVectorClass<int> &rallyPointRegs = rallyPointRegistrationsTeam[team];
+
+		for (int i = 0; i < rallyPointRegs.Count(); i++)
+		{
+			Commands->Send_Custom_Event(nullptr, Commands->Find_Object(rallyPointRegs[i]), CUSTOM_AI_COOP_GROUPRUSH_COMPLETE, 0, 0.0f);
+		}
+
+		rallyPointUnitsNotArrived.Clear();
+		rallyPointRegs.Clear();
+	}
+
+	/**
+	* 0 = closed
+	* 1 = infantry only
+	* 2 = infantry + vehicles allowed
+	*/
+	int Is_Rally_Point_Registration_Open(int team)
+	{
+		// No rally points, no registration
+		DynamicVectorClass<MS_AI_Rally_Point*> &rallyPoints = rallyPointsTeam[team];
+		if (rallyPoints.Count() == 0)
+			return 0;
+
+		bool isOpen = rallyPointRegistrationOpenForTeam[team];
+		if (!isOpen)
+		{
+			return 0;
+		}
+
+		if (auto node = Commands->Find_Object(Get_Rally_Point_ID(team)))
+		{
+			if (auto nodeScript = Find_Script_On_Object(node, "MS_AI_Rally_Point"))
+			{
+				return ((MS_AI_Rally_Point *)nodeScript)->Get_IsInfantryOnly() ? 1 : 2;
+			}
+		}
+
+		return 0;
+	}
+
+	int Get_Rally_Point_ID(int team)
+	{
+		// Correct set of rallyPoints
+		DynamicVectorClass<MS_AI_Rally_Point*> &rallyPoints = rallyPointsTeam[team];
+		// Current node group
+		int &nodeGroup = rallyPointNodeGroupTeam[team];
+		// Current node num
+		int &nodeNum = rallyPointNodeNumTeam[team];
+		// Chosen final node
+		int &nodeID = rallyPointChosenNodeIDTeam[team];
+
+		// Set random node group if not yet set
+		if (nodeGroup == -1)
+		{
+			DynamicVectorClass<int> nodeGroups;
+			for (int i = 0; i < rallyPoints.Count(); i++)
+			{
+				// Only add a group once
+				if (nodeGroups.ID(rallyPoints[i]->Get_NodeGroup()) == -1)
+					nodeGroups.Add(rallyPoints[i]->Get_NodeGroup());
+			}
+			if (nodeGroups.Count() > 0)
+			{
+				nodeGroup = nodeGroups[FreeRandom.Get_Int(nodeGroups.Count())];
+				// Set node num to first (0)
+				nodeNum = 0;
+				nodeID = 0;
+			}
+		}
+
+		// Pick random node's id with matching num if not yet set
+		if (nodeID == 0)
+		{
+			DynamicVectorClass<MS_AI_Rally_Point*> nodes;
+			for (int i = 0; i < rallyPoints.Count(); i++)
+			{
+				// Skip other groups
+				if (rallyPoints[i]->Get_NodeGroup() != nodeGroup)
+					continue;
+
+				if (rallyPoints[i]->Get_NodeNum() == nodeNum)
+					nodes.Add(rallyPoints[i]);
+			}
+			// Choose one of the same nums randomly
+			if (nodes.Count() > 0)
+				nodeID = nodes[FreeRandom.Get_Int(nodes.Count())]->Get_Object()->Get_ID();
+		}
+
+		/*StringClass buff;
+		buff.Format("msg Returning: (team: %d), group: %d, num: %d, ID: %d ", team, nodeGroup, nodeNum, nodeID);
+		M_Console_Input(buff.Peek_Buffer());*/
+
+		return nodeID;
+	}
+
+	void Check_Group_Arrived(int team)
+	{
+		DynamicVectorClass<int> &rallyPointUnitsNotArrived = rallyPointUnitsNotArrivedTeam[team];
+		DynamicVectorClass<int> &rallyPointRegs = rallyPointRegistrationsTeam[team];
+
+		bool &regsOpen = rallyPointRegistrationOpenForTeam[team];
+
+		/*StringClass buff;
+		buff.Format("msg Group arrived: %d/%d (team:%d)", rallyPointRegs.Count() - rallyPointUnitsNotArrived.Count(), rallyPointRegs.Count(), team);
+		M_Console_Input(buff.Peek_Buffer());*/
+
+		// All died? Reset
+		if (rallyPointRegs.Count() == 0)
+		{
+			Reset_GroupRush(team);
+			return;
+		}
+
+
+		// Print out who has not arrived yet
+		/*for (int i = 0; i < rallyPointUnitsNotArrived.Count(); i++)
+		{
+		StringClass buff2;
+		buff2.Format("msg Not arrived yet: %d (team:%d)", rallyPointUnitsNotArrived[i], team);
+		M_Console_Input(buff2.Peek_Buffer());
+		}*/
+
+		// All arrived
+		if (rallyPointUnitsNotArrived.Count() == 0 && !regsOpen)
+		{
+			// Stop kick timer!
+			int timerID = team == 0 ? TIMER_AI_COOP_GROUP_WAIT_TEAM_0 : TIMER_AI_COOP_GROUP_WAIT_TEAM_1;
+			Stop_Timer_Fake(timerID);
+			//M_Console_Input("msg Stopping existing timer (WAIT)");
+
+			int &nodeNum = rallyPointNodeNumTeam[team];
+			int &nodeID = rallyPointChosenNodeIDTeam[team];
+
+			nodeID = 0;
+			nodeNum = nodeNum + 1;
+			int nextID = Get_Rally_Point_ID(team);
+
+			for (int i = 0; i < rallyPointRegs.Count(); i++)
+			{
+				Commands->Send_Custom_Event(nullptr, Commands->Find_Object(rallyPointRegs[i]), CUSTOM_AI_COOP_NEXT_RALLYPOINT, nextID, 0.0f);
+			}
+			rallyPointUnitsNotArrived.Clear();
+			rallyPointUnitsNotArrived = rallyPointRegistrationsTeam[team];
+
+			// None found -> grouprush reached last location (or other problem). Reset
+			if (nextID == 0)
+			{
+				//M_Console_Input("msg Last location reached.");
+				Reset_GroupRush(team);
+			}
+		}
+	}
+
+	void Object_Arrived(int team, int objID)
+	{
+		DynamicVectorClass<int> &rallyPointUnitsNotArrived = rallyPointUnitsNotArrivedTeam[team];
+
+		// Check if ID is gone from the list already
+		if (rallyPointUnitsNotArrived.ID(objID) == -1)
+		{
+			// This number is not in the array
+			return;
+		}
+
+		int &nodeNum = rallyPointNodeNumTeam[team];
+		// First unit arrived to not first node, start timeout
+		if (nodeNum != 0 && rallyPointUnitsNotArrived.Count() == rallyPointRegistrationsTeam[team].Count())
+		{
+			int timerID = team == 0 ? TIMER_AI_COOP_GROUP_WAIT_TEAM_0 : TIMER_AI_COOP_GROUP_WAIT_TEAM_1;
+			Start_Timer_Fake(80, timerID);
+			//Commands->Start_Timer(Owner(), this, 90.0f, timerID);
+		}
+
+		rallyPointUnitsNotArrived.DeleteObj(objID);
+		Check_Group_Arrived(team);
+	}
+
+	void Register_For_GroupRush(int team, int objID)
+	{
+		DynamicVectorClass<int> &rallyPointRegs = rallyPointRegistrationsTeam[team];
+		DynamicVectorClass<int> &rallyPointUnitsNotArrived = rallyPointUnitsNotArrivedTeam[team];
+
+		// Start timer for registration
+		if (rallyPointRegs.Count() == 0)
+		{
+			int timerID = team == 0 ? TIMER_AI_COOP_GROUP_WAIT_TEAM_0 : TIMER_AI_COOP_GROUP_WAIT_TEAM_1;
+			Start_Timer_Fake(130, timerID);
+			//Commands->Start_Timer(Owner(), this, 140.0f, timerID);
+		}
+		// Check if ID is in the list already
+		if (rallyPointRegs.ID(objID) != -1)
+		{
+			//M_Console_Input("msg Trying to add an ID twice to the rallyPointRegs list!");
+			return;
+		}
+
+		/*StringClass buff;
+		buff.Format("msg Adding: %d to grouprush! (team: %d)", objID, team);
+		M_Console_Input(buff.Peek_Buffer());*/
+
+		rallyPointRegs.Add(objID);
+		rallyPointUnitsNotArrived.Add(objID);
+
+		// Close group rush if enough/too many bots registered already
+		if (rallyPointRegs.Count() >= 8 || rallyPointRegs.Count() >= botControllersTeam[team].Count() / 2)
+		{
+			bool &regsOpen = rallyPointRegistrationOpenForTeam[team];
+			regsOpen = false;
+		}
+	}
+
+	void Unregister_From_GroupRush(int team, int objID)
+	{
+		DynamicVectorClass<int> &rallyPointRegs = rallyPointRegistrationsTeam[team];
+		DynamicVectorClass<int> &rallyPointUnitsNotArrived = rallyPointUnitsNotArrivedTeam[team];
+
+		/*StringClass buff;
+		buff.Format("msg Deleting: %d from grouprush! (team: %d)", objID, team);
+		M_Console_Input(buff.Peek_Buffer());*/
+
+		rallyPointRegs.DeleteObj(objID);
+		rallyPointUnitsNotArrived.DeleteObj(objID);
+		// Send 0 as next node and grouprush as complete
+		GameObject* obj = Commands->Find_Object(objID);
+		Commands->Send_Custom_Event(nullptr, obj, CUSTOM_AI_COOP_NEXT_RALLYPOINT, 0, 0.0f);
+		Commands->Send_Custom_Event(nullptr, obj, CUSTOM_AI_COOP_GROUPRUSH_COMPLETE, 0, 0.0f);
+		Check_Group_Arrived(team);
+	}
+
+	void Kick_Missing_GroupRush_Registrants(int team)
+	{
+		DynamicVectorClass<int> notArrivedList = rallyPointUnitsNotArrivedTeam[team];
+
+		if (notArrivedList.Count() == 0)
+		{
+			Check_Group_Arrived(team);
+		}
+		else
+		{
+			for (int i = 0; i < notArrivedList.Count(); i++)
+			{
+				Unregister_From_GroupRush(team, notArrivedList[i]);
+			}
+		}
+	}
+};
+MS_AI_Global_Controller MS_AI_Global_Controller::ms_instance;
+
+
+
+/*!
+* \brief Helper for MS_AI_Global_Controller
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change. DO NOT PLACE THIS MANUALLY!
+*
+* \param Nothing
+*   Nothing
+*/
+class MS_AI_Global_Controller_Helper : public ScriptImpClass
+{
+	void Created(GameObject *obj) override
+	{
+		// Making sure the controller exists
+		MS_AI_Global_Controller::GetInstance().Init_New_Map();
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_AI_CHANGE_BOTCOUNT)
+		{
+			MS_AI_Global_Controller::GetInstance().Change_Bot_Count(param);
+		}
+		else if (type == CUSTOM_AI_CHANGE_BOTCOUNTTEAM)
+		{
+			MS_AI_Global_Controller::GetInstance().Change_Bot_Count_Team(param);
+		}
+		else
+			MS_AI_Global_Controller::GetInstance().Timer_Expired_Fake(type, param);
+	}
+
+	void Timer_Expired(GameObject *obj, int number) override
+	{
+		MS_AI_Global_Controller::GetInstance().Timer_Expired_Fake(number);
+	}
+
+	void Detach(GameObject *obj) override
+	{
+		if (Exe != EXE_LEVELEDIT)
+			MS_AI_Global_Controller::GetInstance().End_Map();
+
+		ScriptImpClass::Detach(obj);
+	}
+};
+REGISTER_SCRIPT(MS_AI_Global_Controller_Helper, "");
+
+
+/*!
+* \brief AI units gather at these points before attacks
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change
+*
+*/
+void MS_AI_Rally_Point::Created(GameObject *obj)
+{
+	StringClass params;
+	params.Format("%d,%d,%d,%d,%d,%d,%d", Get_Team(), obj->Get_ID(), (Get_IsInfantryOnly() ? 12:20), 1, 1, 1, 1);
+	Commands->Attach_Script(obj, "dp88_AI_Objective", params.Peek_Buffer());
+	MS_AI_Global_Controller::GetInstance().rallyPointsTeam[Get_Team()].Add(this);
+
+	/*
+	WideStringClass str;
+	str.Format(L"msg RALLY POINT Team: %d, NodeGroup: %d, NodeNum: %d!", Get_Team(), Get_NodeGroup(), Get_NodeNum());
+	M_Console_Input(str);
+	*/
+}
+
+void MS_AI_Rally_Point::Destroyed(GameObject *obj)
+{
+	MS_AI_Global_Controller::GetInstance().rallyPointsTeam[Get_Team()].DeleteObj(this);
+}
+
+
+int MS_AI_Rally_Point::Get_Team()
+{
+	return Get_Int_Parameter("Team") == 1 ? 1 : 0;
+}
+
+int MS_AI_Rally_Point::Get_NodeGroup()
+{
+	return Get_Int_Parameter("NodeGroup");
+}
+
+int MS_AI_Rally_Point::Get_NodeNum()
+{
+	return Get_Int_Parameter("NodeNum");
+}
+
+bool MS_AI_Rally_Point::Get_IsInfantryOnly()
+{
+	return Get_Int_Parameter("IsInfantryOnly") == 1;
+}
+
+GameObject *MS_AI_Rally_Point::Get_Object()
+{
+	return Owner();
+}
+
+REGISTER_SCRIPT(MS_AI_Rally_Point,
+	"Team=0:int,"
+	"NodeGroup=0:int,"
+	"NodeNum=0:int,"
+	"IsInfantryOnly=0:int"
+);
+
+
+/*!
+* \brief AI defensive bots use these points to patrol/defend the base
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change
+*
+*/
+void MS_AI_Patrol_Point::Created(GameObject *obj)
+{
+	StringClass params;
+	params.Format("%d,%d,%d,%d,%d,%d,%d", Get_Team(), obj->Get_ID(), (Get_IsInfantryOnly() ? 3 : 6), 1, 1, 1, 1);
+	Commands->Attach_Script(obj, "dp88_AI_Objective", params.Peek_Buffer());
+	MS_AI_Global_Controller::GetInstance().patrolPointsTeam[Get_Team()].Add(this);
+
+	/*
+	WideStringClass str;
+	str.Format(L"msg PATROL POINT Team: %d, NodeGroup: %d, NodeNum: %d!", Get_Team(), Get_NodeGroup(), Get_NodeNum());
+	M_Console_Input(str);
+	*/
+}
+
+void MS_AI_Patrol_Point::Destroyed(GameObject *obj)
+{
+	MS_AI_Global_Controller::GetInstance().patrolPointsTeam[Get_Team()].DeleteObj(this);
+}
+
+REGISTER_SCRIPT(MS_AI_Patrol_Point,
+	"Team=0:int,"
+	"NodeGroup=0:int,"
+	"NodeNum=0:int,"
+	"IsInfantryOnly=0:int"
+);
+
+
+/*!
+* \brief AI team controller
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change. DO NOT PLACE THIS MANUALLY!
+*
+* \param Nothing
+*   Nothing
+*/
+class MS_AI_Team_Controller : public ScriptImpClass
+{
+	int team;
+	int nextObjectiveType;
+	DynamicVectorClass<GameObject*> engineers;
+	DynamicVectorClass<GameObject*> defenders;
+	bool beaconPlacementVictoryAllowed;
+
+	void Created(GameObject *obj) override
+	{
+		nextObjectiveType = dp88_AI_Objective::TYPE_OFFENSIVE;
+		team = Get_Int_Parameter("Team");
+		// Neutral is bad, actual team is needed by some scripts
+		Change_Team(obj, team);
+
+		// Let Global Controller know we exist
+		MS_AI_Global_Controller::GetInstance().teamControllers[team] = obj;
+
+		Commands->Start_Timer(obj, this, 0.1f, TIMER_AI_THINK);
+
+		beaconPlacementVictoryAllowed = false;
+		if (The_Cnc_Game())
+			beaconPlacementVictoryAllowed = The_Cnc_Game()->Beacon_Placement_Ends_Game();
+		else if (The_Skirmish_Game())
+			beaconPlacementVictoryAllowed = The_Skirmish_Game()->Beacon_Placement_Ends_Game();
+	}
+
+	void Destroyed(GameObject *obj) override
+	{
+		// Let Global Controller know we are gone
+		MS_AI_Global_Controller::GetInstance().teamControllers[team] = nullptr;
+	}
+
+	void Timer_Expired(GameObject *obj, int number) override
+	{
+		if (The_Game()->Is_Game_Over())
+			return;
+
+		if (number == TIMER_AI_THINK)
+		{
+			Check_Explosives();
+
+			nextObjectiveType = dp88_AI_Objective::TYPE_OFFENSIVE;
+
+			// Search for engineering objectives
+			if (Are_Engineers_Needed(obj))
+				nextObjectiveType = dp88_AI_Objective::TYPE_ENGINEERING;
+			// Search for defensive objectives if no engineering is present
+			else if (Are_Defenders_Needed(obj))
+				nextObjectiveType = dp88_AI_Objective::TYPE_DEFENSIVE;
+
+			Commands->Start_Timer(obj, this, 5.0f, TIMER_AI_THINK);
+		}
+	}
+
+	bool Are_Engineers_Needed(GameObject *obj)
+	{
+		auto objectives = dp88_AI_Objective::CountObjectives(team, dp88_AI_Objective::TYPE_ENGINEERING);
+		auto engisNeeded = (objectives ? 1 : 0) + (M_Has_Timer(obj, this, TIMER_AI_DEFEND) ? 1 : 0);
+		return engineers.Count() < engisNeeded;
+	}
+
+	bool Are_Defenders_Needed(GameObject *obj)
+	{
+		auto objectives = dp88_AI_Objective::CountObjectives(team, dp88_AI_Objective::TYPE_DEFENSIVE);
+		auto defendersNeeded = (objectives ? 2 : 0) + (M_Has_Timer(obj, this, TIMER_AI_DEFEND) ? 2 : 0);
+		return defenders.Count() < defendersNeeded;
+	}
+
+	GameObject *Find_Closest_AI_Building(const Vector3& position, int team)
+	{
+		GameObject* bestBuilding = nullptr;
+		float bestDistance = FLT_MAX;
+		for (int index = 0; index < AIBuildings.Count(); index++)
+		{
+			auto building = AIBuildings[index];
+			if (Get_Object_Type(building) != team)
+				continue;
+			Vector3 offset = Commands->Get_Position(building) - position;
+			float distance = offset.Length2();
+			if (distance < bestDistance)
+			{
+				bestBuilding = building;
+				bestDistance = distance;
+			}
+		}
+		return bestBuilding;
+	}
+
+	void Check_Explosives()
+	{
+		if (!AIBuildings.Count() && !beaconPlacementVictoryAllowed)
+			return;
+
+		// Find beacons around friendly buildings, attach objectives to them
+		for (auto node = GameObjManager::BeaconGameObjList.Head(); node; node = node->Next())
+		{
+			auto object = node->Data();
+			// Skip friendly beacons, undeployed ones and ones that already have a marker on them
+			if (Get_Object_Type(object) == team || Is_Script_Attached(object, "dp88_AI_Marker_Repairable"))
+				continue;
+
+			bool isValidTarget = Is_On_Enemy_Pedestal(object) && beaconPlacementVictoryAllowed;
+
+			// Only check further if it's not a pedestal beacon and valid AI buildings exist
+			if (!isValidTarget && !AIBuildings.Count())
+				continue;
+
+			bool isVehicleTarget = false;
+			if (!isValidTarget)
+			{
+				Vector3 objPos = Commands->Get_Position(object);
+				auto bld = Find_Closest_AI_Building(objPos, team);
+				// This check is necessary as it can happen that only the other team has buildings, and this team does not
+				if (!bld)
+					continue;
+
+				ExplosionDefinitionClass *explosion = (ExplosionDefinitionClass *)DefinitionMgrClass::Find_Definition(object->Get_Definition().Get_Explosion_Obj_Def(), true);
+				if (!explosion)
+					continue;
+
+				const float damageRadius = explosion->DamageRadius + 1.0f;
+				// Attach objective if is close to the building
+				if (bld->As_BuildingGameObj())
+				{
+					Vector3 closestPoly;
+					Find_Closest_Poly_Position(bld->As_BuildingGameObj(), objPos, &closestPoly);
+					isValidTarget = Vector3::Distance(objPos, closestPoly) < damageRadius;
+				}
+				else
+				{
+					auto &boundingBox = bld->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+					isValidTarget = Vector3::Distance(objPos, boundingBox.Center) < damageRadius;
+					isVehicleTarget = !Is_Inside_AABox(boundingBox, objPos);
+				}
+			}
+			if (isValidTarget)
+			{
+				StringClass params;
+				// Send entire team to valid beacon to disarm it and eliminate the planter/defenders
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, dp88_AI_Objective::TYPE_ENGINEERING, 1, 1000, 0, 0, 0);
+				Commands->Attach_Script(object, "dp88_AI_Objective", params.Peek_Buffer());
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, dp88_AI_Objective::TYPE_OFFENSIVE, 4, 1000, 0, 0, 0);
+				Commands->Attach_Script(object, "dp88_AI_Objective", params.Peek_Buffer());
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, dp88_AI_Objective::TYPE_DEFENSIVE, 4, 1000, 0, 0, 0);
+				Commands->Attach_Script(object, "dp88_AI_Objective", params.Peek_Buffer());
+				Commands->Attach_Script(object, "dp88_AI_Marker_Repairable", "5.0");
+				// Also send vehicles if it's placed outside
+				if (isVehicleTarget)
+				{
+					params.Format("%d,%d,%d,%d,%d,%d,%d", team, dp88_AI_Objective::TYPE_OFFENSIVE, 10, 0, 1000, 1000, 1000);
+					Commands->Attach_Script(object, "dp88_AI_Objective", params.Peek_Buffer());
+				}
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: instance now marked as a target for bots", Commands->Get_Preset_Name(object));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+			}
+		}
+
+		if (!AIBuildings.Count())
+			return;
+
+		// Find C4s around friendly buildings, attach objectives to them
+		for (auto node = GameObjManager::C4GameObjList.Head(); node; node = node->Next())
+		{
+			auto object = node->Data();
+			// Skip friendly C4s and ones that already have a marker on them
+			if (Get_Object_Type(object) == team || Is_Script_Attached(object, "dp88_AI_Marker_Repairable"))
+				continue;
+
+			Vector3 objPos = Commands->Get_Position(object);
+			auto bld = Find_Closest_AI_Building(objPos, team);
+			// This check is necessary as it can happen that only the other team has buildings, and this team does not
+			if (!bld)
+				continue;
+			auto &boundingBox = bld->As_BuildingGameObj() ? bld->As_BuildingGameObj()->Get_Bounds() : bld->As_PhysicalGameObj()->Peek_Model()->Get_Bounding_Box();
+			// Attach objective if is inside the building
+			if (Is_Inside_AABox(boundingBox, objPos))
+			{
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, dp88_AI_Objective::TYPE_ENGINEERING, 1, 5, 0, 0, 0);
+				Commands->Attach_Script(object, "dp88_AI_Objective", params.Peek_Buffer());
+				Commands->Attach_Script(object, "dp88_AI_Marker_Repairable", "5.0");
+
+				if (BuildingDebugLocationsDisplayEnabled)
+				{
+					StringClass debug;
+					debug.Format("msg %s: instance now marked as a target for bots", Commands->Get_Preset_Name(object));
+					M_Console_Input(debug.Peek_Buffer());
+				}
+			}
+		}
+	}
+
+
+public:
+	int Signup_For_Next_Objective_Type(GameObject *botCtrl)
+	{
+		if (nextObjectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+			engineers.Add(botCtrl);
+		else if (engineers.ID(botCtrl) != -1)
+			engineers.DeleteObj(botCtrl);
+
+		if (nextObjectiveType == dp88_AI_Objective::TYPE_DEFENSIVE)
+			defenders.Add(botCtrl);
+		else if (defenders.ID(botCtrl) != -1)
+			defenders.DeleteObj(botCtrl);
+
+
+		return nextObjectiveType;
+	}
+
+	const char *Get_Infantry_List()
+	{
+		if (nextObjectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+			return Get_Parameter("InfListEngineering");
+		else if (nextObjectiveType == dp88_AI_Objective::TYPE_DEFENSIVE)
+			return Get_Parameter("InfListDefensive");
+		
+		return Get_Parameter("InfListOffensive");
+	}
+
+	void Report_Bot_Died(GameObject *botCtrl)
+	{
+		if (engineers.ID(botCtrl) != -1)
+			engineers.DeleteObj(botCtrl);
+
+		if (defenders.ID(botCtrl) != -1)
+			defenders.DeleteObj(botCtrl);
+	}
+
+	void Activate_Defense_Protocol(GameObject *obj)
+	{
+		if (M_Has_Timer(obj, this, TIMER_AI_DEFEND))
+		{
+			M_Stop_Timer2(obj, this, TIMER_AI_DEFEND);
+		}
+		Commands->Start_Timer(obj, this, 60.0f, TIMER_AI_DEFEND);
+	}
+};
+REGISTER_SCRIPT(MS_AI_Team_Controller, 
+	"Team=0:int,"
+	"InfListOffensive:string,"
+	"InfListDefensive:string,"
+	"InfListEngineering:string"
+);
+
+
+/*!
+* \brief AI player count controller
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change
+*
+* \param Nothing
+*   Nothing
+*/
+class MS_AI_Player_Count_Controller : public ScriptImpClass
+{
+	int maxBotCount;
+	int spawnTeam;
+	const char *spawnPresetTeam0;
+	const char *spawnPresetTeam1;
+
+	DynamicVectorClass<GameObject*> spawnedControllersTeam0;
+	DynamicVectorClass<GameObject*> spawnedControllersTeam1;
+
+	void Created(GameObject *obj) override
+	{
+		if (!Init_Parameters(obj))
+			return;
+
+		// Create the AI team brains
+		StringClass params;
+
+		auto team0TeamCtrl = Commands->Create_Object("Invisible_Object", Vector3());
+		params.Format("0,%s,%s,%s", 
+			Get_Parameter("InfListOffensiveTeam0"), 
+			Get_Parameter("InfListDefensiveTeam0"),
+			Get_Parameter("InfListEngineeringTeam0")
+		);
+		Commands->Attach_Script(team0TeamCtrl, "MS_AI_Team_Controller", params.Peek_Buffer());
+
+		auto team1TeamCtrl = Commands->Create_Object("Invisible_Object", Vector3());
+		params.Format("1,%s,%s,%s", 
+			Get_Parameter("InfListOffensiveTeam1"),
+			Get_Parameter("InfListDefensiveTeam1"),
+			Get_Parameter("InfListEngineeringTeam1")
+		);
+		Commands->Attach_Script(team1TeamCtrl, "MS_AI_Team_Controller", params.Peek_Buffer());
+
+		if (!Is_Pathfind_Generated())
+		{
+			M_Console_Input("msg WARNING: Pathfind sectors were not generated!");
+		}
+
+		Commands->Start_Timer(obj, this, 2.0f, TIMER_AI_CONTROLLER_PLAYERCHECK);
+	}
+
+	void Timer_Expired(GameObject *obj, int number) override
+	{
+		if (number == TIMER_AI_CONTROLLER_PLAYERCHECK)
+		{
+			// Correction if count is odd
+			int correction = spawnTeam != 2 ? 0 : (maxBotCount % 2 == 1 ? 1 : 0);
+
+			int botCountDiffTeam0 = spawnTeam == 1 ? -spawnedControllersTeam0.Count() : (spawnTeam == 2 ? maxBotCount / 2 : maxBotCount) + correction - Get_Team_Player_Count(0) - spawnedControllersTeam0.Count();
+			int botCountDiffTeam1 = spawnTeam == 0 ? -spawnedControllersTeam1.Count() : (spawnTeam == 2 ? maxBotCount / 2 : maxBotCount) + correction - Get_Team_Player_Count(1) - spawnedControllersTeam1.Count();
+
+			// Fixes to team 0
+			if (botCountDiffTeam0 > 0)
+			{
+				for (int i = 0; i < botCountDiffTeam0; i++)
+					Create_Player_Controller(obj, 0);
+			}
+			else if (botCountDiffTeam0 < 0)
+			{
+				for (int i = 0; i < abs(botCountDiffTeam0); i++)
+				{
+					if (spawnedControllersTeam0.Count() == 0)
+						break;
+
+					GameObject *controller = spawnedControllersTeam0[spawnedControllersTeam0.Count() - 1];
+					spawnedControllersTeam0.DeleteObj(controller);
+					Commands->Destroy_Object(controller);
+				}
+			}
+
+			// Fixes to team 1
+			if (botCountDiffTeam1 > 0)
+			{
+				for (int i = 0; i < botCountDiffTeam1; i++)
+					Create_Player_Controller(obj, 1);
+			}
+			else if (botCountDiffTeam1 < 0)
+			{
+				for (int i = 0; i < abs(botCountDiffTeam1); i++)
+				{
+					if (spawnedControllersTeam1.Count() == 0)
+						break;
+
+					GameObject *controller = spawnedControllersTeam1[spawnedControllersTeam1.Count() - 1];
+					spawnedControllersTeam1.DeleteObj(controller);
+					Commands->Destroy_Object(controller);
+				}
+			}
+			Commands->Start_Timer(obj, this, 5.0f, TIMER_AI_CONTROLLER_PLAYERCHECK);
+		}
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_AI_CHANGE_BOTCOUNT)
+		{
+			maxBotCount = param;
+
+			// For safety, set max and min limit
+			if (maxBotCount >= 300)
+				maxBotCount = 300;
+
+			else if (maxBotCount < 0)
+				maxBotCount = 0;
+
+			// TODO: THIS IS A SMALL PROBLEM
+			// Commands->Notify_Bot_Count_Listeners(maxBotCount);
+		}
+		else if (type == CUSTOM_AI_CHANGE_BOTCOUNTTEAM)
+		{
+			spawnTeam = param;
+		}
+	}
+
+	void Create_Player_Controller(GameObject *obj, int team)
+	{
+		auto &spawnedControllers = team == 0 ? spawnedControllersTeam0 : spawnedControllersTeam1;
+
+		int objectiveType = 1;
+		// TODO: SORT OUT LATER
+		/*
+		int objectiveType = 3;
+
+		// 1 engineer bots max for both teams
+		if (spawnedControllers.Count() >= 1)
+			objectiveType = 2;
+
+		// 2 defensive bots max for both teams
+		if (spawnedControllers.Count() >= 3)
+			objectiveType = 1;
+		*/
+
+		const char *spawnerName;
+		const char *infPresetList;
+		const char *vehPresetList;
+		if (team)
+		{
+			spawnerName = spawnPresetTeam1;
+			if (objectiveType == 3)
+				infPresetList = Get_Parameter("InfListEngineeringTeam1");
+			else if (objectiveType == 2)
+				infPresetList = Get_Parameter("InfListDefensiveTeam1");
+			else
+				infPresetList = Get_Parameter("InfListOffensiveTeam1");
+
+			vehPresetList = Get_Parameter("VehListTeam1");
+		}
+		else
+		{
+			spawnerName = spawnPresetTeam0;
+			if (objectiveType == 3)
+				infPresetList = Get_Parameter("InfListEngineeringTeam0");
+			else if (objectiveType == 2)
+				infPresetList = Get_Parameter("InfListDefensiveTeam0");
+			else
+				infPresetList = Get_Parameter("InfListOffensiveTeam0");
+
+			vehPresetList = Get_Parameter("VehListTeam0");
+		}
+
+		Vector3 createPos = Commands->Get_Position(obj);
+		GameObject *spawnedObject = Commands->Create_Object("Invisible_Object", createPos);
+		spawnedControllers.Add(spawnedObject);
+
+		// Attach script to spawned object so the controller and child can communicate
+		StringClass params;
+		params.Format("%d,%s,%s,%s,%d", team, spawnerName, infPresetList, vehPresetList, objectiveType);
+		Commands->Attach_Script(spawnedObject, "MS_AI_Player_Controller", params.Peek_Buffer());
+	}
+
+	StringClass Validate_Twiddler_Param(const char *paramName)
+	{
+		auto twiddlerName = Get_Parameter(paramName);
+		if (strcmp(twiddlerName, "USEPLAYERPURCHASELISTS") != 0 &&
+			strcmp(twiddlerName, "AUTOENGINEER") != 0 &&
+			strcmp(twiddlerName, "") != 0 && strcmp(twiddlerName, "null") != 0 &&
+			!Is_Valid_Preset(twiddlerName))
+		{
+			StringClass errorMessage;
+			errorMessage.Format("The script has an invalid %s parameter: %s", paramName, twiddlerName);
+			return errorMessage;
+		}
+		return "";
+	}
+
+	bool Init_Parameters(GameObject *obj)
+	{
+		bool valid = true;
+		StringClass errorMessage;
+		StringClass checkedError;
+
+		maxBotCount = Get_Int_Parameter("Count");
+		if (valid && maxBotCount < 0)
+		{
+			errorMessage.Format("The script has the Count parameter as negative: %d", maxBotCount);
+			valid = false;
+		}
+
+		if (valid)
+			checkedError = Validate_Twiddler_Param("InfListOffensiveTeam0");
+	
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("InfListOffensiveTeam1");
+		
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("InfListDefensiveTeam0");
+		
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("InfListDefensiveTeam1");
+
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("InfListEngineeringTeam0");
+
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("InfListEngineeringTeam1");
+
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("VehListTeam0");
+
+		if (checkedError.Is_Empty())
+			checkedError = Validate_Twiddler_Param("VehListTeam1");
+
+		if (!checkedError.Is_Empty())
+		{
+			errorMessage.Format("%s", checkedError.Peek_Buffer());
+			valid = false;
+		}
+
+		spawnPresetTeam0 = Get_Parameter("Team0SpawnAtPreset");
+		if (valid && strcmp(spawnPresetTeam0, "USEPLAYERSPAWNERS") != 0 && !Is_Valid_Preset(spawnPresetTeam0))
+		{
+			errorMessage.Format("The script has an invalid Team0SpawnAtPreset parameter: %s", spawnPresetTeam0);
+			valid = false;
+		}
+		spawnPresetTeam1 = Get_Parameter("Team1SpawnAtPreset");
+		if (valid && strcmp(spawnPresetTeam1, "USEPLAYERSPAWNERS") != 0 && !Is_Valid_Preset(spawnPresetTeam1))
+		{
+			errorMessage.Format("The script has an invalid Team1SpawnAtPreset parameter: %s", spawnPresetTeam1);
+			valid = false;
+		}
+		spawnTeam = Get_Int_Parameter("SpawnTeam");
+		if (valid && (spawnTeam < 0 || spawnTeam > 2))
+		{
+			errorMessage.Format("The script has the SpawnTeam parameter not as 0, 1 or 2: %d", maxBotCount);
+			valid = false;
+		}
+
+		if (!valid)
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: %hs. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), errorMessage.Peek_Buffer());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+		}
+		return valid;
+	}
+};
+REGISTER_SCRIPT(MS_AI_Player_Count_Controller,
+	"Count=0:int,"
+	"InfListOffensiveTeam1=USEPLAYERPURCHASELISTS:string,"
+	"InfListDefensiveTeam1=USEPLAYERPURCHASELISTS:string,"
+	"InfListEngineeringTeam1=AUTOENGINEER:string,"
+	"VehListTeam1=USEPLAYERPURCHASELISTS:string,"
+	"InfListOffensiveTeam0=USEPLAYERPURCHASELISTS:string,"
+	"InfListDefensiveTeam0=USEPLAYERPURCHASELISTS:string,"
+	"InfListEngineeringTeam0=AUTOENGINEER:string,"
+	"VehListTeam0=USEPLAYERPURCHASELISTS:string,"
+	"Team1SpawnAtPreset=USEPLAYERSPAWNERS:string,"
+	"Team0SpawnAtPreset=USEPLAYERSPAWNERS:string,"
+	"SpawnTeam=2:int"
+);
+
+
+/*!
+* \brief AI player controller
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* A controller script for creating/respawning one AI unit, tracking credits and purchasable unit lists.
+* Also implements player-like thinking and more complicated AI decisions, e.g. cooperating with players.
+*
+* \param Nothing
+*   Nothing
+*/
+class MS_AI_Player_Controller : public ScriptImpClass
+{
+	template<typename>
+	friend class ScriptRegistrant;
+
+	int team;
+	int objectiveType;
+	StringClass botName;
+	const char *teamSpawnPreset;
+	const char *spawnAtPresetName;
+	const char *soldierPresetList;
+	const char *vehPresetList;
+
+	ReferencerClass soldierObject;
+	ReferencerClass vehicleObject;
+	//int soldierObjectID;
+	//int vehicleObjectID;
+	// This can be either the soldier or the vehicle
+	ReferencerClass coopObject;
+	//int coopObjectID;
+	int vehicleConstructWaitCount;
+	/* States:
+	0 = not cooping
+	1 = group rushing
+	2 = following player
+	3 = covering player
+	4 = defending base
+	5 = moving to ordered position
+	6 = attacking ordered target
+	7 = defending ordered target
+	8 = unavailable, going to refill or other stuff
+	*/
+	int coopState;
+	ReferencerClass coopTarget;
+	//int coopTargetID;
+	long coopFriendPlayerID;
+	Vector3 coopFriendPrevPos;
+	bool enteredVeh;
+	// ID of the vehicle the soldier exited last, should not be reentered automatically
+	ReferencerClass bannedVehicle;
+	//int bannedVehicleID;
+	float money;
+	float expenses;
+
+	ReferencerClass tempObjective;
+	//int tempObjectiveID;
+
+	// Help prevent refill spamming
+	bool isRefillCooldownInProgress;
+
+	// This is used so Detach and Destroy don't both try to clean up
+	bool isDeleted;
+
+
+	// Helpers for timed reaction ability
+	bool isProcessingRadioMessage;
+	int radioMessageID;
+	int taskType;
+	int taskObjID;
+
+	BotPersonality *personality;
+
+	PlayerRelationData playerRelationData;
+
+	DynamicVectorClass<int> enemiesOnRadar;
+	DynamicVectorClass<int> allyPlayersAround;
+
+	MS_AI_Player_Controller()
+		: personality(nullptr)
+	{
+
+	}
+
+	~MS_AI_Player_Controller()
+	{
+		REF_PTR_RELEASE(personality);
+	}
+
+	void Created(GameObject* obj) override
+	{
+		team = 0;
+		objectiveType = 1;
+		teamSpawnPreset = nullptr;
+		spawnAtPresetName = nullptr;
+		soldierPresetList = nullptr;
+		vehPresetList = nullptr;
+		soldierObject = nullptr;
+		vehicleObject = nullptr;
+		coopObject = nullptr;
+		vehicleConstructWaitCount = 0;
+		coopState = 0;	
+		coopTarget = nullptr;
+		coopFriendPlayerID = 0;
+		coopFriendPrevPos = Vector3();
+		enteredVeh = false;
+		bannedVehicle = nullptr;
+		money = 0;
+		expenses = 0;
+		tempObjective = nullptr;
+		isRefillCooldownInProgress = false;
+		isDeleted = false;
+		isProcessingRadioMessage = false;
+		radioMessageID = 0;
+		taskType = 0;
+		taskObjID = 0;
+		personality = nullptr;
+
+		if (!Init_Parameters(obj))
+			return;
+
+		// Let Global Controller know we exist
+		MS_AI_Global_Controller::GetInstance().botControllersTeam[team].Add(obj);
+
+		// Bot name will be default if this script is created before the count controller is ready (needs a second to read ini)
+		botName = MS_AI_Global_Controller::GetInstance().Get_Free_Bot_Name();
+		// Personality will be tied to bot name later
+		// Ref count 1
+		personality = new BotPersonality(FreeRandom.Get_Int(3)); // 0, 1, 2
+
+		StringClass buff;
+		buff.Format("%s %s", botName.Peek_Buffer(), Get_Translated_String(Get_String_ID_By_Desc("IDS_MP_RENEGADE_JOINS")));
+		Send_Message(MS_RGB_JOINLEFT_MSG, buff.Peek_Buffer());
+
+		// Set team so when sending customs, this object's team can be checked
+		Set_Object_Type(obj, team);
+
+		if (The_Cnc_Game())
+			money = (float)The_Cnc_Game()->Get_Starting_Credits();
+		else if (The_Skirmish_Game())
+			money = (float)The_Skirmish_Game()->Get_Starting_Credits();
+
+		teamSpawnPreset = team ? Get_GDI_Soldier_Name() : Get_Nod_Soldier_Name();
+		
+		Spawn_Object(obj);
+	}
+	
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		// Respawn AI player if it died
+		if (type == CUSTOM_AI_SOLDIER_DIED)
+		{
+			if (tempObjective) {
+				Commands->Destroy_Object(tempObjective);
+				tempObjective = nullptr;
+			}
+			if (coopState)
+			{
+				coopState = 0;
+				if (coopObject == soldierObject)
+					coopObject = nullptr;
+
+				MS_AI_Global_Controller::GetInstance().Unregister_From_GroupRush(team, obj->Get_ID());
+			}
+
+			if (auto teamCtrlScript = Get_Team_Controller())
+			{
+				teamCtrlScript->Report_Bot_Died(obj);
+			}
+
+			soldierObject = nullptr;
+			bannedVehicle = nullptr;
+			// Cleanup after special cases (e.g. cyborg reaper double death +  removes all scripts from passenger, problems.)
+			enteredVeh = false;
+
+			if (isDeleted)
+				return;
+
+			Commands->Start_Timer(obj, this, 5.0f, TIMER_AI_OBJECT_SPAWN_COUNTDOWN);
+			Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP, 0, 0.0f);
+
+		}
+		else if (type == CUSTOM_AI_VEHICLE_DIED || type == CUSTOM_AI_VEHICLE_EXITED)
+		{
+			if (tempObjective) {
+				Commands->Destroy_Object(tempObjective);
+				tempObjective = nullptr;
+			}
+			// If it was the vehicle, activate AI on the soldier
+			if (enteredVeh)
+			{
+				if (coopState)
+				{
+					coopState = 0;
+					if (coopObject == vehicleObject)
+						coopObject = nullptr;
+
+					MS_AI_Global_Controller::GetInstance().Unregister_From_GroupRush(team, obj->Get_ID());
+				}
+
+				if (vehicleObject)
+				{
+					bannedVehicle = vehicleObject;
+					vehicleObject = nullptr;
+				}
+				enteredVeh = false;
+
+				if (isDeleted)
+					return;
+
+				if (soldierObject)
+				{
+					// Look for another vehicle, otherwise activate AI
+					if (!Search_For_Vehicle(obj, 30))
+						Activate_AI_Behaviour(obj, soldierObject);
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_REQUEST_REFUND)
+		{
+			money += expenses;
+			expenses = 0;
+		}
+		else if (type == CUSTOM_AI_SEND_PUBLIC_MESSAGE)
+		{
+			if (param == 0)
+			{
+				if (MS_AI_Global_Controller::GetInstance().killedRespEnabled && personality->Get_Random(BotPersonality::CHAT_CHANCE))
+				{
+					StringClass response = MS_AI_Global_Controller::GetInstance().Get_Killed_Response();
+					Send_Public_Message_String(response.Peek_Buffer());
+				}
+			}
+			else if (param == 1)
+			{
+				if (MS_AI_Global_Controller::GetInstance().killerRespEnabled && personality->Get_Random(BotPersonality::CHAT_CHANCE))
+				{
+					StringClass response = MS_AI_Global_Controller::GetInstance().Get_Killer_Response();
+					Send_Public_Message_String(response.Peek_Buffer());
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_COOP_PLAYER_RADIO_MESSAGE)
+		{
+			React_To_Radio_Message(sender, radioMessageID, taskType, taskObjID);
+			radioMessageID = 0;
+			taskType = 0;
+			taskObjID = 0;
+			isProcessingRadioMessage = false;
+		}
+		else if (type == CUSTOM_AI_COOP_ARRIVED_TO_RALLYPOINT)
+		{
+			MS_AI_Global_Controller::GetInstance().Object_Arrived(team, obj->Get_ID());
+			if (Search_For_Vehicle(obj, 20))
+				MS_AI_Global_Controller::GetInstance().Unregister_From_GroupRush(team, obj->Get_ID());
+		}
+		else if (type == CUSTOM_AI_COOP_NEXT_RALLYPOINT)
+		{
+			Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_COOP_NEXT_RALLYPOINT, param, 0.0);
+		}
+		else if (type == CUSTOM_AI_COOP_GROUPRUSH_COMPLETE || type == CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE)
+		{
+			coopState = 0;
+		}
+		else if (type == CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP)
+		{
+			coopState = 0;
+			coopFriendPlayerID = 0;
+		}
+		else if (type == CUSTOM_AI_SEARCH_FOR_VEHICLE)
+		{
+			if (!coopState)
+				Search_For_Vehicle(obj, 15);
+		}
+		else if (type == CUSTOM_AI_ADD_RADAR_TARGET)
+		{
+			if (param == 0)
+				enemiesOnRadar.Clear();
+			else {
+				auto enemyObj = Commands->Find_Object(param);
+				float checkRange = MS_AI_Global_Controller::GetInstance().radarRadius;
+
+				// Check range
+				if (soldierObject && enemyObj &&
+					Vector3::Distance_Squared(Commands->Get_Position(soldierObject), Commands->Get_Position(enemyObj)) < checkRange * checkRange)
+				{
+					enemiesOnRadar.Add(param);
+					/*StringClass buff;
+					buff.Format("msg %s: Enemy on radar: %s", botName.Peek_Buffer(), Commands->Get_Preset_Name(enemyObj));
+					M_Console_Input(buff.Peek_Buffer());*/
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_ADD_ALLY_TARGET)
+		{
+			if (param == 0)
+				allyPlayersAround.Clear();
+			else {
+				auto otherObj = Commands->Find_Object(param);
+
+				float checkRange = Get_Vehicle(soldierObject) ? 35.0f : 15.0f;
+				// Check range
+				if (soldierObject && otherObj &&
+					Vector3::Distance_Squared(Commands->Get_Position(soldierObject), Commands->Get_Position(otherObj)) < checkRange * checkRange)
+				{
+					allyPlayersAround.Add(param);
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_CHECK_RADAR_TARGETS)
+		{
+			// Update sympathy and with a chance, stop following/covering idle players
+			if (coopState == 2 || coopState == 3)
+			{
+				auto playerObj = Get_GameObj(coopFriendPlayerID);
+				if (playerObj && coopObject)
+				{
+					auto name = Get_Player_Name_By_ID(coopFriendPlayerID);
+					// Find sympathy of this guy
+					auto sympathyData = playerRelationData.GetPlayerData(name);
+					if (!sympathyData)
+						return;
+
+					float checkRange = coopObject->As_VehicleGameObj() ? 35.0f : 15.0f;
+					// Update sympathy
+					if (Vector3::Distance_Squared(Commands->Get_Position(playerObj), coopFriendPrevPos) < 2*2 && 
+						Vector3::Distance_Squared(Commands->Get_Position(playerObj), Commands->Get_Position(coopObject)) < checkRange * checkRange)
+					{
+						if (sympathyData->sympathy > 0.1f)
+							sympathyData->sympathy -= personality->Get_Random(BotPersonality::SYMPATHY_VARIABLE) / 2;
+						else if (sympathyData->sympathy < 0.1f && FreeRandom.Get_Float() > sympathyData->sympathy)
+							Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP, 0, 0.0f);
+					}
+					else 
+					{
+						if (sympathyData->sympathy < 1.0f)
+							sympathyData->sympathy += personality->Get_Random(BotPersonality::SYMPATHY_VARIABLE) / 4;
+					}
+
+					/*WideStringClass str;
+					str.Format(L"msg %S: Updated sympathy for %S: %.3f", botName.Peek_Buffer(), name.Peek_Buffer(), sympathyData->sympathy);
+					M_Console_Input(str);*/
+
+					coopFriendPrevPos = Commands->Get_Position(playerObj);
+				}
+			}
+
+			if (coopState != 0)
+				return;
+			
+			auto soldierObj = soldierObject.Get_Ptr();
+			if (!soldierObj)
+				return;
+
+			auto aiObj = soldierObj;
+
+			if (enteredVeh)
+			{
+				auto vehObj = vehicleObject.Get_Ptr();
+				if (vehObj && vehObj->As_VehicleGameObj() && vehObj->As_VehicleGameObj()->Get_Driver() == soldierObj)
+					aiObj = vehObj;
+				// Soldier inside a vehicle he's not driving: ignore radar targets.
+				else if (Get_Vehicle(soldierObj))
+					return;
+			}
+
+			auto behaviourScript = (MS_AI_Behaviour*)(Find_Script_On_Object(aiObj, "MS_AI_Behaviour"));
+			if (!behaviourScript)
+				return;
+
+			if (personality->Get_Random(BotPersonality::INVESTIGATE_CHANCE))
+			{
+				// Find closest enemy
+				float min_dist_sq = FLT_MAX;
+				GameObject *chosenEnemy = nullptr;
+				for (int i = 0; i < enemiesOnRadar.Count(); i++)
+				{
+					auto enemy = Commands->Find_Object(enemiesOnRadar[i]);
+					// Also check if the bot cares about it (e.g. conscript ignores vehicles (-1 prio) unless it has no objectives)
+					if (!enemy || Get_Vehicle(enemy) || Commands->Get_Health(enemy) <= 1.0f ||
+						!behaviourScript->Should_Chase_Radar_Target(enemy) )
+						continue;
+
+					float dist_sq = Vector3::Distance_Squared(Commands->Get_Position(aiObj), Commands->Get_Position(enemy));
+					if (dist_sq < min_dist_sq)
+					{
+						chosenEnemy = enemy;
+						min_dist_sq = dist_sq;
+					}
+				}
+
+				if (chosenEnemy)
+				{
+					int enemyID = chosenEnemy->Get_ID();
+					/*StringClass buff;
+					buff.Format("msg %s: Enemy chosen: %s", botName.Peek_Buffer(), Commands->Get_Preset_Name(chosenEnemy));
+					M_Console_Input(buff.Peek_Buffer());*/
+
+					Create_Timed_Objective(team, 30.0f, Commands->Get_Position(chosenEnemy), 1, enemyID, enemyID);
+
+					coopState = 6;
+					coopTarget = chosenEnemy;
+					Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+					return;
+				}
+			}
+
+			if (objectiveType != 1)
+				return;
+
+			// Nothing? Then look around for friendlies and cooperate with them
+			if (!M_Has_Timer(obj, this, TIMER_AI_COOP_PAUSE_INITIATION) && personality->Get_Random(BotPersonality::TEAMWORK_CHANCE))
+			{
+				// Find closest, sympathic ally
+				float min_dist_sq = FLT_MAX;
+				GameObject *chosenTarget = nullptr;
+				for (int i = 0; i < allyPlayersAround.Count(); i++)
+				{
+					auto ally = Commands->Find_Object(allyPlayersAround[i]);
+
+					// Also check if the bot cares about it
+					if (!ally || Commands->Get_Health(ally) <= 1.0f)
+						continue;
+
+					if (ally->As_VehicleGameObj() && ally->As_VehicleGameObj()->Get_Driver())
+						ally = ally->As_VehicleGameObj()->Get_Driver();
+
+					// Decreased chance of teamwork if ally is not the same gameobject type (soldier-soldier, vehicle-vehicle)
+					if ((!aiObj->As_VehicleGameObj() && Get_Vehicle(ally)) || (aiObj->As_VehicleGameObj() && !Get_Vehicle(ally)) && FreeRandom.Get_Float() < 0.5f)
+						continue;
+
+					// Get name of this guy
+					auto name = Get_Player_Name(ally);
+					// Find sympathy of this guy
+					auto sympathyData = playerRelationData.GetPlayerData(name);
+					if (!sympathyData)
+						continue;
+
+					// Update sympathy
+					if (sympathyData->sympathy < 1.0f)
+						sympathyData->sympathy += personality->Get_Random(BotPersonality::SYMPATHY_VARIABLE);
+
+					/*WideStringClass str;
+					str.Format(L"msg %S: Updated sympathy for %S: %.3f", botName.Peek_Buffer(), Get_Player_Name(ally).Peek_Buffer(), sympathyData->sympathy);
+					M_Console_Input(str);*/
+
+					if (FreeRandom.Get_Float() < sympathyData->sympathy)
+						continue;
+
+					float dist_sq = Vector3::Distance_Squared(Commands->Get_Position(aiObj), Commands->Get_Position(ally));
+					if (dist_sq < min_dist_sq)
+					{
+						chosenTarget = ally;
+						min_dist_sq = dist_sq;
+					}
+				}
+
+				if (chosenTarget)
+				{
+					StringClass chosenName = Get_Player_Name(chosenTarget);
+					// Who should follow who? Less sympathy: higher chance the bot asks the player to follow and not the other way around.
+					auto sympathyData = playerRelationData.GetPlayerData(chosenName);
+					if (FreeRandom.Get_Float() < sympathyData->sympathy) {
+						coopFriendPlayerID = Get_Player_ID(chosenTarget);
+
+						coopState = 3;
+						Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+						Play_Radio_Message(Radio_Message_Type::MOVE_OUT);
+					}
+					else
+					{
+						Commands->Start_Timer(obj, this, 60.0f, TIMER_AI_COOP_PAUSE_INITIATION);
+						if (personality->Get_Random(BotPersonality::CHAT_CHANCE))
+						{
+							Play_Radio_Message(Radio_Message_Type::FOLLOW_ME);
+						}
+						else if (personality->Get_Random(BotPersonality::CHAT_CHANCE))
+						{
+							DynamicVectorClass<StringClass> followMeMsgs;
+							followMeMsgs.Add("Let's go");
+							followMeMsgs.Add("Come on");
+							followMeMsgs.Add("Come with me");
+							followMeMsgs.Add("Follow me");
+							StringClass response = MS_AI_Global_Controller::GetInstance().Get_Chat_Response(followMeMsgs);
+							Send_Private_Message_String(response.Peek_Buffer(), chosenTarget);
+						}
+					}
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_REQUEST_PATROL_POINT)
+		{
+			auto patrolPointID = MS_AI_Global_Controller::GetInstance().Get_New_Patrol_Point_ID(team, param, sender->As_VehicleGameObj() ? true : false);
+			if (patrolPointID)
+				Commands->Send_Custom_Event(obj, sender, CUSTOM_AI_GO_TO_PATROL_POINT, patrolPointID, 0.0f);
+		}
+		else if (type == CUSTOM_AI_VEHICLE_AMMO_EMPTY)
+		{
+			auto botVeh = Commands->Find_Object(param);
+			if (!botVeh)
+				return;
+
+			auto markerScript = Find_Script_On_Object(botVeh, "MS_AI_Marker_Refiller_Vehicle");
+			if (!markerScript)
+				return;
+
+			auto presetName = ((MS_AI_Marker_Refiller_Vehicle *)markerScript)->Get_Zone_Preset_Name();
+			auto exitZonePresetName = ((MS_AI_Marker_Refiller_Vehicle *)markerScript)->Get_Exit_Zone_Preset_Name();
+
+			if (auto zoneObj = Find_Nearest_Preset(Commands->Get_Position(botVeh), presetName))
+			{
+				Commands->Destroy_Object(tempObjective);
+				auto refillObjective = Commands->Create_Object("Invisible_Object", Commands->Get_Position(zoneObj));
+				tempObjective = refillObjective;
+
+				// Create objective for the bot
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, tempObjective->Get_ID(), 5, 1000, 1000, 1000, 1000);
+				Commands->Attach_Script(refillObjective, "dp88_AI_Objective", params.Peek_Buffer());
+
+				Commands->Send_Custom_Event(obj, botVeh, CUSTOM_AI_GO_TO_RELOAD, tempObjective->Get_ID(), 0.0f);
+			}
+			else if (auto zoneObj2 = exitZonePresetName ? Find_Nearest_Preset(Commands->Get_Position(botVeh), exitZonePresetName) : nullptr)
+			{
+				Commands->Destroy_Object(tempObjective);
+				auto exitObjective = Commands->Create_Object("Invisible_Object", Commands->Get_Position(zoneObj2));
+				tempObjective = exitObjective;
+
+				// Create objective for the bot
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, tempObjective->Get_ID(), 5, 1000, 1000, 1000, 1000);
+				Commands->Attach_Script(exitObjective, "dp88_AI_Objective", params.Peek_Buffer());
+
+				Commands->Send_Custom_Event(obj, botVeh, CUSTOM_AI_GO_TO_EXIT_VEHICLE, tempObjective->Get_ID(), 0.0f);
+			}
+			// No zones present, leave the vehicle
+			else if (soldierObject)
+			{
+				VehicleGameObj *vObj = botVeh->As_VehicleGameObj();
+				Commands->Enable_Vehicle_Transitions(vObj, true);
+				Force_Occupants_Exit(vObj);
+
+				Commands->Send_Custom_Event(obj, botVeh, CUSTOM_AI_DEACTIVATE_AI, 0, 0.4f);
+			}
+		}
+		else if (type == CUSTOM_AI_REFILL_COMPLETE)
+		{
+			Commands->Destroy_Object(tempObjective);
+			tempObjective = nullptr;
+			coopState = 0;
+		}
+		else if (type == CUSTOM_AI_ARRIVED_TO_REFILL)
+		{
+			if (sender && sender->As_SoldierGameObj() && Commands->Get_Health(sender) >= 1.0f)
+			{
+				/*if (sender->As_SoldierGameObj()->Can_Refill())
+				{*/
+					Grant_Refill(sender);
+					isRefillCooldownInProgress = true;
+					Commands->Start_Timer(obj, this, 8.0f, TIMER_AI_REFILL_COOLDOWN);
+				//}
+				// CUSTOM_AI_REFILL_COMPLETE will never arrive, because we remove MS_AI_Behaviour here!
+				Commands->Destroy_Object(tempObjective);
+				tempObjective = nullptr;
+				coopState = 0;
+				// Spawn protection and waiting before purchasing items
+				Commands->Attach_Script(sender, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+				Remove_Script(sender, "MS_AI_Behaviour");
+				Remove_Script(sender, "MS_AI_Player_Controller_Spawned_Object");
+				Commands->Start_Timer(obj, this, 0.1f, TIMER_AI_OBJECT_ACTIVATE_COUNTDOWN);
+			}
+		}
+		else if (type == CUSTOM_AI_ARRIVED_TO_EXIT_VEHICLE)
+		{
+			if (soldierObject)
+			{
+				VehicleGameObj *vObj = sender->As_VehicleGameObj();
+				Commands->Enable_Vehicle_Transitions(vObj, true);
+				Force_Occupants_Exit(vObj);
+
+				Commands->Send_Custom_Event(obj, sender, CUSTOM_AI_DEACTIVATE_AI, 0, 0.4f);
+			}
+		}
+		else if (type == CUSTOM_AI_CHECK_REPAIR_NEEDED || type == CUSTOM_AI_CHECK_GO_HOME)
+		{
+			// The two things do the same, one only checks HP, other wants to refill if not far away
+			bool isRepairCheck = type == CUSTOM_AI_CHECK_REPAIR_NEEDED;
+
+			if (!sender || (isRepairCheck && isRefillCooldownInProgress))
+				return;
+
+			if (sender->As_SoldierGameObj())
+			{
+				auto point = MS_AI_Global_Controller::GetInstance().Get_Closest_Spawn_Point(sender, team);
+				if (point == Vector3())
+					return;
+
+				const float dist = Vector3::Distance(point, Commands->Get_Position(sender));
+				if (isRepairCheck && !personality->Wants_To_Repair(sender, dist))
+					return;
+
+				if (!isRepairCheck && objectiveType == dp88_AI_Objective::TYPE_OFFENSIVE && dist > 100)
+					return;
+
+				// Create objective, send bot here
+				Commands->Destroy_Object(tempObjective);
+				auto refillObjective = Commands->Create_Object("Invisible_Object", point);
+				tempObjective = refillObjective;
+
+				// Create objective for the bot
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d", team, tempObjective->Get_ID(), 1, 1000, 1000, 1000, 1000);
+				Commands->Attach_Script(refillObjective, "dp88_AI_Objective", params.Peek_Buffer());
+
+				Commands->Send_Custom_Event(obj, sender, isRepairCheck ? CUSTOM_AI_GO_TO_REFILL : CUSTOM_AI_GO_SWITCH_OBJECTIVE, tempObjective->Get_ID(), 0.0f);
+				coopState = 8;
+			}
+			// TODO: Objective changing not yet supported for vehicles
+			else if (isRepairCheck && sender->As_VehicleGameObj())
+			{
+				// No money
+				if (Get_Money() < Get_Team_Cost(Commands->Get_Preset_ID(sender), team) * 0.5f)
+					return;
+
+				SList<GameObject> list;
+				Find_All_Objects_With_Script_By_Distance("MS_AI_Marker_Vehicle_Repair_Zone", list, Commands->Get_Position(sender));
+				for (auto x = list.Head(); x != nullptr; x = x->Next())
+				{
+					GameObject* zoneObj = x->Data();
+					auto zoneScript = (MS_AI_Marker_Vehicle_Repair_Zone *)(Find_Script_On_Object(zoneObj, "MS_AI_Marker_Vehicle_Repair_Zone"));
+
+					// TODO: ignore occupied ones
+					// Ignore if enemy team
+					if (zoneScript->Get_Team() != team)
+						continue;
+
+					const float dist = Vector3::Distance(Commands->Get_Position(zoneObj), Commands->Get_Position(sender));
+
+					if (!personality->Wants_To_Repair(sender, dist))
+						return;
+
+					const int repairObjectiveID = zoneObj->Get_ID();
+
+					// Create objective for the bot
+					if (!Is_Script_Attached(zoneObj, "dp88_AI_Objective"))
+					{ 
+						StringClass params;
+						params.Format("%d,%d,%d,%d,%d,%d,%d", team, repairObjectiveID, 3, 1000, 1000, 1000, 1000);
+						Commands->Attach_Script(zoneObj, "dp88_AI_Objective", params.Peek_Buffer());
+					}
+
+					Commands->Send_Custom_Event(obj, sender, CUSTOM_AI_GO_TO_REFILL, repairObjectiveID, 0.0f);
+					coopState = 8;
+					break;
+				}
+			}
+		}
+		else if (type == CUSTOM_EVENT_OWNED_VEHICLE)
+		{
+			// Get the vehicle object
+			vehicleObject = Commands->Find_Object(param);
+			Commands->Start_Timer(obj, this, 2.0f, TIMER_AI_WAIT_FOR_VEHICLE_CONSTRUCT);
+		}
+		else if (type == CUSTOM_AI_ARRIVED_TO_VEHICLE)
+		{
+			if (!soldierObject || enteredVeh)
+				return;
+
+			// If vehicle reached and is still alive
+			if (param == 1 && vehicleObject)
+			{
+				VehicleGameObj *vehicle = vehicleObject->As_VehicleGameObj();
+				bool isSoldierAlreadyInVehicle = false;
+				// Check if already inside, e.g. remote controlled vehicles, cyborg reaper
+				if (vehicle && vehicle->Get_Occupant_Count() && Get_Vehicle_Driver(vehicle) == soldierObject)
+				{
+					isSoldierAlreadyInVehicle = true;
+					// Cancel any existing timeout since we successfully entered the vehicle
+					if (auto spawnedScript = Find_Script_On_Object(soldierObject, "MS_AI_Player_Controller_Spawned_Object"))
+					{
+						if (M_Has_Timer(soldierObject, spawnedScript, TIMER_AI_GO_TO_VEHICLE_TIMEOUT))
+							M_Stop_Timer2(soldierObject, spawnedScript, TIMER_AI_GO_TO_VEHICLE_TIMEOUT);
+					}
+				}
+				// If vehicle is not full
+				if ((vehicle && vehicle->Get_Occupant_Count() < vehicle->Get_Definition().Get_Seat_Count() &&
+					// Must also be either empty, or friendly
+					(vehicle->Get_Occupant_Count() == 0 || Get_Object_Type(vehicle) == team)/* &&
+					// Attached turret case (e.g. Battle Fortress): parent must be friendly
+					(!vehicle->Get_Host_Object() || Get_Object_Type(vehicle->Get_Host_Object()) == team)*/) || isSoldierAlreadyInVehicle)
+				{
+					// Don't take over vehicle with behaviour script
+					if (Is_Script_Attached(soldierObject, "MS_AI_Behaviour"))
+						Remove_Script(soldierObject, "MS_AI_Behaviour");
+
+					// It might have become unteamed, must be teamed for bot to function properly
+					Set_Object_Type(vehicle, team);
+					enteredVeh = true;
+
+					// Activate AI on vehicle if no drivers were previously in
+					if (!vehicle->Get_Driver() || (isSoldierAlreadyInVehicle && !(Is_Script_Attached(vehicle, "MS_AI_Behaviour"))))
+						Activate_AI_Behaviour(obj, vehicle);
+
+					// Attach script ONCE to vehicle object so it can send custom to passengers
+					if (!Is_Script_Attached(vehicle, "MS_AI_Player_Controller_Spawned_Object"))
+					{
+						StringClass params;
+						params.Format("%d,%d", Commands->Get_ID(obj), 1);
+						Commands->Attach_Script(vehicle, "MS_AI_Player_Controller_Spawned_Object", params.Peek_Buffer());
+					}
+
+					Commands->Action_Reset(soldierObject, 999.0f);
+
+					// Put soldier in
+					if (!isSoldierAlreadyInVehicle)
+						Commands->Send_Custom_Event(obj, vehicle, CUSTOM_AI_VEHICLE_REQUEST_ENTER, soldierObject->Get_ID(), 0.0f);
+
+					return;
+				}
+				// Else do not try re-running for it
+				//M_Console_Input("msg Vehicle is full!");
+			}
+			// Otherwise param is 0 OR vehicle could not be entered, attach behaviour is needed or stop the overriding objective
+			vehicleObject = nullptr;
+
+			if (!Is_Script_Attached(soldierObject, "MS_AI_Behaviour"))
+				Activate_AI_Behaviour(obj, soldierObject);
+			else 
+			{
+				Commands->Send_Custom_Event(obj, soldierObject, CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE, 0, 0.0f);
+				Search_For_Vehicle(obj, 30);
+			}
+		}
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == TIMER_AI_OBJECT_SPAWN_COUNTDOWN)
+		{
+			Spawn_Object(obj);
+		}
+		else if (number == TIMER_AI_OBJECT_SPAWN_GHOSTING_COUNTDOWN)
+		{
+			if (soldierObject)
+			{
+				soldierObject->As_PhysicalGameObj()->Peek_Physical_Object()->Set_Collision_Group(Collision_Group_Type::SOLDIER_GHOST_COLLISION_GROUP);
+			}
+		}
+		else if (number == TIMER_AI_OBJECT_ACTIVATE_COUNTDOWN)
+		{
+			if (soldierObject)
+			{
+				Remove_Script(soldierObject, "MS_Switch_Skin_And_Shield_Timer");
+				Purchase_Presets(obj);
+			}
+			else
+			{
+				// This will happen if the soldier fell into the abyss at 0,0,0 or got killed in the 0.1 time.
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_SOLDIER_DIED, 0, 0.0f);
+			}
+		}
+		else if (number == TIMER_AI_WAIT_FOR_VEHICLE_CONSTRUCT)
+		{
+			auto vehicle = vehicleObject.Get_Ptr();
+			// Always check if it still exists
+			if (!vehicle || vehicleConstructWaitCount >= 20)
+			{
+				vehicleConstructWaitCount = 0;
+				Commands->Send_Custom_Event(obj, obj, CUSTOM_AI_ARRIVED_TO_VEHICLE, 0, 0.0f);
+				return;
+			}
+
+			Vector3 runLocation = Vector3();
+			bool isWithinPathfindGrid = Get_Closest_Pathfind_Spot_Size(Commands->Get_Position(vehicle), 1.0f, &runLocation, 2.0f);
+			if (isWithinPathfindGrid)
+			{
+				// If the created vehicle dies before the timer runs out, the go to action will be automatically complete.
+				vehicleConstructWaitCount = 0;
+				Go_To_Vehicle(obj, vehicleObject);
+			}
+			else
+			{
+				// Periodically check if it is reachable
+				vehicleConstructWaitCount++;
+				Commands->Start_Timer(obj, this, 2.0f, TIMER_AI_WAIT_FOR_VEHICLE_CONSTRUCT);
+			}
+		}
+		else if (number == TIMER_AI_REFILL_COOLDOWN)
+		{
+			isRefillCooldownInProgress = false;
+		}
+		else if (number == TIMER_AI_COOP_RETRY_REQUEST)
+		{
+			// This should deal with cases where the joiner got kicked from the grouprush before this request applied
+			if (!coopState)
+				return;
+
+			GameObject* soldier = soldierObject;
+			GameObject* vehicle = vehicleObject;
+			bool soldier_has_ai_behavior = soldier && Is_Script_Attached(soldier, "MS_AI_Behaviour");
+			bool vehicle_has_ai_behavior = vehicle && Is_Script_Attached(vehicle, "MS_AI_Behaviour");
+
+			if (soldier_has_ai_behavior || (vehicle_has_ai_behavior && enteredVeh))
+			{
+				coopObject = soldier_has_ai_behavior ? soldier : vehicle;
+
+				// Send object to first rally point
+				if (coopState == 1)
+				{
+					// Soldier wanting to group rush, but haven't entered vehicle yet
+					if (vehicle && !enteredVeh)
+					{
+						Commands->Start_Timer(obj, this, 1.0f, TIMER_AI_COOP_RETRY_REQUEST);
+					}
+					else
+					{
+						Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_COOP_NEXT_RALLYPOINT, MS_AI_Global_Controller::GetInstance().Get_Rally_Point_ID(team), 0.0);
+						if (MS_AI_Global_Controller::GetInstance().debugGrouprushMessagesEnabled)
+						{
+							Vector3 rgb = Get_Color_For_Team(team);
+							StringClass msg;
+							const char *unitName = Get_Translated_Definition_Name(Commands->Get_Preset_Name(coopObject));
+							msg.Format("%s: Joining group rush! (%s)", botName.Peek_Buffer(), unitName);
+							Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+							delete[] unitName;
+						}
+					}
+				}
+				else if (coopState == 4)
+				{
+					int objectiveObjID = MS_AI_Global_Controller::GetInstance().Get_Temporary_Objective_ID(team);
+					Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_COOP_PLAYER_DEFEND_BASE, objectiveObjID, 0.0);
+				}
+				else if (coopState == 2 || coopState == 3)
+				{
+					auto playerObj = Get_GameObj(coopFriendPlayerID);
+					int playerObjID = playerObj->Get_ID();
+
+					if (!(Find_Script_On_Object(playerObj, "dp88_AI_Objective")))
+					{
+						int range = Get_Vehicle(playerObj) ? 20 : 10;
+						StringClass params;
+						params.Format("%d,%d,%d,%d,%d,%d,%d", team, playerObjID, range, 1000, 1000, 1000, 1000);
+						Commands->Attach_Script(playerObj, "dp88_AI_Objective", params.Peek_Buffer());
+					}
+
+					if (personality->Get_Random(BotPersonality::CHAT_CHANCE))
+					{
+						char* text = coopState == 2 ? "I'm following you" : "I'm covering you";
+						StringClass message;
+						message.Format("%s, %s!", Get_Player_Name_By_ID(coopFriendPlayerID), text);
+						Send_Private_Message_String(message.Peek_Buffer(), playerObj);
+					}
+
+					long custom = coopState == 2 ? CUSTOM_AI_COOP_PLAYER_FOLLOW : CUSTOM_AI_COOP_PLAYER_FOLLOW_COVER;
+
+					Commands->Send_Custom_Event(obj, coopObject, custom, playerObjID, 0.0);
+				}
+				else if (coopState == 5 && coopTarget)
+				{
+					Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_ORDER_MOVE, coopTarget->Get_ID(), 0.0);
+				}
+				else if (coopState == 6 && coopTarget)
+				{
+					Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_ORDER_ATTACK, coopTarget->Get_ID(), 0.0);
+				}
+				else if (coopState == 7 && coopTarget)
+				{
+					Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_ORDER_DEFEND, coopTarget->Get_ID(), 0.0);
+				}
+			}
+			else
+				Commands->Start_Timer(obj, this, 1.0f, TIMER_AI_COOP_RETRY_REQUEST);
+		}
+	}
+
+	void Destroyed(GameObject *obj)
+	{
+		Controller_Gone(obj);
+		Destroy_Script();
+	}
+
+	void Detach(GameObject *obj)
+	{
+		if (Exe != EXE_LEVELEDIT)
+		{
+			Controller_Gone(obj);
+		}
+		ScriptImpClass::Detach(obj);
+	}
+
+	void Controller_Gone(GameObject *obj)
+	{
+		if (isDeleted)
+			return;
+
+		isDeleted = true;
+
+		StringClass buff;
+		buff.Format("%s %s", botName.Peek_Buffer(), Get_Translated_String(Get_String_ID_By_Desc("IDS_MP_PLAYER_LEFT_GAME")));
+		Send_Message(MS_RGB_JOINLEFT_MSG, buff.Peek_Buffer());
+
+		if (strcmp(botName, DEFAULT_BOT_NAME) != 0)
+			MS_AI_Global_Controller::GetInstance().Give_Back_Bot_Name(botName);
+
+		// Let Global Controller know we are gone
+		MS_AI_Global_Controller::GetInstance().botControllersTeam[team].DeleteObj(obj);
+		MS_AI_Global_Controller::GetInstance().Unregister_From_GroupRush(team, obj->Get_ID());
+		if (auto teamCtrlScript = Get_Team_Controller())
+		{
+			teamCtrlScript->Report_Bot_Died(obj);
+		}
+
+		auto soldier = soldierObject.Get_Ptr();
+		if (!soldier)
+			return;
+
+		bool shouldDestroyVehicle = false;
+		if (auto vehicle = vehicleObject.Get_Ptr())
+		{
+			if (vehicle->As_VehicleGameObj()->Get_Driver() == soldier)
+			{
+				// Only destroy the bot vehicle if this bot soldier was its only occupant
+				if (vehicle->As_VehicleGameObj()->Get_Occupant_Count() == 1 && !vehicle->As_VehicleGameObj()->Is_Attached_To_An_Object())
+					shouldDestroyVehicle = true;
+				else
+				{
+					Commands->Enable_Vehicle_Transitions(vehicle, true);
+					Remove_Script(vehicle, "MS_AI_Behaviour");
+				}
+			}
+		}
+		Commands->Destroy_Object(soldier);
+		if (shouldDestroyVehicle)
+		{
+			if (auto vehicle = vehicleObject.Get_Ptr())
+			{
+				Commands->Enable_Vehicle_Transitions(vehicle, false);
+				Commands->Attach_Script(vehicle, "JFW_Destroy_Self_Timer", "1.0,0");
+			}
+		}
+		Commands->Destroy_Object(tempObjective);
+	}
+
+	void Spawn_Object(GameObject *obj)
+	{
+		// Spawn first as the particular team's spawn character
+		GameObject *soldier = ObjectLibraryManager::Create_Object(teamSpawnPreset);
+
+		/*
+		Where to spawn:
+		if USEPLAYERSPAWNERS is set, use that.
+		else, check objects in a similar way how Get_Multiplayer_Spawn_Location does it
+		if cannot teleport to any, use 0,0,0
+		*/
+		if (strcmp(spawnAtPresetName, "USEPLAYERSPAWNERS") == 0)
+		{
+			//M_Console_Input("msg USEPLAYERSPAWNERS true");
+			auto soldierCasted = soldier->As_SoldierGameObj();
+			Matrix3D matrix;
+			Get_Multiplayer_Spawn_Location(team, soldierCasted, matrix);
+			soldierCasted->Set_Transform(matrix);
+		}
+		else
+		{
+			//M_Console_Input("msg Original spawning.");
+
+			// Collect all positions and try them all
+			DynamicVectorClass<GameObject *> availableList;
+			SimpleDynVecClass<GameObject *> fullList = *Get_All_Objects_By_Preset(2, spawnAtPresetName);
+
+			// Check good positions
+			for (int i = 0; i < fullList.Count(); i++)
+			{
+				Vector3 createPos = Commands->Get_Position(fullList[i]);
+				MoveablePhysClass* phys = soldier->As_PhysicalGameObj() ? soldier->As_PhysicalGameObj()->Peek_Physical_Object()->As_MoveablePhysClass() : nullptr;
+				if (phys && phys->Can_Teleport(Matrix3D(createPos)))
+					availableList.Add(fullList[i]);
+			}
+
+			if (availableList.Count() > 0)
+			{
+				GameObject *chosenObj = availableList[FreeRandom.Get_Int(availableList.Count())];
+				Commands->Set_Position(soldier, Commands->Get_Position(chosenObj));
+				Commands->Set_Facing(soldier, Commands->Get_Facing(chosenObj));
+			}
+		}
+
+		soldier->As_SoldierGameObj()->Set_Bot_Tag(botName);
+		soldier->As_SoldierGameObj()->Set_Player_Type(team);
+		soldier->As_SoldierGameObj()->Start_Observers();
+		Remove_Stock_AI_Behaviour(soldier);
+		soldierObject = soldier;
+
+
+		if (objectiveType != 3 && MS_AI_Global_Controller::GetInstance().spawnPositions[team].ID(Commands->Get_Position(soldier)) == -1)
+			MS_AI_Global_Controller::GetInstance().spawnPositions[team].Add(Commands->Get_Position(soldier));
+
+		// Spawn protection and waiting before purchasing items
+		Commands->Attach_Script(soldier, "MS_Switch_Skin_And_Shield_Timer", "Blamo,Blamo,0");
+		// Randomize waiting time a bit
+		const float time = personality->Get_Random(BotPersonality::SPAWN_TIME);
+		Commands->Start_Timer(obj, this, 0.1f, TIMER_AI_OBJECT_SPAWN_GHOSTING_COUNTDOWN);
+		Commands->Start_Timer(obj, this, time, TIMER_AI_OBJECT_ACTIVATE_COUNTDOWN);
+
+		expenses = 0;
+	}
+
+	void Purchase_Presets(GameObject *obj)
+	{
+		const int previousObjType = objectiveType;
+		// Check what objective type is needed if the team controller can tell it
+		if (auto teamCtrlScript = Get_Team_Controller())
+		{
+			objectiveType = teamCtrlScript->Signup_For_Next_Objective_Type(obj);
+			soldierPresetList = teamCtrlScript->Get_Infantry_List();
+		}
+
+		// Non-offensive: clear earlier vehicle stuff
+		if (objectiveType != dp88_AI_Objective::TYPE_OFFENSIVE)
+			vehicleObject = nullptr;
+
+		// TODO later: Current inf list contains current character or not?
+		bool mustChangeCharacter = objectiveType != previousObjType; 
+
+		int groupRushOpen = MS_AI_Global_Controller::GetInstance().Is_Rally_Point_Registration_Open(team);
+		// Try group rush
+		if (!coopState && groupRushOpen && objectiveType == dp88_AI_Objective::TYPE_OFFENSIVE)
+		{
+			MS_AI_Global_Controller::GetInstance().Register_For_GroupRush(team, obj->Get_ID());
+			coopState = 1;
+			Commands->Start_Timer(obj, this, 1.0f, TIMER_AI_COOP_RETRY_REQUEST);
+		}
+
+		const char* chosenPresetName;
+		// Let's look at the available infantry first
+		// Engineering AI always try to buy from their list!
+		bool buyInf = mustChangeCharacter || objectiveType == dp88_AI_Objective::TYPE_ENGINEERING || personality->Get_Random(BotPersonality::BUYINF_CHANCE) || money > 10000;
+
+		if (buyInf)
+		{
+			chosenPresetName = Choose_Unit(soldierPresetList, true);
+			if (chosenPresetName)
+			{
+				Change_Character(soldierObject, chosenPresetName);
+				if (MS_AI_Global_Controller::GetInstance().debugBuyMessagesEnabled)
+				{
+					int cost = Get_Team_Cost(chosenPresetName, team);
+					Vector3 rgb = Get_Color_For_Team(team);
+					StringClass msg;
+					const char *unitName = Get_Translated_Definition_Name(chosenPresetName);
+					msg.Format("%s: Purchasing: %s (%d)", botName.Peek_Buffer(), unitName, cost);
+					Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+					delete[] unitName;
+				}
+			}
+			else if (mustChangeCharacter)
+			{
+				// If buying failed, switch to spawn character
+				Change_Character(soldierObject, teamSpawnPreset);
+			}
+			if (chosenPresetName || mustChangeCharacter)
+				soldierObject->As_PhysicalGameObj()->Peek_Physical_Object()->Set_Collision_Group(Collision_Group_Type::SOLDIER_GHOST_COLLISION_GROUP);
+		}
+
+		// Attach script to soldier object so it can listen to the vehicle owned custom and everything
+		StringClass params;
+		params.Format("%d,%d", Commands->Get_ID(obj), 0);
+		Commands->Attach_Script(soldierObject, "MS_AI_Player_Controller_Spawned_Object", params.Peek_Buffer());
+
+		// If bot purchased a vehicle but was killed before reaching it, retry running to it
+		if (vehicleObject)
+		{
+			if (GameObject* veh = vehicleObject)
+			{
+				// If vehicle is still alive and noone stole it
+				VehicleGameObj* vObject = veh->As_VehicleGameObj();
+				if (vObject && vObject->Get_Occupant_Count() < vObject->Get_Definition().Get_Seat_Count() && !Is_Script_Attached(vObject, "MS_AI_Player_Controller_Spawned_Object"))
+				{
+					//M_Console_Input("msg Retry running for vehicle...");
+					Go_To_Vehicle(obj, vehicleObject);
+					return;
+				}
+			}
+			vehicleObject = nullptr;
+		}
+
+		if (!coopState && Search_For_Vehicle(obj, 100))
+			return;
+
+		// Try to buy a vehicle or not? Engineering AI must not buy
+		// TODO: NO VEHICLE FOR DEFENDERS UNTIL THEY CAN SWITCH OBJECTIVE AND ACTUALLY GO AFTER ATTACKERS PROPERLY
+		bool buyVeh = objectiveType == dp88_AI_Objective::TYPE_OFFENSIVE && (personality->Get_Random(BotPersonality::BUYVEH_CHANCE) || money > 10000);
+		// No vehicle if chosen group rush doesn't allow it
+		if (coopState == 1 && groupRushOpen == 1)
+		{
+			buyVeh = false;
+		}
+
+		if (buyVeh)
+		{
+			chosenPresetName = Choose_Unit(vehPresetList, false);
+			if (chosenPresetName)
+			{
+				if (Create_Vehicle(chosenPresetName, 0.0, soldierObject, team/*, false*/))
+				{
+					if (MS_AI_Global_Controller::GetInstance().debugBuyMessagesEnabled)
+					{
+						int cost = Get_Team_Cost(chosenPresetName, team);
+						Vector3 rgb = Get_Color_For_Team(team);
+						StringClass msg;
+						const char *unitName = Get_Translated_Definition_Name(chosenPresetName);
+						msg.Format("%s: Purchasing: %s (%d)", botName.Peek_Buffer(), unitName, cost);
+						Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+						delete[] unitName;
+					}
+					// Nothing else to do, created soldier is waiting for custom from created vehicle
+					return;
+				}
+				else
+				{
+					// Refund, choose unit deducted cash but Create_Vehicle can be false!
+					money += Get_Team_Cost(chosenPresetName, team);
+				}
+			}
+		}
+
+		// Activate AI on the new character (if no vehicles considered) or the spawn char (if no character was bought)
+		if (soldierObject)
+			Activate_AI_Behaviour(obj, soldierObject);
+	}
+
+	const char* Choose_Unit(const char *checkPresetList, bool isInfantry)
+	{
+		if (!checkPresetList)
+			return nullptr;
+
+		DynamicVectorClass<const char*> namedList;
+
+		if (strcmp(checkPresetList, "AUTOENGINEER") == 0)
+		{
+			namedList = MS_AI_Global_Controller::GetInstance().engineerUnitList;
+		}
+		else
+		{
+			DynamicVectorClass<int> fullList;
+
+			if (strcmp(checkPresetList, "USEPLAYERPURCHASELISTS") == 0)
+			{
+				if (isInfantry)
+				{
+					fullList = Combine_Lists(fullList, Get_Enlisted_Purchase_Items(team));
+					fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_CLASSES));
+					if (Is_Extras_Enabled() || Is_Sidebar_Enabled())
+						fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_SECRET_CLASSES));
+				}
+				else
+				{
+					fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_VEHICLES));
+					if (Is_Extras_Enabled() || Is_Sidebar_Enabled())
+						fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_SECRET_VEHICLES));
+
+					fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_AIR));
+					fullList = Combine_Lists(fullList, Get_Purchase_Items(team, PurchaseSettingsDefClass::TYPE_NAVAL));
+				}
+			}
+			else
+			{
+				TwiddlerClass* twiddler = (TwiddlerClass*)DefinitionMgrClass::Find_Named_Definition(checkPresetList, false);
+				if (twiddler)
+					fullList = twiddler->Get_Definition_List();
+			}
+			for (int i = 0; i < fullList.Count(); i++)
+			{
+				namedList.Add(DefinitionMgrClass::Find_Definition(fullList[i], false)->Get_Name());
+			}
+		}
+
+		DynamicVectorClass<const char*> finalList;
+
+		for (int i = 0; i < namedList.Count(); i++)
+		{
+			const char* presetName = namedList[i];
+
+			// Don't "rebuy" the spawning character
+			if (isInfantry && strcmp(presetName, teamSpawnPreset) == 0)
+				continue;
+
+			bool available = isInfantry ? Is_Infantry_Purchaseable(presetName, team) : Is_Vehicle_Purchaseable(presetName, team);
+
+			if (Get_Team_Cost(presetName, team) <= money && available)
+				finalList.Add(presetName);
+		}
+
+		// Get one out of the list if possible
+		if (finalList.Count() > 0)
+		{
+			const char* chosenPresetName = finalList[FreeRandom.Get_Int(finalList.Count())];
+			int cost = Get_Team_Cost(chosenPresetName, team);
+			money -= cost;
+
+			// Needed if bot kills itself due to being stuck
+			if (isInfantry)
+				expenses += cost;
+
+			return chosenPresetName;
+		}
+		return nullptr;
+	}
+
+	void Activate_AI_Behaviour(GameObject *obj, GameObject *activateOnObj)
+	{
+		// Safety check
+		if (!activateOnObj)
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Trying to activate AI on a nullpointer object (%hs). Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), Commands->Get_Preset_Name(activateOnObj));
+			M_Console_Output(msg.Peek_Buffer());
+			Commands->Destroy_Object(activateOnObj);
+			Destroy_Script();
+			return;
+		}
+
+		int isVehicle = activateOnObj->As_VehicleGameObj() ? 1 : 0;
+
+		// Attach script to spawned object so the controller and child can communicate
+		// Make sure script is only attached once
+		if (!Is_Script_Attached(activateOnObj, "MS_AI_Player_Controller_Spawned_Object"))
+		{
+			StringClass params;
+			params.Format("%d,%d", Commands->Get_ID(obj), isVehicle);
+			Commands->Attach_Script(activateOnObj, "MS_AI_Player_Controller_Spawned_Object", params.Peek_Buffer());
+		}
+
+		// Get AI Preferences
+		StringClass behaviourScriptParams;
+		auto preferencesScript = (MS_AI_Preferences *)(Find_Script_On_Object(activateOnObj, "MS_AI_Preferences"));
+		if (!preferencesScript)
+		{
+			int maxAttackRange = 0;
+			int preferredAttackRange = 0;
+			if (activateOnObj->As_PhysicalGameObj() && activateOnObj->As_PhysicalGameObj()->As_ArmedGameObj())
+			{
+				auto armedObj = activateOnObj->As_PhysicalGameObj()->As_ArmedGameObj();
+				if (auto weapon = armedObj->Get_Weapon())
+				{
+					maxAttackRange = (int)(weapon->PrimaryAmmoDefinition ? weapon->PrimaryAmmoDefinition->Range() : maxAttackRange);
+					preferredAttackRange = (int)(weapon->PrimaryAmmoDefinition ? weapon->PrimaryAmmoDefinition->EffectiveRange(): preferredAttackRange);
+					// Make sure it's not zero or bigger than max 
+					if (preferredAttackRange == 0 || preferredAttackRange > maxAttackRange)
+						preferredAttackRange = maxAttackRange;
+				}
+			}
+
+			// Get default parameters
+			DynamicVectorClass<ScriptParameter> parameters;
+			Get_Script_Parameters("MS_AI_Preferences", parameters);
+			for (int i = 0; i < parameters.Count(); i++)
+			{
+				auto parameter = parameters[i];
+				// Priority_Infantry is the first param
+				if (i == 0)
+				{
+					// Use negative value for engineering units, they should never actively engage enemies, but run to repair instead
+					if (objectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+						behaviourScriptParams.Format("%.2f", -0.10f);
+					else
+						behaviourScriptParams.Format("%s", parameter.value.Peek_Buffer());
+				}
+				// Use the equipped weapon's ranges
+				else if (strcmp(parameter.name, "Max_Attack_Range") == 0)
+				{
+					if (maxAttackRange)
+						behaviourScriptParams.Format("%s,%d", behaviourScriptParams.Peek_Buffer(), maxAttackRange);
+					else
+						behaviourScriptParams.Format("%s,%s", behaviourScriptParams.Peek_Buffer(), parameter.value.Peek_Buffer());
+				}
+				else if (strcmp(parameter.name, "Preferred_Attack_Range") == 0)
+				{
+					if (preferredAttackRange)
+						behaviourScriptParams.Format("%s,%d", behaviourScriptParams.Peek_Buffer(), preferredAttackRange);
+					else
+						behaviourScriptParams.Format("%s,%s", behaviourScriptParams.Peek_Buffer(), parameter.value.Peek_Buffer());
+				}
+				// Use 0 values for engineering units, they should never actively engage enemies, but run to repair instead
+				else if ((strcmp(parameter.name, "Priority_Light_Vehicle") == 0 || strcmp(parameter.name, "Priority_Heavy_Vehicle") == 0 || strcmp(parameter.name, "Priority_VTOL") == 0) && objectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+				{
+					behaviourScriptParams.Format("%s,%d", behaviourScriptParams.Peek_Buffer(), 0);
+				}
+				else if (strcmp(parameter.name, "Priority_Building") == 0 && activateOnObj->As_SoldierGameObj())
+				{
+					behaviourScriptParams.Format("%s,%.2f", behaviourScriptParams.Peek_Buffer(), -0.01f);
+				}
+				else
+					behaviourScriptParams.Format("%s,%s", behaviourScriptParams.Peek_Buffer(), parameter.value.Peek_Buffer());
+			}
+		}
+		else
+		{
+			char str[256];
+			preferencesScript->Get_Parameters_String(str, 256);
+			behaviourScriptParams.Format("%s", str);
+		}
+
+		behaviourScriptParams.Format("%s,%d", behaviourScriptParams.Peek_Buffer(), objectiveType);
+		Commands->Attach_Script(activateOnObj, "MS_AI_Behaviour", behaviourScriptParams.Peek_Buffer());
+
+		auto behaviourScript = (MS_AI_Behaviour *)(Find_Script_On_Object(activateOnObj, "MS_AI_Behaviour"));
+		if (behaviourScript)
+			behaviourScript->Set_Personality(personality);
+	}
+
+	bool Search_For_Vehicle(GameObject *obj, float distanceLimit)
+	{
+		// Soldier not found or soldier is in a vehicle: don't search
+		if (!soldierObject || (vehicleObject && enteredVeh))
+			return false;
+
+		// Only offensive AI
+		if (objectiveType == 1)
+		{
+			// Search for transports (units with more available seats than occupants) and go for one
+			SList<GameObject> list;
+			Find_All_Vehicles_By_Distance(list, Commands->Get_Position(soldierObject));
+			
+			for (auto x = list.Head(); x != nullptr; x = x->Next())
+			{
+				GameObject* object = x->Data();
+				// Check for being a vehicle
+				if (!object->As_VehicleGameObj())
+					continue;
+
+				VehicleGameObj *vehicle = object->As_VehicleGameObj();
+				// Skip vehicles that have no driver but have owner (e.g. freshly bought vehicles)
+				if ((vehicle->Get_Occupant_Count() == 0 && vehicle->Is_Locked()) ||
+					// Skip if this is the banned vehicle
+					vehicle == bannedVehicle ||
+					// Skip enemy vehicles with players inside
+					(vehicle->Get_Occupant_Count() > 0 && Get_Object_Type(object) != team) ||
+					// Attached turret case (e.g. Battle Fortress): parent must be friendly
+					//(vehicle->Get_Host_Object() && Get_Object_Type(vehicle->Get_Host_Object()) != team) ||
+					// 33% chance of ignoring ownerless vehicles with max 1 seat
+					//(vehicle->Get_Definition().Get_Seat_Count() <= 1 && !vehicle->Is_Locked() && FreeRandom.Get_Float() < 0.33f) ||
+					!vehicle->Get_Definition().Get_Seat_Count() ||
+					!vehicle->Get_Transitions_Enabled() ||
+					// Don't enter turret type vehicles
+					vehicle->Is_Turret() ||
+					// Skip if full
+					vehicle->Get_Occupant_Count() >= vehicle->Get_Definition().Get_Seat_Count() ||
+					// Skip if occupied and isn't a transport vehicle
+					(vehicle->Get_Occupant_Count() >= 1 && vehicle->Get_Definition().Get_Seat_Count() < MS_AI_Global_Controller::GetInstance().transportVehicleSeatCount) && !Is_Script_Attached(vehicle, "MS_AI_Marker_Transport_Vehicle_Override"))
+					continue;
+
+				// If too far away, don't bother
+				if (Vector3::Distance_Squared(Commands->Get_Position(soldierObject), Commands->Get_Position(vehicle)) > distanceLimit * distanceLimit)
+					continue;
+
+				// Skip vehicles marked to be ignored
+				if (auto ignoreScript = (MS_AI_Marker_Vehicle_Search_Ignore*)(Find_Script_On_Object(vehicle, "MS_AI_Marker_Vehicle_Search_Ignore")))
+				{
+					int ignorerTeam = ignoreScript->Get_Ignorer_Team();
+					if (ignorerTeam == 2 || ignorerTeam == team)
+						continue;
+				}
+
+				// 33% chance of ignoring anyway
+				/*if (FreeRandom.Get_Float() < 0.33f)
+				continue;*/
+
+				if (MS_AI_Global_Controller::GetInstance().debugAcquireVehicleMessagesEnabled)
+				{
+					Vector3 rgb = Get_Color_For_Team(team);
+					StringClass msg;
+					const char *unitName = Get_Translated_Definition_Name(Commands->Get_Preset_Name(vehicle));
+					msg.Format("%s: Running for %s's vehicle (%s)!", botName.Peek_Buffer(), Get_Player_Name(vehicle->Get_Driver()), unitName);
+					Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+					if (strcmp(unitName, "ERROR: String Not Found") == 0)
+					{
+						msg.Format("%s: Selected vehicle (%s) is probably wrong, has no string!", botName.Peek_Buffer(), Commands->Get_Preset_Name(vehicle));
+						Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+					}
+					delete[] unitName;
+				}
+
+				if (Go_To_Vehicle(obj, vehicle))
+				{
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	bool Go_To_Vehicle(GameObject *controllerObj, GameObject* vehicle)
+	{
+		// Soldier is alive and vehicle exists
+		if (soldierObject && vehicle)
+		{
+			if (!(Is_Script_Attached(vehicle, "dp88_AI_Objective")))
+			{
+				StringClass params;
+				params.Format("%d,%d,%d,%d,%d,%d,%d", 2, vehicle->Get_ID(), 1, 1000, 1000, 1000, 1000);
+				Commands->Attach_Script(vehicle, "dp88_AI_Objective", params.Peek_Buffer());
+			}
+			if (!(Is_Script_Attached(soldierObject, "MS_AI_Behaviour")))
+			{
+				Activate_AI_Behaviour(controllerObj, soldierObject);
+			}
+			else if (!Is_Script_Attached(soldierObject, "MS_AI_Player_Controller_Spawned_Object"))
+			{
+				StringClass params;
+				params.Format("%d,%d", Commands->Get_ID(controllerObj), false);
+				Commands->Attach_Script(soldierObject, "MS_AI_Player_Controller_Spawned_Object", params.Peek_Buffer());
+			}
+			vehicleObject = vehicle;
+			Commands->Send_Custom_Event(controllerObj, soldierObject, CUSTOM_AI_GO_TO_VEHICLE, vehicleObject->Get_ID(), 1.5f);
+			return true;
+		}
+		return false;
+	}
+
+	void Play_Radio_Message(int stringID)
+	{
+		// Send_Client_Announcement is not working properly for non-players and cannot be modified in 4.x, reimplementing here.
+		//Send_Client_Announcement(team, soldierObject->Get_ID(), Get_Radio_Command_String(stringID), ANNOUNCE_TEAM, stringID, true, true);
+
+		auto obj = soldierObject.Get_Ptr();
+
+		if (!obj)
+			return;
+
+		if (auto soldier = obj->As_SoldierGameObj())
+			Set_Emot_Icon(soldier, stringID);
+
+		if (TDBObjClass *tdbobj = TranslateDBClass::Find_Object(Get_Radio_Command_String(stringID)))
+		{
+			const char *unit = Get_Vehicle(obj) ? Get_Translated_Definition_Name(Commands->Get_Preset_Name(Get_Vehicle(obj))) : Get_Translated_Definition_Name(Commands->Get_Preset_Name(obj));
+			Vector3 rgb = Get_Color_For_Team(team);
+			StringClass msg;
+			msg.Format("%s: [%s] %S", botName.Peek_Buffer(), unit, tdbobj->Get_String());
+			Send_Message_Team(team, (int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+			delete[] unit;
+
+			int soundID = tdbobj->Get_Sound_ID();
+			if (auto soundDef = Find_Definition(soundID))
+			{
+				Create_2D_Sound_Team(soundDef->Get_Name(), team);
+			}
+		}
+	}
+
+	void Send_Private_Message_String(char *msg, GameObject *recipient)
+	{
+		unsigned int red, green, blue;
+		Get_Private_Message_Color(&red, &green, &blue);
+
+		StringClass message;
+		message.Format("%s: %s", botName.Peek_Buffer(), msg);
+		Send_Message_Player(recipient, red, green, blue, message.Peek_Buffer());
+		Create_2D_Sound_Player(recipient, "Private_Message");
+	}
+
+	void Send_Public_Message_String(char *msg)
+	{
+		unsigned int red, green, blue;
+		Get_Public_Message_Color(&red, &green, &blue);
+
+		StringClass message;
+		message.Format("%s: %s", botName.Peek_Buffer(), msg);
+		Send_Message(red, green, blue, message.Peek_Buffer());
+		Commands->Create_2D_Sound("Public_Message");
+	}
+
+	MS_AI_Team_Controller *Get_Team_Controller()
+	{
+		if (auto teamCtrlObj = MS_AI_Global_Controller::GetInstance().teamControllers[team])
+		{
+			if (auto teamCtrlScript = (MS_AI_Team_Controller *)(Find_Script_On_Object(teamCtrlObj, "MS_AI_Team_Controller")))
+			{
+				return teamCtrlScript;
+			}
+		}
+		return nullptr;
+	}
+	
+
+public:
+	void Increment_Money(float amount)
+	{
+		money += amount;
+	}
+
+	float Get_Money()
+	{
+		return money;
+	}
+
+	int Get_Bot_ID()
+	{
+		return soldierObject ? soldierObject->Get_ID() : 0;
+	}
+
+	const StringClass& Get_Bot_Name()
+	{
+		return botName;
+	}
+
+	int Get_Coop_State()
+	{
+		return coopState;
+	}
+
+	void Receive_Radio_Message(GameObject *sender, int messageNumber, int orderType = 0, int orderObjID = 0)
+	{
+		if (isProcessingRadioMessage)
+			return;
+
+		isProcessingRadioMessage = true;
+
+		// Save variables, will use them again when timer ran out
+		radioMessageID = messageNumber;
+		taskType = orderType;
+		taskObjID = orderObjID;
+		const float delay = personality->Get_Random(BotPersonality::RADIO_TIME);
+		Commands->Send_Custom_Event(sender, Owner(), CUSTOM_AI_COOP_PLAYER_RADIO_MESSAGE, 0, delay);
+	}
+
+	void React_To_Radio_Message(GameObject *sender, int messageNumber, int orderType = 0, int orderObjID = 0)
+	{
+		GameObject *obj = Owner();
+		GameObject *soldier = soldierObject;
+		// Soldier dead or group rushing: no further checks
+		if (!soldier || coopState == 1 || !sender || !Commands->Get_Preset_Name(sender))
+			return;
+
+		float sympathy = 0;
+		auto playerName = Get_Player_Name(sender);
+		if (auto sympathyData = playerRelationData.GetPlayerData(playerName))
+			sympathy = sympathyData->sympathy;
+
+		if (orderType != 0)
+		{
+			coopTarget = Commands->Find_Object(orderObjID);
+			coopFriendPlayerID = Get_Player_ID(sender);
+		}
+
+		float distanceFromSender = Vector3::Distance(Commands->Get_Position(soldier), Commands->Get_Position(sender));
+
+
+		if (messageNumber == Radio_Message_Type::BUILDING_NEEDS_REPAIRS)
+		{
+			if (objectiveType == dp88_AI_Objective::TYPE_ENGINEERING)
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			else if (personality->Get_Random(BotPersonality::CHAT_CHANCE) && FreeRandom.Get_Float() < 0.1f)
+				Play_Radio_Message(Radio_Message_Type::NEGATIVE);
+		}
+
+		// not offensive: no further checks
+		if (objectiveType != dp88_AI_Objective::TYPE_OFFENSIVE)
+			return;
+
+		if (messageNumber == Radio_Message_Type::GET_IN_THE_VEHICLE)
+		{
+			GameObject *senderVehicle = Get_Vehicle(sender);
+
+			bool moreSoldiersCanFit = false;
+			if (senderVehicle)
+			{
+				auto vehicleObj = senderVehicle->As_VehicleGameObj();
+				moreSoldiersCanFit = (vehicleObj->Get_Occupant_Count() < vehicleObj->Get_Definition().Get_Seat_Count());
+			}
+
+			// No assigned vehicle yet and there are empty seats in sender's vehicle
+			if (!vehicleObject && moreSoldiersCanFit && distanceFromSender < 30)
+			{
+				Go_To_Vehicle(obj, senderVehicle);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::GET_OUT_OF_THE_VEHICLE)
+		{
+			GameObject *vehicleObj = Get_Vehicle(soldier);
+
+			// Is in vehicle driven by message sender
+			if (vehicleObject && vehicleObj && vehicleObj->As_VehicleGameObj()->Get_Driver() == sender)
+			{
+				int seatNum = vehicleObj->As_VehicleGameObj()->Find_Seat(soldier->As_SoldierGameObj());
+				Force_Occupant_Exit(vehicleObj, seatNum);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::DESTROY_THAT_VEHICLE ||
+			messageNumber == Radio_Message_Type::DESTROY_IT_NOW ||
+			messageNumber == Radio_Message_Type::ATTACK_THE_HARVESTER ||
+			messageNumber == Radio_Message_Type::ATTACK_THAT_STRUCTURE)
+		{
+			if (sender == Get_GameObj(coopFriendPlayerID) && orderType == CUSTOM_AI_ORDER_ATTACK)
+			{
+				coopState = 6;
+				Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+				Play_Radio_Message(Radio_Message_Type::DESTROY_IT_NOW);
+			}
+			// Try to find the enemy harvester
+			else if (messageNumber == Radio_Message_Type::ATTACK_THE_HARVESTER)
+			{
+				if (auto harvester = Find_Harvester(team == 1 ? 0 : 1))
+				{
+					// Bots will agree easier if the harvester is close
+					float distFromTarget = Vector3::Distance(Commands->Get_Position(soldier), Commands->Get_Position(harvester));
+					if (distFromTarget < 40 || (distFromTarget < 120 && personality->Get_Random(BotPersonality::TEAMWORK_CHANCE)) || FreeRandom.Get_Float() < sympathy)
+					{ 
+						coopTarget = harvester;
+						coopState = 6;
+						Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+						Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+					}
+				}
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::TAKE_THE_POINT)
+		{
+			if (sender == Get_GameObj(coopFriendPlayerID))
+			{
+				// Moving order instead of releasing
+				if (orderType == CUSTOM_AI_ORDER_MOVE)
+				{
+					coopState = 5;
+					Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+					Play_Radio_Message(Radio_Message_Type::COVER_ME);
+				}
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::DONT_GET_IN_MY_WAY)
+		{
+			if (sender == Get_GameObj(coopFriendPlayerID))
+			{
+				// Soldier sends the message back to this controller, so no need to also send this custom to self
+				Commands->Send_Custom_Event(obj, coopObject, CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP, 0, 0.0f);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::FOLLOW_ME)
+		{
+			bool coveringSamePlayer = (coopState == 3 && sender == Get_GameObj(coopFriendPlayerID));
+
+			// Join if not following anyone yet or already going with player but not with follow command
+			if ((coopState == 0 && distanceFromSender < 80 && personality->Get_Random(BotPersonality::TEAMWORK_CHANCE)) || coveringSamePlayer)
+			{
+				if (long id = Get_Player_ID(sender))
+					coopFriendPlayerID = id;
+
+				coopState = 2;
+				Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::COVER_ME)
+		{
+			bool followingSamePlayer = (coopState == 2 && sender == Get_GameObj(coopFriendPlayerID));
+
+			if ((coopState == 0 && distanceFromSender < 80 && personality->Get_Random(BotPersonality::TEAMWORK_CHANCE)) || followingSamePlayer)
+			{
+				if (long id = Get_Player_ID(sender))
+					coopFriendPlayerID = id;
+
+				coopState = 3;
+				Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::DEFEND_THE_BASE)
+		{
+			int objectiveObjID = MS_AI_Global_Controller::GetInstance().Get_Temporary_Objective_ID(team);
+			float distanceFromBase = Vector3::Distance(Commands->Get_Position(soldier), Commands->Get_Position(Commands->Find_Object(objectiveObjID)));
+
+			if (coopState != 4 && distanceFromBase < 200 && personality->Get_Random(BotPersonality::TEAMWORK_CHANCE))
+			{
+				coopState = 4;
+				Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+		else if (messageNumber == Radio_Message_Type::DEFEND_THAT_STRUCTURE ||
+			messageNumber == Radio_Message_Type::DEFEND_THE_HARVESTER)
+		{
+			if (orderType == CUSTOM_AI_ORDER_DEFEND)
+			{
+				coopState = 7;
+				Commands->Start_Timer(obj, this, 0.0f, TIMER_AI_COOP_RETRY_REQUEST);
+				Play_Radio_Message(Radio_Message_Type::AFFIRMATIVE);
+			}
+		}
+	}
+
+private:
+	bool Init_Parameters(GameObject *obj)
+	{
+		bool valid = true;
+		StringClass errorMessage;
+
+		team = Get_Int_Parameter("Team");
+		if (team != 0 && team != 1)
+		{
+			errorMessage.Format("The script has an invalid Team parameter: %d, this must be 0 or 1", team);
+			valid = false;
+		}
+		spawnAtPresetName = Get_Parameter("SpawnAtPreset");
+		if (valid && strcmp(spawnAtPresetName, "USEPLAYERSPAWNERS") != 0 && !Is_Valid_Preset(spawnAtPresetName))
+		{
+			errorMessage.Format("The script has an invalid SpawnAtPreset parameter: %s", spawnAtPresetName);
+			valid = false;
+		}
+
+		soldierPresetList = Get_Parameter("SoldierTwiddlerPreset");
+		if (strcmp(soldierPresetList, "") == 0 || strcmp(soldierPresetList, "null") == 0)
+			soldierPresetList = nullptr;
+		else if (valid && strcmp(soldierPresetList, "USEPLAYERPURCHASELISTS") != 0 && !Is_Valid_Preset(soldierPresetList))
+		{
+			errorMessage.Format("The script has an invalid SoldierTwiddlerPreset parameter: %s", soldierPresetList);
+			valid = false;
+		}
+
+		vehPresetList = Get_Parameter("VehicleTwiddlerPreset");
+		if (strcmp(vehPresetList, "") == 0 || strcmp(vehPresetList, "null") == 0)
+			vehPresetList = nullptr;
+		else if (valid && strcmp(vehPresetList, "USEPLAYERPURCHASELISTS") != 0 && !Is_Valid_Preset(vehPresetList))
+		{
+			errorMessage.Format("The script has an invalid VehicleTwiddlerPreset parameter: %s", vehPresetList);
+			valid = false;
+		}
+
+		objectiveType = Get_Int_Parameter("ObjectiveType");
+		if (valid && (objectiveType < 1 || objectiveType > 3))
+		{
+			errorMessage.Format("The script has an invalid ObjectiveType parameter: %d, this must be 1, 2 or 3", objectiveType);
+			valid = false;
+		}
+
+		if (!valid)
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: %hs. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), errorMessage.Peek_Buffer());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+		}
+		return valid;
+	}
+
+};
+REGISTER_SCRIPT(MS_AI_Player_Controller,
+	"Team=0:int,"
+	"SpawnAtPreset=USEPLAYERSPAWNERS:string,"
+	"SoldierTwiddlerPreset=USEPLAYERPURCHASELISTS:string,"
+	"VehicleTwiddlerPreset=USEPLAYERPURCHASELISTS:string,"
+	"ObjectiveType=1:int"
+);
+
+
+/*!
+* \brief AI player controller's spawned object
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change. DO NOT PLACE THIS MANUALLY!
+*
+* \param Nothing
+*   Nothing
+*/
+class MS_AI_Player_Controller_Spawned_Object : public ScriptImpClass
+{
+	int controllerID;
+	bool isAlive;
+
+	// If this is a vehicle, it has no controller object
+	bool isVehicle;
+	DynamicVectorClass<int> passengerControllerIDs;
+	int lastEnteredPassengerControllerID;
+	StringClass botName;
+
+	void Created(GameObject* obj) override
+	{
+		controllerID = 0;
+		isAlive = true;
+		isVehicle = false;
+		lastEnteredPassengerControllerID = 0;
+		botName = DEFAULT_BOT_NAME;
+
+		controllerID = Get_Int_Parameter("ControllerId");
+		isVehicle = Get_Int_Parameter("IsVehicle") > 0 ? true : false;
+
+		if (auto ctrl = Get_Controller())
+		{
+			auto ctrlScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(ctrl, "MS_AI_Player_Controller"));
+			if (ctrlScript)
+				botName = ctrlScript->Get_Bot_Name();
+		}
+
+		// Safety check for controller object existence
+		if (!Commands->Find_Object(controllerID) && !isVehicle)
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: The script's controller object can't be found. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+		// Safety check for being a vehicle for real when it should be
+		if (isVehicle && !obj->As_VehicleGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: The script's not on a vehicle when it should be, IsVehicle:%d  Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), isVehicle);
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+
+		// Init passenger "Array"
+		if (isVehicle)
+		{
+			auto vObj = obj->As_VehicleGameObj();
+			for (int i = 0; i < vObj->Get_Definition().Get_Seat_Count(); i++)
+			{
+				// Check for bots already sitting at seats
+				if (auto passenger = vObj->Get_Occupant(i))
+				{
+					if (auto spawnedScript = (MS_AI_Player_Controller_Spawned_Object *)(Find_Script_On_Object(passenger, "MS_AI_Player_Controller_Spawned_Object")))
+					{
+						if (auto botCtrl = spawnedScript->Get_Controller())
+						{
+							passengerControllerIDs.Add(botCtrl->Get_ID());
+							continue;
+						}
+					}
+				}
+				// Otherwise init it as empty
+				passengerControllerIDs.Add(0);
+			}
+		}
+		else
+		{
+			Commands->Start_Timer(obj, this, 20.0f, TIMER_AI_SEARCH_FOR_VEHICLE);
+		}
+
+		Commands->Start_Timer(obj, this, 2.0f, TIMER_AI_THINK);
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_EVENT_OWNED_VEHICLE)
+		{
+			Commands->Send_Custom_Event(sender, Get_Controller(), CUSTOM_EVENT_OWNED_VEHICLE, param, 0.0f);
+		}
+		else if (type == CUSTOM_AI_GO_TO_VEHICLE)
+		{
+			// Timeout, vehicle could have been taken far away, bot should not try to run to it forever
+			Commands->Start_Timer(obj, this, 40.0f, TIMER_AI_GO_TO_VEHICLE_TIMEOUT);
+		}
+		else if (type == CUSTOM_AI_ARRIVED_TO_VEHICLE)
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_ARRIVED_TO_VEHICLE, param, 0.0f);
+		}
+		else if (type == CUSTOM_AI_VEHICLE_REQUEST_ENTER)
+		{
+			SoldierGameObj *soldier = Commands->Find_Object(param)->As_SoldierGameObj();
+			// Check entry permitted before placing soldier in
+			if (isVehicle)
+			{
+				lastEnteredPassengerControllerID = sender->Get_ID();
+				Force_Vehicle_Entry(soldier, obj);
+
+				// Cancel any existing timeout since we successfully entered the vehicle
+				if (M_Has_Timer(obj, this, TIMER_AI_GO_TO_VEHICLE_TIMEOUT))
+					M_Stop_Timer2(obj, this, TIMER_AI_GO_TO_VEHICLE_TIMEOUT);
+
+				// Can transport more infantry? Enable transitions!
+				if (obj->As_VehicleGameObj()->Get_Occupant_Count() < obj->As_VehicleGameObj()->Get_Definition().Get_Seat_Count())
+					Commands->Enable_Vehicle_Transitions(obj, true);
+			}
+			// Otherwise param object would be trying to enter a soldier
+		}
+		else if (type == CUSTOM_EVENT_VEHICLE_ENTERED)
+		{
+			if (lastEnteredPassengerControllerID != 0)
+			{
+				passengerControllerIDs[param] = lastEnteredPassengerControllerID;
+				lastEnteredPassengerControllerID = 0;
+			}
+			// Players entering don't send controller id :)
+		}
+		else if (type == CUSTOM_EVENT_VEHICLE_EXITED)
+		{
+			Commands->Send_Custom_Event(obj, Commands->Find_Object(passengerControllerIDs[param]), CUSTOM_AI_VEHICLE_EXITED, 1, 0.1f);
+			passengerControllerIDs[param] = 0;
+		}
+		else if (type == CUSTOM_AI_REQUEST_REFUND)
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), type, 0, 0.0f);
+		}
+		else if (type == CUSTOM_AI_COOP_ARRIVED_TO_RALLYPOINT)
+		{
+			Commands->Send_Custom_Event(sender, Get_Controller(), type, 0, 0.0f);
+		}
+		else if (type == CUSTOM_AI_TRANSPORT_ARRIVED)
+		{
+			int coopState = 0;
+			if (auto ctrl = Get_Controller())
+			{
+				auto ctrlScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(ctrl, "MS_AI_Player_Controller"));
+				if (ctrlScript)
+					coopState = ctrlScript->Get_Coop_State();
+			}
+
+			if (!isVehicle || coopState == 1)
+				return;
+
+			VehicleGameObj *vObj = obj->As_VehicleGameObj();
+			// Get passengers out
+			for (int i = 0; i < vObj->Get_Occupant_Count(); i++)
+			{
+				SoldierGameObj *passenger = vObj->Get_Occupant(i);
+				// Skip driver
+				if (passenger && i != 0)
+				{
+					Force_Occupant_Exit(vObj, i);
+				}
+			}
+		}
+		else if (type == CUSTOM_AI_COOP_PLAYER_FOLLOW_STOP)
+		{
+			Commands->Send_Custom_Event(sender, Get_Controller(), type, 0, 0.0f);
+		}
+		else if (type == CUSTOM_AI_VEHICLE_AMMO_EMPTY)
+		{
+			Commands->Send_Custom_Event(sender, Get_Controller(), type, param, 0.0f);
+		}
+		else if (type == CUSTOM_AI_REFILL_COMPLETE || type == CUSTOM_AI_CHECK_REPAIR_NEEDED || type == CUSTOM_AI_ARRIVED_TO_REFILL ||
+				 type == CUSTOM_AI_ARRIVED_TO_EXIT_VEHICLE || type == CUSTOM_AI_REQUEST_PATROL_POINT || type == CUSTOM_AI_CHECK_GO_HOME ||
+				 type == CUSTOM_AI_COOP_STOP_SECONDARY_OBJECTIVE)
+		{
+			Commands->Send_Custom_Event(sender, Get_Controller(), type, param, 0.0f);
+		}
+		else if (type == CUSTOM_AI_ENEMY_KILLED)
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_SEND_PUBLIC_MESSAGE, 1, (float)FreeRandom.Get_Int(1, 4));
+		}
+		else if (type == CUSTOM_AI_DEACTIVATE_AI)
+		{
+			Remove_Script(obj, "MS_AI_Behaviour");
+			Destroy_Script();
+		}
+	}
+
+	void Action_Complete(GameObject *obj, int action_id, ActionCompleteReason complete_reason) override
+	{
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == TIMER_AI_SEARCH_FOR_VEHICLE)
+		{
+			if (isAlive && !Get_Vehicle(obj))
+			{
+				Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_SEARCH_FOR_VEHICLE, 0, 0.0f);
+			}
+			Commands->Start_Timer(obj, this, 20.0f, TIMER_AI_SEARCH_FOR_VEHICLE);
+		}
+		else if (number == TIMER_AI_GO_TO_VEHICLE_TIMEOUT)
+		{
+			if (!Get_Vehicle(obj))
+				Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_ARRIVED_TO_VEHICLE, 0, 0.0f);
+		}
+		else if (number == TIMER_AI_THINK)
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_CHECK_RADAR_TARGETS, 0, 0.0f);
+			Commands->Start_Timer(obj, this, 2.0f, TIMER_AI_THINK);
+		}
+	}
+
+	void Killed(GameObject *obj, GameObject *killer) override
+	{
+		// This fixes problems with healthlinker scripts killing the object, then healing then killing again (kill 2x)
+		if (!isAlive)
+			return;
+
+		isAlive = false;
+		// Vehicle died
+		if (isVehicle)
+		{
+			// Send dead custom to all soldiers' controllers
+			for (int i = 0; i < passengerControllerIDs.Count(); i++)
+			{
+				if (passengerControllerIDs[i] != 0)
+					Commands->Send_Custom_Event(obj, Commands->Find_Object(passengerControllerIDs[i]), CUSTOM_AI_VEHICLE_DIED, 0, 0.1f);
+			}
+		}
+		else
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_SOLDIER_DIED, 0, 0.1f);
+			if (killer && killer->As_SmartGameObj() && killer->As_SmartGameObj()->Is_Human_Controlled())
+			{
+				Create_2D_Sound_Player(killer, "My_Kill");
+				// Chat message from bot
+				// TODO: replace random with time to type it in, depending on chosen message character count
+				Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_SEND_PUBLIC_MESSAGE, 0, (float)FreeRandom.Get_Int(1, 4));
+
+				if (Kill_Messages_Disabled())
+					return;
+
+				int killStringID = Get_Object_Type(obj) == Get_Object_Type(killer) ? Get_String_ID_By_Desc("IDS_MP_TREASON_PHRASE") : Get_String_ID_By_Desc("IDS_MP_DEFAULT_KILL_PHRASE");
+
+				Vector3 rgb = Get_Color_For_Team(Get_Object_Type(killer));
+				StringClass msg;
+				msg.Format("%s %s %s", Get_Player_Name(killer), Get_Translated_String(killStringID), botName.Peek_Buffer());
+				Send_Message((int)(rgb.X * 255), (int)(rgb.Y * 255), (int)(rgb.Z * 255), msg.Peek_Buffer());
+			}
+		}
+	}
+
+	void Destroyed(GameObject *obj) override
+	{
+		if (!isAlive)
+			return;
+
+		isAlive = false;
+		// Vehicle died
+		if (isVehicle)
+		{
+			// Send dead custom to all soldiers' controllers
+			for (int i = 0; i < passengerControllerIDs.Count(); i++)
+			{
+				Commands->Send_Custom_Event(obj, Commands->Find_Object(passengerControllerIDs[i]), CUSTOM_AI_VEHICLE_DIED, 0, 0.1f);
+			}
+		}
+		else
+		{
+			Commands->Send_Custom_Event(obj, Get_Controller(), CUSTOM_AI_SOLDIER_DIED, 0, 0.1f);
+		}
+	}
+public:
+	GameObject *Get_Controller()
+	{
+		GameObject *controller = Commands->Find_Object(controllerID);
+		if (!controller)
+		{
+			Destroy_Script();
+			return nullptr;
+		}
+		return controller;
+	}
+
+};
+REGISTER_SCRIPT(MS_AI_Player_Controller_Spawned_Object, "ControllerId=0:int,IsVehicle=0:int");
+
+
+/*!
+* \brief Replace weapon when the unit is AI controlled
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* This script replaces one of the object's weapons the script is attached to with another one
+* if the object is AI controlled. It helps to use the same preset for bot units that need a
+* different weapon than the players.
+*
+* \param OriginalWeapon
+*   The name of the weapon preset to replace.
+* \param NewWeapon
+*   The name of the weapon preset to grant.
+* \param SelectOnGrant
+*   Select the granted weapon (1) or not (0).
+*/
+class MS_AI_Replace_Weapon : public ScriptImpClass {
+	void Created(GameObject *obj) override
+	{
+		if (!obj->As_VehicleGameObj())
+			Activate_Script(obj, obj);
+	}
+
+	void Custom(GameObject *obj, int type, int param, GameObject *sender) override
+	{
+		if (type == CUSTOM_EVENT_VEHICLE_OWNER)
+		{
+			GameObject *driver = Commands->Find_Object(param);
+			Activate_Script(obj, driver);
+		}
+	}
+
+	void Activate_Script(GameObject *obj, GameObject *checkIfAI)
+	{
+		if (!checkIfAI->As_SmartGameObj()->Is_Human_Controlled())
+		{
+			Remove_Weapon(obj, Get_Parameter("OriginalWeapon"));
+			Grant_Weapon(obj, Get_Parameter("NewWeapon"), true, 999999, false);
+			if (Get_Int_Parameter("SelectOnGrant") > 0)
+				Commands->Select_Weapon(obj, Get_Parameter("NewWeapon"));
+		}
+		Destroy_Script();
+	}
+};
+REGISTER_SCRIPT(MS_AI_Replace_Weapon,
+	"OriginalWeapon=null:string,"
+	"NewWeapon=null:string,"
+	"SelectOnGrant=0:int"
+);
+
+
+/*!
+* \brief AI Bomb Specialist, helps AI use C4 weapons by faking the placement
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on presets that carry bombs/c4/etc. Give them a fake bomb count, and when
+* they enter a MS_AI_Bomb_Specialist_Zone, it will deduct a fake bomb from them. This ensures
+* that the amount of bombs they can place is limited to the specificed count.
+* The script helps to use the same preset for bot units that trigger customs in bomb specalist
+* zones.
+*
+* \param BombCount
+*   The count of bombs this unit can carry/place.
+*/
+class MS_AI_Bomb_Specialist : public ScriptImpClass
+{
+	int bombCount;
+
+	void Created(GameObject* obj) override
+	{
+		bombCount = 0;
+		if (!obj->As_SoldierGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only objects of type SoldierGameObj can use the MS_AI_Bomb_Specialist script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+
+		// Only for AI
+		if (obj->As_SmartGameObj()->Is_Human_Controlled())
+		{
+			Destroy_Script();
+			return;
+		}
+
+		bombCount = Get_Int_Parameter("BombCount");
+		if (bombCount < 0)
+			bombCount = 0;
+	}
+
+public:
+	bool UseBomb()
+	{
+		if (bombCount > 0)
+		{
+			bombCount--;
+			return true;
+		}
+		return false;
+	}
+};
+REGISTER_SCRIPT(MS_AI_Bomb_Specialist,
+	"BombCount=1:int"
+);
+
+
+/*!
+* \brief AI Bomb Specialist C4 fake placement trigger zone
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Place this script on script zones that when entered by a bomb specialist unit (with more than
+* 0 bombs), should send a custom to a specific preset. Then anything can happen on that preset.
+* In AR for example the presets attach a bomb to themselves when receiving the custom.
+* Secondly, it also sends the same custom back to enterer bomb specialist unit.
+*
+* \param Team
+*   The team of the entering bomb specialist to trigger on.
+* \param SendCustom
+*   The custom to send to the specified preset and also the enterer. The custom is only sent if
+*   the bomb specialist unit has more than 0 bombs left.
+* \param SendToPreset
+*   The preset to send the custom to. If multiple objects of the same preset name are present,
+*   they will all receive it.
+*/
+class MS_AI_Bomb_Specialist_Zone : public ScriptImpClass
+{
+	int team;
+	const char *presetName;
+
+	void Created(GameObject* obj)
+	{
+		team = Get_Int_Parameter("Team");
+		if (team < 0 || team > 1)
+			team = 0;
+
+		presetName = Get_Parameter("SendToPreset");
+		if (!Is_Valid_Preset(presetName))
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: SendToPreset: %hs is not a valid preset name. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name(), presetName);
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+	}
+
+	void Entered(GameObject *obj, GameObject *enterer) override
+	{
+		// Soldiers only
+		if (enterer->As_SoldierGameObj() &&
+			Get_Object_Type(enterer) == team && Is_Script_Attached(enterer, "MS_AI_Bomb_Specialist"))
+		{
+			// Get script from enterer
+			MS_AI_Bomb_Specialist* bsScript = (MS_AI_Bomb_Specialist *)(Find_Script_On_Object(enterer, "MS_AI_Bomb_Specialist"));
+			if (bsScript && bsScript->UseBomb())
+			{
+				// Send custom to bombable object by preset name
+				Send_Custom_Event_To_Object(obj, presetName, Get_Int_Parameter("SendCustom"), 0, 0.0f);
+				Commands->Send_Custom_Event(obj, enterer, Get_Int_Parameter("SendCustom"), 0, 0.0f);
+			}
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Bomb_Specialist_Zone,
+	"Team=0:int,"
+	"SendCustom=0:int,"
+	"SendToPreset=null:string"
+);
+
+
+/*!
+* \brief AI Refinery money distributor
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change. DO NOT PLACE THIS MANUALLY!
+*
+* \param None
+*   None
+*/
+class MS_AI_Refinery : public ScriptImpClass
+{
+	float moneyPerSec;
+	float moneyFromDump;
+	bool harvDocked;
+
+	void Created(GameObject* obj) override
+	{
+		if (!obj->As_BuildingGameObj() || !obj->As_BuildingGameObj()->As_RefineryGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only objects of type RefineryGameObj can use the MS_AI_Refinery script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+
+		auto ref = obj->As_BuildingGameObj()->As_RefineryGameObj();
+		moneyPerSec = ref->Get_Definition().Get_Funds_Distributed_Per_Sec();
+		moneyFromDump = ref->Get_Definition().Get_Funds_Gathered();
+		harvDocked = false;
+
+		Commands->Start_Timer(obj, this, 1.0f, 0);
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		Commands->Start_Timer(obj, this, 1.0f, 0);
+		if (moneyPerSec != 0.0f)
+			MS_AccessHelper::Give_Bot_Credits(Get_Object_Type(obj), moneyPerSec);
+
+		if (auto ref = obj->As_BuildingGameObj()->As_RefineryGameObj())
+		{
+			if (harvDocked != ref->Get_Is_Harvester_Docked())
+			{
+				harvDocked = ref->Get_Is_Harvester_Docked();
+				// This will trigger the money giving on every undocking, unless undocking happened due to harvester dying (killed during dump)
+				if (!harvDocked && ref->Get_Harvester_Vehicle())
+					MS_AccessHelper::Give_Bot_Credits(Get_Object_Type(obj), moneyFromDump);
+			}
+		}
+	}
+};
+REGISTER_SCRIPT(MS_AI_Refinery, "");
+
+
+/*!
+* \brief AI spectator, able to spectate bots
+* \author Daniel Szabo (daniel.szabo.715@gmail.com)
+* \ingroup scripts_ai
+*
+* Subject to change.
+*
+* \param None
+*   None
+*/
+class MS_AI_Spectator : public JFW_Key_Hook_Base
+{
+	DynamicVectorClass<int> bots;
+	bool spectateNext;
+	bool isSpectating;
+	int currentBotIndex;
+	int currentBotCtrlID;
+	int currentBotID;
+	const char *currentCamera;
+
+	void Created(GameObject* obj) override
+	{
+		spectateNext = true;
+		isSpectating = true;
+		currentBotIndex = 0;	
+		currentBotCtrlID = 0;	
+		currentBotID = 0;	
+		currentCamera = nullptr;
+
+		if (!obj->As_SoldierGameObj())
+		{
+			StringClass msg;
+			msg.Format("[%d:%hs:%hs] Critical Error: Only objects of type SoldierGameObj can use the MS_AI_Spectator script. Destroying script...\n", Commands->Get_ID(obj), Commands->Get_Preset_Name(obj), this->Get_Name());
+			M_Console_Output(msg.Peek_Buffer());
+			Destroy_Script();
+			return;
+		}
+
+		currentCamera = "Normal";
+
+		InstallHook(Get_Parameter("SwitchKeyhook"), obj);
+		Refresh_Bot_List();
+		Toggle_Fly_Mode(obj);
+		Commands->Set_Model(obj, "NULL");
+		Commands->Set_Is_Visible(obj, false);
+		PhysicalGameObj* physical_obj = obj->As_PhysicalGameObj();
+		if (physical_obj)
+		{
+			PhysClass* phys = physical_obj->Peek_Physical_Object();
+			if (phys)
+			{
+				phys->Set_Collision_Group(UNCOLLIDEABLE_GROUP);
+			}
+		}
+
+		KeyHook();
+		Commands->Start_Timer(obj, this, 1.5f, 0);
+	}
+
+	void KeyHook()
+	{
+		GameObject *obj = Owner();
+		if (!obj)
+			return;
+
+		if (bots.Count() == 0)
+		{
+			Refresh_Bot_List();
+			if (bots.Count() == 0)
+			{ 
+				Send_Message_Player(obj, DP88_RGB_GENERAL_MSG, "[AI Spectator]: There are no bots to spectate");
+				isSpectating = false;
+				return;
+			}
+		}
+
+		if (!spectateNext)
+		{
+			Commands->Attach_To_Object_Bone(obj, nullptr, "");
+			spectateNext = true;
+			isSpectating = false;
+			Send_Message_Player(obj, DP88_RGB_GENERAL_MSG, "[AI Spectator]: Free flying mode active");
+			return;
+		}
+		else
+			isSpectating = true;
+
+		if (GameObject* cur_bot = Commands->Find_Object(bots[currentBotIndex]))
+		{
+			currentBotCtrlID = bots[currentBotIndex];
+			Spectate_Current();
+
+			if (auto playerControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(cur_bot, "MS_AI_Player_Controller")))
+			{
+				StringClass message;
+				message.Format("[AI Spectator]: Now Spectating %s", playerControllerScript->Get_Bot_Name().Peek_Buffer());
+				Send_Message_Player(obj, DP88_RGB_GENERAL_MSG, message);
+			}
+
+			currentBotIndex++;
+			if (currentBotIndex >= bots.Count())
+			{
+				currentBotIndex = 0;
+				spectateNext = false;
+
+				Refresh_Bot_List();
+			}
+		}
+		else
+		{
+			if (currentBotIndex < bots.Count())
+				currentBotIndex++;
+			else
+			{
+				currentBotIndex = 0;
+				Refresh_Bot_List();
+			}
+
+			if (bots.Count() > 0)
+				KeyHook();
+		}
+	}
+
+	void Timer_Expired(GameObject* obj, int number) override
+	{
+		if (number == 0)
+		{ 
+			Commands->Start_Timer(obj, this, 1.0f, 0);
+			if (!isSpectating)
+				return;
+
+			if (!Commands->Find_Object(currentBotCtrlID))
+				KeyHook();
+
+			else if (!Commands->Find_Object(currentBotID))
+				Spectate_Current();
+			
+			else
+				Check_Camera(obj);
+		}
+	}
+
+	void Spectate_Current()
+	{
+		GameObject *obj = Owner();
+		if (!obj)
+			return;
+
+		currentBotID = 0;
+
+		if (auto botCtrlObj = Commands->Find_Object(currentBotCtrlID))
+		{ 
+			auto playerControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(botCtrlObj, "MS_AI_Player_Controller"));
+			if (!playerControllerScript)
+				return;
+
+			auto botObj = Commands->Find_Object(playerControllerScript->Get_Bot_ID());
+
+			if (!botObj)
+			{
+				StringClass message;
+				message.Format("[AI Spectator]: %s: waiting to spawn...", playerControllerScript->Get_Bot_Name().Peek_Buffer());
+				Send_Message_Player(obj, DP88_RGB_GENERAL_MSG, message);
+				return;
+			}
+
+			currentBotID = playerControllerScript->Get_Bot_ID();
+
+			// TODO: attach to vehicle and not soldier obj if it's in a vehicle?
+			Commands->Attach_To_Object_Bone(obj, botObj, "");
+			Check_Camera(obj);
+		}
+	}
+
+	void Check_Camera(GameObject* obj)
+	{
+		if (auto botObj = Commands->Find_Object(currentBotID))
+		{
+			// Check if camera obj is the soldier or vehicle (might not be needed?)
+			auto cameraObj = Get_Vehicle(botObj) ? Get_Vehicle(botObj) : botObj;
+			// Get camera
+			const char *cameraProfile = "";
+			if (auto soldier = cameraObj->As_SoldierGameObj()) {
+				cameraProfile = "Normal";
+			}
+			else if (auto vehicle = cameraObj->As_VehicleGameObj()) {
+				cameraProfile = vehicle->Get_Profile();
+			}
+
+			// Update camera if needed
+			if (strcmp(cameraProfile, currentCamera) != 0 && strcmp(cameraProfile, "") != 0)
+			{
+				currentCamera = cameraProfile;
+				Set_Camera_Player(obj, cameraProfile);
+			}
+
+			auto logObj = Get_Vehicle(botObj) && Get_Vehicle(botObj)->As_VehicleGameObj()->Get_Driver() == botObj ? cameraObj : botObj;
+			if (auto behaviourScript = (MS_AI_Behaviour *)(Find_Script_On_Object(logObj, "MS_AI_Behaviour")))
+			{
+				StringClass buf;
+				buf.Format("Objective Type: %d", behaviourScript->Get_Objective_Type());
+				Send_Message_Player(obj, 0, 255, 0, buf.Peek_Buffer());
+
+				if (behaviourScript->Get_State())
+				{
+					StringClass buff;
+					buff.Format("State: %d", behaviourScript->Get_State());
+					Send_Message_Player(obj, 0, 255, 0, buff.Peek_Buffer());
+				}
+
+				if (auto overrideTask = behaviourScript->Get_Override_Task())
+				{
+					StringClass buff;
+					buff.Format("Override task ID: %d, Type: %d", overrideTask->Get_ID(), behaviourScript->Get_Override_Task_Type());
+					Send_Message_Player(obj, 0, 255, 0, buff.Peek_Buffer());
+
+					if (auto target = overrideTask)
+					{
+						StringClass buff2;
+						buff2.Format("Target is: %s", Commands->Get_Preset_Name(target));
+						Send_Message_Player(obj, 0, 255, 0, buff2.Peek_Buffer());
+						/*if (target->As_SmartGameObj() && target->As_SmartGameObj()->Get_Host_Object())
+						{
+							auto hostObj = target->As_SmartGameObj()->Get_Host_Object();
+							StringClass buff3;
+							buff3.Format("Attached to a: %s", Commands->Get_Preset_Name(hostObj));
+							Send_Message_Player(obj, 0, 255, 0, buff3.Peek_Buffer());
+						}*/
+						
+					}
+				}
+				if (auto objective = behaviourScript->Get_Current_Objective())
+				{
+					StringClass buff;
+					buff.Format("Objective dist: %d", (int)Vector3::Distance(Commands->Get_Position(objective), Commands->Get_Position(logObj)));
+					Send_Message_Player(obj, 0, 255, 0, buff.Peek_Buffer());
+				}
+			}
+		}
+	}
+
+	void Refresh_Bot_List()
+	{
+		bots.Delete_All();
+
+		DynamicVectorClass<int> botsTeam1;
+		DynamicVectorClass<int> botsTeam0;
+		auto botObjects = Combine_Lists(MS_AI_Global_Controller::GetInstance().botControllersTeam[1], MS_AI_Global_Controller::GetInstance().botControllersTeam[0]);
+		for (int i = 0; i < botObjects.Count(); i++)
+		{
+			auto botObject = botObjects[i];
+			if (Get_Object_Type(botObject) == 1)
+				botsTeam1.Add(Commands->Get_ID(botObject));
+			else
+				botsTeam0.Add(Commands->Get_ID(botObject));
+		}
+		// Bots are now ordered: first the team1 bots, then team0 bots
+		bots = Combine_Lists(botsTeam1, botsTeam0);
+	}
+};
+REGISTER_SCRIPT(MS_AI_Spectator,"SwitchKeyhook=IDeploy:string");
+
+
+
+/*------------------------
+Helpers to avoid moving scripts to header
+--------------------------*/
+
+
+void MS_AccessHelper::Init_AI_Global_Controller()
+{
+	MS_AI_Global_Controller::GetInstance();
+}
+
+int MS_AccessHelper::Get_AI_Player_Controllers_Count(int team)
+{
+	return MS_AI_Global_Controller::GetInstance().botControllersTeam[team].Count();
+}
+
+void MS_AccessHelper::Give_Bot_Credits(int team, float amount)
+{
+	if (!MS_AI_Global_Controller::GetMapInited())
+		return;
+
+	auto controllers = MS_AI_Global_Controller::GetInstance().botControllersTeam[team];
+	for (int i = 0; i < controllers.Count(); i++)
+	{
+		MS_AI_Player_Controller *playerControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(controllers[i], "MS_AI_Player_Controller"));
+		if (playerControllerScript)
+			playerControllerScript->Increment_Money(amount * MS_AI_Global_Controller::GetInstance().grantCreditsMultiplier);
+	}
+}
+
+float MS_AccessHelper::Steal_Bot_Credits(int team, float percentage)
+{
+	if (!MS_AI_Global_Controller::GetMapInited())
+		return 0;
+
+	float theft = 0;
+	auto controllers = MS_AI_Global_Controller::GetInstance().botControllersTeam[team];
+	for (int i = 0; i < controllers.Count(); i++)
+	{
+		MS_AI_Player_Controller *playerControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(controllers[i], "MS_AI_Player_Controller"));
+		if (playerControllerScript)
+		{
+			float credits = playerControllerScript->Get_Money();
+			credits = credits * percentage;
+			theft += credits;
+			playerControllerScript->Increment_Money(-credits);
+		}
+	}
+	return theft;
+}
+
+void MS_AccessHelper::Receive_Radio_Command(GameObject *sender, int AnnouncementID, int orderType, int orderObjID)
+{
+	if (!MS_AI_Global_Controller::GetMapInited())
+		return;
+
+	MS_AI_Global_Controller::GetInstance().Receive_Radio_Command(sender, AnnouncementID, orderType, orderObjID);
+}
+
+void MS_AccessHelper::Send_Radio_Order_To_Bot(GameObject *sender, GameObject *receiver, int radioIndex, int orderType, int orderObjID)
+{
+	if (receiver)
+	{
+		if (auto playerControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(receiver, "MS_AI_Player_Controller")))
+		{
+			playerControllerScript->Receive_Radio_Message(sender, radioIndex, orderType, orderObjID);
+		}
+	}
+}
+
+float MS_AccessHelper::Get_Money(GameObject *object)
+{
+	if (auto botScript = (MS_AI_Player_Controller_Spawned_Object *)(Find_Script_On_Object(object, "MS_AI_Player_Controller_Spawned_Object")))
+	{
+		if (auto botControllerObj = botScript->Get_Controller())
+		{
+			if (auto botControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(botControllerObj, "MS_AI_Player_Controller")))
+			{
+				return botControllerScript->Get_Money();
+			}
+		}
+	}
+	return 0;
+}
+
+void MS_AccessHelper::Give_Money(GameObject *object, float money)
+{
+	if (auto botScript = (MS_AI_Player_Controller_Spawned_Object *)(Find_Script_On_Object(object, "MS_AI_Player_Controller_Spawned_Object")))
+	{
+		if (auto botControllerObj = botScript->Get_Controller())
+		{
+			if (auto botControllerScript = (MS_AI_Player_Controller *)(Find_Script_On_Object(botControllerObj, "MS_AI_Player_Controller")))
+			{
+				botControllerScript->Increment_Money(money);
+			}
+		}
+	}
+}
+
+void MS_AccessHelper::Activate_Defense_Protocol(int team)
+{
+	if (auto teamCtrlObj = MS_AI_Global_Controller::GetInstance().teamControllers[team])
+	{
+		if (auto teamCtrlScript = (MS_AI_Team_Controller *)(Find_Script_On_Object(teamCtrlObj, "MS_AI_Team_Controller")))
+		{
+			teamCtrlScript->Activate_Defense_Protocol(teamCtrlObj);
+		}
+	}
+}
diff -urN sourceold/scripts/scripts.h source/scripts/scripts.h
--- sourceold/scripts/scripts.h	2019-08-20 10:11:06.818000100 +1000
+++ source/scripts/scripts.h	2020-03-28 21:10:59.047767800 +1000
@@ -318,18 +318,6 @@
 class ActionParamsStruct {
 public:
 	ActionParamsStruct( void );
-	void Set_Basic_Old(ScriptClass *s,unsigned long priority,unsigned long ID,long state);
-	void Set_Attack_Hold(GameObject *Target,float range,float deviation,bool primary,bool Hold);
-	void Set_Attack_Position(const Vector3 & position,float Range,float Deviation,bool Primary);
-	void Set_Goto_Location(const Vector3 & position,float speed,float arrivedistance,bool crouch);
-	void Set_Goto_Following(GameObject *Target,float speed,float arrivedistance,bool following);
-	void Set_Goto_Crouch(GameObject *Target,float speed,float arrivedistance,bool crouch);
-	void Set_Goto_Crouch_Following(GameObject *Target,float speed,float arrivedistance,bool crouch,bool following);
-	void Set_Goto(GameObject *Target,float speed,float arrivedistance);
-	void Set_Goto_Waypath_Start(unsigned long waypathID,unsigned long waypathstartID,unsigned long waypathendID,bool splined);
-	void Set_Goto_Waypath(unsigned long waypathID,bool splined,bool v57,bool Hold);
-	void Set_Goto_Waypath_ID(unsigned long waypathID);
-	void Set_Move_Arrive_Distance(float distance);
 	void Set_Basic( GameObjObserverClass * script, float priority, int action_id, SoldierAIState ai_state = NO_AI_STATE_CHANGE ) { ObserverID = script->Get_ID(); Priority = (int)priority; ActionID = action_id; AIState = ai_state; }
 	void Set_Basic( long observer_id, float priority, int action_id, SoldierAIState ai_state = NO_AI_STATE_CHANGE ) { ObserverID = observer_id; Priority = (int)priority; ActionID = action_id; AIState = ai_state; }
 	void Set_Look( const Vector3 & location, float duration ) { LookLocation = location; LookDuration = duration; }	
@@ -462,89 +450,6 @@
 	LookDuration	= duration;
 	return ;
 }
-void inline ActionParamsStruct::Set_Move_Arrive_Distance(float distance)
-{
-	MoveArrivedDistance = distance;
-}
-void inline ActionParamsStruct::Set_Goto_Location(const Vector3 & position,float speed,float arrivedistance,bool crouch)
-{
-	MoveLocation.X = position.X;
-	MoveLocation.Y = position.Y;
-	MoveLocation.Z = position.Z;
-	MoveSpeed = speed;
-	MoveArrivedDistance = arrivedistance;
-	MoveCrouched = crouch;
-}
-void inline ActionParamsStruct::Set_Goto_Following(GameObject *Target,float speed,float arrivedistance,bool following)
-{
-	MoveObject = Target;
-	MoveSpeed = speed;
-	MoveArrivedDistance = arrivedistance;
-	MoveFollow = following;
-}
-void inline ActionParamsStruct::Set_Goto_Crouch(GameObject *Target,float speed,float arrivedistance,bool crouch)
-{
-	MoveObject = Target;
-	MoveSpeed = speed;
-	MoveArrivedDistance = arrivedistance;
-	MoveCrouched = crouch;
-}
-void inline ActionParamsStruct::Set_Goto_Crouch_Following(GameObject *Target,float speed,float arrivedistance,bool crouch,bool following)
-{
-	MoveObject = Target;
-	MoveSpeed = speed;
-	MoveArrivedDistance = arrivedistance;
-	MoveCrouched = crouch;
-	MoveFollow = following;
-}
-void inline ActionParamsStruct::Set_Goto(GameObject *Target,float speed,float arrivedistance)
-{
-	MoveObject = Target;
-	MoveSpeed = speed;
-	MoveArrivedDistance = arrivedistance;
-}
-void inline ActionParamsStruct::Set_Goto_Waypath_Start(unsigned long waypathID,unsigned long waypathstartID,unsigned long waypathendID,bool splined)
-{
-	WaypathID = waypathID;
-	WaypointStartID = waypathstartID;
-	WaypointEndID = waypathendID;
-	WaypathSplined = splined;
-}
-void inline ActionParamsStruct::Set_Goto_Waypath(unsigned long waypathID,bool splined,bool v57,bool Hold)
-{
-	WaypathID = waypathID;
-	WaypathSplined = splined;
-	AttackActive = v57;
-	AttackCheckBlocked = Hold;
-}
-void inline ActionParamsStruct::Set_Goto_Waypath_ID(unsigned long waypathID)
-{
-	WaypathID = waypathID;
-}
-void inline ActionParamsStruct::Set_Basic_Old(ScriptClass *s,unsigned long priority,unsigned long ID,long state)
-{
-	ID = s->Get_ID();
-	Priority = priority;
-	ActionID = ID;
-	AIState = (SoldierAIState)state;
-}
-void inline ActionParamsStruct::Set_Attack_Hold(GameObject *Target,float Range,float Deviation,bool Primary,bool Hold)
-{
-	AttackObject = Target;
-	AttackRange = Range;
-	AttackError = Deviation;
-	AttackPrimaryFire = Primary;
-	AttackCheckBlocked = Hold;
-}
-void inline ActionParamsStruct::Set_Attack_Position(const Vector3 & position,float Range,float Deviation,bool Primary)
-{
-	AttackRange = Range;
-	AttackLocation.X = position.X;
-	AttackLocation.Y = position.Y;
-	AttackLocation.Z = position.Z;
-	AttackError = Deviation;
-	AttackPrimaryFire = Primary;
-}
 
 enum {
 	CUSTOM_EVENT_SYSTEM_FIRST = 1000000000,
@@ -570,6 +475,7 @@
 	CUSTOM_EVENT_BEACON_DEPLOYED,                                                       //!< Sent when a beacon is deployed
 	CUSTOM_EVENT_REFILL,                                                                //!< Sent on refill at a PT
 	CUSTOM_EVENT_BUILDING_REVIVED,														//!< Sent when building is revived
+	CUSTOM_EVENT_OWNED_VEHICLE,                                                         //!< Special custom sent to the owner of a new vehicle so they can find their vehicle
 };
 
 enum {
diff -urN sourceold/scripts/scripts.vcxproj source/scripts/scripts.vcxproj
--- sourceold/scripts/scripts.vcxproj	2019-06-22 07:31:28.821400000 +1000
+++ source/scripts/scripts.vcxproj	2020-09-26 09:00:03.276360000 +1000
@@ -27,25 +27,25 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -146,6 +146,7 @@
       <AdditionalLibraryDirectories>..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -225,6 +226,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenServerPath (
@@ -293,6 +295,7 @@
       <AdditionalLibraryDirectories>..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -371,6 +374,7 @@
       <AdditionalLibraryDirectories>..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenServerPath (
@@ -475,9 +479,12 @@
     <ClCompile Include="mdb.cpp" />
     <ClCompile Include="mdbdef.cpp" />
     <ClCompile Include="mdbevf.cpp" />
+    <ClCompile Include="ms.cpp" />
+    <ClCompile Include="ms_ai.cpp" />
     <ClCompile Include="neo.cpp" />
     <ClCompile Include="nh.cpp" />
     <ClCompile Include="nhp.cpp" />
+    <ClCompile Include="ObjectLibraryManager.cpp" />
     <ClCompile Include="PersistFactoryClass.cpp" />
     <ClCompile Include="PointerRemapClass.cpp" />
     <ClCompile Include="Quaternion.cpp" />
@@ -681,6 +688,7 @@
     <ClInclude Include="mdbevf.h" />
     <ClInclude Include="MixFileFactoryClass.h" />
     <ClInclude Include="MoveablePhysDefClass.h" />
+    <ClInclude Include="ms.h" />
     <ClInclude Include="NavalFactoryGameObj.h" />
     <ClInclude Include="NavalFactoryGameObjDef.h" />
     <ClInclude Include="neo.h" />
@@ -688,6 +696,7 @@
     <ClInclude Include="nh.h" />
     <ClInclude Include="nhp.h" />
     <ClInclude Include="obboxclass.h" />
+    <ClInclude Include="ObjectLibraryManager.h" />
     <ClInclude Include="OffenseObjectClass.h" />
     <ClInclude Include="Parameter.h" />
     <ClInclude Include="Persist.h" />
diff -urN sourceold/scripts/scripts.vcxproj.filters source/scripts/scripts.vcxproj.filters
--- sourceold/scripts/scripts.vcxproj.filters	2019-06-22 07:31:28.822400000 +1000
+++ source/scripts/scripts.vcxproj.filters	2020-03-28 21:10:59.049791100 +1000
@@ -380,6 +380,15 @@
     <ClCompile Include="MasterCan_Scripts.cpp">
       <Filter>01. Sources</Filter>
     </ClCompile>
+    <ClCompile Include="ObjectLibraryManager.cpp">
+      <Filter>01. Sources</Filter>
+    </ClCompile>
+	<ClCompile Include="ms_ai.cpp">
+      <Filter>01. Sources</Filter>
+    </ClCompile>
+    <ClCompile Include="ms.cpp">
+      <Filter>01. Sources</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="ArmedGameObj.h">
@@ -1282,6 +1291,12 @@
     <ClInclude Include="DB_Innate.h">
       <Filter>01. Sources</Filter>
     </ClInclude>
+    <ClInclude Include="ObjectLibraryManager.h">
+      <Filter>01. Sources</Filter>
+    </ClInclude>
+    <ClInclude Include="ms.h">
+      <Filter>02. Headers</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="documentation\cinematics.dox">
diff -urN sourceold/shared/shared.vcxproj source/shared/shared.vcxproj
--- sourceold/shared/shared.vcxproj	2018-11-17 10:23:57.278600000 +1000
+++ source/shared/shared.vcxproj	2020-09-26 09:00:03.276360000 +1000
@@ -19,14 +19,14 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
     <WholeProgramOptimization>false</WholeProgramOptimization>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -98,6 +98,7 @@
       <BaseAddress>0x12800000</BaseAddress>
       <TargetMachine>MachineX86</TargetMachine>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -164,6 +165,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/swap/Swap.vcxproj source/swap/Swap.vcxproj
--- sourceold/swap/Swap.vcxproj	2018-10-27 22:04:56.583400000 +1000
+++ source/swap/Swap.vcxproj	2020-09-26 09:00:03.277358200 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/tdbedit/tdbedit.vcxproj source/tdbedit/tdbedit.vcxproj
--- sourceold/tdbedit/tdbedit.vcxproj	2018-11-17 10:23:57.287600000 +1000
+++ source/tdbedit/tdbedit.vcxproj	2020-09-26 09:00:03.277358200 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -92,6 +92,7 @@
       <SetChecksum>true</SetChecksum>
       <TargetMachine>MachineX86</TargetMachine>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -154,6 +155,7 @@
       </LinkTimeCodeGeneration>
       <TargetMachine>MachineX86</TargetMachine>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
diff -urN sourceold/teamspeak/teamspeak.vcxproj source/teamspeak/teamspeak.vcxproj
--- sourceold/teamspeak/teamspeak.vcxproj	2018-10-27 22:04:56.595400000 +1000
+++ source/teamspeak/teamspeak.vcxproj	2020-09-26 09:00:03.278355500 +1000
@@ -20,13 +20,13 @@
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release SSGM|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <PlatformToolset>v110_xp</PlatformToolset>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -108,6 +108,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\release SSGM;..\bin\release</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
@@ -185,6 +186,7 @@
       <TargetMachine>MachineX86</TargetMachine>
       <AdditionalLibraryDirectories>..\bin\debug SSGM;..\bin\debug</AdditionalLibraryDirectories>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+      <MinimumRequiredVersion>6.01</MinimumRequiredVersion>
     </Link>
     <PostBuildEvent>
       <Command>if defined RenPath (
