-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathscript.cs
More file actions
1677 lines (1526 loc) · 76 KB
/
script.cs
File metadata and controls
1677 lines (1526 loc) · 76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// R e a d m e
// -----------
//
// In this file you can include any instructions or other comments you want to have injected onto the
// top of your final script. You can safely delete this file if you do not want any such comments.
//
/*Script Author: Onosendai
* Description: This script leverages the Ore Detector Raytrace mode allowing you to automatically record ore it "hits".
* Credits: Big thanks to @Reacher for the MOD allowing Ray Tracing of Ore.
* https://steamcommunity.com/workshop/filedetails/?id=1967157772
Version: 1.1
* Initial version to display and track ore deposits.
Version: 1.2
* Added support for Better Stone v7.0.3f
Version: 1.3
* 360 Ore Scanning and logging. If you turn on fast scanning and leave the stepping at 18 the scan will take about 90 seconds. If you scan EVERY degree of the sphere and wait until you have the proper charge for the distance selected then you will wait longer but it will be more accurate.
* Forward Plane Scanning (-5x5) times (-5x5) forward square. Greater distance will make the square larger as these are degrees from the ore detector front projection
* New Status screen to see the angle (Azimuth and Elevation) of a current scan
* New mini status screen to show on small LCD's
* Added the disable and enable argument. This will disable or enable the ore detector
* Added a scan program argument. This will cause a onetime scan in whatever mode you have set and will then disable the ore detector
* Added save and restore of settings
* Added settings menu for making changes to common configuration options
Version: 1.4
* Better Error handling for when the Ore Detector Raycast mod is not installed. This will aid in giving direction to users when they subscript to the script.
Version: 1.5
* Added function to allow for screens on cockpits. No requirement for external LCD's. Also when displaying the ministatus on a cockpit you can change the font size and it will remember that rather than forcing it to the 4.5/5.5 size.
Version: 1.6
* Fixed the scan once functionality and added the scan once item to settings/save/load.
Version: 1.7
* Fixed an issue with multiple cockpits on the same grid.
Version: 1.8
* Big Thanks to Radar5k for adding the ability to use the Programming Block Screen as your OFP screen. Now any PB with the tag [OFP] can be used to display content for OFP.
* Important Info:
* The new TAG that OFP is looking for is [OFP] not just OFP. IF you have a screen that isnt working from an older build update the OFP_TAG variable below. This was done to allow for users to name their Programming Blocks with the OFP tag without taking over the screen.
* We are now looking for the custom data in LCDs/Programming Blocks/or Cockpits. If any have the OFP_Tag in the name OR have the OFP_SETTINGS_TAG in the custom data section we will load OFP. This allows you to name your screens whatever you like and still have OFP work.
Version: 1.9
* Fixed issue found by Logoth where the detector would get out of sync with the settings on disable.
Version: 2.0
* Issue with multiple ore detectors. If the random one selected by OFP is disabled then scans will not work. Now you can tag your detector with the OFP_TAG and OFP will use that ore detector by default.
Version: 3.0
* Fixed issue found by @Atono on Degrees vs Radian usage
* Now you can filter which ore show up in the ORE GPS Screen by using the menu system.
* You now have pagination on the Ore GPS screen. If you have more than 10 ore deposits you can page through them by default but the limit can be changed. It was noticed that when you get 65,000 characters on a LCD screen it would lag the game server. This is a limitation of the game and not the script.
Version: 3.6
* Added "Ignore Add/Remove" menu item to settings screen for managing ore ignore list
* Users can now toggle individual discovered ore types to be ignored/scanned using menu navigation
* Changed ignore display format from [IGNORED]/[SCANNING] to [X]/[ ] checkbox format
* When ore is set to ignore, it is automatically removed from the known ore list and discovery data
* Ignored ore types are added to the ore detector's blacklist for future scanning sessions
Version: 3.7
* Enhanced debug display with comprehensive ignore list and menu state information
* Added detailed logging for troubleshooting "better stone" mode issues with ignore functionality
Version: 3.8
* Fixed ignore list toggle display issue where [X] checkbox state was not properly updating
* Fixed issue where ignored ores could be re-added to ignore list without proper state tracking
* Improved ignore list state consistency for "better stone" mode compatibility
Version: 3.9
* Fixed display filter indexing bug causing wrong ore types to be toggled
* Stone is now permanently excluded from ignore and display filter menus
* Fixed selection pointer drift when new ores are discovered during menu navigation
*/
//Tag for Ore Finder Plus to look for on the ore detector or LCD. Either tag the name or custom data
// Example Name: LCD Test [OFP]
// LCD Port Side OFP
private static string OFP_TAG = "[OFP]"; // Changed default tag from OFP so we can name the PB "PB OFP" without outputting to the screen. (Radar5k).
private static string OFP_SETTINGS_TAG = "[OreFinderPlus]"; // Keep settings when recompiling. (Radar5k).
//ignoreList will allow you to exclude types of ORE.
//example: "Stone,Ice"
string ignoreList = "Stone";
//Deposit Range is the potential size of a deposit of ore. The Below size is in meters.
//If you start getting the same ore deposits tagged with multiple GPS coordinates
//then make this number larger.
int depositRange = 200;
//Set Maximum number of deposits to show on the GPS screen. If you have more than this number you can page through them.
int maxOrePerScreen = 10;
//*******Dont Touch Below here********
//************************************
//Scan Mode
// 0 = Scan Straight ahead of the ore detector
// 1 = Scan 360 degrees in all directions
// 2 = Scan only a defined forward plane
Dictionary<int, string> OFPScanMode = new Dictionary<int, string>
{
{0,"Forward Scan" },
{1,"360 Scan" },
{2,"Forward Plane Scan" }
};
int scanMode = 1;
//Detection Distance in meters. Default set to 1KM If you're using the 360 scanner or even forward plane scanner you'll want to avoid making this much larger.
int detectionDistance = 1000;
//Quickscan will keep scanning every update10 and update100 even if the ore detector doesnt have enough "charge". If you set fast charge OFF then the detector will
//wait until there is enough charge in the detector to hit the requested distance.
//The Downside to quickscan is that you may miss some ore at distance. The plus side is it wont take 1million years to complete a 360 scan.
//Rule of thumb would be
//360 scan turn on fast scan
//Forward scan or Forward Plane scan use quickscan off as you'll make sure you hit your distance set.
Boolean quickScan = true;
//Only turn this on if you're using DNSpy to view variable data. You will need to configure it to catch DivideByZero Exceptions.
private static bool DEBUGGING = false;
private static double VERSION_NUMBER = 3.9;
private static string PRODUCT_NAME = "Ore Finder Plus";
//used to scan 360 degrees around the ore detector
private const float minAngle = 0;
private const float maxAngle = 360;
private const float sphereElevationMinAngle = -90;
private const float sphereElevationMaxAngle = 90; //Optimization. If we are scanning 360 in the azimuth there is no need to also scan 360 in the elevation as that will double scan the back 180 of the ship.
//Used to create a forward scan of a larger plane. The below will scan -5,5 and -5,5 in a plane in the forward orientation of the ore detector
//If for example you wanted to do a semicircle infront of the ore detector you could
//set this to 90 for both azimuth and elevation. that would give and arc of
// -90<->90 on the azimuth and -90<->90 on the elevation. creating a 180 degree semicircle in front.
private const float forward_azimuth = 5;
private const float forward_elevation = 5;
//This is the degrees to move each scan during a 360 scan.
//Waiting for 1 degree in all directions is way to slow.
//At 18 steps it takes about 90 seconds to complete a 360 scan.
//You can speed this up by increasing the steps size but you may miss deposits.
//Modified to cast to a float so you can now scan by decimal steps (eg .1 or .5 etc)
//Keep in mind that the further that you can out the further the scan endpoints are apart.
float sphereScanStep = (float)18;
//This is the degrees to move each scan during a forward plane
//scan and the default 20 degree angle it takes about.
//With a 5x5 plane in front at a 1 step scan it is VERY accurate
//for that area but takes about 12 seconds to complet a scan.
float forwardScanStep = 1;
float azimuth = minAngle;
float elevation = minAngle;
//scanOnce will be used if you want to press a "scan now" button. wait for the scan to finish and then
//I'll disable the ore detector. probably good for scripting. Can "scan" then once the detector is disabled move the ship 1k and scan again?
//by default we will be on continual scanning so this will be off.
bool scanOnce = false;
//this will hold the "revolutions" of a scan. Perhaps this will give an indicator if the ship can move as the scan is done.
double scans_Completed = 0;
//All the known ore and locations we've found
List<MyDetectedEntityInfo> DiscoveredOre = new List<MyDetectedEntityInfo>();
List<MyDetectedEntityInfo> FilteredOre = new List<MyDetectedEntityInfo>();
readonly IMyOreDetector detector;
public IMyTextSurface surface; // Had to change from readonly in order to write to the screen. (Radar5k).
List<IMyTextPanel> lcdPanels = new List<IMyTextPanel>();
List<IMyCockpit> cockpits = new List<IMyCockpit>();
List<IMyProgrammableBlock> PBs = new List<IMyProgrammableBlock>(); // Hunting for PB's (Radar5k).
//Menu Handling Options
int currentScreen = 0;
/*Screens:
0=Menu
1=Deposits
2=GPS Coordinates
3=Compare All
4=Status Screen (Scans Done, Scan Mode, Ore Count, etc)
5=Clear Data
6=Settings
8=Ignore Add/Remove Screen
*/
int currentDepositFilter = 0;
int maxDepositFilter = 0;
int currentOreScreen = 0;
bool previousAllowed = false;
bool nextAllowed = false;
Dictionary<string, bool> oreFilter = new Dictionary<string, bool>();
int maxScreen = 6;
int currentSubMenu = 0;
int currentSelection = 1;
int maxSelection = 6; //Used for the main menu
int maxSettingsSelection = 7; //Used for the settings menu - increased to 7 for new ignore menu item
MyIni _ini = new MyIni();
string test;
//Miniflash is for the mini status display. When mini flash is set to false the status screen will flash "Scan Complete" for x ticks and then go back to showing status.
bool miniFlash = false;
int miniFlashTicks = 0;
int maxMiniFlashTicks = 100;
double flashedOn = 0;
bool setDistance = false; //used to set the scan distance in the settings menu
bool disableOFP = false;
string sError = "";
string sOFPIniRegex = @"^OFP@.*";
string logData = "";
// New variables for ignore management
int currentIgnoreSelection = 0;
int maxIgnoreSelection = 0;
List<string> discoveredOreTypes = new List<string>();
// Debug tracking variables
string lastCommand = "";
string lastToggleAction = "";
// Use a more reliable ignore storage mechanism
List<string> ignoredOreTypes = new List<string>();
/* ToDO:
* Make ignoreList easy to edit (perhaps in the menu)
* Perhaps add a bootup splash screen
* Add the ability to broadcast the ore to a channel listener so displays on a remote base can see the ore. This would also allow for drones to zoom around scanning and have ore results at the base.
* Only scan 180 degrees instead of 360 (as that is causing a double scan)
*
*/
bool IsKnown(MyDetectedEntityInfo foundOre)
{
bool known = false;
foreach (MyDetectedEntityInfo oreInstance in DiscoveredOre)
{
if (oreInstance.Name == foundOre.Name)
{
try
{
//We have the same ore type so now lets see if the are "close" to eachother.
//If they are we will assume this is the same deposit of ore
double gpsDistance = Vector3D.Distance((Vector3D)oreInstance.HitPosition, (Vector3D)foundOre.HitPosition);
if (gpsDistance < depositRange)
{
known = true;
}
else
{
known = false;
}
}
catch (Exception e)
{
Echo($"Error: {e.Message}");
Me.Enabled = false;
}
if (DEBUGGING)
throw (new DivideByZeroException());
}
}
if (!known)
{
DiscoveredOre.Add(foundOre);
if (!oreFilter.ContainsKey(foundOre.Name))
{
oreFilter.Add(foundOre.Name, true);
}
if (oreFilter[foundOre.Name])
{
FilteredOre.Add(foundOre);
}
// Update discovered ore types list for ignore management, but exclude Stone
if (!discoveredOreTypes.Contains(foundOre.Name) && foundOre.Name != "Stone")
{
discoveredOreTypes.Add(foundOre.Name);
// Update maxIgnoreSelection but preserve selection if it's on "Return to Settings"
int oldMaxIgnoreSelection = maxIgnoreSelection;
maxIgnoreSelection = discoveredOreTypes.Count();
// If the current selection was on "Return to Settings" (which is at the old max),
// update it to the new max to keep it on "Return to Settings"
if (currentIgnoreSelection == oldMaxIgnoreSelection && oldMaxIgnoreSelection > 0)
{
currentIgnoreSelection = maxIgnoreSelection;
}
}
}
return known;
}
void RefreshFilteredOreList()
{
FilteredOre = new List<MyDetectedEntityInfo>();
foreach (MyDetectedEntityInfo oreInstance in DiscoveredOre)
{
if (oreFilter[oreInstance.Name])
{
FilteredOre.Add(oreInstance);
}
}
// reset ore screen page to prevent that you land on an empty one
currentOreScreen = 0;
}
void RemoveIgnoredOreFromKnownList(string oreType)
{
// Don't allow Stone to be removed from ignore list
if (oreType == "Stone")
{
return;
}
// Remove all ore instances of the ignored type from DiscoveredOre
List<MyDetectedEntityInfo> tempList = new List<MyDetectedEntityInfo>();
foreach (MyDetectedEntityInfo oreInstance in DiscoveredOre)
{
if (oreInstance.Name != oreType)
{
tempList.Add(oreInstance);
}
}
DiscoveredOre = tempList;
// Remove from ore filter as well
if (oreFilter.ContainsKey(oreType))
{
oreFilter.Remove(oreType);
}
// Refresh the filtered ore list
RefreshFilteredOreList();
}
void UpdateIgnoreList()
{
// Always ensure Stone is in the ignore list
if (!ignoredOreTypes.Contains("Stone"))
{
ignoredOreTypes.Insert(0, "Stone");
}
// Reconstruct ignore list from the reliable list storage
ignoreList = string.Join(",", ignoredOreTypes);
// Update the ore detector's blacklist
try
{
detector.SetValue("OreBlacklist", ignoreList);
}
catch (Exception e)
{
logData += $"\nError setting OreBlacklist in UpdateIgnoreList: {e.Message}";
}
}
bool IsOreIgnored(string oreType)
{
// Use the reliable list storage instead of parsing the comma-separated string
return ignoredOreTypes.Contains(oreType);
}
void ToggleOreIgnored(string oreType)
{
// Never allow Stone to be toggled
if (oreType == "Stone")
{
lastToggleAction = $"Cannot toggle Stone - it must remain ignored";
return;
}
bool wasIgnored = IsOreIgnored(oreType);
if (wasIgnored)
{
// Remove from ignore list
ignoredOreTypes.Remove(oreType);
lastToggleAction = $"REMOVED {oreType} from ignore list";
}
else
{
// Add to ignore list and remove from known ore list
if (!ignoredOreTypes.Contains(oreType))
{
ignoredOreTypes.Add(oreType);
}
RemoveIgnoredOreFromKnownList(oreType);
lastToggleAction = $"ADDED {oreType} to ignore list and removed from known ore";
}
// Update the string version and detector blacklist
UpdateIgnoreList();
// Update debug log
logData += $"\nToggle Action: {lastToggleAction}";
logData += $"\nNew Ignore List: [{ignoreList}]";
logData += $"\nWas Ignored: {wasIgnored}, Now Ignored: {IsOreIgnored(oreType)}";
logData += $"\nIgnored List Count: {ignoredOreTypes.Count}";
}
void InitializeIgnoreList()
{
// Parse the initial ignore list into the reliable storage
ignoredOreTypes.Clear();
if (!string.IsNullOrEmpty(ignoreList))
{
string[] ores = ignoreList.Split(',');
foreach (string ore in ores)
{
string cleanOre = ore.Trim();
if (!string.IsNullOrEmpty(cleanOre) && !ignoredOreTypes.Contains(cleanOre))
{
ignoredOreTypes.Add(cleanOre);
}
}
}
// Always ensure Stone is in the ignore list
if (!ignoredOreTypes.Contains("Stone"))
{
ignoredOreTypes.Insert(0, "Stone");
}
}
// Helper method to get displayable ores in consistent order
List<KeyValuePair<string, int>> GetDisplayableOres()
{
Dictionary<string, int> oreForDisplay = new Dictionary<string, int>();
foreach (MyDetectedEntityInfo oreInstance in DiscoveredOre)
{
if (!oreFilter.ContainsKey(oreInstance.Name))
{
oreFilter.Add(oreInstance.Name, true);
}
string oreKey = oreInstance.Name;
if (oreForDisplay.ContainsKey(oreKey))
{
int amount = oreForDisplay[oreKey];
amount += 1;
oreForDisplay[oreKey] = amount;
}
else
{
oreForDisplay.Add(oreKey, 1);
}
}
// Create a list excluding Stone for proper indexing
List<KeyValuePair<string, int>> displayableOres = new List<KeyValuePair<string, int>>();
foreach (KeyValuePair<string, int> orePair in oreForDisplay)
{
if (orePair.Key != "Stone") // Exclude Stone from display
{
displayableOres.Add(orePair);
}
}
return displayableOres;
}
void LogInfo(IMyTextSurface panel, bool staticScreen = false)
{
string debugInfo = "***** [OFP DEBUG INFO] *****";
// Menu and Navigation State
debugInfo += $"\nLast Command: {lastCommand}";
debugInfo += $"\nCurrent Screen: {currentScreen}";
debugInfo += $"\nCurrent Selection: {currentSelection}";
debugInfo += $"\nCurrent Ignore Selection: {currentIgnoreSelection}";
debugInfo += $"\nMax Ignore Selection: {maxIgnoreSelection}";
debugInfo += $"\nSet Distance Mode: {setDistance}";
debugInfo += $"\nCurrent Deposit Filter: {currentDepositFilter}";
debugInfo += $"\nMax Deposit Filter: {maxDepositFilter}";
// Ignore List Information
debugInfo += $"\n--- IGNORE LIST INFO ---";
debugInfo += $"\nIgnore List: [{ignoreList}]";
debugInfo += $"\nIgnored List Count: {ignoredOreTypes.Count}";
debugInfo += $"\nIgnored Ores: [{string.Join(",", ignoredOreTypes)}]";
try
{
debugInfo += $"\nDetector Blacklist: [{detector.GetValue<string>("OreBlacklist")}]";
}
catch
{
debugInfo += $"\nDetector Blacklist: [ERROR READING]";
}
debugInfo += $"\nLast Toggle Action: {lastToggleAction}";
// Discovered Ore Types (excluding Stone)
debugInfo += $"\n--- DISCOVERED ORE TYPES ---";
debugInfo += $"\nCount: {discoveredOreTypes.Count}";
for (int i = 0; i < discoveredOreTypes.Count; i++)
{
string oreType = discoveredOreTypes[i];
bool isIgnored = IsOreIgnored(oreType);
string marker = (i == currentIgnoreSelection) ? ">" : " ";
debugInfo += $"\n{marker} [{(isIgnored ? "X" : " ")}] {oreType}";
}
// Display order for deposits screen
debugInfo += $"\n--- DISPLAY ORDER ---";
List<KeyValuePair<string, int>> displayableOres = GetDisplayableOres();
for (int i = 0; i < displayableOres.Count; i++)
{
string marker = (i == currentDepositFilter) ? ">" : " ";
debugInfo += $"\n{marker} {i}: {displayableOres[i].Key}";
}
// Filter and Discovery State
debugInfo += $"\n--- ORE FILTER STATE ---";
foreach (KeyValuePair<string, bool> filter in oreFilter)
{
debugInfo += $"\n{filter.Key}: {filter.Value}";
}
debugInfo += $"\n--- DISCOVERY COUNTS ---";
debugInfo += $"\nDiscovered Ore: {DiscoveredOre.Count}";
debugInfo += $"\nFiltered Ore: {FilteredOre.Count}";
// Original log data
if (!string.IsNullOrEmpty(logData))
{
debugInfo += $"\n--- ORIGINAL LOG DATA ---";
debugInfo += $"\n{logData}";
}
if (!staticScreen)
debugInfo += "\n> Return To Menu";
WriteToLCD(debugInfo, panel);
}
void ClearLCD()
{
foreach (IMyTextPanel panel in lcdPanels)
{
panel.WriteText("");
}
}
string CustomParseCustomData(string[] CustomData)
{
//May have an issue in the custom data so lets see if we can use some regex and recover
string iniExtract = "";
System.Text.RegularExpressions.Regex rx = new System.Text.RegularExpressions.Regex(sOFPIniRegex);
foreach (string strLine in CustomData)
{
System.Text.RegularExpressions.MatchCollection matches = rx.Matches(strLine);
foreach (System.Text.RegularExpressions.Match match in matches)
{
iniExtract += $"{match.Value} \n";
}
}
if (DEBUGGING)
throw (new DivideByZeroException());
return iniExtract;
}
void RegisterLCDs()
{
List<IMyTextPanel> allPanels = new List<IMyTextPanel>();
List<IMyCockpit> allCockpits = new List<IMyCockpit>();
List<IMyProgrammableBlock> allPBs = new List<IMyProgrammableBlock>();
GridTerminalSystem.GetBlocksOfType(allCockpits);
GridTerminalSystem.GetBlocksOfType(allPanels);
GridTerminalSystem.GetBlocksOfType(allPBs);
foreach (IMyCockpit iCockpit in allCockpits)
{
if ((iCockpit.CustomName.Contains(OFP_TAG)) | (iCockpit.CustomData.Contains(OFP_TAG)) | (iCockpit.CustomData.Contains(OFP_SETTINGS_TAG)))
{
MyIniParseResult result;
_ini.TryParse(iCockpit.CustomData, out result);
if ((!iCockpit.CustomData.Contains(OFP_SETTINGS_TAG)))
{
//No active ini data so we will set a default
iCockpit.CustomData = OFP_SETTINGS_TAG;
iCockpit.CustomData += "\n; Edit the below to change how this screen reacts.";
iCockpit.CustomData += "\n; Options:";
iCockpit.CustomData += "\n; default = Allow this screen to navigate all menus";
iCockpit.CustomData += "\n; ore = Always show ore status";
iCockpit.CustomData += "\n; coordinates = Always show coordinate screen";
iCockpit.CustomData += "\n; status = shows the status of current (or single) scan";
iCockpit.CustomData += "\n; ministatus = shows scan status on small screens";
iCockpit.CustomData += "\n; settings = screen to set options";
iCockpit.CustomData += "\n; :::::EXAMPLE::::";
iCockpit.CustomData += "\n; OFP@0=default";
iCockpit.CustomData += "\n; OFP@2=status";
iCockpit.CustomData += "\nOFP@0 = default";
iCockpit.GetSurface(0).ContentType = ContentType.TEXT_AND_IMAGE;
}
//Need to capture the entire cockpit as it will have the custom data section for the INI
cockpits.Add(iCockpit);
}
}
foreach (IMyTextPanel panel in allPanels)
{
if ((panel.CustomName.Contains(OFP_TAG)) | (panel.CustomData.Contains(OFP_TAG)) | (panel.CustomData.Contains(OFP_SETTINGS_TAG)))
{
//Grab all panels that have the OFP_TAG in their name.
//Check if the panel already has our INI data
MyIniParseResult result;
_ini.TryParse(panel.CustomData, out result);
if ((!panel.CustomData.Contains(OFP_SETTINGS_TAG)))
{
//No active ini data so we will set a default
panel.CustomData = OFP_SETTINGS_TAG;
panel.CustomData += "\n; Edit the below to change how this screen reacts.";
panel.CustomData += "\n; Options:";
panel.CustomData += "\n; default = Allow this screen to navigate all menus";
panel.CustomData += "\n; ore = Always show ore status";
panel.CustomData += "\n; coordinates = Always show coordinate screen";
panel.CustomData += "\n; status = shows the status of current (or single) scan";
panel.CustomData += "\n; ministatus = shows scan status on small screens";
panel.CustomData += "\n; settings = screen to set options";
panel.CustomData += "\nScreen = default";
}
lcdPanels.Add(panel);
}
}
// Hunting for Tag in Programmable Block Names and Custom Data (Copy/Paste/Edit of Cockpit loop). (Radar5k).
foreach (IMyProgrammableBlock iPB in allPBs)
{
if ((iPB.CustomName.Contains(OFP_TAG)) | (iPB.CustomData.Contains(OFP_TAG)) | (iPB.CustomData.Contains(OFP_SETTINGS_TAG)))
{
MyIniParseResult result;
_ini.TryParse(iPB.CustomData, out result);
if ((!iPB.CustomData.Contains(OFP_SETTINGS_TAG)))
{
//No active ini data so we will set a default
iPB.CustomData = OFP_SETTINGS_TAG;
iPB.CustomData += "\n; Edit the below to change how this screen reacts.";
iPB.CustomData += "\n; Options:";
iPB.CustomData += "\n; default = Allow this screen to navigate all menus";
iPB.CustomData += "\n; ore = Always show ore status";
iPB.CustomData += "\n; coordinates = Always show coordinate screen";
iPB.CustomData += "\n; status = shows the status of current (or single) scan";
iPB.CustomData += "\n; ministatus = shows scan status on small screens";
iPB.CustomData += "\n; settings = screen to set options";
iPB.CustomData += "\n; :::::EXAMPLE::::";
iPB.CustomData += "\n; OFP@0=default";
iPB.CustomData += "\n; OFP@2=status";
iPB.CustomData += "\nOFP@0 = default";
iPB.GetSurface(0).ContentType = ContentType.TEXT_AND_IMAGE;
}
PBs.Add(iPB);
}
}
if (lcdPanels.Count == 0 && cockpits.Count == 0 && PBs.Count == 0)
{
throw new Exception($"Error: No LCD Panels, Cockpits, or Programmable Blocks found with the {OFP_TAG} tag in their name or Custom Data.");
}
}
void HandleSettings()
{
switch (currentSelection)
{
case (1):
//Disable/Enable OFP
disableOFP = !disableOFP;
if (disableOFP)
{
azimuth = minAngle;
elevation = minAngle;
}
break;
case (2):
scanMode++;
scanMode %= OFPScanMode.Count;
flashedOn = 0;
scans_Completed = 0;
break;
case (3):
scanOnce = !scanOnce;
if (scanOnce)
scans_Completed = 0;
break;
case (4):
//Disable/Enable quickscanning
quickScan = !quickScan;
break;
case (5):
//Setting of Distance to scan
setDistance = !setDistance;
break;
case (6):
//Navigate to Ignore Add/Remove screen
currentScreen = 8;
currentSelection = 1;
currentIgnoreSelection = 0;
logData += $"\nNavigated to Ignore Add/Remove screen";
break;
case (7):
//Return to main menu
currentScreen = 0;
currentSelection = 1;
break;
default:
currentSelection = 1;
currentScreen = 5;
break;
}
RefreshScreens();
}
void HandleIgnoreMenu()
{
if (currentIgnoreSelection < discoveredOreTypes.Count)
{
string selectedOre = discoveredOreTypes[currentIgnoreSelection];
logData += $"\nHandleIgnoreMenu: Toggling ore {selectedOre} at index {currentIgnoreSelection}";
logData += $"\nBefore Toggle - IsIgnored: {IsOreIgnored(selectedOre)}";
ToggleOreIgnored(selectedOre);
logData += $"\nAfter Toggle - IsIgnored: {IsOreIgnored(selectedOre)}";
RefreshScreens();
}
else if (currentIgnoreSelection == discoveredOreTypes.Count)
{
// Return to settings menu
currentScreen = 5;
currentSelection = 1;
logData += $"\nHandleIgnoreMenu: Returning to settings menu";
RefreshScreens();
}
}
void HandleMenu(string cmd)
{
lastCommand = cmd;
switch (cmd.ToLower())
{
case "screen":
currentScreen = (currentScreen >= maxScreen) ? 0 : currentScreen + 1;
break;
case "apply":
switch (currentScreen)
{
case (0):
currentScreen = currentSelection;
currentSelection = 1;
break;
case (1):
//Need to "select" or deselect the ore type
if (currentDepositFilter == maxDepositFilter)
{
currentScreen = 0;
currentSelection = 1;
currentOreScreen = 0;
currentSubMenu = 0;
}
else
{
// Use the same displayable ores logic as the display method
List<KeyValuePair<string, int>> displayableOres = GetDisplayableOres();
try
{
if (currentDepositFilter < displayableOres.Count)
{
string selectedOre = displayableOres[currentDepositFilter].Key;
oreFilter[selectedOre] = !oreFilter[selectedOre];
RefreshFilteredOreList();
logData += $"\nToggled display filter for {selectedOre} to {oreFilter[selectedOre]} at display index {currentDepositFilter}";
}
}
catch (Exception e)
{
logData += $"\nError in display filter toggle: {e.Message}";
}
}
break;
case (2):
switch (currentSubMenu)
{
case (2):
currentOreScreen--;
break;
case (1):
currentOreScreen++;
break;
case (0):
currentScreen = 0;
currentSelection = 1;
currentOreScreen = 0;
break;
}
currentSubMenu = 0;
break;
case (5):
//need to change some settings here and refresh. Only need to change the screen if we are on the "return to menu" option
HandleSettings();
break;
case (8):
//Handle ignore add/remove screen
logData += $"\nApply command on ignore screen, currentIgnoreSelection={currentIgnoreSelection}, discoveredOreTypes.Count={discoveredOreTypes.Count}";
HandleIgnoreMenu();
break;
default:
currentScreen = 0;
currentSelection = 1;
currentOreScreen = 0;
currentSubMenu = 0;
break;
}
break;
case "up":
switch (currentScreen)
{
case (0):
if (currentSelection > 1)
{
currentSelection--;
}
break;
case (1):
if (currentDepositFilter > 0)
{
currentDepositFilter--;
}
break;
case (2):
if (currentSubMenu < 2)
{
currentSubMenu++;
if ((currentSubMenu == 2) && (!previousAllowed))
{
currentSubMenu--;
}
if ((currentSubMenu == 1) && (!nextAllowed))
{
if (previousAllowed)
currentSubMenu++;
else
currentSubMenu = 0;
}
}
break;
case (5):
if (setDistance)
{
detectionDistance += 100;
}
else
{
if (currentSelection > 1)
{
currentSelection--;
}
}
break;
case (8):
//Handle ignore screen navigation
if (currentIgnoreSelection > 0)
{
currentIgnoreSelection--;
logData += $"\nUp command on ignore screen, new selection={currentIgnoreSelection}";
}
break;
}
break;
case "down":
switch (currentScreen)
{
case (0):
if (currentSelection < maxSelection)
{
currentSelection++;
}
break;
case (1):
if (currentDepositFilter < maxDepositFilter)
{
currentDepositFilter++;
}
break;
case (2):
if (currentSubMenu > 0)
{
currentSubMenu--;
if ((currentSubMenu == 1) && (!nextAllowed))
{
currentSubMenu--;
}
}
break;
case (5):
if (setDistance)
{
if (detectionDistance > 0)
{
detectionDistance -= 100;
}
}
else
{
if (currentSelection < maxSettingsSelection)
{
currentSelection++;
}
}
break;
case (8):
//Handle ignore screen navigation
if (currentIgnoreSelection < maxIgnoreSelection)
{
currentIgnoreSelection++;
logData += $"\nDown command on ignore screen, new selection={currentIgnoreSelection}";
}
break;
}
break;
case "menu":
currentScreen = 0;
currentSelection = 1;
break;
case "clear":
currentScreen = 3;
currentSelection = 1;
break;
case "enable":
detector.Enabled = true;
disableOFP = false;
flashedOn = 0;
scans_Completed = 0;
break;
case "disable":
disableOFP = true;
flashedOn = 0;
scans_Completed = 0;
break;
case "scan":
//scan once feature
scanOnce = true;
disableOFP = false;
detector.Enabled = true;
//Best scan once mode is probably 360 scan. However let us allow the end-user to use whatever mode they like.
//This is set above by default to 360 but can be changed in the menu items.
//scanMode = 1;
flashedOn = 0;
scans_Completed = 0;
break;
default:
currentSelection = 1;
currentScreen = 0;
break;
}
logData += $"\nHandleMenu: cmd={cmd} currentScreen={currentScreen} currentDepositFilter={currentDepositFilter}";
RefreshScreens();
}
void RefreshScreens()
{
foreach (IMyCockpit iCockPit in cockpits)
{
MyIniParseResult result;
if (!_ini.TryParse(iCockPit.CustomData, out result))
{
//Error Parsing the CustomData. This could be a conflict with another script like SAM
string[] config_split = iCockPit.CustomData.Split('\n');
string raw_settings = CustomParseCustomData(config_split);
string reconstructed_ini = $"{OFP_SETTINGS_TAG}\n{raw_settings}";
if (!_ini.TryParse(reconstructed_ini, out result))
{
throw (new DivideByZeroException());
}
}
for (int cPanel = 0; cPanel < iCockPit.SurfaceCount - 1; cPanel++)
{
string screenType = _ini.Get("OreFinderPlus", $"OFP@{cPanel}").ToString();
if (screenType != "")
{
iCockPit.GetSurface(cPanel).ContentType = ContentType.TEXT_AND_IMAGE;
switch (screenType)
{
case "default":
ShowScreen(currentScreen, iCockPit.GetSurface(cPanel));
break;
case "menu":
ShowScreen(0, iCockPit.GetSurface(cPanel), true);
break;
case "ore":
ShowScreen(1, iCockPit.GetSurface(cPanel), true);
break;
case "coordinates":
ShowScreen(2, iCockPit.GetSurface(cPanel), true);
break;
case "debug":
ShowScreen(3, iCockPit.GetSurface(cPanel), true);
break;
case "status":
ShowScreen(4, iCockPit.GetSurface(cPanel), true);
break;
case "ministatus":
ShowScreen(7, iCockPit.GetSurface(cPanel), true, true);
break;
case "settings":
ShowScreen(5, iCockPit.GetSurface(cPanel), true);
break;