· 4 years ago · Aug 08, 2021, 05:02 PM
1 //Introduction
2 #region Introduction
3 /*Introduction
4 ----------------------
5 Hello and thank you for downloading Rdavs Easy Guided Missile Script
6 This Script adds an advanced but leightweight tracking algorithm for
7 player made missiles that excels in dogfights and ship to ship warfare.
8
9 Simply paste or install this code into the programmable block and refer
10 to the custom-data of this block for easily accessible instructions
11 and hints and tips.
12
13 Additional information can be found on the workshop page for the script
14 Any questions or queries don't hesitate to contact me!
15
16 Rdav 16/07/2018
17
18 ChangeLog:
19 * Release Version
20 * changed to 'contains #A#' on init
21 * changed to any turrets
22 * //Update 1.01
23 * Fixed damaged block/ no thrusters on missile rare exception
24 * added launch contingency error checking
25 * // Update 1.02
26 * Refactored missile tag now is changable
27 * Updated sound block tag
28 * Updated so can use turrets on missiles (rename turrets on missiles custom info to "NoUse")
29 * Updated so missiles will accelerate out even if their launch distance is incorrectly set
30 * //Update 1.03
31 * Updated naming convention
32 * //Update 1.04
33 * Hotfixed batter recharge issue
34 * // Updade 1.05
35 * Fixed thruster low power issue as incorrectly clamped
36 * Added Automatic Firing
37 */
38
39 //--------------------- Player Editable Values (User Edits Section) -----------------------------
40
41 //CHANGE LAUNCH DISTANCE HERE:
42 //(only uses this value of it is not 0)
43 //Launch distance of the missile (in m) before guidance
44 double LaunchDist = 100;
45 double ArmDistance = 50;
46 double FuseDistance = 5;
47
48 //CHANGE MISSILE TAG HERE:
49 //Changes The prefix tag that the missile uses
50 //Note that the instructions will not update to this tag
51 string MissileTag = "#2#";
52 string TurretTag = "#A#";
53 float MinimumThreat = 1;
54
55 //CHANGE AUTOLAUNCH HERE
56 //change to 'true' if automatic launch is required
57 bool AutoLaunch = false;
58 int Milliseconds_Between_Launch = 180;
59
60 //CHANGE TOW Rayscan Distance HERE
61 //(~note this section is in development, and might not work correctly)
62 //change to whatever distance is required, note that rayscan at longer ranges will be overriden by close up targets
63 //change this to true if you always want the missile able to set priority to rayscanned object
64 bool OverrideToLongTargets = false;
65
66
67 //-------------- Don't Touch Anything Beneath This Line --------------------
68
69 string VERSION = "1.05";
70
71 #endregion
72
73 //Constants And Classes
74 #region Consts & Classes
75 //Classes
76 class MISSILE
77 {
78 //Terminal Blocks On Each Missile
79 public IMyTerminalBlock GYRO;
80 public IMyTerminalBlock TURRET;
81 public IMyTerminalBlock MERGE;
82 public List<IMyThrust> THRUSTERS = new List<IMyThrust>(); //Multiple
83 public IMyTerminalBlock POWER;
84 public List<IMyTerminalBlock> WARHEADS = new List<IMyTerminalBlock>(); //Multiple
85 //public IMyTerminalBlock MatingMergeBlock; //The Mating Merge Block used for launch distance calculation
86
87 //Permanent Missile Details
88 public double MissileAccel = 10;
89 public double MissileMass = 0;
90 public double MissileThrust = 0;
91 public bool IsLargeGrid = false;
92 public double ArmDistance;
93 public double FuseDistance;
94
95 //Runtime Assignables
96 public bool HAS_DETACHED = false;
97 public bool IS_CLEAR = false;
98 public Vector3D TARGET_PREV_POS = new Vector3D();
99 public Vector3D MIS_PREV_POS = new Vector3D();
100
101 public double PREV_Yaw = 0;
102 public double PREV_Pitch = 0;
103
104 }
105 List<MISSILE> MISSILES = new List<MISSILE>();
106
107 //Consts
108 double Global_Timestep = 0.016;
109 double PNGain = 3;
110 double ThisShipSize = 10;
111 string Lstrundata = "Please ensure you have read the \n setup and hints & tips, found within \n the custom data of this block\n";
112 IEnumerator<bool> LaunchStateMachine;
113 IMyLargeTurretBase Turret;
114 IMySoundBlock Alarm;
115 IMyShipController RC;
116 IMyCameraBlock TOWCamera;
117 MyDetectedEntityInfo TemporaryTarget;
118 int AutoLaunchTimer = 0;
119 #endregion
120
121 //Initialiser
122 #region Setup Stages
123 public static WcPbApi api;
124 float MminimumOffRat = 1;
125
126
127 Program()
128 {
129 //Sets Runtime
130 Runtime.UpdateFrequency = Global_Timestep == 0.16? UpdateFrequency.Update10 : UpdateFrequency.Update1;
131
132 if(MinimumThreat>=9)
133 MminimumOffRat = 5F;
134 if(MinimumThreat==8)
135 MminimumOffRat = 4F;
136 if(MinimumThreat==7)
137 MminimumOffRat = 3F;
138 if(MinimumThreat==6)
139 MminimumOffRat = 2F;
140 if(MinimumThreat==5)
141 MminimumOffRat = 1F;
142 if(MinimumThreat==4)
143 MminimumOffRat = 0.5F;
144 if(MinimumThreat==3)
145 MminimumOffRat = 0.25F;
146 if(MinimumThreat==2)
147 MminimumOffRat = 0.125F;
148 if(MinimumThreat==1)
149 MminimumOffRat = 0.0625F;
150 if(MinimumThreat<=0)
151 MminimumOffRat = 0F;
152
153 api = new WcPbApi();
154 try
155 { api.Activate(Me); }
156 catch
157 { Echo("WeaponCore Api is failing! \n Make sure WeaponCore is enabled!"); return; }
158
159 //Setup String
160 string SetupString = "Rdavs Guided Missile Script Hints Tips & Setup \n================================================== \n \n \nSystem Setup \n=================== \n \nTo Set Up The Ship: \n------------------------ \n- Put this Code Into a P-block \n- Install a sound block (optional) \n- Install a turret called #A# (seeker turret) \n- Recompile if prompted to 'reset' \n- To fire from the toolbar 'run' this Pb with \n the argument 'Fire' \n \n\nTo Set Up A Missile: \n--------------------------- \n- Every Missile Requires: \n ~ 1 gyro\n ~ Forward thrusters \n ~ 1 Merge Block \n ~ 1 battery/power source \n ~ Warheads (optional) \n ~ Side thrusters (if in gravity)\n \n- Call everything on missile #A# \n- Weld/paste missile onto launching ship \n- Same missile design can be pasted multiple times \n- Missile(s) are now ready to fire!\n\n \n\nSystem Usage \n=================== \n \nMove to engagement distance (800m), a distinctive \ntarget lock bleeping will sound from the sound block. \n(if sound block installed) \n \n'Run' The programmable block with the argument \n'Fire' this will launch the next available missile. \nthis action can be bound to the toolbar of any cockpit. \n \nA ship can have up to 100 missiles active at any one \npoint, missile setup for every missile is identical \nand thus missiles can be printed, copy pasted, etc. \n\n- NOTE:\n- For ALL missiles the weight of the missile\n (can be found in the 'info' tab in kg)\n should be written as a number into the \n custom data of the missiles gyro.\n This is not compulsory but is necessary for\n accurate guidance.\n\n\nTroubleshooting: \n============================\n\nif you find that your missiles are:\n\n - Sinking and hit the ground in gravity\n - Miss the target / have sub par guidance\n - Not tracking an enemy\n - Not firing at all\n\nHere are some of the most common faults:\n\n- Check the terminal of the PB, this might show\n some useful error readouts\n\n- Boost the acceleration of the missile! \n (especially if unable to hit targets)\n\n- Ensure the turret (called #A#) is turned on \n and set to attack enemy targets, what this turret\n targets is what the script will track\n\n- The weight of the missile should be input\n into the gyros custom data,\n\n- Lateral (side) thrusters are not compulsory but\n are very useful at helping the guidance\n especially in natural gravity\n\n \n \nHints & Tips \n=================== \n \n \nPerformance Tips: \n------------------------ \n \n- Use light and fast missiles for best small ship \n tracking capability, the key to a good hit rate is \n good missile design!\n \n- For 'best' target tracking ensure your missile has \n at least 3x the acceleration of the ship you are \n intending to take out. \n \n- Lateral (sideways) thrusters can be used for better \n gravity correction and handling. \n \n \nUsage Tips: \n---------------------- \n \n- ID'ing the target can be done with common sense, (ie looking \n at what the seeker turret is currently looking at) \n \n- you can fire a missile without lock to 'laser guide it' \n (ie it follows where you are pointing your ship) \n towards an enemy, once close tracking will engage \n \n- The missile will (by default) not engage guidance until further \n than the ships radius away from where it launched. This should \n make it practical for launch tubes/ not damaging the launching ship. \n\n- If a customn launch distance is required this can be changed by changing\n the 'launch distance' value in the code shortly after the introduction\n\n- An extra seeker turret can be put on the missile itself (must be called #A#)\n this missile will then use that turret to guide itself.\n\n- The missile tracking is good but as with anything depends on the pilots\n ability to use them well, read up on real-world missile usage to help \n boost your hit rate.\n\n\n\n \n";
161 Me.CustomData = SetupString;
162
163 //Sets ShipSize
164 ThisShipSize = (Me.CubeGrid.WorldVolume.Radius);
165 ThisShipSize = LaunchDist == 0 ? ThisShipSize : LaunchDist;
166
167 //Collects First Turret
168 List<IMyTerminalBlock> TempCollection = new List<IMyTerminalBlock>();
169 GridTerminalSystem.GetBlocksOfType<IMyLargeTurretBase>(TempCollection, a => a.CustomName.Contains(TurretTag) && a.DetailedInfo != "NoUse");
170 if (TempCollection.Count > 0)
171 {Turret = TempCollection[0] as IMyLargeTurretBase; }
172
173 //Collects Sound/Alarm
174 List<IMyTerminalBlock> TempCollection2 = new List<IMyTerminalBlock>();
175 GridTerminalSystem.GetBlocksOfType<IMySoundBlock>(TempCollection2, a => a.DetailedInfo != "NoUse");
176 if (TempCollection2.Count > 0)
177 {
178 Alarm = TempCollection2[0] as IMySoundBlock;
179 Alarm.SelectedSound = "SoundBlockAlert2";
180 Alarm.LoopPeriod = 99999;
181 Alarm.Play();
182 Alarm.Enabled = false;
183 }
184
185 //Collects RC Unit
186 List<IMyTerminalBlock> TempCollection3 = new List<IMyTerminalBlock>();
187 GridTerminalSystem.GetBlocksOfType<IMyShipController>(TempCollection3, a => a.DetailedInfo != "NoUse");
188 if (TempCollection3.Count > 0)
189 {RC = TempCollection3[0] as IMyShipController;}
190
191 //Collects TOW CameraBlock
192 List<IMyTerminalBlock> TempCollection4 = new List<IMyTerminalBlock>();
193 GridTerminalSystem.GetBlocksOfType<IMyCameraBlock>(TempCollection3, a => a.DetailedInfo == "#A#");
194 if (TempCollection3.Count > 0)
195 { TOWCamera = TempCollection3[0] as IMyCameraBlock; TOWCamera.EnableRaycast = true; }
196
197 }
198 #endregion
199
200 //Main Method
201 #region Main Method
202 void Main(string argument)
203 {
204
205 //General Layout Diagnostics
206 OP_BAR();
207 QuickEcho(MISSILES.Count, "Active (Fired) Missiles:");
208 QuickEcho(Runtime.LastRunTimeMs, "Runtime:");
209 Echo("Version: " + VERSION);
210 Echo("\nInfo:\n---------------");
211 Echo(Lstrundata);
212
213 #region Block Error Readouts
214
215 //Core Block Checks
216 if (RC == null || RC.CubeGrid.GetCubeBlock(RC.Position) == null)
217 { Echo(" ~ No Ship Control Found,\nInstall Forward Facing Cockpit/RC/Flightseat And Press Recompile"); RC = null; return; }
218 if (Turret == null || Turret.CubeGrid.GetCubeBlock(Turret.Position) == null)
219 { Echo(" ~ Searching For A new Seeker\n (Guidance Turret)\n Name Of Turret Needs to be: '#A#'\nInstall Block And Press Recompile"); Turret = null; return; }
220 if (Alarm == null || Alarm.CubeGrid.GetCubeBlock(Alarm.Position) == null)
221 { Echo(" ~ No Sound Block For Lock Tone Found,\nInstall Block And Press Recompile\n (script will work fine without) "); Alarm = null; }
222
223 #endregion
224
225 //Sounds 60 hz growl lock Alarm/
226 Dictionary<MyDetectedEntityInfo, float> ThreatsDict = new Dictionary<MyDetectedEntityInfo, float>();
227 api.GetSortedThreats(Me, ThreatsDict);
228 bool HasTarget = false;
229
230 foreach(MyDetectedEntityInfo k in ThreatsDict.Keys)
231 {
232 if(ThreatsDict[k]>=MminimumOffRat && !k.IsEmpty() && k.Relationship == MyRelationsBetweenPlayerAndBlock.Enemies)
233 {
234 HasTarget = true;
235 break;
236 }
237 }
238
239 if (HasTarget)
240 Echo("Weaponcore Has Default Lock, Ready to Fire");
241 else
242 Echo("Weaponcore Default Lock Unavailable");
243
244 MyDetectedEntityInfo FocusTarget = api.GetAiFocus(Me.CubeGrid.EntityId, 0) ?? default(MyDetectedEntityInfo);
245 if (!FocusTarget.IsEmpty())
246 {
247 Echo("Weaponcore Focus Locked On " + FocusTarget.Name);
248 HasTarget = true;
249 }
250 else
251 Echo("Weaponcore Focus Lock Unavailable");
252
253 //=================== Growl Lockonnoise
254 if (Alarm != null)
255 {
256 if (HasTarget && !Alarm.Enabled)
257 { Alarm.Enabled = true; }
258 else if (HasTarget && Alarm.Enabled)
259 { Alarm.Enabled = false; }
260 }
261
262 //==================== AutolaunchHandler
263 if (HasTarget)
264 { AutoLaunchTimer = (AutoLaunchTimer + 1) % (Milliseconds_Between_Launch); }
265 if (AutoLaunchTimer == Milliseconds_Between_Launch * 0.8 && AutoLaunch)
266 {argument = "Fire";}
267
268 //Begins Launch Scheduler
269 //-----------------------------------------
270 if (argument == "Fire" && LaunchStateMachine == null)
271 {
272 LaunchStateMachine = MissileLaunchHandler().GetEnumerator();
273 }
274 if (argument != "Fire" && argument != "")
275 {
276 Lstrundata = "Unknown/Incorrect launch argument,\ncheck spelling & caps,\nto launch argument should be just: Fire\n ";
277 }
278
279 //Runs Guidance Block (foreach missile)
280 //---------------------------------------
281 for (int i = 0; i < MISSILES.Count; i++)
282 {
283 var ThisMissile = MISSILES[i];
284
285 //Runs Standard System Guidance
286 if (ThisMissile.IS_CLEAR == true)
287 { STD_GUIDANCE(ThisMissile); }
288
289 //Fires Straight (NO OVERRIDES)
290 else if (ThisMissile.IS_CLEAR == false)
291 {
292 if ((ThisMissile.GYRO.GetPosition() - Me.GetPosition()).Length() > ThisShipSize)
293 { ThisMissile.IS_CLEAR = true; }
294 }
295
296 //Disposes If Out Of Range Or Destroyed (misses a beat on one missile)
297 bool Isgyroout = ThisMissile.GYRO.CubeGrid.GetCubeBlock(ThisMissile.GYRO.Position) == null;
298 bool Isthrusterout = ThisMissile.THRUSTERS[0].CubeGrid.GetCubeBlock(ThisMissile.THRUSTERS[0].Position) == null;
299 bool Isouttarange = (ThisMissile.GYRO.GetPosition() - Me.GetPosition()).LengthSquared() > 9000 * 9000;
300 if (Isgyroout || Isthrusterout || Isouttarange)
301 { MISSILES.Remove(ThisMissile); }
302
303 }
304
305 //Launcher Statemachine Disposed If Complete
306 //---------------------------------------------------
307 if (LaunchStateMachine != null)
308 {
309 if (!LaunchStateMachine.MoveNext() || !LaunchStateMachine.Current)
310 {
311 LaunchStateMachine.Dispose();
312 LaunchStateMachine = null;
313 }
314 }
315 }
316
317
318 #endregion
319
320 //SubMethod State Machine For Launching Missiles
321 #region MissileLaunchHandler
322 public IEnumerable<bool> MissileLaunchHandler()
323 {
324
325 //Gathers Missile Information
326 yield return INIT_NEXT_MISSILE();
327
328 //Disables Merge Block
329 MISSILE ThisMissile = MISSILES[MISSILES.Count - 1];
330 var MERGE_A = ThisMissile.MERGE;
331 (MERGE_A as IMyShipMergeBlock).Enabled = false;
332 yield return true;
333 yield return true;
334 yield return true;
335 yield return true;
336 yield return true; //Safety Tick
337
338 //Launches Missile & Gathers Next Scanner
339 PREP_FOR_LAUNCH(MISSILES.Count - 1);
340
341 }
342 #endregion
343
344 //Standard Guidance Routine
345 #region RdavNav Missile Guidance #RFC#
346 /*=================================================
347 RdavNav
348 --------------------------------------- */
349 void STD_GUIDANCE(MISSILE This_Missile)
350 {
351
352 //Finds Current Target
353 Vector3D ENEMY_POS = EnemyScan(This_Missile);
354
355 //---------------------------------------------------------------------------------------------------------------------------------
356
357 //Sorts CurrentVelocities
358 Vector3D MissilePosition = This_Missile.GYRO.CubeGrid.WorldVolume.Center;
359 Vector3D MissilePositionPrev = This_Missile.MIS_PREV_POS;
360 Vector3D MissileVelocity = (MissilePosition - MissilePositionPrev) / Global_Timestep;
361
362 Vector3D TargetPosition = ENEMY_POS;
363 Vector3D TargetPositionPrev = This_Missile.TARGET_PREV_POS;
364 Vector3D TargetVelocity = (TargetPosition - This_Missile.TARGET_PREV_POS) / Global_Timestep;
365
366 //Uses RdavNav Navigation APN Guidance System
367 //-----------------------------------------------
368
369 //Setup LOS rates and PN system
370 Vector3D LOS_Old = Vector3D.Normalize(TargetPositionPrev - MissilePositionPrev);
371 Vector3D LOS_New = Vector3D.Normalize(TargetPosition - MissilePosition);
372 Vector3D Rel_Vel = Vector3D.Normalize(TargetVelocity - MissileVelocity);
373
374 //And Assigners
375 Vector3D am = new Vector3D(1, 0, 0); double LOS_Rate; Vector3D LOS_Delta;
376 Vector3D MissileForwards = This_Missile.THRUSTERS[0].WorldMatrix.Backward;
377
378 //Vector/Rotation Rates
379 if (LOS_Old.Length() == 0)
380 { LOS_Delta = new Vector3D(0, 0, 0); LOS_Rate = 0.0; }
381 else
382 { LOS_Delta = LOS_New - LOS_Old; LOS_Rate = LOS_Delta.Length() / Global_Timestep; }
383
384 //-----------------------------------------------
385
386 //Closing Velocity
387 double Vclosing = (TargetVelocity - MissileVelocity).Length();
388
389 //If Under Gravity Use Gravitational Accel
390 Vector3D GravityComp = -RC.GetNaturalGravity();
391
392 //Calculate the final lateral acceleration
393 Vector3D LateralDirection = Vector3D.Normalize(Vector3D.Cross(Vector3D.Cross(Rel_Vel, LOS_New), Rel_Vel));
394 Vector3D LateralAccelerationComponent = LateralDirection * PNGain * LOS_Rate * Vclosing + LOS_Delta * 9.8 * (0.5 * PNGain); //Eases Onto Target Collision LOS_Delta * 9.8 * (0.5 * Gain)
395
396 //If Impossible Solution (ie maxes turn rate) Use Drift Cancelling For Minimum T
397 double OversteerReqt = (LateralAccelerationComponent).Length() / This_Missile.MissileAccel;
398 if (OversteerReqt > 0.98)
399 {
400 LateralAccelerationComponent = This_Missile.MissileAccel * Vector3D.Normalize(LateralAccelerationComponent + (OversteerReqt * Vector3D.Normalize(-MissileVelocity)) * 40);
401 }
402
403 //Calculates And Applies Thrust In Correct Direction (Performs own inequality check)
404 double ThrustPower = RdavUtils.Vector_Projection_Scalar(MissileForwards, Vector3D.Normalize(LateralAccelerationComponent)); //TESTTESTTEST
405 ThrustPower = This_Missile.IsLargeGrid ? MathHelper.Clamp(ThrustPower, 0.9, 1) : ThrustPower;
406
407 ThrustPower = MathHelper.Clamp(ThrustPower, 0.4, 1); //for improved thrust performance on the get-go
408 foreach (IMyThrust thruster in This_Missile.THRUSTERS)
409 {
410 if (thruster.ThrustOverride != (thruster.MaxThrust * ThrustPower)) //12 increment inequality to help conserve on performance
411 { thruster.ThrustOverride = (float)(thruster.MaxThrust * ThrustPower); }
412 }
413
414 //Calculates Remaining Force Component And Adds Along LOS
415 double RejectedAccel = Math.Sqrt(This_Missile.MissileAccel * This_Missile.MissileAccel - LateralAccelerationComponent.LengthSquared()); //Accel has to be determined whichever way you slice it
416 if (double.IsNaN(RejectedAccel)) { RejectedAccel = 0; }
417 LateralAccelerationComponent = LateralAccelerationComponent + LOS_New * RejectedAccel;
418
419 //-----------------------------------------------
420
421 //Guides To Target Using Gyros
422 am = Vector3D.Normalize(LateralAccelerationComponent + GravityComp);
423 double Yaw; double Pitch;
424 GyroTurn6(am, 18, 0.3, This_Missile.THRUSTERS[0], This_Missile.GYRO as IMyGyro, This_Missile.PREV_Yaw, This_Missile.PREV_Pitch, out Pitch, out Yaw);
425
426 //Updates For Next Tick Round
427 This_Missile.TARGET_PREV_POS = TargetPosition;
428 This_Missile.MIS_PREV_POS = MissilePosition;
429 This_Missile.PREV_Yaw = Yaw;
430 This_Missile.PREV_Pitch = Pitch;
431
432 //Detonates warheads in close proximity
433 if ((TargetPosition - MissilePosition).LengthSquared() < This_Missile.ArmDistance * This_Missile.ArmDistance && This_Missile.WARHEADS.Count > 0) //Arms
434 { foreach (var item in This_Missile.WARHEADS) { (item as IMyWarhead).IsArmed = true; } }
435 if ((TargetPosition - MissilePosition).LengthSquared() < This_Missile.FuseDistance * This_Missile.FuseDistance && This_Missile.WARHEADS.Count > 0) //A mighty earth shattering kaboom
436 { (This_Missile.WARHEADS[0] as IMyWarhead).Detonate(); }
437
438 }
439 #endregion
440
441 //Finds Currently Detected Enemy Location
442 #region Enemy Scan #RFC#
443 /*=================================================
444 Function: RFC Function bar #RFC#
445 --------------------------------------- */
446 Vector3D EnemyScan(MISSILE This_Missile)
447 {
448 //Targeting Module
449 //-----------------------------------------------
450
451 //Retrieves Target Position
452 var This_Missile_Director = This_Missile.TURRET as IMyLargeTurretBase;
453 var ENEMY_POS = new Vector3D();
454
455 //Weaponcore AI focus as first call
456 MyDetectedEntityInfo FocusTarget = api.GetAiFocus(Me.CubeGrid.EntityId, 0) ?? default(MyDetectedEntityInfo);
457 if (!FocusTarget.IsEmpty())
458 ENEMY_POS = FocusTarget.BoundingBox.Center;
459 else
460 {
461 // Otherwise, select highest threat target from all available targets
462 Dictionary<MyDetectedEntityInfo, float> ThreatsDict = new Dictionary<MyDetectedEntityInfo, float>();
463 api.GetSortedThreats(Me, ThreatsDict);
464
465 bool HasTarget = false;
466 float HighestThreat = -1;
467 var TargetPos = new Vector3D();
468
469 foreach(MyDetectedEntityInfo k in ThreatsDict.Keys)
470 {
471 if(ThreatsDict[k]>=MminimumOffRat && !k.IsEmpty() && k.Relationship == MyRelationsBetweenPlayerAndBlock.Enemies)
472 {
473 HasTarget = true;
474 if(ThreatsDict[k] > HighestThreat)
475 {
476 HighestThreat = ThreatsDict[k];
477 TargetPos = k.BoundingBox.Center;
478 }
479 }
480 }
481
482 if (HasTarget)
483 ENEMY_POS = TargetPos;
484
485 //Turret Detection As Secondary
486 else if (This_Missile_Director.GetTargetedEntity().IsEmpty() == false && !(OverrideToLongTargets && TemporaryTarget.IsEmpty()))
487 ENEMY_POS = This_Missile_Director.GetTargetedEntity().Position; //Also based on position for critical hits
488
489 //Finally If nothing Detected on turrets default to LOS
490 else
491 ENEMY_POS = RC.GetPosition() + RC.WorldMatrix.Forward * ((This_Missile.GYRO.GetPosition() - Me.GetPosition()).Length() + 300);
492 }
493
494 return ENEMY_POS;
495 }
496 #endregion
497
498 //Finds First Missile Available
499 #region RFC Initialise Missile Blocks #RFC#
500 /*=================================================
501 Function: RFC Function bar #RFC#
502 --------------------------------------- */
503 bool INIT_NEXT_MISSILE()
504 {
505
506 //Finds Missile Blocks (performs 1 gts)
507 List<IMyTerminalBlock> GYROS = new List<IMyTerminalBlock>();
508 GridTerminalSystem.GetBlocksOfType<IMyGyro>(GYROS, b => b.CustomName.Contains(MissileTag));
509 List<IMyTerminalBlock> TURRETS = new List<IMyTerminalBlock>();
510 GridTerminalSystem.GetBlocksOfType<IMyLargeTurretBase>(TURRETS, b => b.CustomName.Contains(TurretTag));
511 List<IMyThrust> THRUSTERS = new List<IMyThrust>();
512 GridTerminalSystem.GetBlocksOfType<IMyThrust>(THRUSTERS, b => b.CustomName.Contains(MissileTag));
513 List<IMyTerminalBlock> MERGES = new List<IMyTerminalBlock>();
514 GridTerminalSystem.GetBlocksOfType<IMyShipMergeBlock>(MERGES, b => b.CustomName.Contains(MissileTag));
515 List<IMyTerminalBlock> BATTERIES = new List<IMyTerminalBlock>();
516 GridTerminalSystem.GetBlocksOfType<IMyTerminalBlock>(BATTERIES, b => b.CustomName.Contains(MissileTag) && (b is IMyBatteryBlock || b is IMyReactor));
517 List<IMyTerminalBlock> WARHEADS = new List<IMyTerminalBlock>();
518 GridTerminalSystem.GetBlocksOfType<IMyWarhead>(WARHEADS, b => b.CustomName.Contains(MissileTag));
519
520 //Diagnostics For No Gyro
521 Lstrundata = "No More Missile (Gyros) Detected";
522
523 //Iterates Through List To Find Complete Missile Based On Gyro
524 foreach (var Key_Gyro in GYROS)
525 {
526 MISSILE NEW_MISSILE = new MISSILE();
527 NEW_MISSILE.GYRO = Key_Gyro;
528
529 Vector3D GyroPos = Key_Gyro.GetPosition();
530 double Distance = 40;
531
532 //Sorts And Selects Turrets
533 List<IMyTerminalBlock> TempTurrets = TURRETS.FindAll(b => (b.GetPosition() - GyroPos).LengthSquared() < 100000);
534 TempTurrets.Sort((x, y) => (x.GetPosition() - Key_Gyro.GetPosition()).LengthSquared().CompareTo((y.GetPosition() - Key_Gyro.GetPosition()).LengthSquared()));
535
536 //Sorts And Selects Batteries
537 List<IMyTerminalBlock> TempPower = BATTERIES.FindAll(b => (b.GetPosition() - GyroPos).LengthSquared() < Distance * Distance);
538 TempPower.Sort((x, y) => (x.GetPosition() - Key_Gyro.GetPosition()).LengthSquared().CompareTo((y.GetPosition() - Key_Gyro.GetPosition()).LengthSquared()));
539
540 //Sorts And Selects Merges
541 List<IMyTerminalBlock> TempMerges = MERGES.FindAll(b => (b.GetPosition() - GyroPos).LengthSquared() < Distance * Distance);
542 TempMerges.Sort((x, y) => (x.GetPosition() - Key_Gyro.GetPosition()).LengthSquared().CompareTo((y.GetPosition() - Key_Gyro.GetPosition()).LengthSquared()));
543
544 //Sorts And Selects Thrusters
545 NEW_MISSILE.THRUSTERS = THRUSTERS.FindAll(b => (b.GetPosition() - GyroPos).LengthSquared() < Distance * Distance);
546
547 //Sorts And Selects Warheads
548 NEW_MISSILE.WARHEADS = WARHEADS.FindAll(b => (b.GetPosition() - GyroPos).LengthSquared() < Distance * Distance);
549
550 //Checks All Key Blocks Are Present
551 bool HasTurret = TempTurrets.Count > 0;
552 bool HasPower = TempPower.Count > 0;
553 bool HasMerge = TempMerges.Count > 0;
554 bool HasThruster = NEW_MISSILE.THRUSTERS.Count > 0;
555
556 //Echos Some Useful Diagnostics
557 Lstrundata = "Last Missile Failed To Fire\nReason:" +
558 "\nHas Gyro: True" +
559 "\nHas Turret: " + HasTurret +
560 "\nHas Power: " + HasPower +
561 "\nHasMerge: " + HasMerge +
562 "\nHasThruster: " + HasThruster;
563
564 //Assigns and Exits Loop
565 if (HasTurret && HasPower && HasMerge && HasThruster)
566 {
567 NEW_MISSILE.TURRET = TempTurrets[0];
568 NEW_MISSILE.POWER = TempPower[0];
569 NEW_MISSILE.MERGE = TempMerges[0];
570 MISSILES.Add(NEW_MISSILE);
571 Lstrundata = "Launched Missile:" + MISSILES.Count;
572 return true;
573 }
574 }
575 return false;
576 }
577 #endregion
578
579 //Preps For Launch & Launches
580 #region RFC Prep-For Launch Subroutine #RFC#
581 /*=================================================
582 Function: RFC Function bar #RFC#
583 --------------------------------------- */
584 void PREP_FOR_LAUNCH(int INT)
585 {
586 Echo(INT + "");
587 MISSILE ThisMissile = MISSILES[INT];
588 ThisMissile.MissileMass = 0;
589 ThisMissile.MissileThrust = 0;
590
591 //Preps Battery For Launch
592 var POWER_A = ThisMissile.POWER;
593 if (ThisMissile.POWER != null && ThisMissile.POWER is IMyBatteryBlock)
594 {
595 POWER_A.ApplyAction("OnOff_On");
596 (POWER_A as IMyBatteryBlock).ChargeMode = ChargeMode.Discharge;
597 ThisMissile.MissileMass += POWER_A.Mass;
598 }
599
600 //Removes Thrusters That Are Still on the same Grid As launcher
601 List<IMyThrust> TemporaryThrust = new List<IMyThrust>();
602 TemporaryThrust.AddRange(ThisMissile.THRUSTERS);
603 for (int i = 0; i < TemporaryThrust.Count; i++)
604 {
605 var item = TemporaryThrust[i];
606 if (item.CubeGrid != ThisMissile.GYRO.CubeGrid)
607 { ThisMissile.THRUSTERS.Remove(item); continue; }
608 }
609 if (ThisMissile.THRUSTERS.Count == 0)
610 {
611 Lstrundata = "Missile Failed To Fire\nReason: No Detectable Thrusters On Missile Or Missile Still Attached To Launcher";
612 MISSILES.Remove(ThisMissile);
613 return;
614 }
615
616 //Retrieves Largest Thrust Direction
617 Dictionary<Vector3D, double> ThrustDict = new Dictionary<Vector3D, double>();
618 foreach (IMyThrust item in ThisMissile.THRUSTERS)
619 {
620 Vector3D Fwd = item.WorldMatrix.Forward;
621 double Thrval = item.MaxEffectiveThrust;
622
623 if (ThrustDict.ContainsKey(Fwd) == false)
624 { ThrustDict.Add(Fwd, Thrval); }
625 else
626 { ThrustDict[Fwd] = ThrustDict[Fwd] + Thrval; }
627 }
628 List<KeyValuePair<Vector3D, double>> ThrustList = ThrustDict.ToList();
629 ThrustList.Sort((x, y) => y.Value.CompareTo(x.Value));
630 Vector3D ThrForward = ThrustList[0].Key;
631
632 //Preps Thrusters For Launch (removes any not on grid)
633 TemporaryThrust = new List<IMyThrust>();
634 TemporaryThrust.AddRange(ThisMissile.THRUSTERS);
635 for (int i = 0; i < TemporaryThrust.Count; i++)
636 {
637 var item = TemporaryThrust[i];
638
639 //Retrieves Thrusters Only Going In The Forward
640 if (item.WorldMatrix.Forward != ThrForward)
641 { item.ApplyAction("OnOff_On"); ThisMissile.THRUSTERS.Remove(item); continue; }
642
643 //Runs Std Operations
644 item.ApplyAction("OnOff_On");
645 double ThisThrusterThrust = (item as IMyThrust).MaxThrust;
646 (item as IMyThrust).ThrustOverride = (float)ThisThrusterThrust;
647 RdavUtils.DiagTools.Diag_Plot(Me, ThisThrusterThrust);
648 ThisMissile.MissileThrust += ThisThrusterThrust;
649 ThisMissile.MissileMass += item.Mass;
650 }
651
652 //Removes Any Warheads Not On The Grid
653 List<IMyTerminalBlock> TemporaryWarheads = new List<IMyTerminalBlock>();
654 TemporaryWarheads.AddRange(ThisMissile.WARHEADS);
655 for (int i = 0; i < ThisMissile.WARHEADS.Count; i++)
656 {
657 var item = TemporaryWarheads[i];
658
659 if (item.CubeGrid != ThisMissile.GYRO.CubeGrid)
660 { ThisMissile.WARHEADS.Remove(item); continue; }
661
662 ThisMissile.MissileMass += item.Mass;
663 }
664
665 //-----------------------------------------------------
666
667 //Adds Additional Mass & Sets Accel (ovverrides If Possible)
668 ThisMissile.MissileMass += ThisMissile.GYRO.Mass;
669 ThisMissile.MissileMass += ThisMissile.MERGE.Mass;
670 double number;
671 if (double.TryParse(ThisMissile.GYRO.CustomData, out number))
672 { double.TryParse(ThisMissile.GYRO.CustomData, out ThisMissile.MissileMass); }
673 ThisMissile.MissileAccel = ThisMissile.MissileThrust / ThisMissile.MissileMass;
674
675 //Sets Grid Type
676 ThisMissile.IsLargeGrid = ThisMissile.GYRO.CubeGrid.GridSizeEnum == MyCubeSize.Large;
677 ThisMissile.FuseDistance = FuseDistance;
678 ThisMissile.ArmDistance = ArmDistance;
679
680 }
681 #endregion
682
683 //Utils
684 #region RFC Function bar #RFC#
685 string LeftPad = " ";
686 string Scriptname = "Rdav Missile Guidance";
687 /*=================================================
688 Function: RFC Function bar #RFC#
689 --------------------------------------- */
690 string[] FUNCTION_BAR = new string[] { "", " ===||===", " ==|==|==", " =|====|=", " |======|", " ======" };
691 int FUNCTION_TIMER = 0; //For Runtime Indicator
692 void OP_BAR()
693 {
694 FUNCTION_TIMER++;
695 Echo(LeftPad + "~ " + Scriptname + " ~ \n " + FUNCTION_BAR[FUNCTION_TIMER] + "");
696 if (FUNCTION_TIMER == 5) { FUNCTION_TIMER = 0; }
697 }
698 #endregion
699
700 //Utils For Maths Functions
701 #region RdavUtils #RFC#
702 /*=================================================
703 Generic Rdav Utils Class For Math & Functionality
704 -------------------------------------------- */
705 static class RdavUtils
706 {
707
708 //Use For Solutions To Quadratic Equation
709 public static bool Quadractic_Solv(double a, double b, double c, out double X1, out double X2)
710 {
711 //Default Values16
712 X1 = 0;
713 X2 = 0;
714
715 //Discrim Check
716 Double Discr = b * b - 4 * c;
717 if (Discr < 0)
718 { return false; }
719
720 //Calcs Values
721 else
722 {
723 X1 = (-b + Math.Sqrt(Discr)) / (2 * a);
724 X2 = (-b - Math.Sqrt(Discr)) / (2 * a);
725 }
726 return true;
727 }
728
729 //Handles Calculation Of Area Of Diameter
730 public static double CalculateArea(double OuterDiam, double InnerDiam)
731 {
732 //Handles Calculation Of Area Of Diameter
733 //=========================================
734 double PI = 3.14159;
735 double Output = ((OuterDiam * OuterDiam * PI) / 4) - ((InnerDiam * InnerDiam * PI) / 4);
736 return Output;
737 }
738
739 //Use For Magnitudes Of Vectors In Directions (0-IN.length)
740 public static double Vector_Projection_Scalar(Vector3D IN, Vector3D Axis_norm)
741 {
742 double OUT = 0;
743 OUT = Vector3D.Dot(IN, Axis_norm);
744 if (OUT == double.NaN)
745 { OUT = 0; }
746 return OUT;
747 }
748
749 //Use For Vector Components, Axis Normalized, In not (vector 0 - in.length)
750 public static Vector3D Vector_Projection_Vector(Vector3D IN, Vector3D Axis_norm)
751 {
752 Vector3D OUT = new Vector3D();
753 OUT = Vector3D.Dot(IN, Axis_norm) * Axis_norm;
754 if (OUT + "" == "NaN")
755 { OUT = new Vector3D(); ; }
756 return OUT;
757 }
758
759 //Use For Intersections Of A Sphere And Ray
760 public static bool SphereIntersect_Solv(BoundingSphereD Sphere, Vector3D LineStart, Vector3D LineDirection, out Vector3D Point1, out Vector3D Point2)
761 {
762 //starting Values
763 Point1 = new Vector3D();
764 Point2 = new Vector3D();
765
766 //Spherical intersection
767 Vector3D O = LineStart;
768 Vector3D D = LineDirection;
769 Double R = Sphere.Radius;
770 Vector3D C = Sphere.Center;
771
772 //Calculates Parameters
773 Double b = 2 * (Vector3D.Dot(O - C, D));
774 Double c = Vector3D.Dot((O - C), (O - C)) - R * R;
775
776 //Calculates Values
777 Double t1, t2;
778 if (!Quadractic_Solv(1, b, c, out t1, out t2))
779 { return false; } //does not intersect
780 else
781 {
782 Point1 = LineStart + LineDirection * t1;
783 Point2 = LineStart + LineDirection * t2;
784 return true;
785 }
786 }
787
788 //Basic Gets Predicted Position Of Enemy (Derived From Keen Code)
789 public static Vector3D GetPredictedTargetPosition2(IMyTerminalBlock shooter, Vector3 ShipVel, MyDetectedEntityInfo target, float shotSpeed)
790 {
791 Vector3D predictedPosition = target.Position;
792 Vector3D dirToTarget = Vector3D.Normalize(predictedPosition - shooter.GetPosition());
793
794 //Run Setup Calculations
795 Vector3 targetVelocity = target.Velocity;
796 targetVelocity -= ShipVel;
797 Vector3 targetVelOrth = Vector3.Dot(targetVelocity, dirToTarget) * dirToTarget;
798 Vector3 targetVelTang = targetVelocity - targetVelOrth;
799 Vector3 shotVelTang = targetVelTang;
800 float shotVelSpeed = shotVelTang.Length();
801
802 if (shotVelSpeed > shotSpeed)
803 {
804 // Shot is too slow
805 return Vector3.Normalize(target.Velocity) * shotSpeed;
806 }
807 else
808 {
809 // Run Calculations
810 float shotSpeedOrth = (float)Math.Sqrt(shotSpeed * shotSpeed - shotVelSpeed * shotVelSpeed);
811 Vector3 shotVelOrth = dirToTarget * shotSpeedOrth;
812 float timeDiff = shotVelOrth.Length() - targetVelOrth.Length();
813 var timeToCollision = timeDiff != 0 ? ((shooter.GetPosition() - target.Position).Length()) / timeDiff : 0;
814 Vector3 shotVel = shotVelOrth + shotVelTang;
815 predictedPosition = timeToCollision > 0.01f ? shooter.GetPosition() + (Vector3D)shotVel * timeToCollision : predictedPosition;
816 return predictedPosition;
817 }
818 }
819
820 //Generic Diagnostics Tools
821 public static class DiagTools
822 {
823 //Used For Customdata Plotting
824 public static void Diag_Plot(IMyTerminalBlock Block, object Data1)
825 {
826 Block.CustomData = Block.CustomData + Data1 + "\n";
827 }
828
829 //Used For Fast Finding/Dynamically Renaming A Block Based On Type
830 public static void Renam_Block_Typ(IMyGridTerminalSystem GTS, string RenameTo)
831 {
832 List<IMyTerminalBlock> TempCollection = new List<IMyTerminalBlock>();
833 GTS.GetBlocksOfType<IMyRadioAntenna>(TempCollection);
834 if (TempCollection.Count < 1)
835 { return; }
836 TempCollection[0].CustomName = RenameTo;
837 }
838
839 //Used For Fast Finding/Dynamically Renaming A Block Based On CustomData
840 public static void Renam_Block_Cust(IMyGridTerminalSystem GTS, string customnam, string RenameTo)
841 {
842 List<IMyTerminalBlock> TempCollection = new List<IMyTerminalBlock>();
843 GTS.GetBlocksOfType<IMyTerminalBlock>(TempCollection, a => a.CustomData == customnam);
844 if (TempCollection.Count < 1)
845 { return; }
846 else
847 { TempCollection[0].CustomName = RenameTo; }
848 }
849
850 }
851 }
852
853 //Quick Echoing & Rounding For Values
854 void QuickEcho(object This, string Title = "")
855 {
856 if (This is Vector3D)
857 { Echo(Title + " " + Vector3D.Round(((Vector3D)This), 3)); }
858 else if (This is double)
859 { Echo(Title + " " + Math.Round(((double)This), 3)); }
860 else
861 { Echo(Title + " " + This); }
862 }
863
864 //Quick GotHere Diagnostics
865 void GotHere(int number)
866 {
867 Echo("GotHere " + number);
868 }
869
870 #endregion
871
872 //Use For Turning
873 #region GyroTurnMis #RFC#
874 /*=======================================================================================
875 Function: GyroTurn6
876 ---------------------------------------
877 function will: A Variant of PD damped gyroturn used for missiles
878 //----------==--------=------------=-----------=---------------=------------=-----=-----*/
879 void GyroTurn6(Vector3D TARGETVECTOR, double GAIN, double DAMPINGGAIN,IMyTerminalBlock REF, IMyGyro GYRO, double YawPrev, double PitchPrev, out double NewPitch, out double NewYaw)
880 {
881 //Pre Setting Factors
882 NewYaw = 0;
883 NewPitch = 0;
884
885 //Retrieving Forwards And Up
886 Vector3D ShipUp = REF.WorldMatrix.Up;
887 Vector3D ShipForward = REF.WorldMatrix.Backward; //Backward for thrusters
888
889 //Create And Use Inverse Quatinion
890 Quaternion Quat_Two = Quaternion.CreateFromForwardUp(ShipForward, ShipUp);
891 var InvQuat = Quaternion.Inverse(Quat_Two);
892
893 Vector3D DirectionVector = TARGETVECTOR; //RealWorld Target Vector
894 Vector3D RCReferenceFrameVector = Vector3D.Transform(DirectionVector, InvQuat); //Target Vector In Terms Of RC Block
895
896 //Convert To Local Azimuth And Elevation
897 double ShipForwardAzimuth = 0; double ShipForwardElevation = 0;
898 Vector3D.GetAzimuthAndElevation(RCReferenceFrameVector, out ShipForwardAzimuth, out ShipForwardElevation);
899
900 //Post Setting Factors
901 NewYaw = ShipForwardAzimuth;
902 NewPitch = ShipForwardElevation;
903
904 //Applies Some PID Damping
905 ShipForwardAzimuth = ShipForwardAzimuth + DAMPINGGAIN * ((ShipForwardAzimuth - YawPrev) / Global_Timestep);
906 ShipForwardElevation = ShipForwardElevation + DAMPINGGAIN * ((ShipForwardElevation - PitchPrev) / Global_Timestep);
907
908 //Does Some Rotations To Provide For any Gyro-Orientation
909 var REF_Matrix = MatrixD.CreateWorld(REF.GetPosition(), (Vector3)ShipForward, (Vector3)ShipUp).GetOrientation();
910 var Vector = Vector3.Transform((new Vector3D(ShipForwardElevation, ShipForwardAzimuth, 0)), REF_Matrix); //Converts To World
911 var TRANS_VECT = Vector3.Transform(Vector, Matrix.Transpose(GYRO.WorldMatrix.GetOrientation())); //Converts To Gyro Local
912
913 //Logic Checks for NaN's
914 if (double.IsNaN(TRANS_VECT.X) || double.IsNaN(TRANS_VECT.Y) || double.IsNaN(TRANS_VECT.Z))
915 { return; }
916
917 //Applies To Scenario
918 GYRO.Pitch = (float)MathHelper.Clamp((-TRANS_VECT.X) * GAIN, -1000, 1000);
919 GYRO.Yaw = (float)MathHelper.Clamp(((-TRANS_VECT.Y)) * GAIN, -1000, 1000);
920 GYRO.Roll = (float)MathHelper.Clamp(((-TRANS_VECT.Z)) * GAIN, -1000, 1000);
921 GYRO.GyroOverride = true;
922 }
923 #endregion
924
925public class WcPbApi
926{
927 private Action<IMyTerminalBlock, IDictionary<MyDetectedEntityInfo, float>> _getSortedThreats;
928 private Func<long, int, Sandbox.ModAPI.Ingame.MyDetectedEntityInfo> _getAiFocus;
929 private Func<Sandbox.ModAPI.Ingame.IMyTerminalBlock, int, Sandbox.ModAPI.Ingame.MyDetectedEntityInfo> _getWeaponTarget;
930
931 public bool Activate(IMyTerminalBlock pbBlock)
932 {
933 var dict = pbBlock.GetProperty("WcPbAPI")?.As<Dictionary<string, Delegate>>().GetValue(pbBlock);
934 if (dict == null) throw new Exception($"WcPbAPI failed to activate");
935 return ApiAssign(dict);
936 }
937
938 public bool ApiAssign(IReadOnlyDictionary<string, Delegate> delegates)
939 {
940 if (delegates == null)
941 return false;
942 AssignMethod(delegates, "GetSortedThreats", ref _getSortedThreats);
943 AssignMethod(delegates, "GetAiFocus", ref _getAiFocus);
944 AssignMethod(delegates, "GetWeaponTarget", ref _getWeaponTarget);
945 return true;
946 }
947
948 private void AssignMethod<T>(IReadOnlyDictionary<string, Delegate> delegates, string name, ref T field) where T : class
949 {
950 if (delegates == null) {
951 field = null;
952 return;
953 }
954 Delegate del;
955 if (!delegates.TryGetValue(name, out del))
956 throw new Exception($"{GetType().Name} :: Couldn't find {name} delegate of type {typeof(T)}");
957 field = del as T;
958 if (field == null)
959 throw new Exception(
960 $"{GetType().Name} :: Delegate {name} is not type {typeof(T)}, instead it's: {del.GetType()}");
961 }
962 public void GetSortedThreats(IMyTerminalBlock pbBlock, IDictionary<MyDetectedEntityInfo, float> collection) =>
963 _getSortedThreats?.Invoke(pbBlock, collection);
964 public MyDetectedEntityInfo? GetAiFocus(long shooter, int priority = 0) => _getAiFocus?.Invoke(shooter, priority);
965 public MyDetectedEntityInfo? GetWeaponTarget(Sandbox.ModAPI.Ingame.IMyTerminalBlock weapon, int weaponId = 0) =>
966 _getWeaponTarget?.Invoke(weapon, weaponId);
967}
968