// Updated by SKO to fix Keen Update issues. // Version: v1.2 - 30.04.2025 12:30:00 // Fixed: display of grinding/welding items and counters. static double UpdateInvervalAssemblerQueues = 0.1; //Update every x seconds (0=as fast as possible, 0.5=every 500ms, ..) static double UpdateInvervalGrinding = 0.1; //Update every x seconds (0=as fast as possible, 0.5=every 500ms, ..) /// /// Configure the groups with there block names and/or group names. /// You can access screens from TextSurfaceProvider like Cockpits with there name followed by [screenindex] e.g. "Cockpit[0]" /// static BuildAndRepairSystemQueuingGroup[] BuildAndRepairSystemQueuingGroups = { new BuildAndRepairSystemQueuingGroup() { BuildAndRepairSystemGroupName = "BuildAndRepairGroup1", AssemblerGroupName = "AssemblerGroup1", Displays = new [] { new DisplayDefinition { DisplayNames = new [] { "BuildAndRepairGroup1StatusPanel", "Cockpit[0]" }, DisplayKinds = new [] { DisplayKind.ShortStatus, DisplayKind.Status, DisplayKind.WeldTargets, DisplayKind.GrindTargets, DisplayKind.CollectTargets, DisplayKind.MissingItems, DisplayKind.BlockWeldPriority, DisplayKind.BlockGrindPriority }, DisplayMaxLines = 19, DisplaySwitchTime = 4 } } } }; /* Complex Example with multiple groups, displays, .. static BuildAndRepairSystemQueuingGroup[] BuildAndRepairSystemQueuingGroups = { new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group1", BuildAndRepairSystemNames = new [] { "Hangar1BaRSystem1", "Hangar1BaRSystem2" }, AssemblerNames = new[] { "Hangar1Assembler1", "Hangar1Assembler2", "Hangar1Assembler3" }, Displays = new[] { new DisplayDefinition { DisplayNames = new [] { "BaRStatusPanel" }, DisplayKinds = new [] { DisplayKind.Status, DisplayKind.MissingItems, DisplayKind.WeldTargets, DisplayKind.GrindTargets, DisplayKind.CollectTargets }, DisplayMaxLines = 19, DisplaySwitchTime = 4 } } }, new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group2", BuildAndRepairSystemNames = new[] { "Hangar1BaRSystem1", "Hangar1BaRSystem2" }, AssemblerNames = new[] { "Hangar1Assembler1", "Hangar1Assembler2", "Hangar1Assembler3" }, Displays = new [] { new DisplayDefinition { DisplayNames = new [] { "Hangar1BaRSystemStatusPanel1", "Hangar1BaRSystemStatusPanel2" }, DisplayKinds = new [] { DisplayKind.Status }, DisplayMaxLines = 19, DisplaySwitchTime = 0 }, new DisplayDefinition { DisplayNames = new [] { "Hangar1BaRSystemStatusPanel3", "Hangar1BaRSystemStatusPanel4" }, DisplayKinds = new [] { DisplayKind.Status, DisplayKind.MissingItems, DisplayKind.WeldTargets, DisplayKind.GrindTargets, DisplayKind.CollectTargets }, DisplayMaxLines = 10, DisplaySwitchTime = 4 } } }, new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group3", BuildAndRepairSystemNames = new[] { "BuildAndRepair1.1", "BuildAndRepair1.2" }, AssemblerNames = new[] { "Assembler1.1", "Assembler1.2", "Assembler1.3" } }, new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group4", BuildAndRepairSystemGroupName = "BuildAndRepairGroup1", AssemblerNames = new[] { "Assembler1.1", "Assembler1.2", "Assembler1.3" } }, new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group5", BuildAndRepairSystemGroupName = "BuildAndRepairGroup1", AssemblerNames = new[] { "Assembler1.1", "Assembler1.2", "Assembler1.3" } }, new BuildAndRepairSystemQueuingGroup() { Name = "Hangar BaR Group6", BuildAndRepairSystemGroupName = "BuildAndRepairGroup1", AssemblerGroupName = "AssemblerGroup1" } }; */ /// /// No user changeable settings behind this point /// /// /// Kind of Display /// public enum DisplayKind { ShortStatus, Status, WeldTargets, GrindTargets, CollectTargets, MissingItems, BlockWeldPriority, BlockGrindPriority } static BuildAndRepairAutoQueuing _AutoQueuing; public Program() { Runtime.UpdateFrequency = UpdateFrequency.Update100; _AutoQueuing = new BuildAndRepairAutoQueuing(this); } void Main(string arg) { _AutoQueuing.Handle(); } /// /// Group configuration. /// Grouped Systems/Assembler could be defined either by list /// their Names (BuildAndRepairSystemNames\AssemblerNames) and or by giving /// a group name (BuildAndRepairSystemGroupName\AssemblerGroupName) /// public class BuildAndRepairSystemQueuingGroup { public string Name { get; set; } public string[] BuildAndRepairSystemNames { get; set; } public string BuildAndRepairSystemGroupName { get; set; } public string[] AssemblerNames { get; set; } public string AssemblerGroupName { get; set; } public DisplayDefinition[] Displays { get; set; } } /// /// Definition for muliple Displays /// public class DisplayDefinition { /// /// List of Displaynames /// public string[] DisplayNames { get; set; } /// /// You can the Display pages you need from enum DisplayKind. THey will be switch every DisplaySwitchTime seconds /// public DisplayKind[] DisplayKinds { get; set; } = new[] { DisplayKind.Status }; /// /// The maximum of lines that should be displayed in case of list items (Blocks to build, grind, missing, ..) /// public int DisplayMaxLines { get; set; } = 19; /// /// Autoswitch time [s] /// public double DisplaySwitchTime { get; set; } = 5; } /// /// Build and repair system automatic queuing of missing components /// public class BuildAndRepairAutoQueuing { private Program _Program; private bool _IsInit; private double _ElapsedTime; private double _ReInit; private double _NextUpdateAssemblerQueues; private double _NextUpdateGrinding; private BuildAndRepairSystemQueuingGroupData[] _GroupData; public string InitializationResultMessage { get; private set; } public BuildAndRepairAutoQueuing(Program program) { _Program = program; _ElapsedTime = 0; } /// /// Autorepair /// public void Handle() { _ElapsedTime += _Program.Runtime.TimeSinceLastRun.TotalSeconds; if (!_IsInit) { Initialize(); _ReInit = _ElapsedTime + 120; //Refresh every 2 Minutes _NextUpdateAssemblerQueues = _ElapsedTime - 1; //Next refesh now _NextUpdateGrinding = _NextUpdateAssemblerQueues; if (!string.IsNullOrWhiteSpace(InitializationResultMessage)) { _Program.Echo(InitializationResultMessage); } } if (_IsInit) { if (_ElapsedTime > _NextUpdateGrinding) { ScriptControlledGrinding(); _NextUpdateGrinding = _ElapsedTime + UpdateInvervalGrinding; } if (_ElapsedTime > _NextUpdateAssemblerQueues) { CheckAssemblerQueues(); _NextUpdateAssemblerQueues = _ElapsedTime + UpdateInvervalAssemblerQueues; } RefreshDisplays(); if (_ElapsedTime > _ReInit) { _IsInit = false; //Refresh } } } /// /// Initialize lists with blocks to manage /// private void Initialize() { _IsInit = false; InitializationResultMessage = string.Empty; _GroupData = new BuildAndRepairSystemQueuingGroupData[BuildAndRepairSystemQueuingGroups.Length]; var idx = 0; foreach (var queuingGroup in BuildAndRepairSystemQueuingGroups) { _GroupData[idx] = new BuildAndRepairSystemQueuingGroupData(queuingGroup.Displays.Length); _GroupData[idx].Settings = queuingGroup; _GroupData[idx].RepairSystems = InitHandler(queuingGroup); _GroupData[idx].Assemblers = InitAssemblerList(queuingGroup); _GroupData[idx].StatusDisplays = new List(); foreach (var displayDef in queuingGroup.Displays) { var statusDisplay = new StatusAndLogDisplay(_Program, string.IsNullOrEmpty(queuingGroup.Name) ? "BaR Group " + idx : queuingGroup.Name, displayDef.DisplayNames, null); _GroupData[idx].StatusDisplays.Add(statusDisplay); statusDisplay.Clear(); statusDisplay.UpdateDisplay(); } idx++; } _IsInit = true; return; } /// /// Init the group/list of items handler /// public T InitHandler(BuildAndRepairSystemQueuingGroup queuingGroup) where T : EntityHandler, new() { T handler = null; if (!string.IsNullOrWhiteSpace(queuingGroup.BuildAndRepairSystemGroupName)) { var group = _Program.GridTerminalSystem.GetBlockGroupWithName(queuingGroup.BuildAndRepairSystemGroupName); if (group != null) { handler = new T(); handler.Init(group); } } if (queuingGroup.BuildAndRepairSystemNames != null) { foreach (var name in queuingGroup.BuildAndRepairSystemNames) { if (!string.IsNullOrWhiteSpace(name)) { var entity = _Program.GridTerminalSystem.GetBlockWithName(name); if (entity != null) { if (handler == null) handler = new T(); handler.Init(entity); } } } } if (handler == null || handler.Count == 0) { InitializationResultMessage += string.Format("\nFatalError: Group Repairsystems group empty/wrong types!"); handler = null; } return handler; } /// /// Build list of assemblers /// /// public List InitAssemblerList(BuildAndRepairSystemQueuingGroup queuingGroup) { List assemblers = null; if (!string.IsNullOrWhiteSpace(queuingGroup.AssemblerGroupName)) { var group = _Program.GridTerminalSystem.GetBlockGroupWithName(queuingGroup.AssemblerGroupName); if (group != null) { assemblers = new List(); var entities = new List(); group.GetBlocksOfType(entities); foreach (var entity in entities) { assemblers.Add(entity.EntityId); } } } if (queuingGroup.AssemblerNames != null) { foreach (var name in queuingGroup.AssemblerNames) { if (!string.IsNullOrWhiteSpace(name)) { var entity = _Program.GridTerminalSystem.GetBlockWithName(name); if (entity != null && entity is IMyAssembler) { if (assemblers == null) assemblers = new List(); assemblers.Add(entity.EntityId); } } } } if (assemblers == null || assemblers.Count == 0) { InitializationResultMessage += string.Format("Warning: Group Assemblers group empty/wrong types!"); assemblers = null; } return assemblers; } private void RefreshDisplays() { foreach (var groupData in _GroupData) { groupData.RefreshDisplay(_ElapsedTime); } } /// /// This the basic algorithm and spread the items over the list of assemblers. /// private void CheckAssemblerQueues() { foreach (var groupData in _GroupData) { groupData.CheckAssemblerQueues(); } } /// /// Place your code here to handle specialized Grind handling /// private void ScriptControlledGrinding() { //Simple example of Script controlled grind handling //foreach (var groupData in _GroupData) //{ // groupData.RepairSystems.ScriptControlled = true; // var listGrindable = groupData.RepairSystems.PossibleGrindTargets(); // //If nothing to grind or current grinding object no longer in list (allready grinded) // if (groupData.RepairSystems.CurrentPickedGrindTarget == null || listGrindable.IndexOf(groupData.RepairSystems.CurrentPickedGrindTarget) < 0) // { // foreach (var entry in listGrindable) // { // var antenna = entry.FatBlock as IMyRadioAntenna; // if (antenna != null) // { // groupData.RepairSystems.CurrentPickedGrindTarget = entry; // break; // } // var reactor = entry.FatBlock as IMyReactor; // if (reactor != null) // { // groupData.RepairSystems.CurrentPickedGrindTarget = entry; // break; // } // var guns = entry.FatBlock as IMyUserControllableGun; // if (guns != null) // { // groupData.RepairSystems.CurrentPickedGrindTarget = entry; // break; // } // } // } //} } } /// /// /// public class BuildAndRepairSystemQueuingGroupData { public BuildAndRepairSystemQueuingGroup Settings { get; set; } public RepairSystemHandler RepairSystems { get; set; } public List Assemblers { get; set; } public List StatusDisplays { get; set; } private int[] DisplayKindIdx { get; set; } private double[] NextSwitchTime { get; set; } public BuildAndRepairSystemQueuingGroupData(int count) { DisplayKindIdx = new int[count]; NextSwitchTime = new double[count]; } /// /// /// public void CheckAssemblerQueues() { if (RepairSystems != null && Assemblers != null) { var missingItems = RepairSystems.MissingComponents(); foreach (var item in missingItems) { //Test -> //var blueprintDefinition = TryGetBlueprintDefinitionByResultId(materialId); //if (blueprintDefinition == null) return 0; //<- end test RepairSystems.EnsureQueued(Assemblers, item.Key, item.Value); } } } /// /// Refresh the status display /// public void RefreshDisplay(double elapsedTime) { for (var idx = 0; idx < StatusDisplays.Count; idx++) { var display = StatusDisplays[idx]; var settings = Settings.Displays[idx]; if (display != null && settings != null) { display.Clear(); if (settings.DisplayKinds != null && RepairSystems != null) { if (elapsedTime >= NextSwitchTime[idx]) { DisplayKindIdx[idx] = (DisplayKindIdx[idx] + 1) % settings.DisplayKinds.Length; NextSwitchTime[idx] = elapsedTime + settings.DisplaySwitchTime; } switch (settings.DisplayKinds[DisplayKindIdx[idx]]) { case DisplayKind.Status: DisplayStatus(settings, display); break; case DisplayKind.ShortStatus: DisplayShortStatus(settings, display); break; case DisplayKind.WeldTargets: DisplayWeldTargets(settings, display); break; case DisplayKind.GrindTargets: DisplayGrindTargets(settings, display); break; case DisplayKind.CollectTargets: DisplayCollectTargets(settings, display); break; case DisplayKind.MissingItems: DisplayMissingItems(settings, display); break; case DisplayKind.BlockWeldPriority: DisplayBlockWeldPriorityList(settings, display); break; case DisplayKind.BlockGrindPriority: DisplayBlockGrindPriorityList(settings, display); break; } display.UpdateDisplay(); } } } } /// /// Show the short status of the BaR-System /// private void DisplayShortStatus(DisplayDefinition settings, StatusAndLogDisplay display) { display.AddStatus(string.Format("Online : {0}", RepairSystems.CountOfWorking > 0)); display.AddStatus(string.Format("CurrentWelding : {0}", StatusAndLogDisplay.BlockName(RepairSystems.CurrentTarget))); var listB = RepairSystems.PossibleTargets(); display.AddStatus(string.Format("Blocks to weld : {0}", listB != null ? listB.Count : 0)); display.AddStatus(string.Format("CurrentGrinding : {0}", StatusAndLogDisplay.BlockName(RepairSystems.CurrentGrindTarget))); listB = RepairSystems.PossibleGrindTargets(); display.AddStatus(string.Format("Blocks to grind : {0}", listB != null ? listB.Count : 0)); var listF = RepairSystems.PossibleCollectTargets(); display.AddStatus(string.Format("Floating items : {0}", listF != null ? listF.Count : 0)); display.AddStatus(string.Format("Missing item kinds: {0}", StatusAndLogDisplay.BlockName(RepairSystems.MissingComponents().Count))); } /// /// Show the detailed status of the BaR-System /// private void DisplayStatus(DisplayDefinition settings, StatusAndLogDisplay display) { DisplayShortStatus(settings, display); display.AddStatus(string.Format("Search mode : {0}", StatusAndLogDisplay.BlockName(RepairSystems.SearchMode))); display.AddStatus(string.Format("Work mode : {0}", StatusAndLogDisplay.BlockName(RepairSystems.WorkMode))); display.AddStatus(string.Format("Build projected : {0}", StatusAndLogDisplay.BlockName(RepairSystems.AllowBuild))); display.AddStatus(string.Format("UseIgnoreColor : {0}", StatusAndLogDisplay.BlockName(RepairSystems.UseIgnoreColor))); display.AddStatus(string.Format("Script Controlled : {0}", RepairSystems.ScriptControlled)); } /// /// Show the List of blocks to weld /// private void DisplayWeldTargets(DisplayDefinition settings, StatusAndLogDisplay display) { var list = RepairSystems.PossibleTargets(); display.AddStatus(string.Format("Weld Targets: Count {0}", list != null ? list.Count : 0)); if (list == null) return; var iI = 2; foreach (var entry in list) { if (iI >= settings.DisplayMaxLines) { display.AddStatus(" .."); break; } display.AddStatus(string.Format(" {0}", StatusAndLogDisplay.BlockName(entry))); iI++; } } /// /// Show the List of blocks to grind /// private void DisplayGrindTargets(DisplayDefinition settings, StatusAndLogDisplay display) { var list = RepairSystems.PossibleGrindTargets(); display.AddStatus(string.Format("Grind Targets: Count {0}", list != null ? list.Count : 0)); if (list == null) return; var iI = 2; foreach (var entry in list) { if (iI >= settings.DisplayMaxLines) { display.AddStatus(" .."); break; } display.AddStatus(string.Format(" {0}", StatusAndLogDisplay.BlockName(entry))); iI++; } } /// /// Show the List of collectable floating objects /// private void DisplayCollectTargets(DisplayDefinition settings, StatusAndLogDisplay display) { var list = RepairSystems.PossibleCollectTargets(); display.AddStatus(string.Format("Collect Targets: Count {0}", list != null ? list.Count : 0)); if (list == null) return; var iI = 2; foreach (var entry in list) { if (iI >= settings.DisplayMaxLines) { display.AddStatus(" .."); break; } display.AddStatus(string.Format(" {0}", StatusAndLogDisplay.BlockName(entry))); iI++; } } /// /// Show the List of missing materials /// private void DisplayMissingItems(DisplayDefinition settings, StatusAndLogDisplay display) { var list = RepairSystems.MissingComponents(); display.AddStatus(string.Format("Missing Items: Count {0}", list != null ? list.Count : 0)); if (list == null) return; var iI = 2; foreach (var entry in list) { if (iI >= settings.DisplayMaxLines) { display.AddStatus(" .."); break; } display.AddStatus(string.Format(" {0}: Amount={1}", entry.Key.SubtypeName, entry.Value)); iI++; } } /// /// Show the List of block classes and there enabled state /// private void DisplayBlockWeldPriorityList(DisplayDefinition settings, StatusAndLogDisplay display) { display.AddStatus("Weld Priority:"); var list = RepairSystems.WeldPriorityList(); foreach (var entry in list) { display.AddStatus(string.Format(" {0}/{1}", entry.ItemClass, entry.Enabled)); } } /// /// Show the List of block classes and there enabled state /// private void DisplayBlockGrindPriorityList(DisplayDefinition settings, StatusAndLogDisplay display) { display.AddStatus("Grind Priority:"); var list = RepairSystems.GrindPriorityList(); foreach (var entry in list) { display.AddStatus(string.Format(" {0}/{1}", entry.ItemClass, entry.Enabled)); } } /// /// Show the List of component classes and there enabled state /// private void DisplayComponentClassesList(DisplayDefinition settings, StatusAndLogDisplay display) { display.AddStatus("ComponentClassList:"); var list = RepairSystems.ComponentClassList(); foreach (var entry in list) { display.AddStatus(string.Format(" {0}/{1}", entry.ItemClass, entry.Enabled)); } } } /// /// Class to handle the RepairSystems /// public class RepairSystemHandler : EntityHandler { private Func, VRage.Game.MyDefinitionId, int, int> _EnsureQueued; private Func, int> _NeededComponents4Blueprint; /// /// The block clases the system distinguish /// public enum BlockClass { AutoRepairSystem = 1, ShipController, Thruster, Gyroscope, CargoContainer, Conveyor, ControllableGun, PowerBlock, ProgrammableBlock, Projector, FunctionalBlock, ProductionBlock, Door, ArmorBlock } /// /// The componet classes the system distinguish /// public enum ComponentClass { Material = 1, Ingot, Ore, Stone, Gravel } /// /// The search modes supported by the block /// public enum SearchModes { Grids = 0x0001, BoundingBox = 0x0002 } /// /// The work modes supported by the block /// public enum WorkModes { /// /// Grind only if nothing to weld /// WeldBeforeGrind = 0x0001, /// /// Weld onyl if nothing to grind /// GrindBeforeWeld = 0x0002, /// /// Grind only if nothing to weld or /// build waiting for missing items /// GrindIfWeldGetStuck = 0x0004, /// /// Only welding is allowed /// WeldOnly = 0x0008, /// /// Only grinding is allowed /// GrindOnly = 0x0010 } /// /// Block/Component class and it's state /// public class ClassState where T : struct { public T ItemClass { get; } public bool Enabled { get; } public ClassState(T itemClass, bool enabled) { ItemClass = itemClass; Enabled = enabled; } } /// /// Set the Help Others state /// public bool HelpOther { get { return _Entities.Count > 0 ? _Entities[0].HelpOthers : false; } set { foreach (var entity in _Entities) entity.HelpOthers = value; } } /// /// Set AllowBuild (projected blocks) /// public bool AllowBuild { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.AllowBuild") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.AllowBuild", value); } } /// /// Set the search mode of the block /// public SearchModes SearchMode { get { return _Entities.Count > 0 ? (SearchModes)_Entities[0].GetValue("BuildAndRepair.Mode") : SearchModes.Grids; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.Mode", (long)value); } } /// /// Set the search mode of the block /// public WorkModes WorkMode { get { return _Entities.Count > 0 ? (WorkModes)_Entities[0].GetValue("BuildAndRepair.WorkMode") : WorkModes.WeldBeforeGrind; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.WorkMode", (long)value); } } /// /// Enable/Disable the use of the Ignore Color /// If enabled block's with with color 'IgnoreColor' /// will be ignored. /// You could use this do have intentionally unweldet block's /// and still use autorepair of the rest. /// public bool UseIgnoreColor { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.UseIgnoreColor") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.UseIgnoreColor", value); } } /// /// Set the ignore color /// X=Hue 0 .. 1 -> * 360 -> Displayed value /// Y=Saturation -1 .. 1 -> * 100 -> Displayed value /// Z=Value -1 .. 1 -> * 100 -> Displayed value /// public Vector3 IgnoreColor { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.IgnoreColor") : Vector3.Zero; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.IgnoreColor", value); } } /// /// Enable/Disable the use of the Grind Color /// If enabled block's with with color 'GrindColor' /// will be grinded. /// public bool UseGrindColor { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.UseGrindColor") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.UseGrindColor", value); } } /// /// Set the grind color /// X=Hue 0 .. 1 -> * 360 -> Displayed value /// Y=Saturation -1 .. 1 -> * 100 -> Displayed value /// Z=Value -1 .. 1 -> * 100 -> Displayed value /// public Vector3 GrindColor { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.GrindColor") : Vector3.Zero; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.GrindColor", value); } } /// /// If set autogrind enemy blocks in range /// public bool GrindJanitorEnemies { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.GrindJanitorEnemies") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.GrindJanitorEnemies", value); } } /// /// If set autogrind not owned blocks in range /// public bool GrindJanitorNotOwned { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.GrindJanitorNotOwned") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.GrindJanitorNotOwned", value); } } /// /// If set autogrind blocks owned by neutrals in range /// public bool GrindJanitorNeutrals { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.GrindJanitorNeutrals") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.GrindJanitorNeutrals", value); } } /// /// If set autogrind grinds blocks only down to the 'Out of order' level /// public bool GrindJanitorOptionDisableOnly { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.GrindJanitorOptionDisableOnly") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.GrindJanitorOptionDisableOnly", value); } } /// /// If set autogrind grinds blocks only down to the 'Hack' level /// public bool GrindJanitorOptionHackOnly { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.GrindJanitorOptionHackOnly") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.GrindJanitorOptionHackOnly", value); } } /// /// If set block are only weldet to functional level /// public bool WeldOptionFunctionalOnly { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.WeldOptionFunctionalOnly") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.WeldOptionFunctionalOnly", value); } } /// /// Set the with of the working area /// public float AreaWidth { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaWidth") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaWidth", value); } } /// /// Set the left/right offset of the working area from block center /// public float AreaOffsetLeftRight { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaOffsetLeftRight") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaOffsetLeftRight", value); } } /// /// Set the height of the working area /// public float AreaHeight { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaHeight") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaHeight", value); } } /// /// Set the up/down offset of the working area from block center /// public float AreaOffsetUpDown { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaOffsetUpDown") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaOffsetUpDown", value); } } /// /// Set the depth of the working area /// public float AreaDepth { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaDepth") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaDepth", value); } } /// /// Set the depth of the working area /// public float AreaOffsetFrontBack { get { return _Entities.Count > 0 ? _Entities[0].GetValueFloat("BuildAndRepair.AreaOffsetFrontBack") : 0; } set { foreach (var entity in _Entities) entity.SetValueFloat("BuildAndRepair.AreaOffsetFrontBack", value); } } /// /// Get a list with all known block classes and there /// weld enabled state in descending order of priority. /// public List> WeldPriorityList() { if (_Entities.Count > 0) { var list = _Entities[0].GetValue>("BuildAndRepair.WeldPriorityList"); var blockList = new List>(); foreach (var item in list) { var values = item.Split(';'); BlockClass blockClass; bool enabled; if (Enum.TryParse(values[0], out blockClass) && bool.TryParse(values[1], out enabled)) { blockList.Add(new ClassState(blockClass, enabled)); } } return blockList; } return null; } /// /// Get the weld priority of the given block class /// public int GetWeldPriority(BlockClass blockClass) { if (_Entities.Count > 0) { var getPriority = _Entities[0].GetValue>("BuildAndRepair.GetWeldPriority"); return getPriority((int)blockClass); } else return int.MaxValue; } /// /// Set the weld priority of the given block class /// (lower number higher priority) /// public void SetWeldPriority(BlockClass blockClass, int prio) { foreach (var entity in _Entities) { var setPriority = entity.GetValue>("BuildAndRepair.SetWeldPriority"); setPriority((int)blockClass, prio); } } /// /// Get the weld enabled state of the given block class /// Enabled=True Block of that class will be repaired/build /// Enabled=False Block's of that class will be ignored /// public bool GetWeldEnabled(BlockClass blockClass) { if (_Entities.Count > 0) { var getEnabled = _Entities[0].GetValue>("BuildAndRepair.GetWeldEnabled"); return getEnabled((int)blockClass); } else return false; } /// /// Set the weld enabled state of the given block class /// (see GetEnabled) /// public void SetWeldEnabled(BlockClass blockClass, bool enabled) { foreach (var entity in _Entities) { var setEnabled = entity.GetValue>("BuildAndRepair.SetWeldEnabled"); setEnabled((int)blockClass, enabled); } } /// /// Get a list with all known block classes and there /// grind enabled state in descending order of priority. /// public List> GrindPriorityList() { if (_Entities.Count > 0) { var list = _Entities[0].GetValue>("BuildAndRepair.GrindPriorityList"); var blockList = new List>(); foreach (var item in list) { var values = item.Split(';'); BlockClass blockClass; bool enabled; if (Enum.TryParse(values[0], out blockClass) && bool.TryParse(values[1], out enabled)) { blockList.Add(new ClassState(blockClass, enabled)); } } return blockList; } return null; } /// /// Get the grind priority of the given block class /// public int GetGrindPriority(BlockClass blockClass) { if (_Entities.Count > 0) { var getPriority = _Entities[0].GetValue>("BuildAndRepair.GetGrindPriority"); return getPriority((int)blockClass); } else return int.MaxValue; } /// /// Set the grind priority of the given block class /// (lower number higher priority) /// public void SetGrindPriority(BlockClass blockClass, int prio) { foreach (var entity in _Entities) { var setPriority = entity.GetValue>("BuildAndRepair.SetGrindPriority"); setPriority((int)blockClass, prio); } } /// /// Get the grind enabled state of the given block class /// Enabled=True Block of that class will be grinded /// Enabled=False Block's of that class will be ignored /// public bool GetGrindEnabled(BlockClass blockClass) { if (_Entities.Count > 0) { var getEnabled = _Entities[0].GetValue>("BuildAndRepair.GetGrindEnabled"); return getEnabled((int)blockClass); } else return false; } /// /// Set the grind enabled state of the given block class /// (see GetEnabled) /// public void SetGrindEnabled(BlockClass blockClass, bool enabled) { foreach (var entity in _Entities) { var setEnabled = entity.GetValue>("BuildAndRepair.SetGrindEnabled"); setEnabled((int)blockClass, enabled); } } /// /// Get a list with all known component classes and there /// enabeld state in descending order of priority. /// public List> ComponentClassList() { if (_Entities.Count > 0) { var list = _Entities[0].GetValue>("BuildAndRepair.ComponentClassList"); var compList = new List>(); foreach (var item in list) { var values = item.Split(';'); ComponentClass compClass; bool enabled; if (Enum.TryParse(values[0], out compClass) && bool.TryParse(values[1], out enabled)) { compList.Add(new ClassState(compClass, enabled)); } } return compList; } return null; } /// /// Get the priority of the given component class /// public int GetCollectPriority(ComponentClass compClass) { if (_Entities.Count > 0) { var getPriority = _Entities[0].GetValue>("BuildAndRepair.GetCollectPriority"); return getPriority((int)compClass); } else return int.MaxValue; } /// /// Set the priority of the given component class /// (lower number higher priority) /// public void SetCollectPriority(ComponentClass compClass, int prio) { foreach (var entity in _Entities) { var setPriority = entity.GetValue>("BuildAndRepair.SetCollectPriority"); setPriority((int)compClass, prio); } } /// /// Get the enabled state of the given component class /// Enabled=True Component of that class will be collected /// Enabled=False Component's of that class will be ignored /// public bool GetCollectEnabled(ComponentClass compClass) { if (_Entities.Count > 0) { var getEnabled = _Entities[0].GetValue>("BuildAndRepair.GetCollectEnabled"); return getEnabled((int)compClass); } else return false; } /// /// Set the enabled state of the given component class /// (see GetEnabled) /// public void SetCollectEnabled(ComponentClass compClass, bool enabled) { foreach (var entity in _Entities) { var setEnabled = entity.GetValue>("BuildAndRepair.SetCollectEnabled"); setEnabled((int)compClass, enabled); } } /// /// Set if the Block should only collect floating items (ore/ingot/material) /// if nothing else to do (no welding, no grinding, no material for welding) /// public bool CollectIfIdle { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.CollectIfIdle") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.CollectIfIdle", value); } } /// /// Set if the Block should push all ore/ingot imemediately out of its inventory, /// else this will happen only if no more room to store the next items to be picked. /// public bool PushIngotOreImmediately { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.PushIngotOreImmediately") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.PushIngotOreImmediately", value); } } /// /// Get the block that is currently being repaired/build. /// public IMySlimBlock CurrentTarget { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.CurrentTarget") : null; } } /// /// Get the block that is currently being grinded. /// public IMySlimBlock CurrentGrindTarget { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.CurrentGrindTarget") : null; } } /// /// Set if the Block if controlled by script. /// (If controlled by script use PossibleTargets and CurrentPickedTarget /// to set the block that should be build/repaired) /// public bool ScriptControlled { get { return _Entities.Count > 0 ? _Entities[0].GetValueBool("BuildAndRepair.ScriptControlled") : false; } set { foreach (var entity in _Entities) entity.SetValueBool("BuildAndRepair.ScriptControlled", value); } } /// /// Get a list of missing components. /// public Dictionary MissingComponents() { var missingItems = new Dictionary(); foreach (var entity in _Entities) { var dict = entity.GetValue>("BuildAndRepair.MissingComponents"); //Merge dictionaries but only first report of an item or higher amount //(do not add up the missings, as overlapping systems report same missing items) if (dict != null && dict.Count > 0) { int value; foreach (var newItem in dict) { if (missingItems.TryGetValue(newItem.Key, out value)) { if (newItem.Value > value) missingItems[newItem.Key] = newItem.Value; } else { missingItems.Add(newItem.Key, newItem.Value); } } } } return missingItems; } /// /// Get a list of possible repair/build targets. /// (Contains only damaged/deformed/new block's in range of the system) /// public MemorySafeList PossibleTargets() { if (_Entities.Count > 0) { return new MemorySafeList(_Entities[0].GetValue>("BuildAndRepair.PossibleTargets")); } return null; } /// /// Get the Block that should currently repaired/build. /// In order to build the given block the property 'ScriptControlled' has to be true and /// the block has to be in the list of 'PossibleTargets'. /// If 'ScriptControlled' is true and the block is not in the 'PossibleTargets' /// the system will do nothing. /// public IMySlimBlock CurrentPickedTarget { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.CurrentPickedTarget") : null; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.CurrentPickedTarget", value); } } /// /// Get a list of possible grind targets. /// public MemorySafeList PossibleGrindTargets() { if (_Entities.Count > 0) { return new MemorySafeList(_Entities[0].GetValue>("BuildAndRepair.PossibleGrindTargets")); } return null; } /// /// Get the Block that should currently Grinded. /// In order to grind the given block the property 'ScriptControlled' has to be true and /// the block has to be in the list of 'PossibleGrindTargets'. /// If 'ScriptControlled' is true and the block is not in the 'PossibleGrindTargets' /// the system will do nothing. /// public IMySlimBlock CurrentPickedGrindTarget { get { return _Entities.Count > 0 ? _Entities[0].GetValue("BuildAndRepair.CurrentPickedGrindTarget") : null; } set { foreach (var entity in _Entities) entity.SetValue("BuildAndRepair.CurrentPickedGrindTarget", value); } } /// /// Get a list of possible grind targets. /// public MemorySafeList PossibleCollectTargets() { if (_Entities.Count > 0) { return new MemorySafeList(_Entities[0].GetValue>("BuildAndRepair.PossibleCollectTargets")); } return null; } /// /// Ensures that the given amount is either in inventory or the production /// queue of the given production blocks /// public int EnsureQueued(IEnumerable productionBlockIds, VRage.Game.MyDefinitionId materialId, int amount) { if (_Entities.Count > 0) { if (_EnsureQueued == null) { _EnsureQueued = _Entities[0].GetValue, VRage.Game.MyDefinitionId, int, int>>("BuildAndRepair.ProductionBlock.EnsureQueued"); } if (_EnsureQueued != null) { return _EnsureQueued(productionBlockIds, materialId, amount); } return -3; } return -2; } /// /// Retrieve the total components amount needed to build the projected /// blueprint /// /// /// /// public int NeededComponents4Blueprint(IMyProjector projector, Dictionary componentList) { if (_Entities.Count > 0) { if (_NeededComponents4Blueprint == null) { _NeededComponents4Blueprint = _Entities[0].GetValue, int>>("BuildAndRepair.Inventory.NeededComponents4Blueprint"); } if (_NeededComponents4Blueprint != null) { return _NeededComponents4Blueprint(projector, componentList); } return -3; } return -2; } } /// /// Class to handle Entities /// public class EntityHandler : EntityHandler where T : class, IMyTerminalBlock { protected readonly List _Entities = new List(); protected readonly HashSet _DefinitionIdsInclude = new HashSet(); protected readonly HashSet _DefinitionIdsExclude = new HashSet(); /// /// /// public IEnumerable Entities { get { return _Entities; } } public HashSet DefinitionIdsInclude { get { return _DefinitionIdsInclude; } } public HashSet DefinitionIdsExclude { get { return _DefinitionIdsExclude; } } /// /// /// public bool AreEnabled { get; private set; } /// /// Count of Working Enties (on and functional) /// public int CountOfWorking { get { var res = 0; foreach (var entity in _Entities) if (entity.IsWorking && entity.IsFunctional) res++; return res; } } /// /// Get total count /// protected override int GetCount() { return _Entities.Count; } /// /// Load entities from group /// public override void Init(IMyBlockGroup group, bool add = false) { if (!add) _Entities.Clear(); var entities = new List(); group.GetBlocksOfType(entities); foreach (var entity in entities) { AddEntity(entity); } CheckEnabled(); } /// /// Load entity by name /// /// /// public override void Init(VRage.Game.ModAPI.Ingame.IMyEntity newEntity, bool add = false) { if (!add) _Entities.Clear(); var entity = newEntity as T; if (AddEntity(entity)) { CheckEnabled(); } } /// /// Load entity filtered by given collect function /// /// /// public void Init(IMyGridTerminalSystem gridTerminalSystem, Func collect = null, bool add = false) { if (!add) _Entities.Clear(); if (gridTerminalSystem != null) { var entities = new List(); gridTerminalSystem.GetBlocksOfType(entities, collect); foreach (var entity in entities) { AddEntity(entity); } CheckEnabled(); } } /// /// Starten/Stoppen /// protected virtual bool AddEntity(T entity) { if (entity == null || _Entities.IndexOf(entity) >= 0) return false; var newDefId = entity.BlockDefinition; var allowed = DefinitionIdsInclude.Count <= 0; foreach (var defId in DefinitionIdsInclude) { if (defId.TypeId == newDefId.TypeId && (string.IsNullOrEmpty(defId.SubtypeName) || defId.SubtypeName.Equals(newDefId.SubtypeName))) { allowed = true; break; } } if (!allowed) return false; foreach (var defId in DefinitionIdsExclude) { if (defId.TypeId == newDefId.TypeId && (string.IsNullOrEmpty(defId.SubtypeName) || defId.SubtypeName.Equals(newDefId.SubtypeName))) { return false; } } _Entities.Add(entity); return true; } /// /// Starten/Stoppen /// public void Enabled(bool enabled) { foreach (var entity in _Entities) { var funcBlock = entity as IMyFunctionalBlock; if (funcBlock != null && funcBlock.Enabled != enabled) funcBlock.Enabled = enabled; } AreEnabled = enabled; } /// /// /// private void CheckEnabled() { foreach (var entity in _Entities) { if (entity.IsWorking && entity.IsFunctional) { AreEnabled = true; break; } } } } /// /// /// public abstract class EntityHandler { public int Count { get { return GetCount(); } } public abstract void Init(IMyBlockGroup group, bool add = false); public abstract void Init(VRage.Game.ModAPI.Ingame.IMyEntity entity, bool add = false); protected abstract int GetCount(); /// /// /// public static string GetCustomData(string customData, string startTag, string endTag) { var start = customData.IndexOf(startTag); var end = customData.LastIndexOf(endTag); if (start < 0 || end < 0 || end < start) return null; return customData.Substring(start + startTag.Length, end - start - startTag.Length); } /// /// /// public static string GetCustomValue(string customData, string name) { var tag = "<" + name + "="; var start = customData.IndexOf(tag); if (start < 0) return null; var end = customData.IndexOf("/>", start + tag.Length); if (end < 0) return null; return customData.Substring(start + tag.Length, end - start - tag.Length); } } /// /// Status and Log functions /// public class StatusAndLogDisplay { private readonly MyGridProgram _Program; private readonly List _StatusPanels = new List(); private readonly List _LogPanels = new List(); private readonly string _AIName; private string _LogText = ""; private string _StatusText = ""; private string _ErrorText = ""; private int _RefreshDelay; private readonly string[] _LcdStatusPanels; private readonly string[] _LcdLogPanels; /// /// Count of Lines in Log Display /// public int MaxLogLines { get; set; } public bool ShowHeader { get; set; } public StatusAndLogDisplay(MyGridProgram caller, string name, string[] lcdStatusPanels, string[] lcdLogPanels) { ShowHeader = true; _Program = caller; _AIName = name; _LcdStatusPanels = lcdStatusPanels; _LcdLogPanels = lcdLogPanels; MaxLogLines = 20; //Default ReloadDisplays(); } /// /// Reload the displayes (after renaming, adding) /// public string ReloadDisplays() { var res = FindPanels(_Program, _LcdStatusPanels, _StatusPanels); res += FindPanels(_Program, _LcdLogPanels, _LogPanels); return res; } /// /// Cyclic tries to reload the DisplayPanels (so the LCD could be added dynamically) /// public void CyclicReloadDisplays() { _RefreshDelay--; if (_RefreshDelay > 0) return; ReloadDisplays(); _RefreshDelay = 20; } /// /// Write a Log text /// public void Log(string msg) { var useHadline = !string.IsNullOrEmpty(_AIName); var maxlines = MaxLogLines + (useHadline ? 0 : 1); if (!string.IsNullOrEmpty(msg)) { _LogText += "\n" + msg; var lines = _LogText.Split('\n'); if (lines.Length >= maxlines) { _LogText = ""; for (var a = maxlines; a > 0; a--) _LogText += "\n" + lines[lines.Length - a]; } } } /// /// Clears Status und Error /// public void Clear() { _StatusText = ""; _ErrorText = ""; } /// /// /// internal void AddStatus(string line) { _StatusText += line + "\n"; } /// /// /// internal void AddError(string line) { _ErrorText = line + "\n"; } /// /// Write Status, Error, Log to the configured panels /// public void UpdateDisplay() { var text = string.Empty; if (ShowHeader) text = _AIName + " (" + DateTime.Now + "):\n"; if (_ErrorText.Length > 0) text += _ErrorText; text += _StatusText; foreach (var panel in _StatusPanels) SetPanelText(panel, text); _Program.Echo(!string.IsNullOrEmpty(_ErrorText) ? _ErrorText : text); text = !string.IsNullOrEmpty(_AIName) ? _AIName + _LogText : _LogText; foreach (var panel in _LogPanels) SetPanelText(panel, text); } /// /// Finds TextPanels with the given names /// private static string FindPanels(MyGridProgram caller, IReadOnlyList names, ICollection list) { string res = string.Empty; if (names != null && names.Count > 0) { foreach (var name in names) { string blockName; int index; GetNameAndIndex(name, out blockName, out index); var block = caller.GridTerminalSystem.GetBlockWithName(blockName); if (block == null) { res += string.Format("LCD {0} not found\n", blockName); continue; } var textSurface = block as IMyTextSurface; if (textSurface != null) { list.Add(textSurface); continue; } var textSurfaceProvider = block as IMyTextSurfaceProvider; if (textSurfaceProvider != null) { if (textSurfaceProvider.SurfaceCount > index) { list.Add(textSurfaceProvider.GetSurface(index)); continue; } res += string.Format("LCD {0} index {1} out of range (allowed 0..{2})\n", blockName, index, textSurfaceProvider.SurfaceCount - 1); continue; } res += string.Format("{0} is not an LCD.\n", blockName); } } if (!string.IsNullOrEmpty(res)) caller.Echo(res); return res; } private static void GetNameAndIndex(string name, out string blockName, out int index) { index = 0; var idxStart = name.LastIndexOf('['); if (idxStart >= 0) { var idxEnd = name.LastIndexOf(']'); if (idxEnd >= 0 && idxEnd > idxStart) { if (int.TryParse(name.Substring(idxStart + 1, idxEnd - idxStart - 1), out index)) { blockName = name.Substring(0, idxStart); } else blockName = name; } else blockName = name; } else blockName = name; } /// /// Sets panel text if its title is either default or our name. /// public static void SetPanelText(IMyTextSurface panel, string text) { panel.ContentType = ContentType.TEXT_AND_IMAGE; panel.WriteText(text, false); } /// /// Convert displayed values (Terminal) with correct units -> MW /// public static float PowerUnitMultiple(string unit) { if (unit.StartsWith("W")) return 0.000001f; if (unit.StartsWith("kW")) return 0.001f; if (unit.StartsWith("MW")) return 1f; return unit.StartsWith("GW") ? 1000f : 1f; } /// /// /// public static string DisplayPowerValueUnit(float value) { if (Math.Abs(value) < 0.001) return Math.Round(value * 1000000f) + "W"; if (Math.Abs(value) < 1) return Math.Round(value * 1000f) + "kW"; if (Math.Abs(value) < 1000) return Math.Round(value) + "MW"; return Math.Round(value / 1000f) + "GW"; } /// /// /// public static string DisplayPowerRate(float current, float max, string ext = "") { return string.Format("{0:0.00}% {1}{3}/{2}{3}", max > 0 ? current * 100 / max : 0, DisplayPowerValueUnit(current), DisplayPowerValueUnit(max), ext); } /// /// /// /// /// public static double ToDegree(double rad) { return rad * 180 / Math.PI; } /// /// Get Name of Block /// /// /// public static string BlockName(object block, bool includeGrid = false) { var inventory = block as IMyInventory; if (inventory != null) { block = inventory.Owner; } var slimBlock = block as IMySlimBlock; if (slimBlock != null) { if (slimBlock.FatBlock != null) block = slimBlock.FatBlock; else { if (includeGrid) return string.Format("{0}.{1}", slimBlock.CubeGrid != null ? slimBlock.CubeGrid.DisplayName : "Unknown Grid", slimBlock.BlockDefinition.SubtypeName); return string.Format("{0}", slimBlock.BlockDefinition.SubtypeName); } } var terminalBlock = block as IMyTerminalBlock; if (terminalBlock != null) { if (includeGrid) return string.Format("{0}.{1}", terminalBlock.CubeGrid != null ? terminalBlock.CubeGrid.DisplayName : "Unknown Grid", terminalBlock.CustomName); return string.Format("{0}", terminalBlock.CustomName); } var cubeBlock = block as IMyCubeBlock; if (cubeBlock != null) { if (includeGrid) return string.Format("{0} [{1}/{2}]", cubeBlock.CubeGrid != null ? cubeBlock.CubeGrid.DisplayName : "Unknown Grid", cubeBlock.BlockDefinition.TypeIdString, cubeBlock.BlockDefinition.SubtypeName); return string.Format("[{0}/{1}]", cubeBlock.BlockDefinition.TypeIdString, cubeBlock.BlockDefinition.SubtypeName); } var entity = block as IMyEntity; if (entity != null) { return string.Format("{0} ({1})", entity.DisplayName, entity.EntityId); } var cubeGrid = block as IMyCubeGrid; if (cubeGrid != null) return cubeGrid.DisplayName; return block != null ? block.ToString() : "NULL"; } }