· 9 years ago · Jan 09, 2017, 09:02 AM
1/*
2MIMIR MAR-17-2016
3AI for quick setup of advanced monitoring.
4Includes battery management defaults.
5
6Please read long comments below the setup variables.
7*/
8
9// Cached commands come in pairs. The first is the command
10// you send to Mimir, the second is the command Mimir runs.
11List<string> cachedCommands = new List<string> () {
12 "Lights Out", "*Light.Off",
13 // Default airlock control. Your airlock buttons should call Mimir with Airlock Out or Airlock In
14 //"Airlock Out", "*Airlock Door Int.Close;*Airlock Vent.Depressurize_On",
15 //"Airlock In", "*Airlock Door Ext.Close;*Airlock Vent.Depressurize_Off",
16};
17
18// Put your custom logic here! This will run every update.
19void CustomLogic()
20{
21
22 /* // Uncomment these lines for default airlock control's other half. Keep in mind
23 // air vents often freak out in multiplayer and flicker from max to min rapidly.
24 if (CheckHigh("Airlock Vent",0.8))
25 Apply("*Airlock Door Int.Open_On");
26 else if (CheckLow("Airlock Vent",0.1))
27 Apply("*Airlock Door Ext.Open_On");
28 if (CheckHigh("Oxygen Tank", 0.8))
29 Apply("*Oxygen Generator.Off;*Oxygen Farm.Off");
30 else if (CheckLow("Oxygen Tank", 0.5))
31 Apply("*Oxygen Generator.On;*Oxygen Farm.On"); //*/
32
33 // Default large reactor fallbacks.
34 /*
35 if (CheckHigh("Large Reactor"))
36 Apply("*Battery.Recharge");
37 else if (CheckLow("Large Reactor"))
38 Apply("*Battery.Discharge;Batt Alert.PlaySound"); //*/
39}
40
41// Free power in creative mode screws up some of the logic, so
42// you need to specify if we're in creative or survival.
43// (Note: inventories have been weird recently, may not work.)
44bool survivalMode = false;
45
46
47// If true, Mimir's logs will be printed to a screen. If no screen is
48// specified and true, Mimir will log to the nearest screen.
49bool logToScreen = false;
50
51// Name a log screen if you want. You may use "*" notation to
52// log to multiple screens, for example "*Log" will log to all
53// screens with "Log" in their name. Leave null for logging to
54// closest screen.
55string logScreen = null;
56
57
58
59// List text boxes for adaptive interfaces. Don't forget to rig a button array
60// up to send in "AdaptiveUI #", zero-indexed. For example,
61// AdaptiveUI 0, AdaptiveUI 1, AdaptiveUI 2, AdaptiveUI 3
62string[] adaptiveScreens = new string[] {}; //{"Command Menu A", "Command Menu B"};
63
64
65// Use this to list the things that interface screens will show, in the
66// priority of them showing. Use "Adapt" to add text and a command
67// to the menu. IE,
68// Adapt("Bay needs to be closed", "Bay Timer.TriggerNow");
69// You can also use Mission to assign new missions.
70// Mission("Mine some ice!");
71void AdaptiveUI()
72{
73
74}
75
76
77// If you want to use player-defined groups, turn this to true
78// It causes some memory use, so don't run Mimir in fast mode
79// if you're using named groups. Warning: might be broken?
80bool useNamedGroups = false; // some kind of error in lines 499-510, no documentation i've found helps
81
82// If true, Mimir will run every tick rather than every second.
83bool canRunFast = false;
84
85// If Mimir canRunFast and this is null, Mimir will search for the
86// closest timer upon boot-up and assume that is the one that
87// he needs to call every tick. If this is a name, he'll get that
88// timer name explicitly.
89string timer = null;
90
91// Run every this number of milliseconds if we run fast.
92double runDelay = 100;
93
94char commandSeparator = ';';
95
96// Adaptive UI stuff. Runs once per second, max.
97// Shows at the top of the adaptive UI screens.
98string adaptiveUIHeader = "--------------------------------------\n COMMANDS\n--------------------------------------\n\n";
99
100// If you're using custom button arrangements, you can change how many
101// options are printed to the screen.
102int optionsPerScreen = 4;
103
104
105
106/*
107MIMIR
108
109AI for quick setup of advanced monitoring, adaptable player UI, etc.
110
111Write quick pseudocode to create visual feeback for state changes.
112
113Do not use special characters in your block names if you use Mimir.
114
115Mimir can take commands. For the most part these are not any
116better than doing it via programming block, except that they
117search by name, which means it adapts to added blocks and such.
118
119Entry Light.Off
120
121As an argument would turn off a block named "Entry Light" (caps matter).
122
123*Light.On
124
125As an argument would turn on all blocks with "Light" in their name.
126
127This accepts all default commands from Keen - OnOff, TriggerNow, etc.
128I have also added some shortcut commands - for example,
129
130 .On and .Off (which map to OnOff_On & OnOff_Off)
131
132Some commands are not available in Keen's repertoire. For example
133
134 .Text and .Image
135
136Used without additional stuff, it simply switches whether
137the text box shows public text or image.
138
139Main Text.Text(Welcome, Captain!)
140
141Will type "Welcome, Captain!" on a panel named "Main Text".
142
143*Text.Image(Danger)
144
145Will change every text box with the word "Text" in its name to
146showing the "Danger" image.
147
148Default images (capitalization matters):
149
150Offline
151Online
152Arrow
153Cross
154Danger
155No Entry
156Construction
157White screen
158
159You can append more commands using ';'.
160
161*Alarm.On;*Door.Close;Command Status.Text(Warning!\nWarning!\nWarning!)
162
163Special note: you can use "Set(VARNAME)" and "UnSet(VARNAME)" to set
164persistent variables as part of a command. IE,
165
166Set(Airlock Out);*Airlock Door Int.Close;*Airlock Vent.Depressurize_On
167
168
169CACHED COMMANDS
170Long commands are not very much fun to type into the cramped "argument" window,
171so cached commands allow you to use a short phrase and have it result in a longer command.
172You may then edit the command as you wish and the short phrase will result in the updated
173command.
174
175
176CUSTOM LOGIC
177Use the CustomLogic function to quickly and easily create custom logic for
178your ship UI. This logic runs every update, and simply uses some functions
179to make it easy to check things.
180
181Status(BlockName) is the most basic function, and it gets a value between
1820 and 1 representing the block's primary function. IE, off (0) or on (1). Things
183like batteries return percent charged, cargo returns percent full, sensor returns
184if it is detecting something, and so on. This can freely be called many times -
185after the first time it uses a lookup table and does not cause substantial slowdown.
186
187Check(BlockName) is the most basic logic function. This will check and see whether
188the value of the block has transitioned across 0.5. IE, turned from on to off,
189or from off to on. It will return "true" only if there has been a transition between
190this update and last.
191
192Apply(BlockName, Command): Use to change the state of blocks without using annoying
193lookups. Most commands are simply actions (TriggerNow, Play, etc). Special case commands:
194On and Off are parsed correctly.
195Text and Image, applied to text panels, cause the panel to show text or show image.
196Text(SomeString) and Image(SomeString) cause the panel to show specified text or image.
197
198Using these three basic functions, you can usually wire together your ship UI without any
199complex, esoteric commands. The ship can also have automated logic - for example,
200playing a sound when a reactor is broken, or changing whatever cockpit is nearest to
201a sensor that found an ally to the primary cockpit.
202
203Additional functions:
204
205Check(BlockName, Percentage): Specify percentage rather than assuming 0.5.
206IsHigh and IsLow check if the block is high or low
207CheckHigh and CheckLow only trigger when the unit goes high or low.
208
209Exists(BlockName) checks to see if a block exists
210Local(BlockName) checks if the block is attached right now
211Memorize(BlockName) saves the block to memory so even if it is detached, it'll still be accessible.
212
213Output(string BlockName) gets the output percent as 0-1 for batteries, reactors, and solar panels.
214
215IsEnabled, IsDamaged, and IsCharging get that status of the block (batteries only, for that last one)
216
217Fetch(BlockName) will fetch a block (or null).
218
219Set("Value"), UnSet("Value"), and IsSet("Value") use a simple dictionary to allow you to use basic
220flags in your commands. Be careful: these do not survive loads or recompiles.
221
222WorldPos("Name") and GridPos("Name") give the Vector3D of the specified block,
223useful if you know how to use them. However, the Vector3D stuff is a bit opaque
224if this is your first time.
225
226ADAPTIVE UI
227Adaptive UI is used to display commands on text screens, with the intention of allowing the player
228to see the most important details of the ship. Buttons can be wired up to activate these if they are
229adaptive commands, allowing the player to trigger the most important actions that might need
230triggering - for example, battle mode, airlock pressurization, powering up reactors, etc. You can
231use complex logic - for example, "if our solar panels are down, allow the player to turn on reactors"
232
233To wire buttons up, they should pass Mimir the phrase "AdaptiveUI #", starting at 0. For example,
234AdaptiveUI 0, AdaptiveUI 1, AdaptiveUI 2, AdaptiveUI 3
235
236The AdaptiveUI function runs once per second, rather than at the often much faster rate of
237CustomLogic. This makes it safer to do more expensive computations.
238
239Use the special commands
240
241Mission(TEXT) : Sets up a mission suggestion (not selectable)
242Adapt(TEXT, COMMAND): Sets up an adaptive command which can be triggered. It will
243 say "TEXT" on the screen, but perform "COMMAND" when triggered.
244
245
246Some commands for you:
247 Figuring stuff out
248Some classes have dedicated properties, which you have to look up in the source code or a reference.
249However, if you want to get a list of all terminal properties and actions, use
250 Analyze(string name) , in a call, "NAME.Analyze"
251This will print a list of everything to the log, which can be seen in the DetailedInfo or LCD panel output.
252
253 Universal
254Default returns 1 if on, powered, and working.
255Actions:
256 OnOff toggles state
257 On, Off turns to on or off
258Properties in Code:
259 Use SetFloat(blockName, propertyName, value) to set arbitrary properties (not available in an Apply string)
260 Use SetColor(blockName, propertyName, int R, int B, int G) to set arbitrary colors.
261
262 LCD
263In an Apply string, ".Text(SomeText)" and ".Image(SomeImage)"
264In code, use
265 ColorBackground(string name, int R, int G, int B)
266 ColorFont(string name, int R, int G, int B)
267 FontSize(string name, float size)
268
269 REACTORS
270Return 0 if off or in survival and out of uranium. 1 otherwise.
271
272 SOLAR PANELS
273Return 1 if exposed to the sun and on.
274
275 BATTERY BLOCKS
276Return their % charge as their value
277Recharge, Discharge, SemiAuto
278
279 SOUND BLOCKS
280PlaySound is the command you want.
281No known way to change the audio loaded in.
282
283 ROTORS
284Returns angle value between minimum and maximum angle.
285Reverse Reverses
286RotPlus, RotMinus Reverses if necessary to go in indicated direction
287
288 PISTONS
289Returns extension value between 0 (lower limit) and 1 (upper limit)
290Reverse, Extend, Retract
291
292 DOORS
293Returns 1 if open, 0 if closed or broken
294Open Toggles open-ness (sigh...)
295Open_On Opens door
296Open_Off, Close Closes door
297
298 CARGO
299Returns percentage full (1 being full)
300
301 TURRETS
302Use functions Elevation(TurretName), Azimuth(TurretName), SetElevation(name, val),
303SetAzimuth(name, val) or ResetTargeting(name)
304
305 SHIP CONNECTORS
306Returns 0 if unconnected, 1 if connected. Use
307GetConnected("ConnectorName") in code to fetch the ShipConnector on the other side.
308
309 LANDING GEAR
310Returns 0 if unlocked, 1 if locked.
311(In-game programming blocks cannot access the GetAttachedEntity method, sorry.)
312
313 GRAVITY GENERATORS
314You can use SetGravity(Name, Amount) to change their gravity level. This can only
315be done in code, NOT as an argument or cached command.
316
317*/
318
319// Runtime only, don't modify.
320List<string> existingKeys = new List<string>();
321List<double> existingValues = new List<double>();
322
323List<string> newKeys = new List<string>();
324List<double> newValues = new List<double>();
325
326List<string> adaptiveOptions = new List<string>();
327List<string> adaptiveCommands = new List<string>();
328
329// The list of values currently set using Set.
330List<string> setValues = new List<string>();
331
332IMyTimerBlock heart;
333double delayCounter = 0;
334double adaptiveUICounter = 0;
335
336List<IMyTerminalBlock> memorizedBlocks = new List<IMyTerminalBlock>(); // for abusing merge block tricks.
337string log = "";
338
339void Main(string arg)
340{
341 if (GridTerminalSystem == null)
342 throw new Exception("!!!");
343 if (canRunFast) Heartbeat();
344 delayCounter -= Runtime.TimeSinceLastRun.Milliseconds;
345 adaptiveUICounter -= Runtime.TimeSinceLastRun.Milliseconds;
346 if (adaptiveUICounter < 0)
347 {
348 if (canRunFast)
349 adaptiveUICounter = 1000;
350 else
351 adaptiveUICounter = 0;
352 adaptiveOptions.Clear();
353 adaptiveCommands.Clear();
354 AdaptiveUI();
355
356 ShowAdaptiveUI();
357 }
358
359
360 // "tick" call, no command.
361 if ( (arg == null) || (arg.Length < 2) )
362 {
363 if (delayCounter > 0)
364 return;
365 if (canRunFast)
366 delayCounter += runDelay;
367 else
368 delayCounter = 0;
369
370 newValues.Clear();
371 newKeys.Clear();
372 CustomLogic();
373
374 for (int a = 0; a < newKeys.Count; a++)
375 {
376 if (existingKeys.Contains(newKeys[a]))
377 {
378 //Echo ("For " + newKeys[a] + ", value from " + existingValues[existingKeys.IndexOf(newKeys[a])] + " to " + newValues[a]);
379 existingValues[existingKeys.IndexOf(newKeys[a])] = newValues[a];
380 }
381 else
382 {
383 //Echo ("For " + newKeys[a] + ", initial value of " + newValues[a]);
384 existingKeys.Add(newKeys[a]);
385 existingValues.Add(newValues[a]);
386 }
387 }
388
389 return;
390 }
391
392 if (arg.ToLower().StartsWith("adaptiveui"))
393 {
394 // Should add some error catching here, but lazy.
395 int num = int.Parse(arg.Split(' ')[1]);
396 if (num < adaptiveCommands.Count)
397 {
398 Log("Taking adaptive option " + (num + 1));
399 if (adaptiveCommands[num] != null)
400 adaptiveOptions[num] = "...Working...";
401 else
402 {
403 adaptiveOptions[num] = " Just go do it. ";
404 ShowAdaptiveUI();
405 return;
406 }
407 arg = adaptiveCommands[num];
408 ShowAdaptiveUI();
409 }
410 else
411 {
412 Log("No selection #" + num + " on adaptive UI.");
413 return;
414 }
415 }
416
417 Apply(arg);
418}
419
420void ApplyToGroup(string name, string command)
421{
422 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
423 GridTerminalSystem.SearchBlocksOfName(name, blocks);
424 if (blocks.Count > 0)
425 ApplyToGroup(blocks, command);
426 else
427 Log("Couldn't find any blocks named " + name);
428}
429void ApplyToGroup(List<IMyTerminalBlock> blocks, string command)
430{
431 for (int a = 0; a < blocks.Count; a++)
432 {
433 ApplyToBlock(blocks[a], command);
434 }
435
436}
437void Apply(string arg)
438{
439 if ( (arg == null) || (arg.Length < 2) ) return;
440
441 // Command given, process it:
442 if (arg.Contains(commandSeparator.ToString()))
443 {
444 string[] commands = arg.Split(commandSeparator);
445 for (int a = 0; a < commands.Length; a++)
446 {
447 Apply(commands[a].Trim());
448 }
449 return;
450 }
451
452 if (arg.ToLower().StartsWith("set("))
453 {
454 string[] bits = arg.Split('(');
455 if (bits[1].EndsWith(")"))
456 bits[1] = bits[1].Substring(0, bits[1].Length - 1);
457 Set(bits[1]);
458 return;
459 }
460 else if (arg.ToLower().StartsWith("unset("))
461 {
462 string[] bits = arg.Split('(');
463 if (bits[1].EndsWith(")"))
464 bits[1] = bits[1].Substring(0, bits[1].Length - 1);
465 Unset(bits[1]);
466 return;
467 }
468
469
470
471
472 if (cachedCommands.IndexOf(arg) % 2 == 0) // it's a cached command
473 {
474 Log("Cached command #" + (cachedCommands.IndexOf(arg) / 2) + ", " + arg);
475 Apply(cachedCommands[cachedCommands.IndexOf(arg) + 1]);
476 return;
477 }
478
479
480 int index = arg.IndexOf(".");
481
482 if (index < 0)
483 {
484 Log(arg + " is not a command I understand.");
485 return;
486 }
487 if (arg.StartsWith("*"))
488 ApplyToGroup(arg.Substring(1, index - 1), arg.Substring(index + 1));
489 else
490 ApplyToBlock(arg.Substring(0, index), arg.Substring(index + 1));
491}
492
493void Apply(string name, string command)
494{
495 ApplyToBlock(name, command);
496}
497void ApplyToBlock(string name, string command)
498{
499 /*if (useNamedGroups)
500 {
501 List<IMyBlockGroup> groups = new List<IMyBlockGroup>();
502 for (int a = 0; a < groups.Count; a++)
503 {
504 if (groups[a].Name == name)
505 {
506 ApplyToGroup(groups[a].Blocks, command); //Some kind of error here I can't make sense of.
507 return;
508 }
509 }
510 } */
511
512 for (int a= 0; a < memorizedBlocks.Count; a++)
513 {
514 if (memorizedBlocks[a].CustomName == name)
515 {
516 ApplyToBlock(memorizedBlocks[a], command);
517 return;
518 }
519 }
520
521 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(name);
522 ApplyToBlock(block, command);
523
524}
525void ApplyToBlock(IMyTerminalBlock block, string command)
526{
527 if (block == null)
528 {
529 Log ("No block found to " + command + "... check names!");
530 return;
531 }
532
533 string action = command.ToLower();
534 if (action == "analyze")
535 {
536 Analyze(block);
537 return;
538 }
539 if (action == "rotplus")
540 {
541 if (! (block is IMyMotorStator))
542 {
543 Log("Cannot rotate non-rotor.");
544 return;
545 }
546 if ( (block as IMyMotorStator).Velocity < 0)
547
548 ApplyAction(block,"Reverse");
549 return;
550 }
551 if (action == "rotminus")
552 {
553 if (! (block is IMyMotorStator))
554 {
555 Log("Cannot rotate non-rotor.");
556 return;
557 }
558 if ( (block as IMyMotorStator).Velocity > 0)
559 ApplyAction(block,"Reverse");
560 return;
561
562 }
563 if (action == "close")
564 {
565 ApplyAction(block,"Open_Off");
566 return;
567 }
568 if (action == "on")
569 {
570 ApplyAction(block,"OnOff_On");
571 Log(block.CustomName + " OnOff_On");
572 return;
573 }
574 if (action == "off")
575 {
576 ApplyAction(block,"OnOff_Off");
577 Log(block.CustomName + " OnOff_Off");
578 return;
579 }
580 if (action.StartsWith("run") && (block is IMyProgrammableBlock))
581 {
582 (block as IMyProgrammableBlock).TryRun(action.Substring(4));
583 return;
584 }
585 if (action.StartsWith("tryrun") && (block is IMyProgrammableBlock))
586 {
587 (block as IMyProgrammableBlock).TryRun(action.Substring(7));
588 return;
589 }
590
591 if (action.StartsWith("text"))
592 {
593
594 int index = command.IndexOf("(");
595 if (index > 0)
596 {
597
598 string text= command.Substring(index + 1);
599 if (text.IndexOf(")") >= text.Length - 2)
600 text = text.Substring(0, text.Length - 1);
601
602 string parsedText = "";
603 while (text.IndexOf("\\") >= 0)
604 {
605 // Deep magic for carriage return escapes? There's probably an unescape function.
606 parsedText += text.Substring(0, text.IndexOf("\\")) + "\n";
607 text = text.Substring(text.IndexOf("\\") + 2); // we assume ALL escaped characters are newlines.
608 }
609 parsedText += text;
610
611 (block as IMyTextPanel).ShowTextureOnScreen(); // avoid sensor not marking it dirty.
612 (block as IMyTextPanel).WritePublicText(parsedText);
613 (block as IMyTextPanel).ShowPublicTextOnScreen();
614 //if (!logRecursionLock)
615 //Log(block.CustomName + " writing...");
616 //Echo ("Writing " + parsedText);
617 }
618 else
619 {
620 (block as IMyTextPanel).ShowPublicTextOnScreen();
621
622 Log ("Showing text.");
623 }
624 return;
625 }
626 else if (action.StartsWith("image") )
627 {
628 (block as IMyTextPanel).ShowTextureOnScreen();
629 int index = command.IndexOf("(");
630 if (index > 0)
631 {
632 (block as IMyTextPanel).ClearImagesFromSelection();
633
634 string text= command.Substring(index + 1);
635 if (text.IndexOf(")") >= text.Length - 2)
636 text = text.Substring(0, text.Length - 1);
637 (block as IMyTextPanel).AddImageToSelection(text);
638 Log (block.CustomName + " imaged " + text);
639 }
640 else
641 Log("Showing images.");
642
643 return;
644 }
645 Log(block.CustomName + " " + command);
646 ApplyAction(block,command);
647
648}
649void Analyze(string name)
650{ AnalyzeBlock(name); }
651void Analyze(IMyTerminalBlock block)
652{ AnalyzeBlock(block); }
653
654void AnalyzeBlock(string name)
655{
656 IMyTerminalBlock block = Fetch(name);
657 if (block == null)
658 {
659 Log("Couldn't find " + name + " to analyze.");
660 return;
661 }
662 AnalyzeBlock(block);
663}
664
665void AnalyzeBlock(IMyTerminalBlock block)
666{
667 if (block == null)
668 {
669 Log("Couldn't find block to analyze.");
670 return;
671 }
672
673 string vals = "";
674
675 vals += " Actions:";
676 List<ITerminalAction> acts = new List<ITerminalAction>();
677 block.GetActions(acts);
678 for (int a = 0; a < acts.Count; a++)
679 {
680 if (a % 3 == 0)
681 vals += "\n";
682 else
683 vals += ", ";
684 vals += acts[a].Id;
685 }
686
687 List<ITerminalProperty> props = new List<ITerminalProperty>();
688 block.GetProperties(props);
689 vals += "\n\n Properties: ";
690 for (int a = 0; a < props.Count; a++)
691 {
692 if (a % 3 == 0)
693 vals += "\n";
694 else
695 vals += ", ";
696 vals += props[a].Id;
697 }
698 Log(vals);
699
700}
701
702void SetFloat(string blockName, string property, float value)
703{
704 SetFloat(Fetch(blockName), property, value);
705}
706void SetFloat(IMyTerminalBlock block, string property, float value)
707{
708 if (block == null)
709 { Echo("No block found, cannot set float"); return; }
710 if (block.GetProperty(property) == null)
711 { Echo("No property named " + property + ", cannot set float"); return; }
712 block.SetValueFloat(property, value);
713}
714void SetColor(string blockName, string property, int R, int G, int B)
715{
716 SetColor(Fetch(blockName), property, R, G, B);
717}
718void SetColor(IMyTerminalBlock block, string property, int R, int G, int B)
719{
720 if (block == null)
721 { Echo("No block found, cannot set color"); return; }
722 if (block.GetProperty(property) == null)
723 { Echo("No property named " + property + ", cannot set float"); return; }
724 block.SetValue<Color>(property, new Color(R, G, B));
725}
726
727void ColorBackground(string name, int R, int G, int B)
728{
729 SetColor(name, "BackgroundColor", R, G, B);
730}
731void ColorFont(string name, int R, int G, int B)
732{
733 SetColor(name, "FontColor", R, G, B);
734}
735void FontSize(string name, float size)
736{
737 SetFloat(name, "FontSize", size);
738}
739
740float UnitMultiple(string unit)
741{
742 if (unit.StartsWith("W")) return 0.001f;
743 if (unit.StartsWith("kW")) return 1f;
744 if (unit.StartsWith("MW")) return 1000f;
745 if (unit.StartsWith("GW")) return 1000000f;
746 return 0.001f;
747}
748
749string FetchLine(string key, string details)
750{
751 string[] comm = details.Split('\n');
752 for (int a = 0; a < comm.Length; a++)
753 {
754 if (comm[a].StartsWith(key))
755 return comm[a].Split(':')[1].Trim();
756 }
757 //Echo (key + " not found in " + details);
758 return null;
759}
760float FetchLabeledValue(string key, string details)
761{
762 string val = FetchLine(key, details);
763 if (val == null) return 0;
764
765 string[] dets = val.Split(' ');
766 if (dets.Length >= 2)
767 return float.Parse(dets[0]) * UnitMultiple(dets[1]);
768 else
769 return float.Parse(dets[0]);
770}
771
772void Set(string name)
773{
774 if (! setValues.Contains(name))
775 setValues.Add(name);
776 Log("Set " + name);
777}
778void Unset(string name) { UnSet(name); }
779void UnSet(string name)
780{
781 if (setValues.Contains(name))
782 setValues.Remove(name);
783 Log("UnSet " + name);
784}
785bool Isset(string name) { return IsSet(name); }
786bool IsSet(string name)
787{
788 return (setValues.Contains(name));
789}
790
791// Does not check memory
792bool Local(string blockName)
793{
794 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
795 return block != null;
796}
797// Does check memory.
798bool Exists(string blockName)
799{
800 for (int a= 0; a < memorizedBlocks.Count; a++)
801 {
802 if (memorizedBlocks[a].CustomName == blockName)
803 {
804 return true;
805 }
806 }
807
808 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
809 return block != null;
810}
811void Memorize(string blockName)
812{
813 for (int a= 0; a < memorizedBlocks.Count; a++)
814 {
815 if (memorizedBlocks[a].CustomName == blockName)
816 {
817 return;
818 }
819 }
820
821 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(blockName);
822 if (block == null)
823 Log("Could not memorize nonexistant block " + blockName);
824 else
825 memorizedBlocks.Add(block);
826
827}
828
829// Grab a block.
830IMyTerminalBlock Fetch(string blockName)
831{
832 IMyTerminalBlock block = null;
833 for (int a= 0; a < memorizedBlocks.Count; a++)
834 {
835 if (memorizedBlocks[a].CustomName == blockName)
836 {
837 block = memorizedBlocks[a];
838 }
839 }
840 if (block == null)
841 block = GridTerminalSystem.GetBlockWithName(blockName);
842 if (block == null)
843 Log("No block named " + blockName);
844 return block;
845}
846
847IMyShipConnector GetConnected(string connectorName)
848{
849 IMyTerminalBlock block = Fetch(connectorName);
850 if (block == null)
851 {
852 Log(connectorName + " doesn't exist.");
853 return null;
854 }
855 if (block is IMyShipConnector)
856 return (block as IMyShipConnector).OtherConnector;
857 Log(connectorName + " isn't a ship connector.");
858 return null;
859}
860
861// Checks if the block is set to "enabled", specifically.
862bool IsEnabled(string blockName)
863{
864 IMyTerminalBlock block = Fetch(blockName);
865
866 if (block == null)
867 {
868 Log("Could not find " + blockName);
869 return false;
870 }
871
872 if (! block.IsFunctional) return false;
873 if (block is IMyFunctionalBlock)
874 {
875 return (block as IMyFunctionalBlock).Enabled;
876 }
877 return true; // not a functional block, cannot be disabled.
878}
879
880// Checks if the block is damaged.
881bool IsDamaged(string blockName)
882{
883 IMyTerminalBlock block = Fetch(blockName);
884 if (block == null)
885 {
886 Log("Could not find " + blockName);
887 return true; // probably destroyed completely.
888 }
889
890 if (! block.IsFunctional) return true;
891 if (block.IsBeingHacked) return true;
892 return false;
893}
894
895bool IsCharging(string batteryName)
896{
897 IMyTerminalBlock block = Fetch(batteryName);
898 if (block == null)
899 {
900 Log("No block named " + batteryName);
901 return false;
902 }
903 string recharging = FetchLine("Fully recharged", block.DetailedInfo);
904 return (recharging != null);
905}
906
907Vector3D GridPos(string blockName)
908{
909 IMyTerminalBlock block = Fetch(blockName);
910 if (block == null)
911 {
912 Log("No block named " + blockName);
913 return new Vector3D(0,0,0);
914 }
915
916 return block.Position;
917}
918Vector3D WorldPos(string blockName)
919{
920 IMyTerminalBlock block = Fetch(blockName);
921 if (block == null)
922 {
923 Log("No block named " + blockName);
924 return new Vector3D(0,0,0);
925 }
926
927 return block.GetPosition();
928}
929
930
931// Turret haxxxx
932double Azimuth(string turretName)
933{
934 IMyTerminalBlock block = Fetch(turretName);
935 if (block == null)
936 return 0;
937 if (block is IMyLargeTurretBase)
938 return (block as IMyLargeTurretBase).Azimuth; // synching not required.
939 return 0;
940}
941double Elevation(string turretName)
942{
943 IMyTerminalBlock block = Fetch(turretName);
944 if (block == null)
945 return 0;
946 if (block is IMyLargeTurretBase)
947 return (block as IMyLargeTurretBase).Elevation; // synching not required.
948 return 0;
949}
950
951bool SetAzimuth(string turretName, float azimuth)
952{
953 IMyTerminalBlock block = Fetch(turretName);
954 if (block == null)
955 return false;
956 if (!(block is IMyLargeTurretBase))
957 return false;
958 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
959 turret.Azimuth = azimuth;
960 //turret.SyncAzimuth();
961 Log(turretName+ " azimuth to " + azimuth);
962 return true;
963}
964bool SetElevation(string turretName, float elevation)
965{
966 IMyTerminalBlock block = Fetch(turretName);
967 if (block == null)
968 return false;
969 if (!(block is IMyLargeTurretBase))
970 return false;
971 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
972 turret.Elevation = elevation;
973// turret.SyncAzimuth();
974 Log(turretName+ " elevation to " + elevation);
975 return true;
976}
977
978bool ResetTargeting(string turretName)
979{
980 IMyTerminalBlock block = Fetch(turretName);
981 if (block == null)
982 return false;
983 if (!(block is IMyLargeTurretBase))
984 return false;
985 IMyLargeTurretBase turret = block as IMyLargeTurretBase;
986 turret.ResetTargetingToDefault();
987 turret.EnableIdleRotation = false;
988 turret.SyncEnableIdleRotation();
989 Log(turretName+ " targeting reset");
990 return true;
991}
992// Set up a gravity block with a specific gravity output.
993bool SetGravity(string gravityName, float gravity)
994{
995 IMyTerminalBlock block = Fetch(gravityName);
996 if (block == null)
997 {
998 Log(gravityName + " doesn't exist.");
999 return false;
1000 }
1001 return SetGravity(block, gravity);
1002}
1003bool SetGravity(IMyTerminalBlock block, float gravity)
1004{
1005 if (block == null)
1006 {
1007 Log("No such gravity block.");
1008 return false;
1009 }
1010 if (! (block is IMyGravityGeneratorBase))
1011 {
1012 Log("Not a gravity generator.");
1013 return false;
1014 }
1015
1016 block.SetValueFloat("Gravity", gravity * 10);
1017 return true;
1018}
1019
1020// Gets the output percent as 0-1 for batteries, reactors, and solar panels.
1021double Output(string reactorName)
1022{
1023 IMyTerminalBlock block = Fetch(reactorName);
1024 if (block == null)
1025 {
1026 Log("No battery, solar panel, or reactor named " + reactorName);
1027 return 0;
1028 }
1029 return Output(block);
1030}
1031double Output(IMyTerminalBlock block)
1032{
1033 if (block == null)
1034 {
1035 Log("No battery, solar panel, or reactor passed to Output function.");
1036 return 0;
1037 }
1038
1039 if ( (block is IMyBatteryBlock) || (block is IMyReactor) || (block is IMySolarPanel) )
1040 {
1041 float power = FetchLabeledValue("Current Output",block.DetailedInfo);
1042 float max = FetchLabeledValue("Max Output",block.DetailedInfo);
1043 //Echo (power + " of " + max + " in battery.");
1044 if (max == 0)
1045 return 0;
1046 return power / max;
1047 }
1048 Log(block.CustomName + " isn't a battery, solar panel, or reactor.");
1049 return 0;
1050}
1051
1052// Gets a value between 0 and 1.
1053double Status(string blockName)
1054{
1055 if (newKeys.Contains(blockName))
1056 {
1057 return newValues[newKeys.IndexOf(blockName)];
1058 }
1059
1060 double val = _Status(blockName);
1061
1062 newKeys.Add(blockName);
1063 newValues.Add(val);
1064
1065 return val;
1066}
1067double Status(IMyTerminalBlock block)
1068{
1069 if (block == null)
1070 {
1071 Log("Could not find block");
1072 return 0;
1073 }
1074 return _Status(block);
1075}
1076
1077double _Status(string blockName)
1078{
1079 IMyTerminalBlock block = Fetch(blockName);
1080 if (block == null)
1081 {
1082 Log("Could not find " + blockName);
1083 return 0;
1084 }
1085 return _Status(block);
1086}
1087double _Status(IMyTerminalBlock block)
1088{
1089 if (block == null)
1090 {
1091 Log("That's not a valid block, can't get status.");
1092 return 0;
1093 }
1094
1095 if (! block.IsFunctional) return 0;
1096
1097 if (block is IMyDoor)
1098 {
1099 return (block as IMyDoor).OpenRatio;
1100 //return ( (block as IMyDoor).Open ? 1 : 0); // very granular.
1101 }
1102
1103
1104 if (block is IMyShipConnector)
1105 return ( (block as IMyShipConnector).IsConnected ? 1 : 0);
1106 if (block is IMyLandingGear)
1107 return ( (block as IMyLandingGear).IsLocked ? 1 : 0);
1108
1109 if (block is IMyAirVent)
1110 return (block as IMyAirVent).GetOxygenLevel();
1111
1112 if (block is IMyProductionBlock)
1113 {
1114 return (block as IMyProductionBlock).IsProducing ? 1 : 0;
1115 }
1116
1117
1118 if (block is IMyJumpDrive) // we can tell if it's ready to jump, but not how charged it is.
1119 {
1120 if (block.GetValueBool("Recharge")) return 1;
1121 }
1122
1123 if ( (block is IMyBatteryBlock) || (block is IMyJumpDrive) )
1124 {
1125 if (! block.IsFunctional) return 0; // battery is off?
1126 float power = FetchLabeledValue("Stored power",block.DetailedInfo);
1127 float max = FetchLabeledValue("Max Stored",block.DetailedInfo);
1128
1129 //Echo (power + " of " + max + " in battery.");
1130 return power / max;
1131 }
1132 if (block is IMyReactor)
1133 {
1134 float power = FetchLabeledValue("Current Output",block.DetailedInfo);
1135
1136 if (power > 0) return 1;
1137 // Not producing power. It might be because nobody NEEDS any.
1138
1139 if (survivalMode)
1140 {
1141 VRage.Game.ModAPI.Ingame.IMyInventory inv = block.GetInventory(0);
1142 if (inv.CurrentVolume.RawValue > 0) return 1; // has uranium (or something).
1143 // Might be turned off, broken, or out of uranium.
1144 return 0;
1145 }
1146 // Fall through to basic functionalBlock stuff.
1147 }
1148
1149 if (block is IMySolarPanel)
1150 {
1151 float power = FetchLabeledValue("Max Output",block.DetailedInfo);
1152 if (power == 0)
1153 {
1154 return 0;
1155 }
1156 // fall through for default on/off checking.
1157 }
1158 if (block is IMyPistonBase)
1159 {
1160 IMyPistonBase piston = block as IMyPistonBase;
1161 return (piston.CurrentPosition - piston.MinLimit) / (piston.MaxLimit - piston.MinLimit);
1162 }
1163 if (block is IMyMotorStator)
1164 {
1165 IMyMotorStator motor = block as IMyMotorStator;
1166 float ll = motor.LowerLimit;
1167 float ul = motor.UpperLimit;
1168 if (float.IsInfinity(ll)) ll = -360;
1169 if (float.IsInfinity(ul)) ul = 360;
1170 return (motor.Angle - ll) / (ul -ll);
1171 }
1172 if (block is IMySensorBlock)
1173 {
1174 if ( (block as IMySensorBlock).IsActive == true)
1175 return 1;
1176 return 0;
1177 }
1178 if (block is IMyCargoContainer)
1179 {
1180 VRage.Game.ModAPI.Ingame.IMyInventory inv = block.GetInventory(0);
1181 // this nonsense is because .rawValue returns weird shit and there's no way to cast it to an actual useful number aside from .ToString()
1182 return (double.Parse(inv.CurrentVolume + "") / double.Parse(inv.MaxVolume + ""));
1183 }
1184 if (block is IMyOxygenTank)
1185 return (block as IMyOxygenTank).GetOxygenLevel();
1186
1187
1188
1189 if (block is IMyFunctionalBlock)
1190 {
1191 if ( (block as IMyFunctionalBlock).Enabled) return 1;
1192 return 0;
1193 }
1194 if (! block.IsFunctional) return 0;
1195
1196 return 1;
1197
1198}
1199// Returns whether the value is "high", which can mean "full" for cargo bays.
1200bool IsHigh(string blockName)
1201{
1202 return Status(blockName) > 0.5;
1203}
1204bool IsHigh(string blockName, double limit)
1205{
1206 return Status(blockName) > limit;
1207}
1208bool IsLow(string blockName)
1209{
1210 return Status(blockName) < 0.5;
1211}
1212bool IsLow(string blockName, double limit)
1213{
1214 return Status(blockName) < limit;
1215}
1216
1217// Checks if the status of the block has changed.
1218bool Check(string blockName)
1219{
1220 return Check(blockName, 0.5);
1221}
1222bool Check(string blockName, double limit)
1223{
1224 double val = Status(blockName);
1225 if (! existingKeys.Contains(blockName))
1226 {
1227 Log("Block " + blockName + " initialized, always counts as a transition.");
1228 return true;
1229 }
1230
1231 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1232
1233 return ( (oldVal < limit) ^ (val < limit) );
1234
1235}
1236bool CheckHigh(string blockName)
1237{
1238 return CheckHigh(blockName, 0.5);
1239}
1240bool CheckHigh(string blockName, double limit)
1241{
1242 double val = Status(blockName);
1243 if (! existingKeys.Contains(blockName))
1244 {
1245 return val >= limit;
1246 }
1247
1248 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1249
1250 return ( (oldVal < limit) && (val >= limit) );
1251
1252}
1253bool CheckLow(string blockName)
1254{
1255 return CheckLow(blockName, 0.5);
1256}
1257bool CheckLow(string blockName, double limit)
1258{
1259 double val = Status(blockName);
1260 if (! existingKeys.Contains(blockName))
1261 {
1262 return val < limit;
1263 }
1264
1265 double oldVal = existingValues[existingKeys.IndexOf(blockName)];
1266
1267 return ( (oldVal >= limit) && (val < limit) );
1268
1269}
1270void Adapt(string text, string command)
1271{
1272 adaptiveOptions.Add(text);
1273 adaptiveCommands.Add(command);
1274}
1275void Mission(string text)
1276{
1277 adaptiveOptions.Add(text);
1278 adaptiveCommands.Add(null);
1279}
1280
1281void ShowAdaptiveUI()
1282{
1283 for (int a = 0; a < adaptiveScreens.Length; a++)
1284 {
1285 IMyTerminalBlock screen = GridTerminalSystem.GetBlockWithName(adaptiveScreens[a]);
1286 if (screen == null)
1287 {
1288 Log("Adaptive UI screen '" + adaptiveScreens[a] + "' does not exist.");
1289 continue;
1290 }
1291 if (! (screen is IMyTextPanel))
1292 {
1293 Log("Adaptive UI screen '" + adaptiveScreens[a] + "' is not a text panel.");
1294 continue;
1295 }
1296 ShowAdaptiveUI(screen as IMyTextPanel);
1297 }
1298}
1299void ShowAdaptiveUI(IMyTextPanel screen)
1300{
1301
1302 int max = adaptiveOptions.Count;
1303 if (max > optionsPerScreen) max = optionsPerScreen;
1304
1305 string output = adaptiveUIHeader ;
1306 for (int a = 0; a < max; a++)
1307 {
1308 if (adaptiveCommands[a] == null)
1309 output += " " + adaptiveOptions[a] + "\n";
1310 else
1311 output += " " + (a + 1) + ": " + adaptiveOptions[a] + "\n";
1312 }
1313 screen.WritePublicText(output);
1314 screen.ShowPublicTextOnScreen();
1315}
1316void ApplyAction(IMyTerminalBlock block, string actionName)
1317{
1318 if (block.GetActionWithName(actionName) == null)
1319 {
1320 Log(block.CustomName + " cannot " + actionName);
1321 return;
1322 }
1323 block.ApplyAction(actionName);
1324}
1325
1326void Heartbeat()
1327{
1328 if (heart == null)
1329 {
1330 if (timer == null)
1331 {
1332 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
1333 GridTerminalSystem.GetBlocksOfType<IMyTimerBlock>(blocks);
1334 if (blocks.Count == 0)
1335 {
1336 Log("Attempted to find closest timer block, but there are NO TIMER BLOCKS!");
1337 return;
1338 }
1339 heart = blocks[0] as IMyTimerBlock;
1340 for (int a = 1; a < blocks.Count; a++)
1341 {
1342 if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), heart.GetPosition()) )
1343 heart = blocks[a] as IMyTimerBlock;
1344 }
1345 Log("Selected closest timer: " + heart.CustomName);
1346 }
1347 else
1348 {
1349 IMyTerminalBlock block = GridTerminalSystem.GetBlockWithName(timer);
1350 if (block is IMyTimerBlock)
1351 heart = block as IMyTimerBlock;
1352 else
1353 {
1354 Log("Could not find a timer named " + timer);
1355 return;
1356 }
1357 }
1358 }
1359 heart.ApplyAction("TriggerNow");
1360}
1361
1362bool logRecursionLock = false;
1363
1364void Log(string text)
1365{
1366 Echo(text);
1367 if (! logToScreen) return;
1368
1369 if (log.Split('\n').Length > 10) log = "";
1370 log += " " + text + "\n";
1371 if (logRecursionLock) return;
1372 logRecursionLock = true;
1373
1374 if (logScreen == null)
1375 {
1376 List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
1377 GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(blocks);
1378 if (blocks.Count == 0)
1379 {
1380 Log("Attempted to find closest text panel block, but there are NO TEXT PANELS!");
1381 return;
1382 }
1383
1384 IMyTerminalBlock best = blocks[0];
1385 for (int a = 1; a < blocks.Count; a++)
1386 {
1387 if (Vector3D.Distance(Me.GetPosition(), blocks[a].GetPosition()) < Vector3D.Distance(Me.GetPosition(), best.GetPosition()) )
1388 best = blocks[a];
1389 }
1390 Log("Selected log panel named: " + best.CustomName);
1391 logScreen = best.CustomName;
1392 }
1393
1394 Apply(logScreen, "Text( " + Me.CustomName + " Log:\n" + log + ")");
1395 logRecursionLock = false;
1396}