· 9 years ago · Nov 05, 2016, 06:00 PM
1/*
2RATATOSK LCD Mirroring via laser antenna
3mar.17.2016
4
5Setup: Requires programming block, laser antenna, timer block, and text panel
6Set the timer block up to call this and itself each second.
7Automatically selects the closest laser antenna and text panel.
8
9Do this on both ships, then make sure their laser antennas connect.
10
11This is a one-to-one mirroring system, meaning one screen,
12one programming block, one antenna.
13
14Optional: create a block named "Warning Light"
15It will turn on when connection fails, turn off when connection is good.
16
17This can use Mimir commands by prepending a transmission with
18$REMOTE$
19If you call Mimir with $REMOTE$(arbitrarytexthere)
20It will broadcast that text regardless of what the output text box is.
21You can also change what happens with the connect/disconnect,
22it's just a mimir command.
23
24Based on
25MIMIR Mar-17-2016
26AI for quick setup of advanced monitoring.
27
28Please read long comments below the setup variables.
29*/
30
31// If null, use the closest screen. Otherwise, find the screen
32// with the specified name.
33string useNamedScreen = null; // = "Ratatosk Output";
34
35// If null, find the closest laser antenna. Otherwise, find
36// the antenna with the specified name.
37string useNamedAntenna = null; // = "Ratatosk Antenna";
38
39// If true, our antenna and LCD can only be from our CubeGrid -
40// IE, docked ships will not hijack.
41bool tightlyLocked = true;
42
43// Turn off if you don't like the communication showing on the HUD.
44bool HUDify = false;
45
46// If this is true, a timed-out broadcast is counted as transmitted,
47// keeping antennas names reasonable. If false, it will attempt
48// to rebroadcast continually, useful if you are ferrying data
49// via a drone.
50bool giveUpEasy = true;
51
52// This tells the system to broadcast the rest of the line as a command
53// to the far side. The far side will execute it if it matches their
54// command string. Usually you won't want to change this.
55string commandString = "$REMOTE$";
56
57// Change this if you're using a non-English language version.
58// It's the first word of the laser antenna's attempt to connect.
59string brokenAntennaText = "Trying ";
60
61// Change these if you want other things to happen.
62string onLinkBroken = "Warning Light.On";
63string onLinkEstablished = "Warning Light.Off";
64
65
66
67// Cached commands come in pairs. The first is the command
68// you send to Mimir, the second is the command Mimir runs.
69List<string> cachedCommands = new List<string> () {
70 "Lights Out", "*Light.Off",
71 // Default airlock control. Your airlock buttons should call Mimir with Airlock Out or Airlock In
72 //"Airlock Out", "*Airlock Door.Close;*Airlock Vent.Depressurize_On",
73 //"Airlock In", "*Airlock Door.Close;*Airlock Vent.Depressurize_Off",
74};
75
76
77// These are managed live by the script, don't modify them.
78IMyLaserAntenna antenna;
79IMyTextPanel ioScreen;
80bool flickNeeded = false;
81bool wasPermanent = false;
82bool broadcasting = false;
83bool processed = true;
84int recheckTimer = 0;
85int broadcastError = 0;
86bool linkEstablishedCall = true;
87
88string GPSCoords(IMyTerminalBlock block)
89{
90 string pos = block.GetPosition() + "";
91 pos = pos.Substring(1, pos.Length - 2); // cut off the brackets.
92 string[] bits = pos.Split(' ');
93 pos = "GPS:" + block.CustomName + ":" + bits[0].Substring(2) + ":" + bits[1].Substring(2) + ":" + bits[2].Substring(2) + ":";
94 //Example: GPS:Com 1's Antenna:47.11:-476.78:-228.27:
95 return pos;
96}
97
98void Connect(IMyLaserAntenna antennaA, IMyLaserAntenna antennaB)
99{
100 Log("Connecting " + antennaA.CustomName + " to " + antennaB.CustomName);
101 antennaA.SetTargetCoords(GPSCoords(antennaB));
102 antennaB.SetTargetCoords(GPSCoords(antennaA));
103 antennaA.Connect();
104 antennaB.Connect();
105 if (antennaA.CubeGrid == antennaB.CubeGrid)
106 Log("Warning: these are on the same cube grid.");
107 else
108 Log("(Connecting base to ship...)");
109}
110
111void ConnectToNearest()
112{
113 Log("Connecting " + Me.CustomName + " to nearest laser antenna:");
114 if ( (antenna == null) || (ioScreen == null) )
115 {
116 Log("Wait, I'm broken.");
117 return;
118 }
119 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
120 GridTerminalSystem.GetBlocksOfType<IMyLaserAntenna>(blocks);
121
122 IMyLaserAntenna target = null;
123
124 for (int a= 0; a < blocks.Count; a++)
125 {
126 if (blocks[a] == antenna) continue;
127 if (target == null)
128 {
129 target = blocks[a] as IMyLaserAntenna;
130 continue;
131 }
132 if (Vector3D.Distance(antenna.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(antenna.GetPosition(), target.GetPosition()) )
133 target = blocks[a] as IMyLaserAntenna;
134 }
135 if (target == null)
136 Log("Cannot find valid target...");
137 else
138 {
139 Connect(antenna, target);
140 ioScreen.WritePublicText("Connected " + antenna.CustomName + " to " + target.CustomName);
141 }
142}
143
144string connectAntennasContainingWord = "Local";
145void ConnectAllLocalAntenna()
146{
147 Log("Connecting all local antenna with '" + connectAntennasContainingWord + "' in the name...");
148 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
149 GridTerminalSystem.GetBlocksOfType<IMyLaserAntenna>(blocks);
150 if (blocks.Count == 0)
151 {
152 Log("Error: no laser antennas.");
153 return;
154 }
155
156 bool[] filled = new bool[blocks.Count];
157 for (int a= 0; a < blocks.Count; a++)
158 {
159 if (! blocks[a].CustomName.Contains(connectAntennasContainingWord))
160 {
161 Log("Not connecting " + blocks[a].CustomName);
162 filled[a] = true;
163 }
164 }
165
166
167 for (int a= 0; a < blocks.Count - 1; a++)
168 {
169 if (filled[a])
170 continue;
171
172 IMyLaserAntenna target = null;
173
174 for (int b = a + 1; b < blocks.Count; b++)
175 {
176 if (filled[b]) continue;
177
178 if (target == null)
179 {
180 target = blocks[b] as IMyLaserAntenna;
181 continue;
182 }
183 if (Vector3D.Distance(blocks[a].GetPosition(), blocks[b].GetPosition()) < Vector3D.Distance(blocks[a].GetPosition(), target.GetPosition()) )
184 target = blocks[b] as IMyLaserAntenna;
185 }
186 if (target == null)
187 {
188 Log("Error: no pair for " + blocks[a].CustomName);
189 }
190 else
191 {
192 Connect(blocks[a] as IMyLaserAntenna, target);
193 filled[a] = true;
194 filled[blocks.IndexOf(target)] = true;
195 }
196 }
197}
198
199void LoadBlocks()
200{
201 // Load antennae
202
203 if (useNamedAntenna == null)
204 {
205 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
206 GridTerminalSystem.GetBlocksOfType<IMyLaserAntenna>(blocks);
207 if (blocks.Count == 0)
208 {
209 Log("Error: no laser antennas.");
210 return;
211 }
212 antenna = blocks[0] as IMyLaserAntenna;
213 for (int a = 1; a < blocks.Count; a++)
214 {
215 if ((tightlyLocked) && (Me.CubeGrid != blocks[a].CubeGrid))
216 continue;
217
218 if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), antenna.GetPosition()) )
219 antenna = blocks[a] as IMyLaserAntenna;
220 }
221 Log("Picked the nearest antenna out of " + blocks.Count);
222 }
223 else
224 {
225 IMyTerminalBlock temp = Fetch(useNamedAntenna);
226 if (temp is IMyLaserAntenna)
227 antenna = temp as IMyLaserAntenna;
228 else
229 {
230 Log(useNamedAntenna + " isn't a laser antenna.");
231 return;
232 }
233 }
234 Log(GPSCoords(antenna));
235
236 // Find Screen
237
238 if (useNamedScreen == null)
239 {
240 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
241 GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(blocks);
242 if (blocks.Count == 0)
243 {
244 Log("Error: no text panels.");
245 return;
246 }
247
248 int brokenScreens = 0;
249 ioScreen = null;
250 for (int a = 0; a < blocks.Count; a++)
251 {
252 if ((tightlyLocked) && (Me.CubeGrid != blocks[a].CubeGrid))
253 continue;
254
255 if (! blocks[a].IsFunctional)
256 {
257 brokenScreens++;
258 continue;
259 }
260 if (ioScreen == null)
261 ioScreen = blocks[a] as IMyTextPanel;
262 else if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), ioScreen.GetPosition()) )
263 ioScreen = blocks[a] as IMyTextPanel;
264 }
265 if (ioScreen == null)
266 Log("Couldn't find a valid screen.");
267 else
268 Log("Picked screen named " + ioScreen.CustomName);
269 }
270 else
271 {
272 IMyTerminalBlock temp = Fetch(useNamedScreen);
273 if (temp != null)
274 {
275 ioScreen = temp as IMyTextPanel;
276 Log("Found a screen with my name.");
277 }
278 else
279 Log("Cannot find a screen named " + Me.CustomName + " Out");
280 }
281}
282
283void ProcessInput()
284{
285 processed = true;
286 antenna.SetValueBool("isPerm", true);
287 antenna.SetCustomName(Me.CustomName + "'s Antenna");
288
289 if (HUDify)
290 antenna.SetValueBool("ShowOnHUD", true);
291
292 Log("Processing input!");
293 string details = antenna.DetailedInfo;
294 string[] dets = details.Split('\n');
295 if (dets.Length < 4)
296 {
297 Log("No input received...");
298 }
299 else
300 {
301 string received = "";
302 for (int a= 3; a < dets.Length; a++)
303 {
304 if (dets[a].StartsWith(commandString))
305 {
306 dets[a] = dets[a].Substring(commandString.Length);
307 Log("Recv remote command:");
308 Apply(dets[a]);
309 }
310 received += dets[a] + "\n";
311
312 }
313 if (ioScreen != null)
314 {
315 Storage = "\n" + received;
316 ioScreen.WritePublicText(received);
317 Log("Wrote to screen...");
318 }
319 else
320 Log("No display to output to...");
321 }
322
323}
324bool AntennaDisconnected() {
325 if (antenna == null) return true;
326 string[] dets = antenna.DetailedInfo.Split('\n');
327 return dets[2].StartsWith(brokenAntennaText);
328}
329
330// Put your custom logic here! This will run every update.
331void CustomLogic()
332{
333 if ( (antenna == null) || (recheckTimer <= 0) )
334 {
335 if ((linkEstablishedCall) && (antenna == null) )
336 {
337 Apply(onLinkBroken);
338 linkEstablishedCall = false;
339 }
340 LoadBlocks();
341 recheckTimer = 30;
342 }
343 else
344 recheckTimer--;
345
346 if (AntennaDisconnected())
347 {
348 if (linkEstablishedCall)
349 {
350 Log("LINK BROKEN...");
351 Apply(onLinkBroken);
352 linkEstablishedCall = false;
353 }
354 wasPermanent = false;
355 }
356 else
357 {
358 if (! linkEstablishedCall)
359 {
360 Log("LINK ESTABLISHED...");
361 Apply(onLinkEstablished);
362 linkEstablishedCall = true;
363 }
364 }
365
366
367
368 if ( (ioScreen != null) && (! broadcasting) ) // don't do a mid-broadcast update, it doesn't work.
369 {
370 if (Storage != "\n" + ioScreen.GetPublicText())
371 {
372 if (HUDify)
373 antenna.SetValueBool("ShowOnHUD", true);
374 Log("Broadcasting public text!");
375 Storage = "\n" + ioScreen.GetPublicText();
376 antenna.SetCustomName("\n" + ioScreen.GetPublicText());
377 broadcastError = 10;
378 broadcasting = true;
379 antenna.SetValueBool("isPerm", false);
380 return;
381 }
382 }
383
384 if (broadcasting) // are we sending data? Wait for a response.
385 {
386 if (antenna.IsPermanent) // response received
387 {
388 if (HUDify)
389 antenna.SetValueBool("ShowOnHUD", false);
390 Log("Done broadcasting.");
391 antenna.SetCustomName(Me.CustomName + "'s Antenna");
392 broadcasting = false;
393 }
394 else
395 {
396 broadcastError--;
397 if (broadcastError <= 0)
398 {
399 broadcasting = false;
400 antenna.SetCustomName(Me.CustomName + "'s Antenna");
401 if (! giveUpEasy) // do we need to rebroadcast it until successful?
402 Storage = null;
403 }
404 }
405 return;
406 }
407
408 if (antenna.IsPermanent) // no comm required
409 {
410 if (! wasPermanent)
411 {
412 if (HUDify)
413 antenna.SetValueBool("ShowOnHUD", false);
414 wasPermanent = true;
415 }
416 return;
417 }
418
419 // IsPermanent flicked off. Reboot and refresh detailedInfo
420 if (wasPermanent)
421 {
422 wasPermanent = false;
423 flickNeeded = true;
424 antenna.SetCustomName("Receiving...");
425 Log("Comm detected. Flicking.");
426 }
427
428 if (antenna.IsWorking)
429 {
430 if (flickNeeded)
431 {
432 antenna.SetValueBool("OnOff", false);
433 flickNeeded = false;
434 processed = false;
435 }
436 else if (processed == false)
437 {
438 ProcessInput();
439 }
440 else // delay on reacquiring means this happens a lot: we're done but we have to say so.
441 antenna.SetValueBool("isPerm", true);
442 }
443 else
444 antenna.SetValueBool("OnOff", true);
445
446 /* // Uncomment these lines for default airlock control's other half. Keep in mind
447 // air vents often freak out in multiplayer and flicker from max to min rapidly.
448 if (CheckHigh("Airlock Vent",0.8))
449 Apply("*Airlock Door Int.Open_On");
450 else if (CheckLow("Airlock Vent",0.1))
451 Apply("*Airlock Door Ext.Open_On");
452 if (CheckHigh("Oxygen Tank", 0.8))
453 Apply("*Oxygen Generator.Off;*Oxygen Farm.Off");
454 else if (CheckLow("Oxygen Tank", 0.5))
455 Apply("*Oxygen Generator.On;*Oxygen Farm.On"); //*/
456
457 // Default large reactor fallbacks.
458 /*
459 if (CheckHigh("Large Reactor"))
460 Apply("*Battery.Recharge");
461 else if (CheckLow("Large Reactor"))
462 Apply("*Battery.Discharge;Batt Alert.PlaySound"); //*/
463}
464
465// Free power in creative mode screws up some of the logic, so
466// you need to specify if we're in creative or survival.
467// This might not work, they keep changing inventory systems.
468bool survivalMode = true;
469
470
471// List text boxes for adaptive interfaces. Don't forget to rig a button array
472// up to send in "AdaptiveUI #", zero-indexed. For example,
473// AdaptiveUI 0, AdaptiveUI 1, AdaptiveUI 2, AdaptiveUI 3
474string[] adaptiveScreens = new string[] {}; //{"Command Menu A", "Command Menu B"};
475
476
477// Use this to list the things that interface screens will show, in the
478// priority of them showing. Use "Adapt" to add text and a command
479// to the menu. IE,
480// Adapt("Bay needs to be closed", "Bay Timer.TriggerNow");
481// You can also use Mission to assign new missions.
482// Mission("Mine some ice!");
483void AdaptiveUI()
484{
485
486}
487
488// If true, Mimir's logs will be printed to a screen. If no screen is
489// specified and true, Mimir will log to the nearest screen.
490bool logToScreen = false;
491
492// Name a log screen if you want. You may use "*" notation to
493// log to multiple screens, for example "*Log" will log to all
494// screens with "Log" in their name. Leave null for logging to
495// closest screen.
496string logScreen = null;
497
498
499// If you want to use player-defined groups, turn this to true
500// It causes some memory use, so don't run Mimir in fast mode
501// if you're using named groups.
502bool useNamedGroups = false;
503
504// If true, Mimir will run every tick rather than every second.
505bool canRunFast = false;
506
507// If Mimir canRunFast and this is null, Mimir will search for the
508// closest timer upon boot-up and assume that is the one that
509// he needs to call every tick. If this is a name, he'll get that
510// timer name explicitly.
511string timer = null;
512
513// Run every this number of milliseconds if we run fast.
514double runDelay = 100;
515
516char commandSeparator = ';';
517
518// Adaptive UI stuff. Runs once per second, max.
519// Shows at the top of the adaptive UI screens.
520string adaptiveUIHeader = "--------------------------------------\n COMMANDS\n--------------------------------------\n\n";
521
522// If you're using custom button arrangements, you can change how many
523// options are printed to the screen.
524int optionsPerScreen = 4;
525
526
527
528
529/*
530MIMIR
531
532AI for quick setup of advanced monitoring, adaptable player UI, etc.
533
534Write quick pseudocode to create visual feeback for state changes.
535
536Do not use special characters in your block names if you use Mimir.
537
538Mimir can take commands. For the most part these are not any
539better than doing it via programming block, except that they
540search by name, which means it adapts to added blocks and such.
541
542Entry Light.Off
543
544As an argument would turn off a block named "Entry Light" (caps matter).
545
546*Light.On
547
548As an argument would turn on all blocks with "Light" in their name.
549
550This accepts all default commands from Keen - OnOff, TriggerNow, etc.
551I have also added some shortcut commands - for example,
552
553 .On and .Off (which map to OnOff_On & OnOff_Off)
554
555Some commands are not available in Keen's repertoire. For example
556
557 .Text and .Image
558
559Used without additional stuff, it simply switches whether
560the text box shows public text or image.
561
562Main Text.Text(Welcome, Captain!)
563
564Will type "Welcome, Captain!" on a panel named "Main Text".
565
566*Text.Image(Danger)
567
568Will change every text box with the word "Text" in its name to
569showing the "Danger" image.
570
571Default images (capitalization matters):
572
573Offline
574Online
575Arrow
576Cross
577Danger
578No Entry
579Construction
580White screen
581
582You can append more commands using ';'.
583
584*Alarm.On;*Door.Close;Command Status.Text(Warning!\nWarning!\nWarning!)
585
586Special note: you can use "Set(VARNAME)" and "UnSet(VARNAME)" to set
587persistent variables as part of a command. IE,
588
589Set(Airlock Out);*Airlock Door Int.Close;*Airlock Vent.Depressurize_On
590
591
592CACHED COMMANDS
593Long commands are not very much fun to type into the cramped "argument" window,
594so cached commands allow you to use a short phrase and have it result in a longer command.
595You may then edit the command as you wish and the short phrase will result in the updated
596command.
597
598
599CUSTOM LOGIC
600Use the CustomLogic function to quickly and easily create custom logic for
601your ship UI. This logic runs every update, and simply uses some functions
602to make it easy to check things.
603
604Status(BlockName) is the most basic function, and it gets a value between
6050 and 1 representing the block's primary function. IE, off (0) or on (1). Things
606like batteries return percent charged, cargo returns percent full, sensor returns
607if it is detecting something, and so on. This can freely be called many times -
608after the first time it uses a lookup table and does not cause substantial slowdown.
609
610Check(BlockName) is the most basic logic function. This will check and see whether
611the value of the block has transitioned across 0.5. IE, turned from on to off,
612or from off to on. It will return "true" only if there has been a transition between
613this update and last.
614
615Apply(BlockName, Command): Use to change the state of blocks without using annoying
616lookups. Most commands are simply actions (TriggerNow, Play, etc). Special case commands:
617On and Off are parsed correctly.
618Text and Image, applied to text panels, cause the panel to show text or show image.
619Text(SomeString) and Image(SomeString) cause the panel to show specified text or image.
620
621Using these three basic functions, you can usually wire together your ship UI without any
622complex, esoteric commands. The ship can also have automated logic - for example,
623playing a sound when a reactor is broken, or changing whatever cockpit is nearest to
624a sensor that found an ally to the primary cockpit.
625
626Additional functions:
627
628Check(BlockName, Percentage): Specify percentage rather than assuming 0.5.
629IsHigh and IsLow check if the block is high or low
630CheckHigh and CheckLow only trigger when the unit goes high or low.
631
632Exists(BlockName) checks to see if a block exists
633Local(BlockName) checks if the block is attached right now
634Memorize(BlockName) saves the block to memory so even if it is detached, it'll still be accessible.
635
636Output(string BlockName) gets the output percent as 0-1 for batteries, reactors, and solar panels.
637
638IsEnabled, IsDamaged, and IsCharging get that status of the block (batteries only, for that last one)
639
640Fetch(BlockName) will fetch a block (or null).
641
642Set("Value"), UnSet("Value"), and IsSet("Value") use a simple dictionary to allow you to use basic
643flags in your commands. Be careful: these do not survive loads or recompiles.
644
645WorldPos("Name") and GridPos("Name") give the Vector3D of the specified block,
646useful if you know how to use them. However, the Vector3D stuff is a bit opaque
647if this is your first time.
648
649ADAPTIVE UI
650Adaptive UI is used to display commands on text screens, with the intention of allowing the player
651to see the most important details of the ship. Buttons can be wired up to activate these if they are
652adaptive commands, allowing the player to trigger the most important actions that might need
653triggering - for example, battle mode, airlock pressurization, powering up reactors, etc. You can
654use complex logic - for example, "if our solar panels are down, allow the player to turn on reactors"
655
656To wire buttons up, they should pass Mimir the phrase "AdaptiveUI #", starting at 0. For example,
657AdaptiveUI 0, AdaptiveUI 1, AdaptiveUI 2, AdaptiveUI 3
658
659The AdaptiveUI function runs once per second, rather than at the often much faster rate of
660CustomLogic. This makes it safer to do more expensive computations.
661
662Use the special commands
663
664Mission(TEXT) : Sets up a mission suggestion (not selectable)
665Adapt(TEXT, COMMAND): Sets up an adaptive command which can be triggered. It will
666 say "TEXT" on the screen, but perform "COMMAND" when triggered.
667
668
669Some commands for you:
670 Figuring stuff out
671Some classes have dedicated properties, which you have to look up in the source code or a reference.
672However, if you want to get a list of all terminal properties and actions, use
673 Analyze(string name) , in a call, "NAME.Analyze"
674This will print a list of everything to the log, which can be seen in the DetailedInfo or LCD panel output.
675
676 Universal
677Default returns 1 if on, powered, and working.
678Actions:
679 OnOff toggles state
680 On, Off turns to on or off
681Properties in Code:
682 Use SetFloat(blockName, propertyName, value) to set arbitrary properties (not available in an Apply string)
683 Use SetColor(blockName, propertyName, int R, int B, int G) to set arbitrary colors.
684
685 LCD
686In an Apply string, ".Text(SomeText)" and ".Image(SomeImage)"
687In code, use
688 ColorBackground(string name, int R, int G, int B)
689 ColorFont(string name, int R, int G, int B)
690 FontSize(string name, float size)
691
692 REACTORS
693Return 0 if off or in survival and out of uranium. 1 otherwise.
694
695 SOLAR PANELS
696Return 1 if exposed to the sun and on.
697
698 BATTERY BLOCKS
699Return their % charge as their value
700Recharge, Discharge, SemiAuto
701
702 SOUND BLOCKS
703PlaySound is the command you want.
704No known way to change the audio loaded in.
705
706 ROTORS
707Returns angle value between minimum and maximum angle.
708Reverse Reverses
709RotPlus, RotMinus Reverses if necessary to go in indicated direction
710
711 PISTONS
712Returns extension value between 0 (lower limit) and 1 (upper limit)
713Reverse, Extend, Retract
714
715 DOORS
716Returns 1 if open, 0 if closed or broken
717Open Toggles open-ness (sigh...)
718Open_On Opens door
719Open_Off, Close Closes door
720
721 CARGO
722Returns percentage full (1 being full)
723
724 TURRETS
725Use functions Elevation(TurretName), Azimuth(TurretName), SetElevation(name, val),
726SetAzimuth(name, val) or ResetTargeting(name)
727
728 SHIP CONNECTORS
729Returns 0 if unconnected, 1 if connected. Use
730GetConnected("ConnectorName") in code to fetch the ShipConnector on the other side.
731
732 LANDING GEAR
733Returns 0 if unlocked, 1 if locked.
734(In-game programming blocks cannot access the GetAttachedEntity method, sorry.)
735
736 GRAVITY GENERATORS
737You can use SetGravity(Name, Amount) to change their gravity level. This can only
738be done in code, NOT as an argument or cached command.
739
740*/
741
742// Runtime only, don't modify.
743List<string> existingKeys = new List<string>();
744List<double> existingValues = new List<double>();
745
746List<string> newKeys = new List<string>();
747List<double> newValues = new List<double>();
748
749List<string> adaptiveOptions = new List<string>();
750List<string> adaptiveCommands = new List<string>();
751
752// The list of values currently set using Set.
753List<string> setValues = new List<string>();
754
755IMyTimerBlock heart;
756double delayCounter = 0;
757double adaptiveUICounter = 0;
758
759List<IMyTerminalBlock> memorizedBlocks = new List<IMyTerminalBlock>(); // for abusing merge block tricks.
760string log = "";
761
762void Main(string arg)
763{
764 if (GridTerminalSystem == null)
765 throw new Exception("!!!");
766 if (arg == "Connect")
767 {
768 ConnectAllLocalAntenna();
769 return;
770 }
771 if (arg == "ConnectMe")
772 {
773 ConnectToNearest();
774 return;
775 }
776
777 if (arg.StartsWith(commandString))
778 {
779 Log("REMOTE COMMAND INITIATED");
780 if (ioScreen == null)
781 {
782 Log("Error: no broadcast screen.");
783 }
784 else
785 {
786 ioScreen.WritePublicText(arg);
787 Storage = null; // force broadcast even if it's the same.
788 }
789 arg = null;
790 }
791
792
793 if (canRunFast) Heartbeat();
794 delayCounter -= ElapsedTime.Milliseconds;
795 adaptiveUICounter -= ElapsedTime.Milliseconds;
796 if (adaptiveUICounter < 0)
797 {
798 if (canRunFast)
799 adaptiveUICounter = 1000;
800 else
801 adaptiveUICounter = 0;
802 adaptiveOptions.Clear();
803 adaptiveCommands.Clear();
804 AdaptiveUI();
805
806 ShowAdaptiveUI();
807 }
808
809
810 // "tick" call, no command.
811 if ( (arg == null) || (arg.Length < 2) )
812 {
813 if (delayCounter > 0)
814 return;
815 if (canRunFast)
816 delayCounter += runDelay;
817 else
818 delayCounter = 0;
819
820 newValues.Clear();
821 newKeys.Clear();
822 CustomLogic();
823
824 for (int a = 0; a < newKeys.Count; a++)
825 {
826 if (existingKeys.Contains(newKeys[a]))
827 {
828 //Echo ("For " + newKeys[a] + ", value from " + existingValues[existingKeys.IndexOf(newKeys[a])] + " to " + newValues[a]);
829 existingValues[existingKeys.IndexOf(newKeys[a])] = newValues[a];
830 }
831 else
832 {
833 //Echo ("For " + newKeys[a] + ", initial value of " + newValues[a]);
834 existingKeys.Add(newKeys[a]);
835 existingValues.Add(newValues[a]);
836 }
837 }
838
839 return;
840 }
841
842 if (arg.ToLower().StartsWith("adaptiveui"))
843 {
844 // Should add some error catching here, but lazy.
845 int num = int.Parse(arg.Split(' ')[1]);
846 if (num < adaptiveCommands.Count)
847 {
848 Log("Taking adaptive option " + (num + 1));
849 if (adaptiveCommands[num] != null)
850 adaptiveOptions[num] = "...Working...";
851 else
852 {
853 adaptiveOptions[num] = " Just go do it. ";
854 ShowAdaptiveUI();
855 return;
856 }
857 arg = adaptiveCommands[num];
858 ShowAdaptiveUI();
859 }
860 else
861 {
862 Log("No selection #" + num + " on adaptive UI.");
863 return;
864 }
865 }
866
867 Apply(arg);
868}
869
870void ApplyToGroup(string name, string command)
871{
872 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
873 GridTerminalSystem.SearchBlocksOfName(name, blocks);
874 if (blocks.Count > 0)
875 ApplyToGroup(blocks, command);
876 else
877 Log("Couldn't find any blocks named " + name);
878}
879void ApplyToGroup(List<IMyTerminalBlock> blocks, string command)
880{
881 for (int a = 0; a < blocks.Count; a++)
882 {
883 ApplyToBlock(blocks[a], command);
884 }
885
886}
887void Apply(string arg)
888{
889 if ( (arg == null) || (arg.Length < 2) ) return;
890
891 // Command given, process it:
892 if (arg.Contains(commandSeparator.ToString()))
893 {
894 string[] commands = arg.Split(commandSeparator);
895 for (int a = 0; a < commands.Length; a++)
896 {
897 Apply(commands[a].Trim());
898 }
899 return;
900 }
901
902 if (arg.ToLower().StartsWith("set("))
903 {
904 string[] bits = arg.Split('(');
905 if (bits[1].EndsWith(")"))
906 bits[1] = bits[1].Substring(0, bits[1].Length - 1);
907 Set(bits[1]);
908 return;
909 }
910 else if (arg.ToLower().StartsWith("unset("))
911 {
912 string[] bits = arg.Split('(');
913 if (bits[1].EndsWith(")"))
914 bits[1] = bits[1].Substring(0, bits[1].Length - 1);
915 Unset(bits[1]);
916 return;
917 }
918
919
920
921
922 if (cachedCommands.IndexOf(arg) % 2 == 0) // it's a cached command
923 {
924 Log("Cached command #" + (cachedCommands.IndexOf(arg) / 2) + ", " + arg);
925 Apply(cachedCommands[cachedCommands.IndexOf(arg) + 1]);
926 return;
927 }
928
929
930 int index = arg.IndexOf(".");
931
932 if (index < 0)
933 {
934 Log(arg + " is not a command I understand.");
935 return;
936 }
937 if (arg.StartsWith("*"))
938 ApplyToGroup(arg.Substring(1, index - 1), arg.Substring(index + 1));
939 else
940 ApplyToBlock(arg.Substring(0, index), arg.Substring(index + 1));
941}
942
943void Apply(string name, string command)
944{
945 ApplyToBlock(name, command);
946}
947void ApplyToBlock(string name, string command)
948{
949 if (useNamedGroups)
950 {
951 List<IMyBlockGroup> groups = new List<IMyBlockGroup>();
952 for (int a = 0; a < groups.Count; a++)
953 {
954 if (groups[a].Name == name)
955 {
956 ApplyToGroup(groups[a].Blocks, command);
957 return;
958 }
959 }
960 }
961
962 for (int a= 0; a < memorizedBlocks.Count; a++)
963 {
964 if (memorizedBlocks[a].CustomName == name)
965 {
966 ApplyToBlock(memorizedBlocks[a], command);
967 return;
968 }
969 }
970
971 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(name);
972 ApplyToBlock(block, command);
973
974}
975void ApplyToBlock(IMyTerminalBlock block, string command)
976{
977 if (block == null)
978 {
979 Log ("No block found to " + command + "... check names!");
980 return;
981 }
982
983 string action = command.ToLower();
984 if (action == "analyze")
985 {
986 Analyze(block);
987 return;
988 }
989 if (action == "rotplus")
990 {
991 if (! (block is IMyMotorStator))
992 {
993 Log("Cannot rotate non-rotor.");
994 return;
995 }
996 if ( (block as IMyMotorStator).Velocity < 0)
997
998 ApplyAction(block,"Reverse");
999 return;
1000 }
1001 if (action == "rotminus")
1002 {
1003 if (! (block is IMyMotorStator))
1004 {
1005 Log("Cannot rotate non-rotor.");
1006 return;
1007 }
1008 if ( (block as IMyMotorStator).Velocity > 0)
1009 ApplyAction(block,"Reverse");
1010 return;
1011
1012 }
1013 if (action == "close")
1014 {
1015 ApplyAction(block,"Open_Off");
1016 return;
1017 }
1018 if (action == "on")
1019 {
1020 ApplyAction(block,"OnOff_On");
1021 Log(block.CustomName + " OnOff_On");
1022 return;
1023 }
1024 if (action == "off")
1025 {
1026 ApplyAction(block,"OnOff_Off");
1027 Log(block.CustomName + " OnOff_Off");
1028 return;
1029 }
1030 if (action.StartsWith("run") && (block is IMyProgrammableBlock))
1031 {
1032 (block as IMyProgrammableBlock).TryRun(action.Substring(4));
1033 return;
1034 }
1035 if (action.StartsWith("tryrun") && (block is IMyProgrammableBlock))
1036 {
1037 (block as IMyProgrammableBlock).TryRun(action.Substring(7));
1038 return;
1039 }
1040
1041 if (action.StartsWith("text"))
1042 {
1043
1044 int index = command.IndexOf("(");
1045 if (index > 0)
1046 {
1047
1048 string text= command.Substring(index + 1);
1049 if (text.IndexOf(")") >= text.Length - 2)
1050 text = text.Substring(0, text.Length - 1);
1051
1052 string parsedText = "";
1053 while (text.IndexOf("\\") >= 0)
1054 {
1055 // Deep magic for carriage return escapes? There's probably an unescape function.
1056 parsedText += text.Substring(0, text.IndexOf("\\")) + "\n";
1057 text = text.Substring(text.IndexOf("\\") + 2); // we assume ALL escaped characters are newlines.
1058 }
1059 parsedText += text;
1060
1061 (block as IMyTextPanel).ShowTextureOnScreen(); // avoid sensor not marking it dirty.
1062 (block as IMyTextPanel).WritePublicText(parsedText);
1063 (block as IMyTextPanel).ShowPublicTextOnScreen();
1064 //if (!logRecursionLock)
1065 //Log(block.CustomName + " writing...");
1066 //Echo ("Writing " + parsedText);
1067 }
1068 else
1069 {
1070 (block as IMyTextPanel).ShowPublicTextOnScreen();
1071
1072 Log ("Showing text.");
1073 }
1074 return;
1075 }
1076 else if (action.StartsWith("image") )
1077 {
1078 (block as IMyTextPanel).ShowTextureOnScreen();
1079 int index = command.IndexOf("(");
1080 if (index > 0)
1081 {
1082 (block as IMyTextPanel).ClearImagesFromSelection();
1083
1084 string text= command.Substring(index + 1);
1085 if (text.IndexOf(")") >= text.Length - 2)
1086 text = text.Substring(0, text.Length - 1);
1087 (block as IMyTextPanel).AddImageToSelection(text);
1088 Log (block.CustomName + " imaged " + text);
1089 }
1090 else
1091 Log("Showing images.");
1092
1093 return;
1094 }
1095 Log(block.CustomName + " " + command);
1096 ApplyAction(block,command);
1097
1098}
1099void Analyze(string name)
1100{ AnalyzeBlock(name); }
1101void Analyze(IMyTerminalBlock block)
1102{ AnalyzeBlock(block); }
1103
1104void AnalyzeBlock(string name)
1105{
1106 IMyTerminalBlock block = Fetch(name);
1107 if (block == null)
1108 {
1109 Log("Couldn't find " + name + " to analyze.");
1110 return;
1111 }
1112 AnalyzeBlock(block);
1113}
1114
1115void AnalyzeBlock(IMyTerminalBlock block)
1116{
1117 if (block == null)
1118 {
1119 Log("Couldn't find block to analyze.");
1120 return;
1121 }
1122
1123 string vals = "";
1124
1125 vals += " Actions:";
1126 List<ITerminalAction> acts = new List<ITerminalAction>();
1127 block.GetActions(acts);
1128 for (int a = 0; a < acts.Count; a++)
1129 {
1130 if (a % 3 == 0)
1131 vals += "\n";
1132 else
1133 vals += ", ";
1134 vals += acts[a].Id;
1135 }
1136
1137 List<ITerminalProperty> props = new List<ITerminalProperty>();
1138 block.GetProperties(props);
1139 vals += "\n\n Properties: ";
1140 for (int a = 0; a < props.Count; a++)
1141 {
1142 if (a % 3 == 0)
1143 vals += "\n";
1144 else
1145 vals += ", ";
1146 vals += props[a].Id;
1147 }
1148 Log(vals);
1149
1150}
1151
1152void SetFloat(string blockName, string property, float value)
1153{
1154 SetFloat(Fetch(blockName), property, value);
1155}
1156void SetFloat(IMyTerminalBlock block, string property, float value)
1157{
1158 if (block == null)
1159 { Echo("No block found, cannot set float"); return; }
1160 if (block.GetProperty(property) == null)
1161 { Echo("No property named " + property + ", cannot set float"); return; }
1162 block.SetValueFloat(property, value);
1163}
1164void SetColor(string blockName, string property, int R, int G, int B)
1165{
1166 SetColor(Fetch(blockName), property, R, G, B);
1167}
1168void SetColor(IMyTerminalBlock block, string property, int R, int G, int B)
1169{
1170 if (block == null)
1171 { Echo("No block found, cannot set color"); return; }
1172 if (block.GetProperty(property) == null)
1173 { Echo("No property named " + property + ", cannot set float"); return; }
1174 block.SetValue<Color>(property, new Color(R, G, B));
1175}
1176
1177void ColorBackground(string name, int R, int G, int B)
1178{
1179 SetColor(name, "BackgroundColor", R, G, B);
1180}
1181void ColorFont(string name, int R, int G, int B)
1182{
1183 SetColor(name, "FontColor", R, G, B);
1184}
1185void FontSize(string name, float size)
1186{
1187 SetFloat(name, "FontSize", size);
1188}
1189
1190float UnitMultiple(string unit)
1191{
1192 if (unit.StartsWith("W")) return 0.001f;
1193 if (unit.StartsWith("kW")) return 1f;
1194 if (unit.StartsWith("MW")) return 1000f;
1195 if (unit.StartsWith("GW")) return 1000000f;
1196 return 0.001f;
1197}
1198
1199string FetchLine(string key, string details)
1200{
1201 string[] comm = details.Split('\n');
1202 for (int a = 0; a < comm.Length; a++)
1203 {
1204 if (comm[a].StartsWith(key))
1205 return comm[a].Split(':')[1].Trim();
1206 }
1207 //Echo (key + " not found in " + details);
1208 return null;
1209}
1210float FetchLabeledValue(string key, string details)
1211{
1212 string val = FetchLine(key, details);
1213 if (val == null) return 0;
1214
1215 string[] dets = val.Split(' ');
1216 if (dets.Length >= 2)
1217 return float.Parse(dets[0]) * UnitMultiple(dets[1]);
1218 else
1219 return float.Parse(dets[0]);
1220}
1221
1222void Set(string name)
1223{
1224 if (! setValues.Contains(name))
1225 setValues.Add(name);
1226 Log("Set " + name);
1227}
1228void Unset(string name) { UnSet(name); }
1229void UnSet(string name)
1230{
1231 if (setValues.Contains(name))
1232 setValues.Remove(name);
1233 Log("UnSet " + name);
1234}
1235bool Isset(string name) { return IsSet(name); }
1236bool IsSet(string name)
1237{
1238 return (setValues.Contains(name));
1239}
1240
1241// Does not check memory
1242bool Local(string blockName)
1243{
1244 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
1245 return block != null;
1246}
1247// Does check memory.
1248bool Exists(string blockName)
1249{
1250 for (int a= 0; a < memorizedBlocks.Count; a++)
1251 {
1252 if (memorizedBlocks[a].CustomName == blockName)
1253 {
1254 return true;
1255 }
1256 }
1257
1258 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
1259 return block != null;
1260}
1261void Memorize(string blockName)
1262{
1263 for (int a= 0; a < memorizedBlocks.Count; a++)
1264 {
1265 if (memorizedBlocks[a].CustomName == blockName)
1266 {
1267 return;
1268 }
1269 }
1270
1271 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
1272 if (block == null)
1273 Log("Could not memorize nonexistant block " + blockName);
1274 else
1275 memorizedBlocks.Add(block);
1276
1277}
1278
1279// Grab a block.
1280IMyTerminalBlock Fetch(string blockName)
1281{
1282 IMyTerminalBlock block = null;
1283 for (int a= 0; a < memorizedBlocks.Count; a++)
1284 {
1285 if (memorizedBlocks[a].CustomName == blockName)
1286 {
1287 block = memorizedBlocks[a];
1288 }
1289 }
1290 if (block == null)
1291 block = GridTerminalSystem.GetBlockWithName(blockName);
1292 if (block == null)
1293 Log("No block named " + blockName);
1294 return block;
1295}
1296
1297IMyShipConnector GetConnected(string connectorName)
1298{
1299 IMyTerminalBlock block = Fetch(connectorName);
1300 if (block == null)
1301 {
1302 Log(connectorName + " doesn't exist.");
1303 return null;
1304 }
1305 if (block is IMyShipConnector)
1306 return (block as IMyShipConnector).OtherConnector;
1307 Log(connectorName + " isn't a ship connector.");
1308 return null;
1309}
1310
1311// Checks if the block is set to "enabled", specifically.
1312bool IsEnabled(string blockName)
1313{
1314 IMyTerminalBlock block = Fetch(blockName);
1315
1316 if (block == null)
1317 {
1318 Log("Could not find " + blockName);
1319 return false;
1320 }
1321
1322 if (! block.IsFunctional) return false;
1323 if (block is IMyFunctionalBlock)
1324 {
1325 return (block as IMyFunctionalBlock).Enabled;
1326 }
1327 return true; // not a functional block, cannot be disabled.
1328}
1329
1330// Checks if the block is damaged.
1331bool IsDamaged(string blockName)
1332{
1333 IMyTerminalBlock block = Fetch(blockName);
1334 if (block == null)
1335 {
1336 Log("Could not find " + blockName);
1337 return true; // probably destroyed completely.
1338 }
1339
1340 if (! block.IsFunctional) return true;
1341 if (block.IsBeingHacked) return true;
1342 return false;
1343}
1344
1345bool IsCharging(string batteryName)
1346{
1347 IMyTerminalBlock block = Fetch(batteryName);
1348 if (block == null)
1349 {
1350 Log("No block named " + batteryName);
1351 return false;
1352 }
1353 string recharging = FetchLine("Fully recharged", block.DetailedInfo);
1354 return (recharging != null);
1355}
1356
1357Vector3D GridPos(string blockName)
1358{
1359 IMyTerminalBlock block = Fetch(blockName);
1360 if (block == null)
1361 {
1362 Log("No block named " + blockName);
1363 return new Vector3D(0,0,0);
1364 }
1365
1366 return block.Position;
1367}
1368Vector3D WorldPos(string blockName)
1369{
1370 IMyTerminalBlock block = Fetch(blockName);
1371 if (block == null)
1372 {
1373 Log("No block named " + blockName);
1374 return new Vector3D(0,0,0);
1375 }
1376
1377 return block.GetPosition();
1378}
1379
1380
1381// Turret haxxxx
1382double Azimuth(string turretName)
1383{
1384 IMyTerminalBlock block = Fetch(turretName);
1385 if (block == null)
1386 return 0;
1387 if (block is IMyLargeTurretBase)
1388 return (block as IMyLargeTurretBase).Azimuth; // synching not required.
1389 return 0;
1390}
1391double Elevation(string turretName)
1392{
1393 IMyTerminalBlock block = Fetch(turretName);
1394 if (block == null)
1395 return 0;
1396 if (block is IMyLargeTurretBase)
1397 return (block as IMyLargeTurretBase).Elevation; // synching not required.
1398 return 0;
1399}
1400
1401bool SetAzimuth(string turretName, float azimuth)
1402{
1403 IMyTerminalBlock block = Fetch(turretName);
1404 if (block == null)
1405 return false;
1406 if (!(block is IMyLargeTurretBase))
1407 return false;
1408 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
1409 turret.Azimuth = azimuth;
1410 //turret.SyncAzimuth();
1411 Log(turretName+ " azimuth to " + azimuth);
1412 return true;
1413}
1414bool SetElevation(string turretName, float elevation)
1415{
1416 IMyTerminalBlock block = Fetch(turretName);
1417 if (block == null)
1418 return false;
1419 if (!(block is IMyLargeTurretBase))
1420 return false;
1421 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
1422 turret.Elevation = elevation;
1423// turret.SyncAzimuth();
1424 Log(turretName+ " elevation to " + elevation);
1425 return true;
1426}
1427
1428bool ResetTargeting(string turretName)
1429{
1430 IMyTerminalBlock block = Fetch(turretName);
1431 if (block == null)
1432 return false;
1433 if (!(block is IMyLargeTurretBase))
1434 return false;
1435 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
1436 turret.ResetTargetingToDefault();
1437 turret.EnableIdleRotation = false;
1438 turret.SyncEnableIdleRotation();
1439 Log(turretName+ " targeting reset");
1440 return true;
1441}
1442// Set up a gravity block with a specific gravity output.
1443bool SetGravity(string gravityName, float gravity)
1444{
1445 IMyTerminalBlock block = Fetch(gravityName);
1446 if (block == null)
1447 {
1448 Log(gravityName + " doesn't exist.");
1449 return false;
1450 }
1451 return SetGravity(block, gravity);
1452}
1453bool SetGravity(IMyTerminalBlock block, float gravity)
1454{
1455 if (block == null)
1456 {
1457 Log("No such gravity block.");
1458 return false;
1459 }
1460 if (! (block is IMyGravityGeneratorBase))
1461 {
1462 Log("Not a gravity generator.");
1463 return false;
1464 }
1465
1466 block.SetValueFloat("Gravity", gravity * 10);
1467 return true;
1468}
1469
1470// Gets the output percent as 0-1 for batteries, reactors, and solar panels.
1471double Output(string reactorName)
1472{
1473 IMyTerminalBlock block = Fetch(reactorName);
1474 if (block == null)
1475 {
1476 Log("No battery, solar panel, or reactor named " + reactorName);
1477 return 0;
1478 }
1479 return Output(block);
1480}
1481double Output(IMyTerminalBlock block)
1482{
1483 if (block == null)
1484 {
1485 Log("No battery, solar panel, or reactor passed to Output function.");
1486 return 0;
1487 }
1488
1489 if ( (block is IMyBatteryBlock) || (block is IMyReactor) || (block is IMySolarPanel) )
1490 {
1491 float power = FetchLabeledValue("Current Output",block.DetailedInfo);
1492 float max = FetchLabeledValue("Max Output",block.DetailedInfo);
1493 //Echo (power + " of " + max + " in battery.");
1494 if (max == 0)
1495 return 0;
1496 return power / max;
1497 }
1498 Log(block.CustomName + " isn't a battery, solar panel, or reactor.");
1499 return 0;
1500}
1501
1502// Gets a value between 0 and 1.
1503double Status(string blockName)
1504{
1505 if (newKeys.Contains(blockName))
1506 {
1507 return newValues[newKeys.IndexOf(blockName)];
1508 }
1509
1510 double val = _Status(blockName);
1511
1512 newKeys.Add(blockName);
1513 newValues.Add(val);
1514
1515 return val;
1516}
1517double Status(IMyTerminalBlock block)
1518{
1519 if (block == null)
1520 {
1521 Log("Could not find block");
1522 return 0;
1523 }
1524 return _Status(block);
1525}
1526
1527double _Status(string blockName)
1528{
1529 IMyTerminalBlock block = Fetch(blockName);
1530 if (block == null)
1531 {
1532 Log("Could not find " + blockName);
1533 return 0;
1534 }
1535 return _Status(block);
1536}
1537double _Status(IMyTerminalBlock block)
1538{
1539 if (block == null)
1540 {
1541 Log("That's not a valid block, can't get status.");
1542 return 0;
1543 }
1544
1545 if (! block.IsFunctional) return 0;
1546
1547 if (block is IMyDoor)
1548 {
1549 return (block as IMyDoor).OpenRatio;
1550 //return ( (block as IMyDoor).Open ? 1 : 0); // very granular.
1551 }
1552
1553
1554 if (block is IMyShipConnector)
1555 return ( (block as IMyShipConnector).IsConnected ? 1 : 0);
1556 if (block is IMyLandingGear)
1557 return ( (block as IMyLandingGear).IsLocked ? 1 : 0);
1558
1559 if (block is IMyAirVent)
1560 return (block as IMyAirVent).GetOxygenLevel();
1561
1562 if (block is IMyProductionBlock)
1563 {
1564 return (block as IMyProductionBlock).IsProducing ? 1 : 0;
1565 }
1566
1567
1568 if (block is IMyJumpDrive) // we can tell if it's ready to jump, but not how charged it is.
1569 {
1570 if (block.GetValueBool("Recharge")) return 1;
1571 }
1572
1573 if ( (block is IMyBatteryBlock) || (block is IMyJumpDrive) )
1574 {
1575 if (! block.IsFunctional) return 0; // battery is off?
1576 float power = FetchLabeledValue("Stored power",block.DetailedInfo);
1577 float max = FetchLabeledValue("Max Stored",block.DetailedInfo);
1578
1579 //Echo (power + " of " + max + " in battery.");
1580 return power / max;
1581 }
1582 if (block is IMyReactor)
1583 {
1584 float power = FetchLabeledValue("Current Output",block.DetailedInfo);
1585
1586 if (power > 0) return 1;
1587 // Not producing power. It might be because nobody NEEDS any.
1588
1589 if (survivalMode)
1590 {
1591 VRage.Game.ModAPI.Ingame.IMyInventory inv = block.GetInventory(0);
1592 if (inv.CurrentVolume.RawValue > 0) return 1; // has uranium (or something).
1593 // Might be turned off, broken, or out of uranium.
1594 return 0;
1595 }
1596 // Fall through to basic functionalBlock stuff.
1597 }
1598
1599 if (block is IMySolarPanel)
1600 {
1601 float power = FetchLabeledValue("Max Output",block.DetailedInfo);
1602 if (power == 0)
1603 {
1604 return 0;
1605 }
1606 // fall through for default on/off checking.
1607 }
1608 if (block is IMyPistonBase)
1609 {
1610 IMyPistonBase piston = block as IMyPistonBase;
1611 return (piston.CurrentPosition - piston.MinLimit) / (piston.MaxLimit - piston.MinLimit);
1612 }
1613 if (block is IMyMotorStator)
1614 {
1615 IMyMotorStator motor = block as IMyMotorStator;
1616 float ll = motor.LowerLimit;
1617 float ul = motor.UpperLimit;
1618 if (float.IsInfinity(ll)) ll = -360;
1619 if (float.IsInfinity(ul)) ul = 360;
1620 return (motor.Angle - ll) / (ul -ll);
1621 }
1622 if (block is IMySensorBlock)
1623 {
1624 if ( (block as IMySensorBlock).LastDetectedEntity != null)
1625 return 1;
1626 return 0;
1627 }
1628 if (block is IMyCargoContainer)
1629 {
1630 VRage.Game.ModAPI.Ingame.IMyInventory inv = block.GetInventory(0);
1631 // this nonsense is because .rawValue returns weird shit and there's no way to cast it to an actual useful number aside from .ToString()
1632 return (double.Parse(inv.CurrentVolume + "") / double.Parse(inv.MaxVolume + ""));
1633 }
1634 if (block is IMyOxygenTank)
1635 return (block as IMyOxygenTank).GetOxygenLevel();
1636
1637
1638
1639 if (block is IMyFunctionalBlock)
1640 {
1641 if ( (block as IMyFunctionalBlock).Enabled) return 1;
1642 return 0;
1643 }
1644 if (! block.IsFunctional) return 0;
1645
1646 return 1;
1647
1648}
1649// Returns whether the value is "high", which can mean "full" for cargo bays.
1650bool IsHigh(string blockName)
1651{
1652 return Status(blockName) > 0.5;
1653}
1654bool IsHigh(string blockName, double limit)
1655{
1656 return Status(blockName) > limit;
1657}
1658bool IsLow(string blockName)
1659{
1660 return Status(blockName) < 0.5;
1661}
1662bool IsLow(string blockName, double limit)
1663{
1664 return Status(blockName) < limit;
1665}
1666
1667// Checks if the status of the block has changed.
1668bool Check(string blockName)
1669{
1670 return Check(blockName, 0.5);
1671}
1672bool Check(string blockName, double limit)
1673{
1674 double val = Status(blockName);
1675 if (! existingKeys.Contains(blockName))
1676 {
1677 Log("Block " + blockName + " initialized, always counts as a transition.");
1678 return true;
1679 }
1680
1681 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1682
1683 return ( (oldVal < limit) ^ (val < limit) );
1684
1685}
1686bool CheckHigh(string blockName)
1687{
1688 return CheckHigh(blockName, 0.5);
1689}
1690bool CheckHigh(string blockName, double limit)
1691{
1692 double val = Status(blockName);
1693 if (! existingKeys.Contains(blockName))
1694 {
1695 return val >= limit;
1696 }
1697
1698 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1699
1700 return ( (oldVal < limit) && (val >= limit) );
1701
1702}
1703bool CheckLow(string blockName)
1704{
1705 return CheckLow(blockName, 0.5);
1706}
1707bool CheckLow(string blockName, double limit)
1708{
1709 double val = Status(blockName);
1710 if (! existingKeys.Contains(blockName))
1711 {
1712 return val < limit;
1713 }
1714
1715 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1716
1717 return ( (oldVal >= limit) && (val < limit) );
1718
1719}
1720void Adapt(string text, string command)
1721{
1722 adaptiveOptions.Add(text);
1723 adaptiveCommands.Add(command);
1724}
1725void Mission(string text)
1726{
1727 adaptiveOptions.Add(text);
1728 adaptiveCommands.Add(null);
1729}
1730
1731void ShowAdaptiveUI()
1732{
1733 for (int a = 0; a < adaptiveScreens.Length; a++)
1734 {
1735 IMyTerminalBlock screen = GridTerminalSystem.GetBlockWithName(adaptiveScreens[a]);
1736 if (screen == null)
1737 {
1738 Log("Adaptive UI screen '" + adaptiveScreens[a] + "' does not exist.");
1739 continue;
1740 }
1741 if (! (screen is IMyTextPanel))
1742 {
1743 Log("Adaptive UI screen '" + adaptiveScreens[a] + "' is not a text panel.");
1744 continue;
1745 }
1746 ShowAdaptiveUI(screen as IMyTextPanel);
1747 }
1748}
1749void ShowAdaptiveUI(IMyTextPanel screen)
1750{
1751
1752 int max = adaptiveOptions.Count;
1753 if (max > optionsPerScreen) max = optionsPerScreen;
1754
1755 string output = adaptiveUIHeader ;
1756 for (int a = 0; a < max; a++)
1757 {
1758 if (adaptiveCommands[a] == null)
1759 output += " " + adaptiveOptions[a] + "\n";
1760 else
1761 output += " " + (a + 1) + ": " + adaptiveOptions[a] + "\n";
1762 }
1763 screen.WritePublicText(output);
1764 screen.ShowPublicTextOnScreen();
1765}
1766void ApplyAction(IMyTerminalBlock block, string actionName)
1767{
1768 if (block.GetActionWithName(actionName) == null)
1769 {
1770 Log(block.CustomName + " cannot " + actionName);
1771 return;
1772 }
1773 block.ApplyAction(actionName);
1774}
1775
1776void Heartbeat()
1777{
1778 if (heart == null)
1779 {
1780 if (timer == null)
1781 {
1782 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
1783 GridTerminalSystem.GetBlocksOfType<IMyTimerBlock>(blocks);
1784 if (blocks.Count == 0)
1785 {
1786 Log("Attempted to find closest timer block, but there are NO TIMER BLOCKS!");
1787 return;
1788 }
1789 heart = blocks[0] as IMyTimerBlock;
1790 for (int a = 1; a < blocks.Count; a++)
1791 {
1792 if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), heart.GetPosition()) )
1793 heart = blocks[a] as IMyTimerBlock;
1794 }
1795 Log("Selected closest timer: " + heart.CustomName);
1796 }
1797 else
1798 {
1799 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(timer);
1800 if (block is IMyTimerBlock)
1801 heart = block as IMyTimerBlock;
1802 else
1803 {
1804 Log("Could not find a timer named " + timer);
1805 return;
1806 }
1807 }
1808 }
1809 heart.ApplyAction("TriggerNow");
1810}
1811
1812bool logRecursionLock = false;
1813
1814void Log(string text)
1815{
1816 Echo(text);
1817 if (! logToScreen) return;
1818
1819 if (log.Split('\n').Length > 10) log = "";
1820 log += " " + text + "\n";
1821 if (logRecursionLock) return;
1822 logRecursionLock = true;
1823
1824 if (logScreen == null)
1825 {
1826 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
1827 GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(blocks);
1828 if (blocks.Count == 0)
1829 {
1830 Log("Attempted to find closest text panel block, but there are NO TEXT PANELS!");
1831 return;
1832 }
1833
1834 IMyTerminalBlock best = blocks[0];
1835 for (int a = 1; a < blocks.Count; a++)
1836 {
1837 if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), best.GetPosition()) )
1838 best = blocks[a];
1839 }
1840 Log("Selected log panel named: " + best.CustomName);
1841 logScreen = best.CustomName;
1842 }
1843
1844 Apply(logScreen, "Text( " + Me.CustomName + " Log:\n" + log + ")");
1845 logRecursionLock = false;
1846}