Guest User

Gun Fire Control System Mk3 (fix by gergelypuskaval)

a guest
May 14th, 2017
206
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 76.49 KB | None | 0 0
  1. // Gun Fire Control System (GFCS) script by xmuni
  2. // Mark 3
  3.  
  4. // Space Engineers in-game script for the programmable block
  5. // This script coordinates multiple ship turrets in order to accurately aim at a moving target with deflection (lead).
  6. // Visit the mod page for instructions on how to use this script: http://steamcommunity.com/sharedfiles/filedetails/?id=516792514
  7.  
  8. // This script uses the BlueG_Radar mod and code by Bradley Regula: http://steamcommunity.com/sharedfiles/filedetails/?id=369578004
  9. // This script uses the GetDirectionTo function by robbym: http://forum.keenswh.com/threads/getdirectionto-function.7242345
  10.  
  11.  
  12.  
  13.  
  14. //**************************//
  15. //******* Settings *******//
  16. //*************************//
  17.  
  18.  
  19.  
  20. // 1. BASIC SETTINGS
  21.  
  22.  
  23. bool vanilla_tracking = false;          // set to true to use this script without the BlueG Radar. (*) See the mod page for instructions.
  24. bool only_shoot_in_range = true ;       // if true, Auto Fire will only fire a turret's guns if the target is in range.
  25. double defaultconvergence = 1000;       // distance (in meters) that the guns will converge at if there's no target. Unused if a convergence text panel is found.
  26. bool no_director = false;               // if true, the script won't look for a director and instead will just look for the closest target (it defaults to Auto Target).
  27. bool switch_to_controlled = true;       // if true, controlling a director will make it the currently used director. If false, only the first available director (starting from Director1) will be used, each other director will be used only if all previous directors haven't been found. If this is true and Auto Target is on, controlling a director will temporarily disable Auto Target.
  28. bool idle_if_no_targets = true;         // (only for Auto Target mode) if true, rotor turrets will return to idle position (all rotors turn to zero or between the max/min angle) if the sensors don't detect any target. If false, the rotors will just stop wherever they are.
  29.  
  30.  
  31. // (*) IMPORTANT NOTE: This is only for use without the BlueG Radar. While it's on, the script will track the movement of the director to calculate speed (thus without needing the BlueG Radar).
  32. // However, you will have to obtain the target distance (convergence) by other means, and supply it to the text panel for the convergence.
  33. // You will also want to use the timer toggle Auto Lead to turn off lead calculation when you don't need it (i.e. when firing at stationary targets or at empty space). Otherwise it will interfere with the turret aim while you're turning the director.
  34.  
  35.  
  36. // these are the default settings for the timer toggles (only used in case the relative timer toggle is not found). True = on; False = off.
  37. bool default_autolead = true;           // true = guns will lead a moving target; false = guns will always shoot where you aim.
  38. bool default_autoaim = true;            // (only used when autotarget is off) true = turrets will aim for the center of mass of the current target; false = turrets will aim exactly where you're pointing them (with or without lead)
  39. bool default_autotarget = false;        // true = turrets will automatically aim at the closest target, disregarding the director; false = turrets will always follow the director
  40. bool default_autofire = false;          // true = guns will fire if there's a target in front and in range; false = guns won't automatically fire
  41.  
  42.  
  43. // 2. NAMES OF BLOCKS
  44.  
  45.  
  46. // text panels that the script reads from
  47. string panel_turret_groups = "Text panel GFCS";           // text panel or LCD panel with the settings for each turret group. If you have multiple panels with this name, the script will read from all of them. Turn off a panel if you want the script to temporarily ignore it.
  48. string panel_convergence = "Text panel convergence";      // (optional) text panel or LCD panel with the convergence (pretty much necessary if you're using vanilla tracking; otherwise can still be used to set the default convergence)
  49.  
  50. // terms that make up the names of the director blocks
  51. string DIRECTOR_BASE_NAME = "Director";                   // name of each director turret block (followed by the director number) and base name of the rotor director blocks (again, followed by the director number)
  52. string name_director_remote = "Remote Control";           // only for rotor-based directors (e.g. "Director Remote Control)
  53. string name_director_rotorx = "RotorX";                   // only for rotor-based directors (e.g. "Director RotorX)
  54. string name_director_rotory = "RotorY";                   // only for rotor-based directors (e.g. "Director RotorY)
  55.  
  56. // you can use the Super Sensor mod (http://steamcommunity.com/sharedfiles/filedetails/?id=504736273) instead of (or in addition to) the BlueG Radar in order to detect targets.
  57. string name_super_sensor = "Super Sensor";                // the name of each super sensor should contain this.
  58.  
  59. // optional timer blocks that act as toggles
  60. string name_autolead = "Timer Block Auto Lead";           // (optional) Name of the timer block that toggles target leading (deflection). Very useful if you're using vanilla tracking.
  61. string name_autotarget = "Timer Block Auto Target";       // (optional) Name of the timer block that toggles automatic targeting (the turrets aim at the closest target)
  62. string name_autoaim = "Timer Block Auto Aim";             // (optional) Name of the timer block that toggles Auto Aim (whether the turrets will aim at the center of mass of the current target, or exactly where you're aiming)
  63. string name_autofire = "Timer Block Auto Fire";           // (optional) Name of the timer block that toggles Auto Fire (whether the turrets will fire automatically if a target is in range)
  64. string name_fire_all = "Timer Block Fire All";            // (optional) Same as Auto Fire, but works even when there is no target detected (i.e. turrets will shoot at empty space if the director points there)
  65. string name_idle = "Timer Block Idle";                    // (optional) Name of the timer block that toggles turrets idling (only for rotor turrets). All rotors will turn to zero and all pistons will retract.
  66.  
  67. // optional text/LCD panels
  68. string panel_warnings = "Text panel warnings";            // this will tell you whether turrets are missing or incomplete
  69. string panel_status = "Text panel status";                // this will display information on the script's status
  70. string panel_targets = "Text panel targets";              // this will list all of the targets found
  71. string panel_position = "Text panel position";            // this will display the target's position
  72. string panel_speed = "Text panel speed";                  // this will display the target's speed
  73. string panel_acceleration = "Text panel acceleration";    // this will display the target's acceleration
  74. string panel_your_ship_speed = "Text panel your speed";   // this will display the speed of your own ship
  75. string panel_override = "Text panel override";            // the script will read or write the target's coordinates to or from this text panel depending on whether "write_override_info" is set to false or true. The text panel should be turned on (turn it off to temporarily suspend the override).
  76.  
  77.  
  78. // 3. ADVANCED SETTINGS
  79.  
  80.  
  81. bool write_override_info = false;    // if false (default), the script will read the target's coordinates from the override text panel and use them instead of the using info from the director and sensors. If true, the script will instead write the info from its director and sensors to the override text panel (which another script can then read from).
  82. double turret_threshold = 4;         // each turret's guns will be turned off if they are off target by at least this many degrees(*). If you want the guns to be always on, set this to 180 or more.
  83. double director_threshold = 4;       // when auto aim is on, the turrets will only lock on to targets within this many degrees to the director(*)
  84. int frequency = 60;                  // how many times per second this script will run (only applies to turret movement). Set it to 0 or 60+ to have it run constantly (i.e. 60 times/second).
  85. int lists_size = 10;                 // how many instances of speed and acceleration are averaged to get the current speed and acceleration. 60 = 1 second.
  86. int lists_size_vanilla = 120;        // same as lists_size, but only for vanilla tracking. 60 = latest 1 second tracked.
  87. bool lock_director = true;           // if true, the script will lock the director's rotors if the director isn't being controlled. This is to prevent it from swinging around while you're turning the ship from another cockpit.
  88. bool moving_ship = true;             // if true, the script will account for your own ship's movement when aiming the turrets. Note that rotating the ship may temporarily reduce the turrets' accuracy if they're far away from the ship's center of mass.
  89.  
  90.  
  91. // (*) This is both to the right/left and up/down, so the total angle is double this number.
  92.  
  93.  
  94.  
  95. //***********************************//
  96. //******* end of settings ********//
  97. //******* ignore all below ******//
  98. //**********************************//
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128. // parsing the sensor name
  129. string sensorBaseName = "[radarg"; // not a typo
  130. System.Text.RegularExpressions.Regex rxVector = new System.Text.RegularExpressions.Regex(
  131.     @"\<([+-]?[0-9]+(?:\.[0-9]*)?),([+-]?[0-9]+(?:\.[0-9]*)?),([+-]?[0-9]+(?:\.[0-9]*)?)\>");
  132.  
  133. int time_counter = 0;
  134. int counter = 0;
  135. int game_rate = 60; // how many times the game updates (not the framerate). Keep at 60 for target tracking to work properly.
  136. int starting_directors = 0;
  137. int directors_found = 0;
  138. int current_director = 0;
  139. int total_number_of_turrets = 0;
  140. int turrets_in_range = 0;
  141. bool found_controlled = false;
  142. bool using_override = false;
  143. bool empty_space_target = false;
  144. bool error = false;
  145.  
  146. string warnings = "";
  147. string found_groups = "";
  148.  
  149. bool firstrun = true;
  150.  
  151.  
  152. IMyTerminalBlock textpanel_override = null;
  153. IMyTerminalBlock timerblock_fire = null;
  154.  
  155. List<IMyTerminalBlock> sensors = new List<IMyTerminalBlock>();
  156. List<IMyTerminalBlock> super_sensors = new List<IMyTerminalBlock>();
  157.  
  158.  
  159. // list of objects detected by all sensors
  160. List<Vector3D> targets = new List<Vector3D>();
  161.  
  162. Vector3D targetposition = new Vector3D(0,0,0);
  163. Vector3D speed = new Vector3D(0,0,0);
  164. Vector3D acceleration = new Vector3D(0,0,0);
  165.  
  166. Vector3D platformposition = new Vector3D(0,0,0); // the position of the grid with all the turrets
  167. Vector3D platform_speed = new Vector3D(0,0,0); // the speed of the platform where the turrets are placed
  168.  
  169. List<Vector3D> speeds = new List<Vector3D>(); // list of the latest speeds recorded
  170. List<Vector3D> accelerations = new List<Vector3D>(); // list of the latest accelerations recorded
  171.  
  172. Vector3D center_position = new Vector3D(0,0,0);        // location of the director
  173. Vector3D forward_position = new Vector3D(0,0,0);    // front of the director
  174. Vector3D up_position = new Vector3D(0,0,0);            // above the director
  175.  
  176. Dictionary<string, int> turret_group_counts = new Dictionary<string, int>(); // list of turret groups and their starting number of turrets. Each group is counted once and then checked every 10 seconds.
  177.  
  178.  
  179.  
  180. void Main()
  181. {
  182.      
  183.     if(firstrun)
  184.     {
  185.         starting_directors = count_directors();
  186.         textpanel_override = getblock_nullable(panel_override);
  187.         timerblock_fire = getblock_nullable(name_fire_all);
  188.         GridTerminalSystem.GetBlocksOfType<IMySensorBlock>(sensors, x => x.CustomName.Contains(sensorBaseName));
  189.         GridTerminalSystem.GetBlocksOfType<IMySensorBlock>(super_sensors, x => x.CustomName.Contains(name_super_sensor));
  190.         firstrun=false;
  191.     }
  192.      
  193.     warnings = ""; // clear warnings
  194.     error = false; // reset error flag
  195.     using_override = false; // reset override flag
  196.     empty_space_target = false;
  197.     directors_found = 0; // reset the number of directors
  198.     turrets_in_range = 0; // reset the number of turrets in range
  199.      
  200.     time_counter++; // add 1/60th of a second (16.7 milliseconds)
  201.     counter++;
  202.      
  203.     // if over 60, loop back to 1
  204.     if(time_counter>game_rate)
  205.         time_counter=1;
  206.      
  207.     // make sure that 60 (the game_rate) can be divided by the frequency
  208.     if(frequency<=0 || frequency>game_rate)
  209.         frequency=game_rate;
  210.      
  211.     read_convergence();
  212.      
  213.     // look through all the directors and choose which to use (setting the three orientation positions)
  214.     if(time_counter%(game_rate/frequency)==0 && no_director==false)
  215.         update_director_positions(starting_directors);
  216.  
  217.     // for debugging
  218.     write_to_panel("Text panel center", center_position, "Center");
  219.     write_to_panel("Text panel forward", forward_position, "Forward");
  220.     write_to_panel("Text panel up", up_position, "Up");
  221.      
  222.     // update all the targets according to the sensors
  223.     get_targets();
  224.      
  225.      
  226.     // if there's an override panel that's turned on (and the script is set to read from it), use those coordinates
  227.     if(write_override_info==false && textpanel_override!=null && (textpanel_override as IMyFunctionalBlock).Enabled)
  228.     {
  229.         Vector3D newposition = parse_override(textpanel_override as IMyTextPanel);
  230.         using_override = true;
  231.          
  232.         if(check_block_toggle(name_autolead, default_autolead))
  233.         {
  234.             Vector3D newspeed = get_speed_or_acceleration(targetposition, newposition);
  235.             newspeed = update_speeds_or_accelerations(speeds, newspeed); // adds this speed to the list of speeds and returns the average speed
  236.             Vector3D newacceleration = get_speed_or_acceleration(speed, newspeed);
  237.             acceleration = update_speeds_or_accelerations(accelerations, newacceleration); // adds this acceleration to the list of accelerations and returns the average acceleration
  238.             speed = newspeed;
  239.         }
  240.         else
  241.             reset_target();
  242.          
  243.         targetposition = newposition;
  244.     }
  245.      
  246.      
  247.     // only used for tracking without RadarG sensors. Tracks the director movement. Requires manual convergence update.
  248.     else if(vanilla_tracking)
  249.     {
  250.         lists_size = lists_size_vanilla; // since the director is more shaky, change the number of positions registered (more = smoother)
  251.          
  252.         Vector3D newposition = extend(center_position, center_position, forward_position, defaultconvergence); // get the position in front of the director (defaultconvergence is manually supplied)
  253.          
  254.         // if Auto Aim is on, lead according to its movement
  255.         if(check_block_toggle(name_autolead, default_autolead))
  256.         {
  257.             Vector3D newspeed = get_speed_or_acceleration(targetposition, newposition);
  258.             newspeed = update_speeds_or_accelerations(speeds, newspeed); // adds this speed to the list of speeds and returns the average speed
  259.             Vector3D newacceleration = get_speed_or_acceleration(speed, newspeed);
  260.             acceleration = update_speeds_or_accelerations(accelerations, newacceleration); // adds this acceleration to the list of accelerations and returns the average acceleration
  261.             speed = newspeed;
  262.         }
  263.          
  264.         else
  265.             reset_target();
  266.          
  267.         targetposition = newposition;
  268.     }
  269.      
  270.      
  271.     // if there are no targets, aim in front of the director
  272.     else if(targets.Count<1)
  273.     {
  274.         if(no_director)
  275.             targetposition = new Vector3D(0,0,0); // stop all turrets
  276.         else
  277.         {
  278.             reset_target();
  279.             empty_space_target = true;
  280.             targetposition = extend(center_position, center_position, forward_position, defaultconvergence);
  281.         }
  282.     }
  283.  
  284.     // if there's at least one target
  285.     else
  286.     {
  287.         Vector3D newposition = new Vector3D(0,0,0);
  288.          
  289.         // either aim at the closest target, if Auto Target is on
  290.         if( (check_block_toggle(name_autotarget, default_autotarget)==true) || no_director==true )
  291.         {
  292.             newposition = get_closest_target();
  293.              
  294.             // update the target's speed and acceleration (for leading), unless lead is disabled
  295.             if(check_block_toggle(name_autolead, default_autolead))
  296.             {
  297.                 Vector3D newspeed = get_speed_or_acceleration(targetposition, newposition);
  298.                 newspeed = update_speeds_or_accelerations(speeds, newspeed); // adds this speed to the list of speeds and returns the average speed
  299.                 Vector3D newacceleration = get_speed_or_acceleration(speed, newspeed);
  300.                 acceleration = update_speeds_or_accelerations(accelerations, newacceleration); // adds this acceleration to the list of accelerations and returns the average acceleration
  301.                 speed = newspeed;
  302.             }
  303.             else
  304.                 reset_target();
  305.              
  306.             targetposition = newposition;
  307.         }
  308.      
  309.         // or aim at the frontmost target (if auto aim is off, aim at the right distance in front of the director)
  310.         else
  311.         {
  312.             newposition = gettargetposition();
  313.              
  314.             if(newposition == new Vector3D(0,0,0)) // if no front target was found
  315.             {
  316.                 empty_space_target = true;
  317.                 targetposition = extend(center_position, center_position, forward_position, defaultconvergence); // aim directly in front of the director
  318.             }
  319.              
  320.             else // if there's a front target
  321.             {
  322.                 if(check_block_toggle(name_autolead, default_autolead)) // update the target's speed and acceleration (for leading), unless lead is disabled
  323.                 {
  324.                     Vector3D newspeed = get_speed_or_acceleration(targetposition, newposition);
  325.                     newspeed = update_speeds_or_accelerations(speeds, newspeed); // adds this speed to the list of speeds and returns the average speed
  326.                     Vector3D newacceleration = get_speed_or_acceleration(speed, newspeed);
  327.                     acceleration = update_speeds_or_accelerations(accelerations, newacceleration); // adds this acceleration to the list of accelerations and returns the average acceleration
  328.                     speed = newspeed;
  329.                 }
  330.                 else
  331.                     reset_target();
  332.  
  333.                 targetposition = newposition;
  334.             }
  335.         }
  336.     }
  337.      
  338.      
  339. //    write_to_panel(panel_position, targetposition, "Position");
  340. //    write_to_panel(panel_speed, speed, "Speed");
  341. //    write_to_panel(panel_acceleration, acceleration, "Acceleration");
  342. //    write_override(panel_override_write, targetposition, speed, acceleration);
  343.  
  344.     // write the coordinates to the override text panel
  345.     if(write_override_info)
  346.         write_override(panel_override, targetposition);
  347.      
  348.     // update the platform speed
  349.     if(moving_ship)
  350.     {
  351.         Vector3D newplatformposition = new Vector3D(0,0,0);
  352.          
  353.         IMyCubeBlock this_pb = Me as IMyCubeBlock;
  354.  
  355.         IMyCubeGrid ship = this_pb.CubeGrid;
  356.         newplatformposition = ship.GetPosition(); // gets the pivot point of this ship
  357.      
  358.         platform_speed = get_speed_or_acceleration(platformposition, newplatformposition);
  359.         platformposition = newplatformposition;
  360.         write_to_panel(panel_your_ship_speed, platform_speed, "Your speed");
  361.     }
  362.      
  363.     total_number_of_turrets=0;
  364.      
  365. //    acceleration *= 1.16; // increase acceleration by 16% to make up for the smoothing
  366.      
  367.     // read the turret groups from the text panel and aim them at the target
  368.     if(time_counter%(game_rate/frequency)==0)
  369.         parse_turret_groups(targetposition);
  370.      
  371.     // display warnings on missing blocks
  372.     write_warning(warnings);
  373.      
  374.     // list all the targets found
  375.     write_targets();
  376.      
  377.     // display info on the script status
  378.     write_status();
  379. }
  380.  
  381.  
  382.  
  383. // read the turret groups from a text panel
  384. // format for each rotor turret group:    turretid, shellspeed, range, speed_yaw, speed_pitch
  385. // format for each AI turret group:        turretid, shellspeed, range
  386. // example: A, 1700, 4500, 4, 4
  387.  
  388. void parse_turret_groups(Vector3D target)
  389. {
  390.     found_groups = "";
  391.      
  392.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  393.     GridTerminalSystem.SearchBlocksOfName(panel_turret_groups, panels);
  394.      
  395.     if(panels.Count<1)
  396.     {
  397.         Echo("Error: turret group text panel not found.\n");
  398.         error=true;
  399.     }
  400.      
  401.     else
  402.     for(int i=0; i<panels.Count; i++)
  403.     if((panels[i] as IMyFunctionalBlock).Enabled)
  404.     {
  405.         string text = (panels[i] as IMyTextPanel).GetPublicText();
  406.          
  407.         if(text=="")
  408.         {
  409.             warnings += "No turret groups found on the GFCS text panel's public text. Name of panel: '"+panels[i].CustomName+"'\n";
  410.             return;
  411.         }
  412.          
  413.         Vector3D newtarget = new Vector3D(0,0,0);
  414.          
  415.         // if auto aim is on, aim at the center of the target
  416.         if(check_block_toggle(name_autoaim, default_autoaim) || check_block_toggle(name_autotarget, default_autotarget))
  417.             newtarget = target;
  418.          
  419.         // if auto aim is off, aim straight in front of the director
  420.         else
  421.             newtarget = extend(center_position, center_position, forward_position, Vector3D.Distance(center_position, target));
  422.          
  423.         string[] lines = text.Split(new [] {'\r', '\n'}); // separate each line of the panel text
  424.          
  425.         for(int j=0; j<lines.Length; j++)
  426.         {
  427.              
  428.             if(lines[j].Contains(",")) // if the line has at least one comma
  429.             {
  430.              
  431.                 string[] turretgroup = lines[j].Split(',');
  432.                  
  433.                 // check if there are at least 3 elements
  434.                 if(turretgroup.Length<3)
  435.                 {
  436.                     Echo("Error: turret group panel formatted incorrectly (line "+(j+1).ToString()+").\n");
  437.                     error=true;
  438.                     return;
  439.                 }
  440.                  
  441.                 string name = turretgroup[0];
  442.                  
  443.                 string[] velocities = turretgroup[1].Split('/'); // split initialspeed/finalspeed/shellacceleration
  444.                  
  445.                 double shellspeed = string_to_double(velocities[0]);
  446.                  
  447.                 // check if there's also info on final speed and shell acceleration
  448.                 double finalspeed = 0;
  449.                 double shellacceleration = 0;
  450.                 if(velocities.Length>2)
  451.                 {
  452.                     finalspeed = string_to_double(velocities[1]);
  453.                     shellacceleration = string_to_double(velocities[2]);
  454.                 }
  455.                  
  456.                 double range = string_to_double(turretgroup[2]);
  457.                  
  458.                 // if the line stops at "range", treat this group as an AI turret group
  459.                 if(turretgroup.Length==3)
  460.                     aim_ai_turret(newtarget, name, shellspeed, finalspeed, shellacceleration, range);
  461.                  
  462.                 // otherwise, treat this group as a rotor turret group
  463.                 else
  464.                 {
  465.                     // check if there are at least 5 elements
  466.                     if(turretgroup.Length<5)
  467.                     {
  468.                         Echo("Error: turret group panel formatted incorrectly (line "+(j+1).ToString()+").\n");
  469.                         error=true;
  470.                         return;
  471.                     }
  472.                      
  473.                     double yaw = string_to_double(turretgroup[3]);
  474.                     double pitch = string_to_double(turretgroup[4]);
  475.                  
  476.                     turret_group(newtarget, name, shellspeed, finalspeed, shellacceleration, range, yaw, pitch);
  477.                 }
  478.             }
  479.         }
  480.     }
  481. }
  482.  
  483.  
  484.  
  485. // parses the numbers for each turret group
  486.  
  487. double string_to_double(string text)
  488. {
  489.     if(text=="")
  490.         return 0;
  491.  
  492.     return double.Parse(text, new System.Globalization.CultureInfo("en-US")); // only use the point instead of the comma as the decimal mark
  493. }
  494.  
  495.  
  496.  
  497. // count how many turrets there are in a group, and have them track the target
  498.  
  499. void turret_group(Vector3D target, string turretid, double shellspeed, double finalspeed, double shellacceleration, double range, double speed_yaw, double speed_pitch)
  500. {
  501.     int number_of_turrets = 0;
  502.      
  503.     int value;
  504.     if(turret_group_counts.TryGetValue(turretid, out value)) // check if there's already the number of turrets stored for this turret group
  505.     {
  506.         number_of_turrets = value;
  507.          
  508.         // every ten seconds, check again if any new turret was added
  509.         if(counter%600==0)
  510.         {
  511.             counter=0;
  512.             int count = count_turrets(turretid); // count the turrets again
  513.             if(count>value)
  514.             {
  515.                 number_of_turrets = count; // update the local turret count
  516.                 turret_group_counts[turretid] = count; // update the dictionary
  517.             }
  518.         }
  519.     }
  520.          
  521.     else // only happens on the first run
  522.     {
  523.         number_of_turrets = count_turrets(turretid);
  524.         turret_group_counts.Add(turretid, number_of_turrets);
  525.     }
  526.      
  527.     if(number_of_turrets==1)
  528.         found_groups += "Turret group "+turretid+": 1  turret\n";
  529.     else
  530.         found_groups += "Turret group "+turretid+": "+number_of_turrets.ToString()+" turrets\n";
  531.  
  532.     // aim the turrets at the target
  533.  
  534.     if(number_of_turrets==1)
  535.     {
  536.         List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  537.         GridTerminalSystem.SearchBlocksOfName(turretid+"1", blocks);
  538.          
  539.         if(blocks.Count>0)
  540.             aim_rotor_turret(target, turretid+"1", shellspeed, finalspeed, shellacceleration, range, speed_yaw, speed_pitch);
  541.         else
  542.             aim_rotor_turret(target, turretid, shellspeed, finalspeed, shellacceleration, range, speed_yaw, speed_pitch);
  543.     }
  544.  
  545.     else    
  546.         for(int i=0; i<number_of_turrets; i++)
  547.             aim_rotor_turret(target, turretid+(i+1).ToString(), shellspeed, finalspeed, shellacceleration, range, speed_yaw, speed_pitch);
  548. }
  549.  
  550.  
  551.  
  552. // look for a block used as a toggle
  553. // if it's on, return true
  554. // if it's off, return false
  555. // if there's no block, return the default state
  556.  
  557. bool check_block_toggle(string name, bool defaultstate)
  558. {
  559.     List<IMyTerminalBlock> toggles = new List<IMyTerminalBlock>();
  560.     GridTerminalSystem.SearchBlocksOfName(name, toggles);
  561.      
  562.     if(toggles.Count<1)
  563.         return defaultstate;
  564.      
  565.     if((toggles[0] as IMyFunctionalBlock).Enabled)
  566.         return true;
  567.      
  568.     if((toggles[0] as IMyFunctionalBlock).Enabled==false)
  569.         return false;
  570.      
  571.     return defaultstate; // control should never reach this
  572. }
  573.  
  574.  
  575.  
  576. // checks if the fire toggle is on or off
  577.  
  578. bool check_firetoggle()
  579. {
  580.     if(timerblock_fire==null)
  581.         return false;
  582.      
  583.     if((timerblock_fire as IMyFunctionalBlock).Enabled)
  584.         return true;
  585.      
  586.     return false;
  587. }
  588.  
  589.  
  590.  
  591. // looks through all the available targets and picks the one that's most in front of the director
  592.  
  593. Vector3D gettargetposition()
  594. {    
  595.     double best_bearing = 0;
  596.     Vector3D best_target = new Vector3D(0,0,0);
  597.      
  598.     double pitch = 0;
  599.     double yaw = 0;
  600.      
  601.     for(int i=0; i<targets.Count; i++)
  602.     {
  603.         pitch=0;
  604.         yaw=0;
  605.          
  606.         GetDirectionTo(targets[i], center_position, forward_position, up_position, ref pitch, ref yaw);
  607.  
  608.         pitch = Math.Abs(pitch);
  609.         yaw = Math.Abs(yaw);        
  610.          
  611.         // find the target with the lowest pitch+yaw
  612.         if(pitch<director_threshold && yaw<director_threshold && (pitch+yaw<best_bearing || best_bearing==0))
  613.         {
  614.             best_bearing = pitch+yaw; // update the best bearing
  615.             best_target = targets[i]; // update the best target
  616.         }
  617.     }
  618.      
  619.     // if a target was found
  620.     if(best_target != new Vector3D(0,0,0))
  621.         return best_target;
  622.      
  623.     // if no target was found
  624.     reset_target();
  625. //    targets.Clear();
  626.     return new Vector3D(0,0,0);
  627. }
  628.  
  629.  
  630.  
  631. // finds the closest target
  632. // if there are no targets, returns (0,0,0)
  633.  
  634. Vector3D get_closest_target()
  635. {
  636.      
  637.     if(targets.Count<1)
  638.     {
  639.         reset_target();
  640.         return new Vector3D(0,0,0);
  641.     }
  642.      
  643.     double distance = Vector3D.Distance(center_position, targets[0]);
  644.     int n = 0;
  645.      
  646.     for(int i=1; i<targets.Count; i++)
  647.     {
  648.         double current_distance = Vector3D.Distance(center_position, targets[i]);
  649.         if(current_distance<distance)
  650.         {
  651.             distance = current_distance;
  652.             n = i;
  653.         }
  654.     }
  655.      
  656.     return targets[n];
  657. }
  658.  
  659.  
  660.  
  661. // updates the list of targets
  662.  
  663. void get_targets()
  664. {
  665.     targets.Clear();
  666.      
  667.     // check for any new sensors
  668. /*    GridTerminalSystem.GetBlocksOfType<IMySensorBlock>(local_sensors, x => x.CustomName.Contains(sensorBaseName));
  669.     GridTerminalSystem.GetBlocksOfType<IMySensorBlock>(local_super_sensors, x => x.CustomName.Contains(name_super_sensor));
  670.      
  671.     if(local_sensors.Count > sensors.Count)
  672.         sensors = local_sensors;
  673.     if(local_super_sensors.Count > super_sensors.Count)
  674.         super_sensors = local_super_sensors;
  675. */
  676.  
  677.     // look through all the sensors
  678.     for(int i=0; i<sensors.Count; i++)
  679.     {
  680.         var match = rxVector.Match(sensors[i].CustomName);
  681.          
  682.         // if a target is found
  683.         if(match.Success)
  684.         {
  685.             float x = Single.Parse( match.Groups[1].Value );
  686.             float y = Single.Parse( match.Groups[2].Value );
  687.             float z = Single.Parse( match.Groups[3].Value );
  688.              
  689.             Vector3D targetposition = new Vector3D(x, y, z);
  690.              
  691.             // add it to the list of targets
  692.             targets.Add(targetposition);
  693.         }
  694.     }
  695.      
  696.     // add all super sensor targets
  697.     for(int i=0; i<super_sensors.Count; i++)
  698.     {
  699.         IMySensorBlock sensor = super_sensors[i] as IMySensorBlock;
  700.         if(sensor.LastDetectedEntity.Equals(null) == false)
  701.             targets.Add(sensor.LastDetectedEntity.Position);
  702.     }
  703. }
  704.  
  705.  
  706.  
  707. // tell a turret to track a target using these settings:
  708.  
  709. // turretid:    the turret's name identifier (like "A1", "D4", "F7")
  710. // shellspeed:    the initial speed of the shells fired by the turret's guns
  711. // finalspeed:    the final speed of the shells fired by the turret's guns
  712. // shellacc.:    the acceleration of the shells
  713. // range:        the range of the turret's guns
  714. // speed_yaw:    the velocity of the horizontal turret rotor
  715. // speed_pitch:    the velocity of the vertical turret rotor
  716.  
  717. void aim_rotor_turret(Vector3D target, string turretid, double shellspeed, double finalspeed, double shellacceleration, double range, double speed_yaw, double speed_pitch)
  718. {
  719.  
  720.     Vector3D turret_center = new Vector3D(0,0,0);    // center
  721.     Vector3D turret_forward = new Vector3D(0,0,0);    // forward
  722.     Vector3D turret_up = new Vector3D(0,0,0);        // up
  723.      
  724.     // look for a remote to get the turret orientation
  725.     IMyTerminalBlock turret_remote = get_turret_remote(turretid) as IMyTerminalBlock;
  726.      
  727.     if(turret_remote!=null)
  728.     {
  729.         turret_center = turret_remote.GetPosition();
  730.         turret_forward = get_block_forward_position(turret_remote, 1.0);
  731.         turret_up = get_block_up_position(turret_remote, 1.0);
  732.     }
  733.      
  734.     // if there's no remote, look for the three orientation blocks
  735.     else
  736.     {
  737.         List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  738.         GridTerminalSystem.SearchBlocksOfName(("Turret "+turretid), blocks);
  739.      
  740.         for(int i=0; i<blocks.Count; i++)
  741.         {
  742.             if(blocks[i].CustomName.Contains(turretid) && blocks[i].CustomName.Contains("Center"))
  743.                 turret_center = blocks[i].GetPosition();
  744.             else if(blocks[i].CustomName.Contains(turretid) && blocks[i].CustomName.Contains("Forward"))
  745.                 turret_forward = blocks[i].GetPosition();
  746.             else if(blocks[i].CustomName.Contains(turretid) && blocks[i].CustomName.Contains("Up"))
  747.                 turret_up = blocks[i].GetPosition();
  748.         }
  749.          
  750.         if(turret_center==new Vector3D(0,0,0) || turret_forward==new Vector3D(0,0,0) || turret_up==new Vector3D(0,0,0))
  751.         {
  752.             warnings = warnings + "Turret "+turretid+": missing 'Turret "+turretid+" Remote Control'.\n";
  753.             return;
  754.         }
  755.     }
  756.      
  757.     // display warnings if there's no RotorX or RotorY
  758.      
  759.     List<IMyTerminalBlock> rotors_x = new List<IMyTerminalBlock>();
  760.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+" RotorX", rotors_x);
  761.      
  762.     if(rotors_x.Count<1)
  763.         warnings = warnings + "Turret "+turretid+": missing 'Turret "+turretid+" RotorX'.\n";
  764.  
  765.     List<IMyTerminalBlock> rotors_y = new List<IMyTerminalBlock>();
  766.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+" RotorY", rotors_y);
  767.      
  768.     if(rotors_y.Count<1)
  769.         warnings = warnings + "Turret "+turretid+": missing 'Turret "+turretid+" RotorY'.\n";
  770.      
  771.     // add this turret to the number of turrets
  772.      
  773.     if(rotors_x.Count>1)
  774.         total_number_of_turrets += rotors_x.Count;
  775.     else
  776.         total_number_of_turrets++;
  777.      
  778.     // don't display warnings for missing pistons
  779.      
  780.     List<IMyTerminalBlock> pistons_x = new List<IMyTerminalBlock>();
  781.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+" PistonX", pistons_x);
  782.      
  783.     List<IMyTerminalBlock> pistons_y = new List<IMyTerminalBlock>();
  784.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+" PistonY", pistons_y);
  785.      
  786.     // look for all of the turret's guns
  787.     List<IMyTerminalBlock> guns = new List<IMyTerminalBlock>();
  788.     GridTerminalSystem.SearchBlocksOfName(("Turret " + turretid + " Gun"), guns);
  789.      
  790.     // look for all of the turret's timers
  791.     List<IMyTerminalBlock> timers = new List<IMyTerminalBlock>();
  792.     GridTerminalSystem.GetBlocksOfType<IMyTimerBlock>(timers, x => x.CustomName.Contains("Turret "+turretid) && x.CustomName.Contains("Timer"));
  793.      
  794.     // idle the turrets if idle is on, or if there's no target while auto target is on
  795.     if(check_block_toggle(name_idle, false) || ((check_block_toggle(name_autotarget, default_autotarget) || no_director==true) && targets.Count<1 && idle_if_no_targets==true))
  796.     {
  797.         // throw new Exception(targets.Count.ToString());
  798.         for(int i=0; i<rotors_x.Count; i++)
  799.             idle_rotor(rotors_x[i] as IMyMotorStator, speed_yaw);
  800.          
  801.         for(int i=0; i<rotors_y.Count; i++)
  802.             idle_rotor(rotors_y[i] as IMyMotorStator, speed_pitch);
  803.          
  804.         for(int i=0; i<pistons_x.Count; i++)
  805.             idle_piston(pistons_x[i] as IMyPistonBase, speed_yaw);
  806.          
  807.         for(int i=0; i<pistons_y.Count; i++)
  808.             idle_piston(pistons_y[i] as IMyPistonBase, speed_pitch);
  809.  
  810.         for(int i=0; i<guns.Count; i++)
  811.             guns[i].ApplyAction("OnOff_Off");
  812.          
  813.         for(int i=0; i<timers.Count; i++)
  814.             guns[i].ApplyAction("OnOff_Off");
  815.          
  816.         return;
  817.     }
  818.      
  819.     // if there is no target and no director, stop all rotors and pistons
  820.      
  821.     else if(target == new Vector3D(0,0,0) || (targets.Count<1 && no_director==true))
  822.     {
  823.         for(int i=0; i<rotors_x.Count; i++)
  824.             rotors_x[i].SetValueFloat("Velocity", 0);
  825.         for(int i=0; i<rotors_y.Count; i++)
  826.             rotors_y[i].SetValueFloat("Velocity", 0);
  827.         for(int i=0; i<pistons_x.Count; i++)
  828.             pistons_x[i].SetValueFloat("Velocity", 0);
  829.         for(int i=0; i<pistons_y.Count; i++)
  830.             pistons_y[i].SetValueFloat("Velocity", 0);
  831.          
  832.         for(int i=0; i<timers.Count; i++)
  833.             guns[i].ApplyAction("OnOff_Off");
  834.          
  835.         return;
  836.     }
  837.      
  838.     // predict the target's future position
  839.      
  840.     Vector3D predictedtarget = target + speed + acceleration - platform_speed;
  841.     double distance = Vector3D.Distance(turret_forward, predictedtarget);
  842.     double traveltime = distance/shellspeed;
  843.      
  844.     // if the projectile accelerates
  845.     if(finalspeed!=0 && shellacceleration!=0)
  846.         traveltime = calculate_time_with_acceleration(distance, shellspeed, finalspeed, shellacceleration);
  847.  
  848.     Vector3D lead = target + ((speed+acceleration-platform_speed) * traveltime);
  849.  
  850.     // now find the angles to the predicted position
  851.      
  852.     double gyroPitch = 0;
  853.     double gyroYaw = 0;
  854.      
  855.     if(guns.Count>0)
  856.     {
  857.         Vector3D average_position = guns_average_position(guns);
  858.         turret_forward = extend(average_position, turret_center, turret_forward, 1.0);
  859.         turret_up = extend(average_position, turret_center, turret_up, 1.0);
  860.         turret_center = average_position;
  861.     }
  862.      
  863.     GetDirectionTo (lead, turret_center, turret_forward, turret_up, ref gyroPitch, ref gyroYaw);
  864.  
  865.     double speed_y = speed_pitch;
  866.     double speed_x = speed_yaw;
  867.  
  868.     // reduce rotor speed if the angle is within (-cap, cap)
  869.     double cap = speed_pitch/2.0;
  870.     if(cap<0.5)
  871.         cap=0.5;
  872.      
  873.     if(gyroPitch > -cap && gyroPitch < cap)
  874.         speed_y = speed_y * gyroPitch / cap;
  875.      
  876.     else if(gyroPitch < 0)
  877.         speed_y = -speed_y;
  878.      
  879.     cap = speed_yaw/2.0;
  880.     if(cap<0.5)
  881.         cap=0.5;
  882.      
  883.     if(gyroYaw > -cap && gyroYaw < cap)
  884.         speed_x = speed_x * gyroYaw / cap;
  885.      
  886.     else if(gyroYaw < 0)
  887.         speed_x = -speed_x;
  888.      
  889.     // if the turret orientation is constrained, turn the best way so that it doesn't get stuck at max or min angle
  890.     if(rotors_x.Count>0)
  891.     {
  892.         IMyMotorStator rotorx = rotors_x[0] as IMyMotorStator;
  893.         if( (rotorx.UpperLimit).ToString()!="Infinity" && (rotorx.LowerLimit).ToString()!="Infinity" )
  894.         {
  895.             float max = rotorx.UpperLimit;
  896.             float min = rotorx.LowerLimit;
  897.             float angle = rotorx.Angle;
  898.             double bearing = ToRadians(gyroYaw); // gyroYaw is actually in degrees, so it has to converted into radians
  899.              
  900.             float mid = (max+min)/2.0f; // the angle exactly between the two limits
  901.             float angle_from_mid = angle - mid; // how far right or left the turret is from the mid location
  902.             double bearing_to_target = angle_from_mid + bearing; // the angle from mid to the target
  903.  
  904.             // if the bearing is more than 180 degrees (to the left or right) it means that it's better to turn the other way (180 degrees = Pi, since the angle is in radians)
  905.             if(bearing_to_target<-Math.PI || bearing_to_target>Math.PI)
  906.                 speed_x = -speed_x;
  907.         }
  908.     }
  909.  
  910.      
  911.     // set the turning speed of each rotor and piston
  912.      
  913.     // RotorX
  914.     for(int i=0; i<rotors_x.Count; i++)
  915.     {
  916.         if(contains_reverse(rotors_x[i].CustomName))
  917.             rotors_x[i].SetValueFloat("Velocity", (float)-speed_x);
  918.          
  919.         else
  920.             rotors_x[i].SetValueFloat("Velocity", (float)speed_x);
  921.     }
  922.      
  923.     // RotorY
  924.     for(int i=0; i<rotors_y.Count; i++)
  925.     {
  926.         if(contains_reverse(rotors_y[i].CustomName))
  927.             rotors_y[i].SetValueFloat("Velocity", (float)-speed_y);
  928.          
  929.         else
  930.             rotors_y[i].SetValueFloat("Velocity", (float)speed_y);
  931.     }
  932.      
  933.     // PistonX
  934.     for(int i=0; i<pistons_x.Count; i++)
  935.     {
  936.         if(contains_reverse(pistons_x[i].CustomName))
  937.             pistons_x[i].SetValueFloat("Velocity", (float)-speed_x/2.0f);
  938.          
  939.         else
  940.             pistons_x[i].SetValueFloat("Velocity", (float)speed_x/2.0f);
  941.     }
  942.      
  943.     // PistonY
  944.     for(int i=0; i<pistons_y.Count; i++)
  945.     {
  946.         if(contains_reverse(pistons_y[i].CustomName))
  947.             pistons_y[i].SetValueFloat("Velocity", (float)-speed_y/2.0f);
  948.          
  949.         else
  950.             pistons_y[i].SetValueFloat("Velocity", (float)speed_y/2.0f);
  951.     }
  952.  
  953.     // check if the target is in range
  954.      
  955.     distance = Vector3D.Distance(turret_center, lead);
  956.     if(distance<range)
  957.         turrets_in_range++;
  958.      
  959.     // turn off the guns if they are not on target of if there's something in the way    
  960.  
  961.     if(guns.Count<1 && timers.Count<1)
  962.         return;
  963.      
  964.     List<IMyTerminalBlock> turret_sensors = new List<IMyTerminalBlock>();
  965.     GridTerminalSystem.GetBlocksOfType<IMySensorBlock>(turret_sensors, (x => x.CustomName.Contains("Turret "+turretid)));
  966.      
  967.     // whether there's something in front of the turret
  968.     bool detected = false;
  969.      
  970.     // check if there's anything in front of the turret
  971.     for(int i=0; i<turret_sensors.Count; i++)
  972.         //if((turret_sensors[i] as IMySensorBlock).LastDetectedEntity!=null)
  973.         if((turret_sensors[i] as IMySensorBlock).LastDetectedEntity.Equals(null) == false)
  974.             detected = true;
  975.      
  976.     // if the sensors detect anything, turn off the guns
  977.     if(detected)
  978.     {
  979.         for(int i=0; i<guns.Count; i++)
  980.             guns[i].ApplyAction("OnOff_Off");
  981.         for(int i=0; i<timers.Count; i++)
  982.             timers[i].ApplyAction("OnOff_Off");
  983.     }
  984.      
  985.     // if the sensors don't detect anything
  986.     else
  987.     {
  988.         // the turret is on target if the yaw and pitch are within the threshold and, in the case only_shoot_in_range is on, if the target is in range
  989.         bool on_target = within(gyroPitch, turret_threshold)==true && within(gyroYaw, turret_threshold)==true;
  990.  
  991.         // if the turret is not on target, or if the target is out of range, turn off the guns
  992.         if(!on_target)
  993.         {
  994.             for(int i=0; i<guns.Count; i++)
  995.                 guns[i].ApplyAction("OnOff_Off");
  996.             for(int i=0; i<timers.Count; i++)
  997.                 timers[i].ApplyAction("OnOff_Off");
  998.         }
  999.          
  1000.         // otherwise, turn the guns on if Auto Fire is on and there's a target (and the turret is on target, and the target is in range), fire the turret's guns.
  1001.         else if( (check_block_toggle(name_autofire, default_autofire) && empty_space_target==false && on_target==true && (distance<range || only_shoot_in_range==false)) || check_firetoggle()==true )
  1002.         {
  1003.             for(int i=0; i<guns.Count; i++)
  1004.             {
  1005.                 guns[i].ApplyAction("OnOff_On");
  1006.                 guns[i].ApplyAction("ShootOnce");
  1007.             }
  1008.              
  1009.             // activate the turret's fire timer(s)
  1010.             for(int i=0; i<timers.Count; i++)
  1011.             {
  1012.                 IMyTimerBlock timer = timers[i] as IMyTimerBlock;
  1013.                 timer.ApplyAction("OnOff_On");
  1014.                  
  1015.                 // start the timer (if it contains "start" and isn't started already)
  1016.                 if(timer.CustomName.Contains("Start") && timer.IsCountingDown==false)
  1017.                     timer.ApplyAction("Start");
  1018.                 // or start the timer
  1019.                 else if(timer.CustomName.Contains("Start")==false)
  1020.                     timer.ApplyAction("TriggerNow");
  1021.             }
  1022.         }
  1023.          
  1024.         // otherwise, just turn all the guns and timers on
  1025.         else
  1026.         {
  1027.             for(int i=0; i<guns.Count; i++)
  1028.                 guns[i].ApplyAction("OnOff_On");
  1029.             for(int i=0; i<timers.Count; i++)
  1030.                 timers[i].ApplyAction("OnOff_On");
  1031.         }
  1032.     }
  1033. }
  1034.  
  1035.  
  1036.  
  1037. // gets the forward/up positions (world orientation) of a block
  1038. // HUGE THANKS to ZerothAngel on Reddit for the code
  1039.  
  1040. // get forward position
  1041. Vector3D get_block_forward_position(IMyTerminalBlock block, double distance)
  1042. {
  1043.     var forwardPos = block.Position + Base6Directions.GetIntVector(block.Orientation.TransformDirection(Base6Directions.Direction.Forward));
  1044.     var forward = block.CubeGrid.GridIntegerToWorld(forwardPos);
  1045.     var forwardVector = Vector3D.Normalize(forward - block.GetPosition());
  1046.     return block.GetPosition() + distance * forwardVector;
  1047. }
  1048.  
  1049. // get up position
  1050. Vector3D get_block_up_position(IMyTerminalBlock block, double distance)
  1051. {
  1052.     var upPos = block.Position + Base6Directions.GetIntVector(block.Orientation.TransformDirection(Base6Directions.Direction.Up));
  1053.     var up = block.CubeGrid.GridIntegerToWorld(upPos);
  1054.     var upVector = Vector3D.Normalize(up - block.GetPosition());
  1055.     return block.GetPosition() + distance * upVector;
  1056. }
  1057.  
  1058.  
  1059.  
  1060. // same as aim_rotor_turret, but for turret blocks (without rotors)
  1061.  
  1062. void aim_ai_turret(Vector3D target, string name, double shellspeed, double finalspeed, double shellacceleration, double range)
  1063. {
  1064.      
  1065.     List<IMyTerminalBlock> turrets = new List<IMyTerminalBlock>();
  1066.     GridTerminalSystem.SearchBlocksOfName("Turret "+name, turrets);
  1067.      
  1068.     if(turrets.Count==1)
  1069.         found_groups += "Turret group "+name+": 1  turret\n";
  1070.     else
  1071.         found_groups += "Turret group "+name+": "+(turrets.Count).ToString()+" turrets\n";
  1072.      
  1073.     for(int i=0; i<turrets.Count; i++)
  1074.     {
  1075.          
  1076.         total_number_of_turrets++;
  1077.          
  1078.         IMyLargeTurretBase turret = turrets[i] as IMyLargeTurretBase;
  1079.          
  1080.         // turn off the turret if idle is on
  1081.         if(check_block_toggle(name_idle, false) || (targets.Count<1 && no_director==true))
  1082.             turret.ApplyAction("OnOff_Off");
  1083.          
  1084.         else
  1085.         {
  1086.             turret.ApplyAction("OnOff_On");
  1087.          
  1088.             Vector3D predictedtarget = target + speed + acceleration - platform_speed;
  1089.              
  1090.             double distance = Vector3D.Distance(turrets[i].GetPosition(), predictedtarget);
  1091.             double traveltime = distance/shellspeed;
  1092.              
  1093.             // if the projectile accelerates
  1094.             if(finalspeed!=0 && shellacceleration!=0)
  1095.                 traveltime = calculate_time_with_acceleration(distance, shellspeed, finalspeed, shellacceleration);
  1096.              
  1097.             Vector3D lead = target + ((speed+acceleration-platform_speed) * traveltime);
  1098.  
  1099.             distance = Vector3D.Distance(turret.GetPosition(), lead);
  1100.              
  1101.             // aim the turret
  1102.             turret.SetTarget(lead);
  1103.              
  1104.             // fire only if the target is in range
  1105.             if(distance<range || only_shoot_in_range==false)
  1106.             {
  1107.             //    turret.ApplyAction("OnOff_On");
  1108.                 turrets_in_range++;
  1109.                 if( (check_block_toggle(name_autofire, default_autofire) && empty_space_target==false && (distance<range || only_shoot_in_range==false)) || check_firetoggle()==true ) // if Auto Fire is on and there's a target (and the target is in range), fire the turret's guns.
  1110.                     if(time_counter%2==0) // only every other frame, because otherwise the turrets get stuck. Idk why (possibly because of the latest update).
  1111.                         turret.ApplyAction("ShootOnce");
  1112.             }
  1113.         }
  1114.     }
  1115.      
  1116. }
  1117.  
  1118.  
  1119.  
  1120. // finds the average position of multiple guns
  1121.  
  1122. Vector3D guns_average_position(List<IMyTerminalBlock> guns)
  1123. {
  1124.     Vector3D vector_sum = new Vector3D(0,0,0);
  1125.      
  1126.     for(int i=0; i<guns.Count; i++)
  1127.         vector_sum += guns[i].GetPosition(); // sum all the positions
  1128.      
  1129.     return vector_sum / guns.Count; // and divide them by their number (getting the average)
  1130. }
  1131.  
  1132.  
  1133.  
  1134. // returns a rotor to zero degrees or the degree between the two angle limits
  1135.  
  1136. void idle_rotor(IMyMotorStator rotor, double velocity)
  1137. {
  1138.     double threshold = velocity/2.0;
  1139.     double angle = (double)rotor.Angle;
  1140.     angle = angle%Math.PI;
  1141.     if(angle > Math.PI/2.0)
  1142.         angle = angle - (2*Math.PI); // angle - 360 degrees
  1143.      
  1144.     double target_angle = 0;
  1145.     if( (rotor.UpperLimit).ToString()!="Infinity" && (rotor.LowerLimit).ToString()!="Infinity" )
  1146.         target_angle = (rotor.UpperLimit + rotor.LowerLimit)/2.0f; // midpoint between min and max angle
  1147.      
  1148.     double newvelocity = Math.Abs(velocity * ToDegrees(target_angle-angle) / threshold);
  1149.     if(target_angle<angle)
  1150.         newvelocity = -newvelocity;
  1151.      
  1152.     if(newvelocity>velocity)
  1153.         newvelocity=velocity;
  1154.     else if(newvelocity<-velocity)
  1155.         newvelocity=-velocity;
  1156.      
  1157.     if(angle>-0.008f && angle<0.008f) // within 1 degree
  1158.         newvelocity = 0;
  1159.      
  1160.     rotor.SetValueFloat("Velocity", (float)newvelocity);
  1161. }
  1162.  
  1163.  
  1164.  
  1165. // restores a piston to its minimum length
  1166.  
  1167. void idle_piston(IMyPistonBase piston, double velocity)
  1168. {
  1169.     piston.SetValueFloat("Velocity", (float)-velocity);
  1170. }
  1171.  
  1172.  
  1173.  
  1174. // caps a number to (-cap, cap)
  1175.  
  1176. double cap_both_ways(double value, double cap)
  1177. {
  1178.     if(cap>0 && value>cap)
  1179.         return cap;
  1180.     else if(cap<0 && value<-cap)
  1181.         return -cap;
  1182.     else
  1183.         return value;    
  1184. }
  1185.  
  1186.  
  1187.  
  1188. // checks if a name contains "reverse"
  1189.  
  1190. bool contains_reverse(string name)
  1191. {
  1192.     if(name.Contains("Reverse") || name.Contains("reverse") | name.Contains("REVERSE"))
  1193.         return true;
  1194.      
  1195.     return false;
  1196. }
  1197.  
  1198.  
  1199. // given the past and present position or speed, it divides their difference by the time to calculate speed or acceleration
  1200.  
  1201. Vector3D get_speed_or_acceleration(Vector3D position, Vector3D newposition)
  1202. {
  1203.      
  1204.     if(position==new Vector3D(0,0,0))
  1205.         return position;
  1206.  
  1207.     // position or speed difference / time
  1208.     return (newposition-position)/(1.0/(double)frequency);
  1209. }
  1210.  
  1211.  
  1212.  
  1213. // updates the list of the latest speeds with the newest detected speed
  1214. // return the average of the latest speeds
  1215. // also works for the acceleration
  1216.  
  1217. Vector3D update_speeds_or_accelerations(List<Vector3D> speeds_list, Vector3D newspeed)
  1218. {
  1219.      
  1220.     // add the new speed/acceleration to the list (but ignore the first one)
  1221.     if(speeds_list.Count<1)
  1222.         speeds_list.Add(new Vector3D(0,0,0));
  1223.     else
  1224.         speeds_list.Add(newspeed);
  1225.      
  1226.     // keep the size of the list within lists_size
  1227.     if(speeds_list.Count>lists_size)
  1228.         speeds_list.RemoveAt(0);
  1229.      
  1230.     // don't even try and calculate the speed until you have a full list of speeds available
  1231.     if(speeds_list.Count<lists_size)
  1232.         return new Vector3D(0,0,0);
  1233.  
  1234.     // update the average speed/acceleration
  1235.      
  1236.     Vector3D averagespeed = new Vector3D(0,0,0);
  1237.      
  1238.     for(int i=0; i<speeds_list.Count; i++)
  1239.         averagespeed = averagespeed + speeds_list[i];
  1240.     averagespeed = averagespeed / speeds_list.Count;
  1241.      
  1242.     return averagespeed;
  1243. }
  1244.  
  1245.  
  1246.  
  1247. // set all lead values to zero
  1248.  
  1249. void reset_target()
  1250. {
  1251.     targetposition = new Vector3D(0,0,0);
  1252.     speed = new Vector3D(0,0,0);
  1253.     acceleration = new Vector3D(0,0,0);
  1254.     speeds.Clear();
  1255.     accelerations.Clear();
  1256. }
  1257.  
  1258.  
  1259.  
  1260. // finds the spot at a certain distance from "reference", in the same direction as from "a" to "b"
  1261. Vector3D extend(Vector3D reference, Vector3D a, Vector3D b, double newdistance)
  1262. {
  1263.     if(newdistance==0)
  1264.         return reference;
  1265.      
  1266.     Vector3D shift = a-b;
  1267.     double distance = Vector3D.Distance(a, b);
  1268.     Vector3D extended = shift * newdistance / distance;
  1269.      
  1270.     return reference - extended;
  1271. }
  1272.  
  1273.  
  1274.  
  1275. // looks for a director and uses it (sets the director's "block" positions). Takes the number of directors
  1276.  
  1277. void update_director_positions(int number_of_directors)
  1278. {
  1279.      
  1280.     // local flags
  1281.     bool directorfound = false;
  1282.     int first_director_found = 0;
  1283.      
  1284.     // look through the directors, starting from the last one
  1285.     for(int i=1; i<=number_of_directors; i++)
  1286.     {
  1287.         Vector3D center = new Vector3D(0,0,0);
  1288.         Vector3D forward = new Vector3D(0,0,0);
  1289.         Vector3D up = new Vector3D(0,0,0);
  1290.          
  1291.         bool controlled = false;
  1292.  
  1293.         IMyLargeTurretBase director_turret = get_single_director(i) as IMyLargeTurretBase;
  1294.         IMyRemoteControl director_remote = null;
  1295.          
  1296.         // first look for a single block director
  1297.         if(director_turret!=null)
  1298.         {
  1299.             center = director_turret.GetPosition();
  1300.             forward = get_block_forward_position(director_turret, 1.0);
  1301.             up = get_block_up_position(director_turret, 1.0);
  1302.              
  1303.             // updates forward and up with the turret's orientation
  1304.             forward = get_turret_front_position(director_turret, 1, out up);
  1305.          
  1306.             // shift the turret position up/down according to the value in the name, like <-1>
  1307.             double adjustment = parse_adjustment(director_turret.CustomName);
  1308.             center = extend(center, center, up, adjustment);
  1309.              
  1310.             controlled = director_turret.IsUnderControl;
  1311.         }
  1312.          
  1313.         // if there's a turret block director, try again looking for a remote control (rotor director)
  1314.         else
  1315.         {
  1316.             director_remote = getblock_nullable(DIRECTOR_BASE_NAME+i.ToString()+" "+name_director_remote) as IMyRemoteControl;
  1317.              
  1318.             if(i==1 && director_remote==null)
  1319.                 director_remote = getblock_nullable(DIRECTOR_BASE_NAME+" "+name_director_remote) as IMyRemoteControl;
  1320.              
  1321.             // if a rotor director was found
  1322.             if(director_remote!=null)
  1323.             {
  1324.                 center = director_remote.GetPosition();
  1325.                 forward = get_block_forward_position(director_remote, 1.0);
  1326.                 up = get_block_up_position(director_remote, 1.0);
  1327.                  
  1328.                 controlled = director_remote.IsUnderControl;
  1329.                  
  1330.                 // lock or unlock the rotor director
  1331.                 if(lock_director)
  1332.                 {
  1333.                     if(controlled)
  1334.                         lock_unlock_remote(i.ToString(), false);
  1335.                     else
  1336.                         lock_unlock_remote(i.ToString(), true);
  1337.                      
  1338.                     if(current_director==1 && controlled)
  1339.                         lock_unlock_remote("", false);
  1340.                     else if(current_director==1 && controlled==false)
  1341.                         lock_unlock_remote("", true);
  1342.                 }
  1343.             }
  1344.         }
  1345.          
  1346.         // if a director of either type was found
  1347.         if(director_turret!=null || director_remote!=null)
  1348.         {
  1349.             // if no director has been found yet, use this director for now (these values will be overwritten if another director is found)
  1350.             if(directors_found==0)
  1351.             {
  1352.                 first_director_found = i;
  1353.  
  1354.                 center_position = center;
  1355.                 forward_position = forward;
  1356.                 up_position = up;
  1357.             }
  1358.              
  1359.             directors_found++;
  1360.              
  1361.             // use this director if switch_to_controlled is off, or if switch_to_controlled is on and this is controlled (unless a director has already been found)
  1362.             if( directorfound==false && ((switch_to_controlled==false) || (switch_to_controlled==true && controlled==true)) )
  1363.             {
  1364.                 directorfound=true;
  1365.                 current_director=i;
  1366.                  
  1367.                 // update global variables since a director has been found
  1368.                 center_position = center;
  1369.                 forward_position = forward;
  1370.                 up_position = up;
  1371.             }
  1372.         }
  1373.     }
  1374.      
  1375.     // stop here if a director has been found
  1376.     if(directors_found>0 && directorfound==false)
  1377.     {
  1378.         current_director = first_director_found;
  1379.         return;
  1380.     }
  1381.  
  1382.     // if no director at all has been found and the script needs a director, display an error
  1383.     if(directors_found==0 && no_director==false)
  1384.     {
  1385.         Echo("Error: no director found.\n");
  1386.         error=true;
  1387.     }
  1388. }
  1389.  
  1390.  
  1391.  
  1392. // find a specific turret director block
  1393.  
  1394. IMyTerminalBlock get_single_director(int number)
  1395. {
  1396.     List<IMyTerminalBlock> director_turrets = new List<IMyTerminalBlock>();
  1397.     GridTerminalSystem.GetBlocksOfType<IMyLargeTurretBase>(director_turrets, x => x.CustomName.StartsWith(DIRECTOR_BASE_NAME));
  1398.      
  1399.     for(int i=0; i<director_turrets.Count; i++)
  1400.         if(director_turrets[i].CustomName.Contains(number.ToString()) || (number==1 && director_turrets[i].CustomName==DIRECTOR_BASE_NAME))
  1401.             return director_turrets[i];
  1402.      
  1403.     return null;
  1404. }
  1405.  
  1406.  
  1407.  
  1408. // look for one of the three director orientation blocks (if it's not found, return null)
  1409.  
  1410. IMyTerminalBlock get_director_block(int number, string type)
  1411. {
  1412.     List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  1413.     GridTerminalSystem.SearchBlocksOfName(DIRECTOR_BASE_NAME, blocks);
  1414.      
  1415.     for(int i=0; i<blocks.Count; i++)
  1416.         if(blocks[i].CustomName==(DIRECTOR_BASE_NAME+number.ToString()+" "+type) || (number==1 && blocks[i].CustomName==DIRECTOR_BASE_NAME+" "+type))
  1417.             return blocks[i];
  1418.      
  1419.     return null;
  1420. }
  1421.  
  1422.  
  1423.  
  1424. // finds the front position (orientation) of a turret, and also outputs the adjusted up position
  1425.  
  1426. Vector3D get_turret_front_position(IMyLargeTurretBase turret, double distance, out Vector3D up_position)
  1427. {
  1428.     float azimuth = turret.Azimuth;
  1429.     float elevation = turret.Elevation;
  1430.      
  1431.     Vector3D direction = new Vector3D(0,0,0);
  1432.      
  1433.     // x = right
  1434.     // y = up
  1435.     // -z = forward
  1436.     Vector3D.CreateFromAzimuthAndElevation(azimuth, elevation, out direction);
  1437.      
  1438.     Vector3D turret_center = turret.GetPosition();
  1439.     Vector3D turret_forward = get_block_forward_position(turret, 1);
  1440.     Vector3D turret_up = get_block_up_position(turret, 1);
  1441.     Vector3D turret_right = get_right(turret_center, turret_forward, turret_up);
  1442.      
  1443.     double shift_forward = -direction.GetDim(2) * distance; // -z
  1444.     double shift_up = direction.GetDim(1) * distance; // y
  1445.     double shift_right = direction.GetDim(0) * distance; // x
  1446.      
  1447.     Vector3D front = extend(turret_center, turret_center, turret_forward, shift_forward);
  1448.     front = extend(front, turret_center, turret_right, shift_right);
  1449.     front = extend(front, turret_center, turret_up, shift_up);
  1450.  
  1451.     Vector3D actual_up = get_turretblock_up(turret_center, turret_center, turret_up, front, elevation);
  1452.     up_position = actual_up;
  1453.  
  1454.     return front;
  1455. }
  1456.  
  1457.  
  1458.  
  1459. // finds the spot 1 meter above a turret, tilted according to the turret's orientation
  1460. Vector3D get_turretblock_up(Vector3D turretposition, Vector3D center, Vector3D up, Vector3D forward, float elevation)
  1461. {
  1462.     Vector3D above_turret = extend(turretposition, center, up, 1); // find the spot 1 meter straight above the turret block (no elevation adjustment)
  1463.     double opposite_side = Math.Sin(elevation); // find the projection of the above spot onto the oriented gun
  1464.     return extend(above_turret, forward, turretposition, opposite_side); // now use that distance to shift the above spot
  1465. }
  1466.  
  1467.  
  1468.  
  1469. // gets the value that's in a block's name inside "<>"
  1470. double parse_adjustment(string name)
  1471. {
  1472.     if(name.Contains("<") && name.Contains(">"))
  1473.     {
  1474.         string text_in_parentheses = name.Split('<', '>')[1];
  1475.          
  1476.         if(text_in_parentheses!="")
  1477.             return Double.Parse(text_in_parentheses);
  1478.     }
  1479.      
  1480.     return 0;
  1481. }
  1482.  
  1483.  
  1484.  
  1485. // calculates the time for a shell to travel a certain distance while accelerating (even if it the time accelerating doesn't match that distance)
  1486.  
  1487. double calculate_time_with_acceleration(double distance, double shellspeed, double finalspeed, double shellacceleration)
  1488. {
  1489.     double time_accelerating = (finalspeed-shellspeed)/shellacceleration; // how long it takes for the projectile to reach the final speed
  1490.     double distance_while_accelerating = shellspeed*time_accelerating + (0.5 * shellacceleration * (time_accelerating * time_accelerating)); // distance traveled until the projectile has reached the final speed
  1491.      
  1492.     double distance_left = distance - distance_while_accelerating; // this will be positive if there's still distance left to travel; it will be negative if the projectile would reach the target while still accelerating
  1493.      
  1494.     // if there's still distance to travel, calculate the additional time and add it
  1495.     if(distance_left>=0)
  1496.     {
  1497.         double time_left = distance_left / finalspeed;
  1498.         return time_accelerating + time_left;
  1499.     }
  1500.      
  1501.     // otherwise, just calculate the time until the distance is reached, using the quadratic equation t^2*1/2*a + t*v - d = 0
  1502.     // to be honest most of the time this won't be necessary (most projectiles will have reached the final speed far before reaching the target)
  1503.     else
  1504.     {
  1505.         // solving for t:    t = -v +- sqrt( v^2 -4*1/2*a*(-d))
  1506.         //    divided by               2 * 1/2 * a
  1507.         double discriminant = Math.Sqrt(shellspeed*shellspeed -2*shellacceleration*-distance);
  1508.         double root1 = (-shellspeed + discriminant) / shellacceleration;
  1509.         double root2 = (-shellspeed - discriminant) / shellacceleration;
  1510.         return pick_greater(root1, root2); // pick the greatest solution
  1511.     }
  1512. }
  1513.  
  1514.  
  1515.  
  1516. // counts the number of turrets in a group (actually counts the number of center blocks in that group)
  1517.  
  1518. int count_turrets(string turretid)
  1519. {
  1520.     List<IMyTerminalBlock> centers = new List<IMyTerminalBlock>(); // center blocks
  1521.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid, centers, (x => x.CustomName.Contains("Center")));
  1522.      
  1523.     List<IMyTerminalBlock> remotes = new List<IMyTerminalBlock>(); // RotorX blocks
  1524.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid, remotes, (x => x.CustomName.Contains("Remote Control")));
  1525.      
  1526.     List<IMyTerminalBlock> rotors = new List<IMyTerminalBlock>(); // RotorX blocks
  1527.     GridTerminalSystem.SearchBlocksOfName("Turret "+turretid, rotors, (x => x.CustomName.Contains("RotorX")));
  1528.      
  1529.     int max_count = 0;
  1530.      
  1531.     if(rotors.Count >= centers.Count + remotes.Count)
  1532.         max_count = rotors.Count;
  1533.     else
  1534.         max_count = centers.Count + remotes.Count;
  1535.  
  1536.     int count = 0;
  1537.      
  1538.     // count the number of remote controls or center blocks for each turret
  1539.     for(int i=1; i<=max_count; i++)
  1540.     {
  1541.         List<IMyTerminalBlock> turret_remotes = new List<IMyTerminalBlock>();
  1542.         GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+i.ToString(), turret_remotes, x => x.CustomName.Contains("Remote Control"));
  1543.          
  1544.         if(turret_remotes.Count>0)
  1545.             count++;
  1546.         else
  1547.         {
  1548.             List<IMyTerminalBlock> turretcenters = new List<IMyTerminalBlock>();
  1549.             GridTerminalSystem.SearchBlocksOfName("Turret "+turretid+i.ToString(), turretcenters, x => x.CustomName.Contains("Center"));
  1550.              
  1551.             if(turretcenters.Count>0)
  1552.                 count++;
  1553.         }
  1554.     }
  1555.      
  1556.     return count;
  1557. }
  1558.  
  1559.  
  1560.  
  1561. // counts the directors (only for the first run)
  1562.  
  1563. int count_directors()
  1564. {
  1565.     List<IMyTerminalBlock> director_turrets = new List<IMyTerminalBlock>();
  1566.     GridTerminalSystem.GetBlocksOfType<IMyLargeTurretBase>(director_turrets, (x => x.CustomName.Contains(DIRECTOR_BASE_NAME)));
  1567.  
  1568.     List<IMyTerminalBlock> director_remotes = new List<IMyTerminalBlock>();
  1569.     GridTerminalSystem.GetBlocksOfType<IMyRemoteControl>(director_remotes, (x => x.CustomName.Contains(DIRECTOR_BASE_NAME+" "+name_director_remote)));
  1570.  
  1571.     return director_turrets.Count + director_remotes.Count;
  1572. }
  1573.  
  1574.  
  1575. void GetDirectionTo(Vector3D target, IMyTerminalBlock block, ref double Pitch, ref double Yaw)
  1576. {
  1577.     Vector3D center = block.GetPosition();
  1578.     Vector3D forward = get_block_forward_position(block, 1);
  1579.     Vector3D up = get_block_up_position(block, 1);
  1580.     GetDirectionTo(target, center, forward, up, ref Pitch, ref Yaw);
  1581. }
  1582.  
  1583.  
  1584.  
  1585. // TV:        target position
  1586. // OV:        origin position
  1587. // FV:        forward position
  1588. // UV:        up position
  1589. // Pitch:    (ref) vertical angle to the target
  1590. // Yaw:        (ref) horizontal angle to the target
  1591.  
  1592. void GetDirectionTo(Vector3D TV, Vector3D OV, Vector3D FV, Vector3D UV, ref double Pitch, ref double Yaw)
  1593. {
  1594.      
  1595.     VRageMath.Vector3D RV = get_right(OV, FV, UV);
  1596.  
  1597.     double TVOV = (OV - TV).Length();     //Get magnitudes of vectors.
  1598.  
  1599.     double TVFV = (FV - TV).Length();
  1600.     double TVUV = (UV - TV).Length();
  1601.     double TVRV = (RV - TV).Length();
  1602.  
  1603.     double OVFV = (FV - OV).Length();
  1604.     double OVUV = (UV - OV).Length();
  1605.     double OVRV = (RV - OV).Length();
  1606.  
  1607.     double ThetaP = Math.Acos((TVUV * TVUV - OVUV * OVUV - TVOV * TVOV) / (-2 * OVUV * TVOV));     //Use law of cosines to determine angles.
  1608.     double ThetaY = Math.Acos((TVRV * TVRV - OVRV * OVRV - TVOV * TVOV) / (-2 * OVRV * TVOV));
  1609.  
  1610.     double RPitch = 90 - (ThetaP * 180 / Math.PI);     //Convert from radians to degrees.
  1611.     double RYaw = 90 - (ThetaY * 180 / Math.PI);
  1612.  
  1613.     if (TVOV < TVFV)
  1614.         RPitch = 180 - RPitch;     //Normalize angles to -180 to 180 degrees.
  1615.      
  1616.     if(RPitch > 180)
  1617.         RPitch = -1 * (360 - RPitch);
  1618.  
  1619.     if (TVOV < TVFV)
  1620.         RYaw = 180 - RYaw;
  1621.      
  1622.     if (RYaw > 180)
  1623.         RYaw = -1 * (360 - RYaw);
  1624.  
  1625.     Pitch = RPitch;     //Set Pitch and Yaw outputs.
  1626.     Yaw = RYaw;
  1627. }
  1628.  
  1629.  
  1630.  
  1631. // finds the position to the right of a set of three blocks (center/front/up)
  1632.  
  1633. Vector3D get_right(Vector3D center, Vector3D front, Vector3D up)
  1634. {
  1635.     front = front - center;
  1636.     up = up - center;
  1637.     return center + Vector3D.Cross(front, up);
  1638. }
  1639.  
  1640.  
  1641.  
  1642. // sets the convergence according to what's on the convergence text panel
  1643.  
  1644. void read_convergence()
  1645. {
  1646.      
  1647.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1648.     GridTerminalSystem.SearchBlocksOfName(panel_convergence, panels);
  1649.      
  1650.     if(panels.Count<1)
  1651.         return;
  1652.      
  1653.     string text = (panels[0] as IMyTextPanel).GetPublicText();
  1654.      
  1655.     if(text!="")
  1656.         defaultconvergence = Double.Parse(text);
  1657. }
  1658.  
  1659.  
  1660.  
  1661. // just to pick which root to use from the quadratic equation
  1662.  
  1663. double pick_greater(double a, double b)
  1664. {
  1665.     if(a>b)
  1666.         return a;
  1667.     return b;    
  1668. }
  1669.  
  1670.  
  1671.  
  1672. // if the director's remote control is not controlled, lock the director rotors
  1673.  
  1674. bool lock_unlock_remote(string id, bool lock_unlock)
  1675. {    
  1676.     IMyTerminalBlock director_x = getblock_nullable(DIRECTOR_BASE_NAME+id+" "+name_director_rotorx);
  1677.     IMyTerminalBlock director_y = getblock_nullable(DIRECTOR_BASE_NAME+id+" "+name_director_rotory);
  1678.  
  1679.     if(director_x==null || director_y==null)
  1680.         return false;
  1681.      
  1682.     // lock all rotors
  1683.     if(lock_unlock)
  1684.     {
  1685.         director_x.ApplyAction("OnOff_On");
  1686.         director_y.ApplyAction("OnOff_On");
  1687.     }
  1688.      
  1689.     // unlock all rotors
  1690.     else
  1691.     {
  1692.         director_x.ApplyAction("OnOff_Off");
  1693.         director_y.ApplyAction("OnOff_Off");
  1694.     }
  1695.  
  1696.     return true;
  1697. }
  1698.  
  1699.  
  1700.  
  1701. // write a string to a text panel
  1702.  
  1703. void write(string name, string text, string header)
  1704. {
  1705.     if(header!="")
  1706.         text = header+"\n"+text;
  1707.      
  1708.     write(name, text);
  1709. }
  1710. void write(string name, string text)
  1711. {
  1712.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1713.     GridTerminalSystem.SearchBlocksOfName(name, panels);
  1714.      
  1715.     for(int i=0; i<panels.Count; i++)
  1716.     {
  1717.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1718.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1719.     }
  1720. }
  1721.  
  1722.  
  1723.  
  1724. // list all targets found on a text panel
  1725.  
  1726. void write_targets()
  1727. {
  1728.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1729.     GridTerminalSystem.SearchBlocksOfName(panel_targets, panels);
  1730.      
  1731.     if(panels.Count<1)
  1732.         return;
  1733.      
  1734.     string text = "No targets found";
  1735.      
  1736.     // add each target with the distance to this programmable block and with the GPS coordinates
  1737.     if(targets.Count>0)
  1738.     {
  1739.         text = "Targets found\n";
  1740.         IMyCubeBlock this_pb = Me as IMyCubeBlock;
  1741.          
  1742.         for(int i=0; i<targets.Count; i++)
  1743.         {
  1744.             string distance = Vector3D.Distance(this_pb.GetPosition(), targets[i]).ToString("N0");
  1745.             string x = targets[i].GetDim(0).ToString("N0");
  1746.             string y = targets[i].GetDim(1).ToString("N0");
  1747.             string z = targets[i].GetDim(2).ToString("N0");
  1748.             text += "\n"+String.Format("\n#{0}: {1} m\n({2}, {3}, {4})", i+1, distance, x, y, z);
  1749.         }
  1750.     }
  1751.      
  1752.     for(int i=0; i<panels.Count; i++)
  1753.     {
  1754.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1755.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1756.     }
  1757. }
  1758.  
  1759.  
  1760.  
  1761. // display position/speed/acceleration on a text panel
  1762.  
  1763. void write_to_panel(string name, Vector3D thing, string header)
  1764. {
  1765.      
  1766.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1767.     GridTerminalSystem.SearchBlocksOfName(name, panels);
  1768.      
  1769.     string x = (thing.GetDim(0)).ToString("N1");
  1770.     string y = (thing.GetDim(1)).ToString("N1");
  1771.     string z = (thing.GetDim(2)).ToString("N1");
  1772.  
  1773.     string length = (thing.Length()).ToString("N1");
  1774.      
  1775.     if(header!="")
  1776.         header = header + "\n";
  1777.      
  1778.     string separator = ", ";
  1779.     string text = header+ x +separator+ y +separator+ z +"\n("+length+")";
  1780.  
  1781.     for(int i=0; i<panels.Count; i++)
  1782.     {
  1783.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1784.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1785.     }
  1786. }
  1787.  
  1788.  
  1789.  
  1790. // writes the override info onto a text panel
  1791.  
  1792. void write_override(string name, Vector3D position)
  1793. {
  1794.      
  1795.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1796.     GridTerminalSystem.SearchBlocksOfName(name, panels);
  1797.      
  1798.     if(panels.Count<1)
  1799.         return;
  1800.      
  1801.     string px = (position.GetDim(0)).ToString()+"\n";
  1802.     string py = (position.GetDim(1)).ToString()+"\n";
  1803.     string pz = (position.GetDim(2)).ToString();
  1804.      
  1805.     string text = px+py+pz;
  1806.  
  1807.     for(int i=0; i<panels.Count; i++)
  1808.     {
  1809.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1810.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1811.     }
  1812. }
  1813.  
  1814.  
  1815.  
  1816. // display a number (for now only used for debugging)
  1817.  
  1818. void write_number(string name, double number, string header)
  1819. {
  1820.     if(header!="")
  1821.         header = header+"\n";
  1822.          
  1823.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1824.     GridTerminalSystem.SearchBlocksOfName(name, panels);
  1825.      
  1826.     string number_string = number.ToString("N3");
  1827.      
  1828.     string text = header+number_string;
  1829.      
  1830.     for(int i=0; i<panels.Count; i++)
  1831.     {
  1832.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1833.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1834.     }
  1835. }
  1836.  
  1837.  
  1838.  
  1839. // display the "warnings" string on the text panel for warnings
  1840.  
  1841. void write_warning(string text)
  1842. {
  1843.      
  1844.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1845.     GridTerminalSystem.SearchBlocksOfName(panel_warnings, panels);
  1846.      
  1847.     if(text=="")
  1848.     {
  1849.         text = total_number_of_turrets.ToString()+" turrets found.";
  1850.         if(total_number_of_turrets>0)
  1851.             text = text+"\nAll turrets are operational.";
  1852.     }
  1853.      
  1854.     for(int i=0; i<panels.Count; i++)
  1855.     {
  1856.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1857.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1858.     }
  1859. }
  1860.  
  1861.  
  1862.  
  1863. // dispays info about the script (whether it's running, # of turrets, toggles, etc) on the status text panel and programmable block
  1864.  
  1865. void write_status()
  1866. {
  1867.      
  1868.     List<IMyTerminalBlock> panels = new List<IMyTerminalBlock>();
  1869.     GridTerminalSystem.SearchBlocksOfName(panel_status, panels);
  1870.      
  1871.     if(panels.Count<1)
  1872.         return;
  1873.      
  1874.     string running = "GFCS Script:";
  1875.     if(time_counter<=30)
  1876.         running = "GFCS Script: running";
  1877.      
  1878.     string sensors_found = "Sensors found: "+(sensors.Count+super_sensors.Count).ToString();
  1879.     string targets_found = "Targets detected: "+targets.Count.ToString();
  1880.      
  1881.     string turrets = "Turrets currently found: "+total_number_of_turrets.ToString();
  1882.     string in_range = "Turrets in range: "+turrets_in_range.ToString();
  1883.      
  1884.     string directors = "Directors found: "+directors_found.ToString()+" out of "+starting_directors.ToString();
  1885.      
  1886.     string director = "Current director: "+current_director.ToString();
  1887.      
  1888.     if(using_override)
  1889.         director += " (override active)";
  1890.      
  1891.     string autolead = "Auto Lead:         off";
  1892.     string autotarget = "Auto Target:      off";
  1893.     string autoaim = "Auto Aim:          off";
  1894.     string autofire = "Auto Fire:          off";
  1895.     string idle = "Idle:                   off";
  1896.      
  1897.     if(check_block_toggle(name_autolead, default_autolead))
  1898.         autolead = "Auto Lead:    on";
  1899.     if(check_block_toggle(name_autotarget, default_autotarget))
  1900.         autotarget = "Auto Target: on";
  1901.     if(check_block_toggle(name_autoaim, default_autoaim))
  1902.         autoaim = "Auto Aim:     on";
  1903.     if(check_block_toggle(name_autofire, default_autofire))
  1904.         autofire = "Auto Fire:     on";
  1905.     if(check_block_toggle(name_idle, false))
  1906.         idle = "Idle:              on";
  1907.      
  1908.     string line = "\n";
  1909.     string text =    running+line+line+
  1910.                     sensors_found+line+
  1911.                     targets_found+line+line+
  1912.                     directors+line+
  1913.                     director+line+line+
  1914.                     turrets+line+
  1915.                     in_range+line+line+
  1916.                     found_groups+line+
  1917.                     autolead+line+
  1918.                     autotarget+line+
  1919.                     autoaim+line+
  1920.                     autofire+line+
  1921.                     idle;
  1922.      
  1923.     for(int i=0; i<panels.Count; i++)
  1924.     {
  1925.         (panels[i] as IMyTextPanel).WritePublicText(text, false);
  1926.         (panels[i] as IMyTextPanel).ShowPublicTextOnScreen();
  1927.     }
  1928.      
  1929.     if(error==false)
  1930.         Echo(text);
  1931. }
  1932.  
  1933.  
  1934.  
  1935. // look for a block, and return null if not found
  1936.  
  1937. IMyTerminalBlock getblock_nullable(string name)
  1938. {
  1939.     List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  1940.     GridTerminalSystem.SearchBlocksOfName(name, blocks);
  1941.      
  1942.     for(int i=0; i<blocks.Count; i++)
  1943.         if(string.Equals(blocks[i].CustomName, name, StringComparison.OrdinalIgnoreCase))
  1944.             return blocks[i];
  1945.      
  1946.     return null;
  1947. }
  1948.  
  1949.  
  1950.  
  1951. // find a turret's remote control block
  1952.  
  1953. IMyTerminalBlock get_turret_remote(string turretid)
  1954. {
  1955.     List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  1956.     GridTerminalSystem.SearchBlocksOfName(turretid, blocks, x => x.CustomName.Contains("Remote Control"));
  1957.      
  1958.     if(blocks.Count>0)
  1959.         return blocks[0];
  1960.      
  1961.     return null;
  1962. }
  1963.  
  1964.  
  1965.  
  1966. // reads GPS coordinates from a text panel's public text
  1967.  
  1968. Vector3D parse_override(IMyTextPanel panel)
  1969. {
  1970.     string text = panel.GetPublicText();
  1971.      
  1972.     string[] lines = text.Split(new [] {'\r', '\n'}); // separate each line of the panel text
  1973.      
  1974.     if(lines.Length<3)
  1975.     {
  1976.         error = true;
  1977.         Echo("Error: override text panel formatted incorrectly.");
  1978.         targetposition = new Vector3D(0,0,0);
  1979.         return new Vector3D(0,0,0);
  1980.     }
  1981.      
  1982.     double x = Double.Parse(lines[0]);
  1983.     double y = Double.Parse(lines[1]);
  1984.     double z = Double.Parse(lines[2]);
  1985.  
  1986.     return new Vector3D(x,y,z); // update the target's position with the override info
  1987. }
  1988.  
  1989.  
  1990.  
  1991. // check if a number if within (-X, X)
  1992.  
  1993. bool within(double number, double threshold)
  1994. {
  1995.     if(Math.Abs(number)<=threshold)
  1996.         return true;
  1997.      
  1998.     return false;
  1999. }
  2000.  
  2001.  
  2002.  
  2003. // convert radians to degrees
  2004.  
  2005. float ToDegrees(float angle)
  2006. {
  2007.     return (float)(angle * (180.0 / Math.PI));
  2008. }
  2009. double ToDegrees(double angle)
  2010. {
  2011.     return angle * (180.0 / Math.PI);
  2012. }
  2013.  
  2014.  
  2015. // convert degrees to radians
  2016.  
  2017. double ToRadians(double angle)
  2018. {
  2019.     return Math.PI * angle / 180.0;
  2020. }
Advertisement
Add Comment
Please, Sign In to add comment