Hmmmm, I love "applications" with different (root) pathes. I do not use SaveGameScripts so I do not notice nor consider this path. But the file there is empty:
The handling is a little bit nasty. I set the LearnMode to true. I let the mod run into the EntityType missing message. The mod added to "CsCompilerConfiguration.json" the empty player section, the empty admin section, and the savegame section with the "EntityType" entry. I moved the entry to the playersection in this file. I saved the file and restarted the server. After the restart the permission is still missing (message still on the LCD) and the file "CsCompilerConfiguration.json" is resettet to the default state like in the picture above. No sections, no entry, no LearnMode and no permission. So after every occurence I have to immidiately copy the entry to the other default file and to reactivate the LearnMode.
Very Strange - the second attempt: I set the LearnMode to true. I let the mod run into the EntityType missing message. The mod added to "CsCompilerConfiguration.json" the empty player section, the empty admin section, and the savegame section with the "EntityType" entry. I moved the entry to the playersection of the "DefaultCsCompilerConfiguration.json" file. I saved the file and restarted the server. After the restart the permission is still missing (message still on the LCD), but now the files seems to "swap". The mod cleared the file "DefaultCsCompilerConfiguration.json" completely: and therefore fills the "CsCompilerConfiguration.json" file with many entries in the savegame section. I would guess I have to shovel entries and restart the server many times until no new entry is added?! "Shoveling" doesn't work because LearnMode only consider the SymbolPermission section. The "Using" and "AssemblyReferences" are still missing. I need a fresh default file from GitHub.
Adding "EntityType" (like the LearnMode suggests in the savegamesection) to the player section seems to break the file parser. Regardless in which of the two files I add the entry "EntityType" at the next startup the file is cleared by the mod.
It is enough if you move the found entries from the savegame to the player section in the file CsCompilerConfiguration.json. You do not need to change the default file. PS: All my mods are designed so that the configuration takes place in the savegame. This is automatically backed up to the server and you have the option of using different configurations in the SP.
I understand and agree with the outsoursing of setting files from the application directory. Its just mindblowing to remember each different file location at many different apps/programs/mods from many different coders. I would just love to see standardized way to find all dependencies of a program in a simple way, regardless from which coder. For example you mentioned the mod settings-file in your guide video, for the entity access distance for example. I search for this file a long time.....today I found it in the "savegame" directory. To the entries: I downloaded a fresh default file and let the LearnMode list the missing entries. I moved all entries to the player section in the "CsCompilerConfiguration.json" file: And it works. I must have made the same (formatting) mistake several times. But I'm wondering about the file wipe. I had made format mistakes at other entries before and the mod simply not use/load the file. Whatever format mistake I have made this time the mod react with a filewipe instead of ignoring it.
For your container example I got the "only on admin structure" message. Because the LearnMode not listing this missing entries I copied all "useful-sounding" entries from the admin section of the default file to the savegame file: But I still got the message.
In this case the are entries in the admin section in the default settings. You can override this when you set these settings in you file in the player section. BUT access to this opens exploits that's the reason for the admin level in the default.
To avoid the continous missing things and to take advantage of reuseable script modules I switch over to savegamescripts. For debugging I wrapped the whole script with a generic try-catch-clause, but I got no exceptions until now on my display. The code runs without error and updates the LCD but it seems that all the very useful hints appearing at ingame script writing are not thrown as catchable exception. Even the "Errors" that throw catchable exceptions at ingame scripts not throwing anything when copied to a savegame script. Or is there a better way to debug programming errors in savegamescripts? Code: Char cArgumentSeperator = ':'; String sProcessorName = "CargoCtrlCpu*"; IBlockData deviceProcessor = CsRoot.Devices(E.S, sProcessorName).FirstOrDefault(); if(E.S.IsPowerd && deviceProcessor != null){ ILcd iLCDProcessor = CsRoot.GetDevices<ILcd>(deviceProcessor).FirstOrDefault(); try{ String[] sArgs = deviceProcessor.CustomName.Split(cArgumentSeperator); String sDisplayName = ""; if (sArgs.Count() >= 2) {sDisplayName = sArgs[1];} ILcd[] iLCDDisplays = CsRoot.GetDevices<ILcd>(CsRoot.Devices(E.S, sDisplayName)); iLCDProcessor.SetText("Update: " + DateTime.Now); setDisplayLine(iLCDDisplays, "Update: " + DateTime.Now); //...... }catch(Exception e){ appendDisplayLine(iLCDProcessor, e.ToString()); } } void setDisplayLine(ILcd[] lcds, String sLine){ lcds?.ForEach(lcd => {lcd.SetText(sLine);}); } void appendDisplayLine(ILcd[] lcds, String sLine){ lcds?.ForEach(lcd => {appendDisplayLine(lcd, sLine);}); } void appendDisplayLine(ILcd lcd, String sLine){ lcd.SetText(lcd.GetText()+Environment.NewLine+sLine); }
You can set "ScriptTrackingError": true, in the [EGS]\Saves\Games\[Savegame]\Mods\EmpyrionScripting\Configuration.json !!!! ;-) Then ALL compile errors of scripts are written to [EGS]\Saves\Games\[Savegame]\Mods\EmpyrionScripting\ScriptTracking\[ID]\*.error files
First I activated "ScriptTrackingError" and nothing happens. No "ScriptTracking" folder, no file. After that I activated "ScriptTracking", too: Then the "ScriptTracking" folder appears, but only for your demo vessel: My base and my hovervessels on which my safe game srcipts running are not tracked. The first list entry is the Entity-Id:
I added a ingame script to my base and the entity-Id folder appears in the SriptTracking folder. But the Tracking files in the folder only shows ingame scripts. It looks like save game scripts are not tracked.
I try to find the remote connection device on my structure. The Id is 1627. The following code compiles and runs fine and the remote connection device is listed in the control panel of the vessel. But "CsRoot.Devices" seems to ignore it. The console output is empty. Code: CsRoot.Devices(E.S, "*").Where(c => c.Id == 1627).ForEach(c => { Console.WriteLine(c.CustomName); });
Remember devices by name only works if the devices have a custom name in the device tab od the control panel (here i renamed it to "WiFi") ;-)
Thats the reason I try to identify it by Id. I don't want be care about the CustomName in this case. Its interesting that "CsRoot.Devices(E.S "*")" not recognize a device, if I do not change the CustomName. In the Controlpanel is a "Name" visible for my RemoteDevice. But its the standard name because I never changed it. So its a little bit confusing that "CsRoot.Devices(E.S "*")" not found the device which has still a "visible" name in the controlpanel. Even if its not a "defined Custom Name". In the Controlpanel I cannot differ between "CustomName" and "Standard Name".
Yep ... the function runs via the API function "CustomNames" and there are only the deviating ones :-(
I stumble upon a strange behaviour. I have a "List<String>" with different container names: >Fracht_Ausruestung* >Fracht_Material* >Fracht_Material* >Fracht_Material* >Fracht_Bloecke* >Fracht_Bloecke* >Fracht_Kuehl* >Fracht_Kuehl* >Fracht_Samen* >Fracht_Ausruestung* >Fracht_Ausruestung* >Fracht_Bloecke* >Fracht_Bloecke* >Fracht_Ausruestung* >Fracht_Ausruestung* >Fracht_Ausruestung* All methods I try to remove the duplicates do not work properly. Some duplicates remain in the list. I tried ".Distinct()", ".GroupBy()", "Contains()", ".Exists()", ".Where()" and so on. Every time with the same result: As you can see just one duplicate of ">Fracht_Material*" and just one duplicate of ">Fracht_Ausruestung*" remains. I compared the strings manually without a noteable difference. I copied the namings from one source line to the others lines so the string must be identical. In addition if you look at the identical percentage values the "CsRoot.GetDevices()" method reads the same containers from the structure with both name-strings. I don't get it. Could this be a mistake in the script parser? I noticed for example, that the ".Contains(String, StringComparison)" overload not exists ingame. The script has dependencies so have a closer look if copying: Code: // ########################################## // Author: Florian Krause // Version 1.0 / 2020-07-25: Initial // Function: list cargo fill levels for all containers from the management list // ########################################## // Language appearence settings const String sManagmentTableName = "Frachtverwaltung"; const String sLanguage = "Deutsch"; const String sDisplayHeadline = "Frachtcontainer Status:"; const String sCargoManagementTableMissingMessage = "Frachtverwaltungstabelle nicht gefunden: "; const String sHeadlineFaultyParameter = "Fehlerhafte Parameter:"; const String sHeadlineUnknownBoxes = "Unbekannte Container:"; const String sParameterErrorNoContFound = "Keine Containerliste gefunden"; // Script-internal settings const String sProcessorName = "StatCargoCpu*"; const String sInfoDisplayFontSize = "<size=4>"; const String sLoggingDisplayFontSize = "<size=3>"; const int iWarningLevel = 25; const int iCriticalLevel = 10; const String sMainTextColor = "<color=white>"; const String sNormalBarColor = "<color=green>"; const String sWarningBarColor = "<color=yellow>"; const String sCriticalBarColor = "<color=red>"; const String sContainerTagsHeadlineTag = "Container"; Char cArgumentSeperator = ':'; Char cCargoManagementListLineSeperator = '\n'; Char cCargoManagementListItemSeperator = ':'; // without structure powered and without "processing device" the scrip should "sleep" IBlockData deviceProcessor = CsRoot.Devices(E.S, sProcessorName).FirstOrDefault(); if(E.S.IsPowerd && deviceProcessor != null){ // prepare output for debugging ILcd iLCDProcessor = CsRoot.GetDevices<ILcd>(deviceProcessor).FirstOrDefault(); try{ CsRoot.I18nDefaultLanguage = sLanguage; // search information displays ILcd[] iLCDDisplays = null; String[] sArgs = deviceProcessor.CustomName.Split(cArgumentSeperator); if (sArgs.Count() >= 2) { iLCDDisplays = CsRoot.GetDevices<ILcd>(CsRoot.Devices(E.S, sArgs[1])); } // prepare displays setDisplayLine(iLCDProcessor, sLoggingDisplayFontSize + sProcessorName + " Log:"); appendDisplayLine(iLCDProcessor, "Update: " + DateTime.Now); setDisplayLine(iLCDDisplays, sInfoDisplayFontSize + sMainTextColor + sDisplayHeadline); appendDisplayLine(iLCDDisplays, "Update: " + DateTime.Now); // load cargo control table List<KeyValuePair<String, String>> cargoManagementList = new List<KeyValuePair<String, String>>(); ILcd iLCDDataTable = CsRoot.GetDevices<ILcd>(CsRoot.Devices(E.S, sManagmentTableName)).FirstOrDefault(); if (iLCDDataTable != null) { iLCDDataTable.GetText().Split(cCargoManagementListLineSeperator).ForEach(sLine => { string[] sLineItems = sLine.Split(cCargoManagementListItemSeperator); if(sLineItems.Count() == 2){ cargoManagementList.Add(new KeyValuePair<String, String>(sLineItems[0], sLineItems[1])); } }); appendDisplayLine(iLCDProcessor, "- Read-In Management-Settings successful"); // get container list List<String> tempList = null; List<String> containerList = new List<String>(); cargoManagementList.ForEach(setting => { switch(setting.Key){ case sContainerTagsHeadlineTag:tempList = containerList;break; default: if (tempList != null) { if (setting.Value != "") { if (tempList.Contains(setting.Value) == false){ tempList.Add(setting.Value); } }else{ tempList = null; } } break; } }); appendDisplayLine(iLCDProcessor, "- accomplish container list successful"); // calculate and draw container fill levels List<String> unknownBoxes = new List<String>(); List<String> faultParameters = new List<String>(); double dFillLevelAct; double dFillLevelMax; object oVolume; if (containerList.Count() != 0) { containerList.ForEach(sContainerName => { IContainer[] containerGroup = CsRoot.GetDevices<IContainer>(CsRoot.Devices(E.S, sContainerName)); if (containerGroup.Count() != 0){ dFillLevelAct = 0; dFillLevelMax = 0; containerGroup.ForEach(container => { dFillLevelMax += container.VolumeCapacity; container.GetContent().ForEach(item => { oVolume = CsRoot.ConfigFindAttribute(item.id, "Volume"); if(oVolume != null){ dFillLevelAct += item.count * double.Parse(oVolume.ToString()); } }); }); drawBar(iLCDDisplays, sContainerName, dFillLevelAct, dFillLevelMax, true); }else{ unknownBoxes.Add(sContainerName); } }); }else{ faultParameters.Add(sParameterErrorNoContFound); } appendDisplayLine(iLCDProcessor, "- draw container list successful"); // error printing if (faultParameters.Count() > 0){ appendDisplayLine(iLCDProcessor, sHeadlineFaultyParameter); appendDisplayLine(iLCDDisplays, sHeadlineFaultyParameter); faultParameters.ForEach(parameter => { appendDisplayLine(iLCDProcessor, parameter); appendDisplayLine(iLCDDisplays, parameter); }); } if (unknownBoxes.Count() > 0){ appendDisplayLine(iLCDProcessor, sHeadlineUnknownBoxes); appendDisplayLine(iLCDDisplays, sHeadlineUnknownBoxes); unknownBoxes.ForEach(box => { appendDisplayLine(iLCDProcessor, box); appendDisplayLine(iLCDDisplays, box); }); } }else{ appendDisplayLine(iLCDProcessor, sCargoManagementTableMissingMessage + sManagmentTableName); appendDisplayLine(iLCDDisplays, sCargoManagementTableMissingMessage + sManagmentTableName); } }catch(Exception e){ appendDisplayLine(iLCDProcessor, e.ToString()); } } void drawBar(ILcd[] lcds, String sName, double dActValue, double dMaxValue, bool bInversColor){ double dLevel = 0; if (dMaxValue > 0) {dLevel = dActValue / dMaxValue * 100;} String sBarColor = sNormalBarColor; if (!bInversColor && dLevel < iCriticalLevel) { sBarColor = sCriticalBarColor; }else if (!bInversColor && dLevel < iWarningLevel) { sBarColor = sWarningBarColor; }else if (bInversColor && dLevel >= (100 - iCriticalLevel)) { sBarColor = sCriticalBarColor; }else if (bInversColor && dLevel >= (100 - iWarningLevel)) { sBarColor = sWarningBarColor; } appendDisplayLine(lcds, sMainTextColor + sName); appendDisplayLine(lcds, String.Format("{0}{1} {2}{3:N2}%", sBarColor, CsRoot.Bar(dActValue, 0, dMaxValue, 15), sMainTextColor, dLevel)); } void setDisplayLine(ILcd[] lcds, String sLine){ lcds?.ForEach(lcd => {setDisplayLine(lcd, sLine);}); } void setDisplayLine(ILcd lcd, String sLine){ lcd?.SetText(sLine); } void appendDisplayLine(ILcd[] lcds, String sLine){ lcds?.ForEach(lcd => {appendDisplayLine(lcd, sLine);}); } void appendDisplayLine(ILcd lcd, String sLine){ lcd?.SetText(lcd.GetText() + Environment.NewLine + sLine); }
Weird...."String.Length" brings the hint. There are invisible, unmarkable, unnoticeable, symbol-less, unfunctional characters in the Lcds. Would ".Trim()" help to get rid of these "magical" characters? Is that the reason "CsRoot.Devices()" is functional even with the polluted string? The characters seem not to be "normal - space" characters.
I suggest to add... Code: //<noparse> //</noparse> ...at the begin and end of your script if you use C#. Maybe it’s the textmesh stuff that causes this.