diff --git a/Advanced Gravity Collector/Data/Blocks.sbc b/Advanced Gravity Collector/Data/Blocks.sbc index 8ff4eb16..af22d20e 100644 --- a/Advanced Gravity Collector/Data/Blocks.sbc +++ b/Advanced Gravity Collector/Data/Blocks.sbc @@ -99,6 +99,7 @@ Z Y 212 + 200 \ No newline at end of file diff --git a/Advanced Gravity Collector/Data/Categories.sbc b/Advanced Gravity Collector/Data/Categories.sbc index 2ba1e2b0..4b1ae50e 100644 --- a/Advanced Gravity Collector/Data/Categories.sbc +++ b/Advanced Gravity Collector/Data/Categories.sbc @@ -7,7 +7,7 @@ DisplayName_Category_LargeBlocks - LargeBlocks + Section1_Position1_LargeBlocks Collector/LargeGravityCollector @@ -18,7 +18,7 @@ DisplayName_Category_SmallBlocks - SmallBlocks + Section1_Position1_SmallBlocks Collector/MediumGravityCollector @@ -28,10 +28,11 @@ GuiBlockCategoryDefinition - DisplayName_Category_ConveyorBlocks - Conveyors + DisplayName_Category_Logistics + Section1_Position3_Logistics - GravityCollector + Collector/LargeGravityCollector + Collector/MediumGravityCollector diff --git a/Advanced Gravity Collector/Data/Scripts/GravityCollector/GravityCollector.cs b/Advanced Gravity Collector/Data/Scripts/GravityCollector/GravityCollector.cs index c7744da7..9397d460 100644 --- a/Advanced Gravity Collector/Data/Scripts/GravityCollector/GravityCollector.cs +++ b/Advanced Gravity Collector/Data/Scripts/GravityCollector/GravityCollector.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Sandbox.Common.ObjectBuilders; using Sandbox.Definitions; @@ -9,54 +10,58 @@ using Sandbox.ModAPI.Interfaces.Terminal; using VRage.Game; using VRage.Game.Components; +using VRage.Game.Entity; using VRage.Game.ModAPI; using VRage.ModAPI; using VRage.ObjectBuilders; using VRage.Utils; using VRageMath; -namespace Digi.GravityCollector -{ + +namespace Digi.GravityCollector { [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Collector), false, "MediumGravityCollector", "LargeGravityCollector")] - public class GravityCollector : MyGameLogicComponent - { + public class GravityCollector : MyGameLogicComponent { public const float RANGE_MIN = 0; - public const float RANGE_MAX_MEDIUM = 40; - public const float RANGE_MAX_LARGE = 60; + public const float RANGE_MAX_MEDIUM = 400; + public const float RANGE_MAX_LARGE = 600; public const float RANGE_OFF_EXCLUSIVE = 1; - public const float STRENGTH_MIN = 1; public const float STRENGTH_MAX = 200; - - public const int APPLY_FORCE_SKIP_TICKS = 3; // how many ticks between applying forces to floating objects - public const double MAX_VIEW_RANGE_SQ = 500 * 500; // max distance that the cone and pulsing item sprites can be seen from, squared value. - - public const float MASS_MUL = 10; // multiply item mass to get force - public const float MAX_MASS = 5000; // max mass to multiply - + public const int APPLY_FORCE_SKIP_TICKS = 3; + public const double MAX_VIEW_RANGE_SQ = 500 * 500; + public const float MASS_MUL = 10; + public const float MAX_MASS = 5000; public const string CONTROLS_PREFIX = "GravityCollector."; public readonly Guid SETTINGS_GUID = new Guid("0DFC6F70-310D-4D1C-A55F-C57913E20389"); - public const int SETTINGS_CHANGED_COUNTDOWN = (60 * 1) / 10; // div by 10 because it runs in update10 + public const int SETTINGS_CHANGED_COUNTDOWN = (60 * 1) / 10; + private AdvancedCollectionSystem collectionSystem; + public float adaptiveForceMultiplier = 1.0f; + public double ConeAngle => coneAngle; + public float Offset => offset; + public bool IsInCollectionCone(Vector3D position) + { + var conePos = block.WorldMatrix.Translation + (block.WorldMatrix.Forward * -offset); + var dirNormalized = Vector3D.Normalize(position - conePos); + var angle = Math.Acos(MathHelper.Clamp(Vector3D.Dot(block.WorldMatrix.Forward, dirNormalized), -1, 1)); + return angle <= coneAngle; + } public float Range { get { return Settings.Range; } set { Settings.Range = MathHelper.Clamp((int)Math.Floor(value), RANGE_MIN, maxRange); - SettingsChanged(); - - if(Settings.Range < RANGE_OFF_EXCLUSIVE) + if (Settings.Range < RANGE_OFF_EXCLUSIVE) { NeedsUpdate = MyEntityUpdateEnum.NONE; } else { - if((NeedsUpdate & MyEntityUpdateEnum.EACH_10TH_FRAME) == 0) + if ((NeedsUpdate & MyEntityUpdateEnum.EACH_10TH_FRAME) == 0) NeedsUpdate |= MyEntityUpdateEnum.EACH_10TH_FRAME; } - block?.Components?.Get()?.Update(); } } @@ -67,38 +72,35 @@ public float StrengthMul set { Settings.Strength = MathHelper.Clamp(value, STRENGTH_MIN / 100f, STRENGTH_MAX / 100f); - SettingsChanged(); - block?.Components?.Get()?.Update(); } } IMyCollector block; MyPoweredCargoContainerDefinition blockDef; - public readonly GravityCollectorBlockSettings Settings = new GravityCollectorBlockSettings(); int syncCountdown; - double coneAngle; float offset; float maxRange; - int skipTicks; List floatingObjects; - GravityCollectorMod Mod => GravityCollectorMod.Instance; + public CollectorNetwork network; bool DrawCone { get { - if(MyAPIGateway.Utilities.IsDedicated || !block.ShowOnHUD) + if (MyAPIGateway.Utilities.IsDedicated || !block.ShowOnHUD) return false; - - var relation = block.GetPlayerRelationToOwner(); - - return (relation != MyRelationsBetweenPlayerAndBlock.Enemies); + IMyPlayer localPlayer = MyAPIGateway.Session?.Player; + if (localPlayer == null) + return false; + long localPlayerId = localPlayer.IdentityId; + MyRelationsBetweenPlayerAndBlock relation = block.GetUserRelationToOwner(localPlayerId); + return relation != MyRelationsBetweenPlayerAndBlock.Enemies; } } @@ -112,17 +114,14 @@ public override void UpdateOnceBeforeFrame() try { SetupTerminalControls(); - block = (IMyCollector)Entity; - - if(block.CubeGrid?.Physics == null) + if (block.CubeGrid?.Physics == null) return; - blockDef = (MyPoweredCargoContainerDefinition)block.SlimBlock.BlockDefinition; - floatingObjects = new List(); + collectionSystem = new AdvancedCollectionSystem(this); - switch(block.BlockDefinition.SubtypeId) + switch (block.BlockDefinition.SubtypeId) { case "MediumGravityCollector": maxRange = RANGE_MAX_MEDIUM; @@ -137,23 +136,21 @@ public override void UpdateOnceBeforeFrame() } NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME | MyEntityUpdateEnum.EACH_10TH_FRAME; - - // override block's power usage behavior - var sink = block.Components?.Get(); + MyResourceSinkComponent sink = block.Components?.Get(); sink?.SetRequiredInputFuncByType(MyResourceDistributorComponent.ElectricityId, ComputeRequiredPower); - - // set default settings Settings.Strength = 1.0f; Settings.Range = maxRange; - if(!LoadSettings()) + if (!LoadSettings()) { ParseLegacyNameStorage(); } - SaveSettings(); // required for IsSerialized() + SaveSettings(); + network = CollectorNetwork.GetNetwork(block.CubeGrid.EntityId); + network.RegisterCollector(this); } - catch(Exception e) + catch (Exception e) { Log.Error(e); } @@ -163,15 +160,15 @@ public override void Close() { try { - if(block == null) + if (block == null) return; - floatingObjects?.Clear(); floatingObjects = null; - + network?.UnregisterCollector(this); + network = null; block = null; } - catch(Exception e) + catch (Exception e) { Log.Error(e); } @@ -179,10 +176,9 @@ public override void Close() float ComputeRequiredPower() { - if(!block.IsWorking) + if (!block.IsWorking) return 0f; - - var baseUsage = 0.002f; // same as vanilla collector + var baseUsage = 0.002f; var maxPowerUsage = blockDef.RequiredPowerInput; var mul = (StrengthMul / (STRENGTH_MAX / 100f)) * (Range / maxRange); return baseUsage + maxPowerUsage * mul; @@ -195,7 +191,7 @@ public override void UpdateBeforeSimulation10() SyncSettings(); FindFloatingObjects(); } - catch(Exception e) + catch (Exception e) { Log.Error(e); } @@ -207,30 +203,27 @@ void FindFloatingObjects() entities.Clear(); floatingObjects.Clear(); - if(Range < RANGE_OFF_EXCLUSIVE || !block.IsWorking || !block.CubeGrid.Physics.Enabled) + if (Range < RANGE_OFF_EXCLUSIVE || !block.IsWorking || !block.CubeGrid.Physics.Enabled) { - if((NeedsUpdate & MyEntityUpdateEnum.EACH_FRAME) != 0) + if ((NeedsUpdate & MyEntityUpdateEnum.EACH_FRAME) != 0) { UpdateEmissive(); NeedsUpdate &= ~MyEntityUpdateEnum.EACH_FRAME; } - return; } - if((NeedsUpdate & MyEntityUpdateEnum.EACH_FRAME) == 0) + if ((NeedsUpdate & MyEntityUpdateEnum.EACH_FRAME) == 0) NeedsUpdate |= MyEntityUpdateEnum.EACH_FRAME; - var collectPos = block.WorldMatrix.Translation + (block.WorldMatrix.Forward * offset); - var sphere = new BoundingSphereD(collectPos, Range + 10); - + Vector3D collectPos = GetCollectionPoint(); + BoundingSphereD sphere = new BoundingSphereD(collectPos, Range + 10); MyGamePruningStructure.GetAllTopMostEntitiesInSphere(ref sphere, entities, MyEntityQueryType.Dynamic); - foreach(var ent in entities) + foreach (MyEntity ent in entities) { - var floatingObject = ent as IMyFloatingObject; - - if(floatingObject != null && floatingObject.Physics != null) + IMyFloatingObject floatingObject = ent as IMyFloatingObject; + if (floatingObject != null && floatingObject.Physics != null) floatingObjects.Add(floatingObject); } @@ -241,22 +234,18 @@ void FindFloatingObjects() void UpdateEmissive(bool pulling = false) { - var color = Color.Red; + Color color = Color.Red; float strength = 0f; - - if(block.IsWorking) + if (block.IsWorking) { strength = 1f; - - if(pulling) + if (pulling) color = Color.Cyan; else color = new Color(10, 255, 0); } - - if(prevColor == color) + if (prevColor == color) return; - prevColor = color; block.SetEmissiveParts("Emissive", color, strength); } @@ -265,99 +254,73 @@ public override void UpdateAfterSimulation() { try { - if(Range < RANGE_OFF_EXCLUSIVE) + if (Range < RANGE_OFF_EXCLUSIVE || !block.IsWorking) return; - bool applyForce = false; - if(++skipTicks >= APPLY_FORCE_SKIP_TICKS) - { + collectionSystem.Update(); + + bool applyForce = (++skipTicks >= APPLY_FORCE_SKIP_TICKS); + if (applyForce) skipTicks = 0; - applyForce = true; - } - if(!applyForce && MyAPIGateway.Utilities.IsDedicated) + if (!applyForce && MyAPIGateway.Utilities.IsDedicated) return; - var conePos = block.WorldMatrix.Translation + (block.WorldMatrix.Forward * -offset); - bool inViewRange = false; - - if(!MyAPIGateway.Utilities.IsDedicated) - { - var cameraMatrix = MyAPIGateway.Session.Camera.WorldMatrix; - inViewRange = Vector3D.DistanceSquared(cameraMatrix.Translation, conePos) <= MAX_VIEW_RANGE_SQ; - - if(inViewRange && DrawCone) - DrawInfluenceCone(conePos); - } - - if(!applyForce && !inViewRange) - return; + ProcessVisuals(); - if(floatingObjects.Count == 0) + if (floatingObjects.Count == 0) { - UpdateEmissive(); + UpdateEmissive(false); return; } - var collectPos = block.WorldMatrix.Translation + (block.WorldMatrix.Forward * offset); - var blockVel = block.CubeGrid.Physics.GetVelocityAtPoint(collectPos); - var rangeSq = Range * Range; - int pulling = 0; - - for(int i = (floatingObjects.Count - 1); i >= 0; --i) - { - var floatingObject = floatingObjects[i]; - - if(floatingObject.MarkedForClose || !floatingObject.Physics.Enabled) - continue; // it'll get removed by FindFloatingObjects() - - var objPos = floatingObject.GetPosition(); - var distSq = Vector3D.DistanceSquared(collectPos, objPos); - - if(distSq > rangeSq) - continue; // too far from cone - - var dirNormalized = Vector3D.Normalize(objPos - conePos); - var angle = Math.Acos(MathHelper.Clamp(Vector3D.Dot(block.WorldMatrix.Forward, dirNormalized), -1, 1)); - - if(angle > coneAngle) - continue; // outside of the cone's FOV - - if(applyForce) - { - var collectDir = Vector3D.Normalize(objPos - collectPos); - - var vel = floatingObject.Physics.LinearVelocity - blockVel; - var stop = vel - (collectDir * collectDir.Dot(vel)); - var force = -(stop + collectDir) * Math.Min(floatingObject.Physics.Mass * MASS_MUL, MAX_MASS) * StrengthMul; - - force *= APPLY_FORCE_SKIP_TICKS; // multiplied by how many ticks were skipped - - //MyTransparentGeometry.AddLineBillboard(Mod.MATERIAL_SQUARE, Color.Yellow, objPos, force, 1f, 0.1f); - - floatingObject.Physics.AddForce(MyPhysicsForceType.APPLY_WORLD_FORCE, force, null, null); - } + ProcessObjects(applyForce); + } + catch (Exception e) + { + Log.Error(e); + } + } - if(inViewRange) - { - var mul = (float)Math.Sin(DateTime.UtcNow.TimeOfDay.TotalMilliseconds * 0.01); - var radius = floatingObject.Model.BoundingSphere.Radius * MinMaxPercent(0.75f, 1.25f, mul); + private void ProcessVisuals() + { + if (MyAPIGateway.Utilities.IsDedicated) + return; - MyTransparentGeometry.AddPointBillboard(Mod.MATERIAL_DOT, Color.LightSkyBlue * MinMaxPercent(0.2f, 0.4f, mul), objPos, radius, 0); - } + Vector3D conePos = block.WorldMatrix.Translation + (block.WorldMatrix.Forward * -offset); + MatrixD cameraMatrix = MyAPIGateway.Session.Camera.WorldMatrix; + bool inViewRange = Vector3D.DistanceSquared(cameraMatrix.Translation, conePos) <= MAX_VIEW_RANGE_SQ; - pulling++; - } + if (inViewRange && DrawCone) + DrawInfluenceCone(conePos); + } + private void ProcessObjects(bool applyForce) + { + int pulling = 0; + Vector3D cameraPos = MyAPIGateway.Session.Camera.WorldMatrix.Translation; + bool inViewRange = Vector3D.DistanceSquared(cameraPos, GetCollectionPoint()) <= MAX_VIEW_RANGE_SQ; - if(applyForce) - UpdateEmissive(pulling > 0); - } - catch(Exception e) + foreach (IMyFloatingObject obj in floatingObjects) { - Log.Error(e); + if (!IsValidCollectionTarget(obj)) + continue; + + GravityCollector bestCollector = network.GetBestCollectorFor(obj); + if (bestCollector != this) continue; + float priority = CalculateCollectionPriority(obj); + collectionSystem.QueueObject(obj, priority); + pulling++; + + if (!inViewRange || MyAPIGateway.Utilities.IsDedicated) continue; + Vector3D objPos = obj.GetPosition(); + var mul = (float)Math.Sin(DateTime.UtcNow.TimeOfDay.TotalMilliseconds * 0.01); + var radius = obj.Model.BoundingSphere.Radius * MinMaxPercent(0.75f, 1.25f, mul); + MyTransparentGeometry.AddPointBillboard(Mod.MATERIAL_DOT, Color.LightSkyBlue * MinMaxPercent(0.2f, 0.4f, mul), objPos, radius, 0); } - } + if (applyForce) + UpdateEmissive(pulling > 0); + } void DrawInfluenceCone(Vector3D conePos) { Vector4 color = Color.Cyan.ToVector4() * 10; @@ -365,35 +328,31 @@ void DrawInfluenceCone(Vector3D conePos) const float LINE_THICK = 0.02f; const int WIRE_DIV_RATIO = 16; - var coneMatrix = block.WorldMatrix; + MatrixD coneMatrix = block.WorldMatrix; coneMatrix.Translation = conePos; - //MyTransparentGeometry.AddPointBillboard(Mod.MATERIAL_DOT, Color.Lime, collectPos, 0.05f, 0); - - float rangeOffset = Range + (offset * 2); // because range check starts from collectPos but cone starts from conePos + float rangeOffset = Range + (offset * 2); float baseRadius = rangeOffset * (float)Math.Tan(coneAngle); - //MySimpleObjectDraw.DrawTransparentCone(ref coneMatrix, baseRadius, rangeWithOffset, ref color, 16, Mod.MATERIAL_SQUARE); - - var apexPosition = coneMatrix.Translation; - var directionVector = coneMatrix.Forward * rangeOffset; - var maxPosCenter = conePos + coneMatrix.Forward * rangeOffset; - var baseVector = coneMatrix.Up * baseRadius; + Vector3D apexPosition = coneMatrix.Translation; + Vector3D directionVector = coneMatrix.Forward * rangeOffset; + Vector3D maxPosCenter = conePos + coneMatrix.Forward * rangeOffset; + Vector3D baseVector = coneMatrix.Up * baseRadius; Vector3 axis = directionVector; axis.Normalize(); float stepAngle = (float)(Math.PI * 2.0 / (double)WIRE_DIV_RATIO); - var prevConePoint = apexPosition + directionVector + Vector3.Transform(baseVector, Matrix.CreateFromAxisAngle(axis, (-1 * stepAngle))); + Vector3D prevConePoint = apexPosition + directionVector + Vector3.Transform(baseVector, Matrix.CreateFromAxisAngle(axis, (-1 * stepAngle))); prevConePoint = (apexPosition + Vector3D.Normalize((prevConePoint - apexPosition)) * rangeOffset); - var quad = default(MyQuadD); + MyQuadD quad = default(MyQuadD); - for(int step = 0; step < WIRE_DIV_RATIO; step++) + for (int step = 0; step < WIRE_DIV_RATIO; step++) { - var conePoint = apexPosition + directionVector + Vector3.Transform(baseVector, Matrix.CreateFromAxisAngle(axis, (step * stepAngle))); - var lineDir = (conePoint - apexPosition); + Vector3D conePoint = apexPosition + directionVector + Vector3.Transform(baseVector, Matrix.CreateFromAxisAngle(axis, (step * stepAngle))); + Vector3D lineDir = (conePoint - apexPosition); lineDir.Normalize(); conePoint = (apexPosition + lineDir * rangeOffset); @@ -403,15 +362,6 @@ void DrawInfluenceCone(Vector3D conePos) MyTransparentGeometry.AddLineBillboard(Mod.MATERIAL_SQUARE, color, conePoint, (maxPosCenter - conePoint), 1f, LINE_THICK); - // Unusable because SQUARE has reflectivity and this method uses materials' reflectivity... making it unable to be made transparent, also reflective xD - //var normal = Vector3.Up; - //MyTransparentGeometry.AddTriangleBillboard( - // apexPosition, prevConePoint, conePoint, - // normal, normal, normal, - // new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), - // Mod.MATERIAL_SQUARE, uint.MaxValue, conePoint, planeColor); - // also NOTE: if triangle is used, color needs .ToLinearRGB(). - quad.Point0 = prevConePoint; quad.Point1 = conePoint; quad.Point2 = apexPosition; @@ -430,25 +380,25 @@ void DrawInfluenceCone(Vector3D conePos) bool LoadSettings() { - if(block.Storage == null) + if (block.Storage == null) return false; string rawData; - if(!block.Storage.TryGetValue(SETTINGS_GUID, out rawData)) + if (!block.Storage.TryGetValue(SETTINGS_GUID, out rawData)) return false; try { - var loadedSettings = MyAPIGateway.Utilities.SerializeFromBinary(Convert.FromBase64String(rawData)); + GravityCollectorBlockSettings loadedSettings = MyAPIGateway.Utilities.SerializeFromBinary(Convert.FromBase64String(rawData)); - if(loadedSettings != null) + if (loadedSettings != null) { Settings.Range = loadedSettings.Range; Settings.Strength = loadedSettings.Strength; return true; } } - catch(Exception e) + catch (Exception e) { Log.Error($"Error loading settings!\n{e}"); } @@ -460,43 +410,43 @@ bool ParseLegacyNameStorage() { string name = block.CustomName.TrimEnd(' '); - if(!name.EndsWith("]", StringComparison.Ordinal)) + if (!name.EndsWith("]", StringComparison.Ordinal)) return false; int startIndex = name.IndexOf('['); - if(startIndex == -1) + if (startIndex == -1) return false; var settingsStr = name.Substring(startIndex + 1, name.Length - startIndex - 2); - if(settingsStr.Length == 0) + if (settingsStr.Length == 0) return false; string[] args = settingsStr.Split(';'); - if(args.Length == 0) + if (args.Length == 0) return false; string[] data; - foreach(string arg in args) + foreach (string arg in args) { data = arg.Split('='); float f; int i; - if(data.Length == 2) + if (data.Length == 2) { - switch(data[0]) + switch (data[0]) { case "range": - if(int.TryParse(data[1], out i)) + if (int.TryParse(data[1], out i)) Range = i; break; case "str": - if(float.TryParse(data[1], out f)) + if (float.TryParse(data[1], out f)) StrengthMul = f; break; } @@ -509,16 +459,16 @@ bool ParseLegacyNameStorage() void SaveSettings() { - if(block == null) - return; // called too soon or after it was already closed, ignore + if (block == null) + return; - if(Settings == null) + if (Settings == null) throw new NullReferenceException($"Settings == null on entId={Entity?.EntityId}; modInstance={GravityCollectorMod.Instance != null}"); - if(MyAPIGateway.Utilities == null) + if (MyAPIGateway.Utilities == null) throw new NullReferenceException($"MyAPIGateway.Utilities == null; entId={Entity?.EntityId}; modInstance={GravityCollectorMod.Instance != null}"); - if(block.Storage == null) + if (block.Storage == null) block.Storage = new MyModStorageComponent(); block.Storage.SetValue(SETTINGS_GUID, Convert.ToBase64String(MyAPIGateway.Utilities.SerializeToBinary(Settings))); @@ -526,13 +476,13 @@ void SaveSettings() void SettingsChanged() { - if(syncCountdown == 0) + if (syncCountdown == 0) syncCountdown = SETTINGS_CHANGED_COUNTDOWN; } void SyncSettings() { - if(syncCountdown > 0 && --syncCountdown <= 0) + if (syncCountdown > 0 && --syncCountdown <= 0) { SaveSettings(); @@ -542,15 +492,11 @@ void SyncSettings() public override bool IsSerialized() { - // called when the game iterates components to check if they should be serialized, before they're actually serialized. - // this does not only include saving but also streaming and blueprinting. - // NOTE for this to work reliably the MyModStorageComponent needs to already exist in this block with at least one element. - try { SaveSettings(); } - catch(Exception e) + catch (Exception e) { Log.Error(e); } @@ -558,25 +504,84 @@ public override bool IsSerialized() return base.IsSerialized(); } - /// - /// Returns the specified percentage multiplier (0 to 1) between min and max. - /// + public Vector3D GetCollectionPoint() + { + return block.WorldMatrix.Translation + (block.WorldMatrix.Forward * offset); + } + + public float GetEffectiveVolume() + { + float height = Range; + float radius = height * (float)Math.Tan(coneAngle); + return (float)(Math.PI * radius * radius * height / 3.0); + } + + public float GetIdealVelocityForDistance(double distance) + { + float normalizedDist = (float)(distance / Range); + float baseVelocity = 20f; + return baseVelocity * (1f - normalizedDist * 0.5f); + } + + public void UpdateForceMultiplier(float multiplier) + { + adaptiveForceMultiplier = MathHelper.Clamp(multiplier, 0.1f, 100.0f); + } + + private float CalculateCollectionPriority(IMyEntity entity) + { + float priority = 0f; + + var distance = Vector3D.Distance(GetCollectionPoint(), entity.GetPosition()); + priority += 1.0f - (float)(distance / Range); + + if (entity.Physics != null) + { + priority += Math.Min(entity.Physics.Mass / MAX_MASS, 1.0f) * 0.5f; + } + + if (entity is IMyFloatingObject) + { + priority += 0.3f; + } + + return priority; + } + + private bool IsValidCollectionTarget(IMyEntity entity) + { + if (entity?.Physics == null || entity.MarkedForClose) + return false; + + Vector3D objPos = entity.GetPosition(); + Vector3D collectPos = GetCollectionPoint(); + + if (Vector3D.DistanceSquared(collectPos, objPos) > Range * Range) + return false; + + Vector3D dirNormalized = Vector3D.Normalize(objPos - collectPos); + var angle = Math.Acos(MathHelper.Clamp(Vector3D.Dot(block.WorldMatrix.Forward, dirNormalized), -1, 1)); + + return angle <= coneAngle; + } + static float MinMaxPercent(float min, float max, float percentMul) { return min + (percentMul * (max - min)); } #region Terminal controls + static void SetupTerminalControls() { - var mod = GravityCollectorMod.Instance; + GravityCollectorMod mod = GravityCollectorMod.Instance; - if(mod.ControlsCreated) + if (mod.ControlsCreated) return; mod.ControlsCreated = true; - var controlRange = MyAPIGateway.TerminalControls.CreateControl(CONTROLS_PREFIX + "Range"); + IMyTerminalControlSlider controlRange = MyAPIGateway.TerminalControls.CreateControl(CONTROLS_PREFIX + "Range"); controlRange.Title = MyStringId.GetOrCompute("Pull Range"); controlRange.Tooltip = MyStringId.GetOrCompute("Max distance the cone extends to."); controlRange.Visible = Control_Visible; @@ -587,7 +592,7 @@ static void SetupTerminalControls() controlRange.Writer = Control_Range_Writer; MyAPIGateway.TerminalControls.AddControl(controlRange); - var controlStrength = MyAPIGateway.TerminalControls.CreateControl(CONTROLS_PREFIX + "Strength"); + IMyTerminalControlSlider controlStrength = MyAPIGateway.TerminalControls.CreateControl(CONTROLS_PREFIX + "Strength"); controlStrength.Title = MyStringId.GetOrCompute("Pull Strength"); controlStrength.Tooltip = MyStringId.GetOrCompute($"Formula used:\nForce = Min(ObjectMass * {MASS_MUL.ToString()}, {MAX_MASS.ToString()}) * (Strength / 100)"); controlStrength.Visible = Control_Visible; @@ -608,34 +613,34 @@ static bool Control_Visible(IMyTerminalBlock block) static float Control_Strength_Getter(IMyTerminalBlock block) { - var logic = GetLogic(block); + GravityCollector logic = GetLogic(block); return (logic == null ? STRENGTH_MIN : logic.StrengthMul * 100); } static void Control_Strength_Setter(IMyTerminalBlock block, float value) { - var logic = GetLogic(block); - if(logic != null) + GravityCollector logic = GetLogic(block); + if (logic != null) logic.StrengthMul = ((int)value / 100f); } static void Control_Strength_Writer(IMyTerminalBlock block, StringBuilder writer) { - var logic = GetLogic(block); - if(logic != null) + GravityCollector logic = GetLogic(block); + if (logic != null) writer.Append((int)(logic.StrengthMul * 100f)).Append('%'); } static float Control_Range_Getter(IMyTerminalBlock block) { - var logic = GetLogic(block); + GravityCollector logic = GetLogic(block); return (logic == null ? 0 : logic.Range); } static void Control_Range_Setter(IMyTerminalBlock block, float value) { - var logic = GetLogic(block); - if(logic != null) + GravityCollector logic = GetLogic(block); + if (logic != null) logic.Range = (int)Math.Floor(value); } @@ -646,21 +651,448 @@ static float Control_Range_Min(IMyTerminalBlock block) static float Control_Range_Max(IMyTerminalBlock block) { - var logic = GetLogic(block); + GravityCollector logic = GetLogic(block); return (logic == null ? 0 : logic.maxRange); } static void Control_Range_Writer(IMyTerminalBlock block, StringBuilder writer) { - var logic = GetLogic(block); - if(logic != null) + GravityCollector logic = GetLogic(block); + if (logic != null) { - if(logic.Range < RANGE_OFF_EXCLUSIVE) + if (logic.Range < RANGE_OFF_EXCLUSIVE) writer.Append("OFF"); else writer.Append(logic.Range.ToString("N2")).Append(" m"); } } + #endregion + + } + + /// + /// Manages coordination between multiple collectors on the same grid + /// + public class CollectorNetwork { + private static Dictionary gridNetworks = new Dictionary(); + private List collectors = new List(); + private Dictionary objectAssignments = new Dictionary(); + private readonly long gridId; + + public static CollectorNetwork GetNetwork(long gridId) + { + CollectorNetwork network; + if (!gridNetworks.TryGetValue(gridId, out network)) + { + network = new CollectorNetwork(gridId); + gridNetworks[gridId] = network; + } + return network; + } + + public CollectorNetwork(long gridId) + { + this.gridId = gridId; + } + + public void RegisterCollector(GravityCollector collector) + { + if (!collectors.Contains(collector)) + { + collectors.Add(collector); + //Log.Info($"Collector {collector.Entity.EntityId} registered to grid network {gridId}"); + } + } + + public void UnregisterCollector(GravityCollector collector) + { + collectors.Remove(collector); + + var reassignObjects = objectAssignments.Where(kvp => kvp.Value == collector) + .Select(kvp => kvp.Key) + .ToList(); + foreach (IMyEntity obj in reassignObjects) + { + objectAssignments.Remove(obj); + } + } + + public GravityCollector GetBestCollectorFor(IMyEntity entity) + { + GravityCollector assigned; + if (objectAssignments.TryGetValue(entity, out assigned)) + return assigned; + + Vector3D pos = entity.GetPosition(); + return collectors.OrderBy(c => Vector3D.DistanceSquared(c.GetCollectionPoint(), pos)) + .FirstOrDefault(); + } + + public void AssignObject(IMyEntity entity, GravityCollector collector) + { + objectAssignments[entity] = collector; + } + + public void ReleaseObject(IMyEntity entity) + { + objectAssignments.Remove(entity); + } + } + + /// + /// Handles advanced object collection behavior and queuing + /// + public class AdvancedCollectionSystem { + private readonly GravityCollector collector; + private readonly Dictionary activePaths = new Dictionary(); + private readonly PriorityQueue collectionQueue = new PriorityQueue(); + private readonly HashSet processingObjects = new HashSet(); + + private const int MAX_CONCURRENT_COLLECTIONS = 8; + private const float DENSITY_THRESHOLD = 5f; + private const float ADAPTIVE_FORCE_MAX_MULTIPLIER = 20.0f; + + private int updateCounter = 0; + private const int PERFORMANCE_LOG_INTERVAL = 600;// Log every 10 seconds at 60 updates/second + + public AdvancedCollectionSystem(GravityCollector collector) + { + this.collector = collector; + } + + public void Update() + { + DateTime startTime = DateTime.Now; + + UpdateActivePaths(); + ProcessQueue(); + AdaptForceSettings(); + + // Clear the queue after processing to prevent accumulation + collectionQueue.Clear(); + + if (++updateCounter >= PERFORMANCE_LOG_INTERVAL) + { + updateCounter = 0; + var processingTime = (DateTime.Now - startTime).TotalMilliseconds; + //Log.Info($"Collection System Performance: {processingTime:F2}ms, Active Objects: {processingObjects.Count}, Queued: {collectionQueue.Count}"); + } + } + + private void UpdateActivePaths() + { + foreach (var path in activePaths.ToList()) + { + if (!IsPathValid(path.Key)) + { + // Log.Info($"Object {path.Key.EntityId} left collection cone or range, stopping collection"); + activePaths.Remove(path.Key); + processingObjects.Remove(path.Key); + collector.network.ReleaseObject(path.Key); + continue; + } + + path.Value.Update(); + ApplyPathForces(path.Key, path.Value); + } + } + + private void ProcessQueue() + { + while(processingObjects.Count < MAX_CONCURRENT_COLLECTIONS && collectionQueue.Count > 0) + { + IMyEntity nextObject = collectionQueue.Dequeue(); + if (IsValidForCollection(nextObject)) + { + InitiateCollection(nextObject); + } + } + } + + private void InitiateCollection(IMyEntity entity) + { + CollectionPath path = new CollectionPath(collector, entity); + activePaths[entity] = path; + processingObjects.Add(entity); + collector.network.AssignObject(entity, collector); + } + private const float DENSITY_FALLOFF_FACTOR = 0.5f; // Adjust this value to control how quickly density decreases with distance + private void AdaptForceSettings() + { + Vector3D collectionPoint = collector.GetCollectionPoint(); + float totalWeightedDensity = 0; + float totalWeight = 0; + + foreach (var entity in processingObjects) + { + float distance = (float)Vector3D.Distance(entity.GetPosition(), collectionPoint); + float weight = 1f / (1f + distance * DENSITY_FALLOFF_FACTOR); + totalWeightedDensity += weight; + totalWeight += 1; + } + + float effectiveVolume = collector.GetEffectiveVolume(); + float averageDensity = (totalWeight > 0) ? (totalWeightedDensity / totalWeight) : 0; + float normalizedDensity = averageDensity / effectiveVolume; + + float adaptiveMul = MathHelper.Clamp( + 1.0f - (normalizedDensity / DENSITY_THRESHOLD), + 1.0f / ADAPTIVE_FORCE_MAX_MULTIPLIER, + ADAPTIVE_FORCE_MAX_MULTIPLIER + ); + + collector.UpdateForceMultiplier(adaptiveMul); + + // Log.Info($"Adaptive Force Multiplier: {adaptiveMul}, Normalized Density: {normalizedDensity}"); + } + + public void QueueObject(IMyEntity entity, float priority) + { + if (!processingObjects.Contains(entity) && !activePaths.ContainsKey(entity)) + { + collectionQueue.Enqueue(entity, priority); + } + } + + private bool IsPathValid(IMyEntity entity) + { + if (entity?.Physics == null || entity.MarkedForClose) + return false; + + var distance = Vector3D.Distance(entity.GetPosition(), collector.GetCollectionPoint()); + if (distance > collector.Range) + return false; + + // Use the new method to check if in cone + return collector.IsInCollectionCone(entity.GetPosition()); + } + + private bool IsValidForCollection(IMyEntity entity) + { + return entity?.Physics != null && !entity.MarkedForClose; + } + + private void ApplyPathForces(IMyEntity entity, CollectionPath path) + { + if (entity?.Physics == null) + { + Log.Info($"Failed to apply forces to entity {entity?.EntityId}: Physics null"); + return; + } + + Vector3 currentVelocity = entity.Physics.LinearVelocity; + Vector3D idealForce = path.GetIdealForce(currentVelocity); + + idealForce *= collector.adaptiveForceMultiplier; + + Vector3D randomOffset = new Vector3D( + MyUtils.GetRandomFloat(-0.1f, 0.1f), + MyUtils.GetRandomFloat(-0.1f, 0.1f), + MyUtils.GetRandomFloat(-0.1f, 0.1f) + ); + + idealForce += idealForce * randomOffset; + + try + { + entity.Physics.AddForce( + MyPhysicsForceType.APPLY_WORLD_FORCE, + idealForce, + entity.GetPosition(), + null + ); + + // Log.Info($"Applied force to entity {entity.EntityId}: Force={idealForce.Length():F2}"); // TODO: ratelimit this + } + catch (Exception e) + { + Log.Error($"Failed to apply force to entity {entity.EntityId}: {e.Message}"); + } + } + } + + + /// + /// Represents and calculates an optimal collection path for an object + /// + /// + /// Represents and calculates an optimal collection path for an object + /// + public class CollectionPath { + private readonly GravityCollector collector; + private readonly IMyEntity target; + private readonly List pathPoints = new List(); + private Vector3D currentIdealPosition; + private Vector3D currentIdealVelocity; + + private const int PATH_POINTS = 10; + private const float PATH_UPDATE_FREQUENCY = 6; + private const float ARRIVAL_THRESHOLD = 0.5f; + + public CollectionPath(GravityCollector collector, IMyEntity target) + { + this.collector = collector; + this.target = target; + CalculatePath(); + } + + public void Update() + { + if (MyAPIGateway.Session.GameDateTime.Millisecond % (1000 / PATH_UPDATE_FREQUENCY) == 0) + { + CalculatePath(); + } + + UpdateIdealPositionAndVelocity(); + } + + private void CalculatePath() + { + pathPoints.Clear(); + Vector3D start = target.GetPosition(); + Vector3D end = collector.GetCollectionPoint(); + + for (int i = 0; i < PATH_POINTS; i++) + { + var t = i / (float)(PATH_POINTS - 1); + Vector3D point = Vector3D.Lerp(start, end, t); + + point = AvoidObstacles(point); + pathPoints.Add(point); + } + } + + private Vector3D AvoidObstacles(Vector3D point) + { + const double AVOIDANCE_RADIUS = 2.0; + const double REPULSION_STRENGTH = 100.0; + + var nearbyEntities = new List();// Change to MyEntity + BoundingSphereD sphere = new BoundingSphereD(point, AVOIDANCE_RADIUS); + MyGamePruningStructure.GetAllTopMostEntitiesInSphere(ref sphere, nearbyEntities, MyEntityQueryType.Dynamic); + + Vector3D avoidanceForce = Vector3D.Zero; + foreach (MyEntity entity in nearbyEntities) + { + if (entity == target) continue; + + Vector3D entityPos = entity.PositionComp.GetPosition(); + Vector3D direction = point - entityPos; + var distance = direction.Length(); + + if (!(distance < AVOIDANCE_RADIUS)) continue; + direction.Normalize(); + avoidanceForce += direction * (REPULSION_STRENGTH * (1.0 - distance / AVOIDANCE_RADIUS)); + } + + return point + avoidanceForce; + } + + + private void UpdateIdealPositionAndVelocity() + { + Vector3D currentPos = target.GetPosition(); + Vector3D collectorPos = collector.GetCollectionPoint(); + + // Check if we've arrived at the collector + if (Vector3D.Distance(currentPos, collectorPos) < ARRIVAL_THRESHOLD) + { + currentIdealVelocity = Vector3D.Zero; + return; + } + + var pathIndex = FindClosestPathIndex(currentPos); + if (pathIndex < pathPoints.Count - 1) + { + currentIdealPosition = pathPoints[pathIndex + 1]; + currentIdealVelocity = Vector3D.Normalize(currentIdealPosition - currentPos) * + collector.GetIdealVelocityForDistance(Vector3D.Distance(currentPos, collectorPos)); + } + } + + public Vector3D GetIdealForce(Vector3D currentVelocity) + { + Vector3D targetVelocity = currentIdealVelocity; + return (targetVelocity - currentVelocity) * target.Physics.Mass; + } + + private int FindClosestPathIndex(Vector3D position) + { + int closestIndex = 0; + double closestDistance = double.MaxValue; + + for (int i = 0; i < pathPoints.Count; i++) + { + double distance = Vector3D.DistanceSquared(position, pathPoints[i]); + if (distance < closestDistance) + { + closestDistance = distance; + closestIndex = i; + } + } + + return closestIndex; + } + } + + public class PriorityQueue { + private SortedDictionary> queue = new SortedDictionary>(); + private Queue> recycledQueues = new Queue>(); + + public int Count { get; private set; } + + public void Enqueue(T item, float priority) + { + Queue itemQueue; + if (!queue.TryGetValue(priority, out itemQueue)) + { + itemQueue = GetQueue(); + queue[priority] = itemQueue; + } + + itemQueue.Enqueue(item); + Count++; + } + + public T Dequeue() + { + if (Count == 0) + throw new InvalidOperationException("Queue is empty"); + + var highestPriority = queue.Keys.Last(); + var itemQueue = queue[highestPriority]; + T item = itemQueue.Dequeue(); + + if (itemQueue.Count == 0) + { + queue.Remove(highestPriority); + RecycleQueue(itemQueue); + } + + Count--; + return item; + } + + public void Clear() + { + foreach (var itemQueue in queue.Values) + { + itemQueue.Clear(); + RecycleQueue(itemQueue); + } + queue.Clear(); + Count = 0; + } + + private Queue GetQueue() + { + return recycledQueues.Count > 0 ? recycledQueues.Dequeue() : new Queue(); + } + + private void RecycleQueue(Queue itemQueue) + { + recycledQueues.Enqueue(itemQueue); + } } } \ No newline at end of file diff --git a/Advanced Gravity Collector/Data/Scripts/concatrecursivelyandminify.bat b/Advanced Gravity Collector/Data/Scripts/concatrecursivelyandminify.bat new file mode 100644 index 00000000..9bbffa34 --- /dev/null +++ b/Advanced Gravity Collector/Data/Scripts/concatrecursivelyandminify.bat @@ -0,0 +1,60 @@ +@echo off +setlocal enabledelayedexpansion +chcp 65001 +REM ^^ this is to change the encoding to UTF-8, apparently + +echo Starting operation... + +set TEMP_OUTPUT=temp_concatenated.txt +set FINAL_OUTPUT=minified_output.txt +set /a COUNT=0 +set /a ERRORS=0 + +if exist "%TEMP_OUTPUT%" ( + echo Existing temporary file found. Deleting... + del "%TEMP_OUTPUT%" +) + +if exist "%FINAL_OUTPUT%" ( + echo Existing output file found. Deleting... + del "%FINAL_OUTPUT%" +) + +for /r %%i in (*.cs) do ( + echo Processing: "%%i" + type "%%i" >> "%TEMP_OUTPUT%" + if errorlevel 1 ( + echo Error processing "%%i". + set /a ERRORS+=1 + ) else ( + set /a COUNT+=1 + ) +) + +echo Concatenation completed. +echo Total files processed: %COUNT% +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the concatenation. +) else ( + echo No errors encountered during concatenation. +) + +echo Minifying concatenated file... +csmin < "%TEMP_OUTPUT%" > "%FINAL_OUTPUT%" +if errorlevel 1 ( + echo Error occurred during minification. + set /a ERRORS+=1 +) else ( + echo Minification completed successfully. +) + +echo Cleaning up temporary file... +del "%TEMP_OUTPUT%" + +echo Operation completed. +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the entire operation. +) else ( + echo No errors encountered during the entire operation. +) +pause \ No newline at end of file diff --git a/Advanced Gravity Collector/metadata.mod b/Advanced Gravity Collector/metadata.mod new file mode 100644 index 00000000..0a020fdf --- /dev/null +++ b/Advanced Gravity Collector/metadata.mod @@ -0,0 +1,4 @@ + + + 1.0 + \ No newline at end of file diff --git a/Advanced Gravity Collector/modinfo.sbmi b/Advanced Gravity Collector/modinfo.sbmi new file mode 100644 index 00000000..c361359a --- /dev/null +++ b/Advanced Gravity Collector/modinfo.sbmi @@ -0,0 +1,11 @@ + + + 76561198071098415 + 0 + + + 3346612989 + Steam + + + \ No newline at end of file diff --git a/Advanced Gravity Collector/Data/Categories_Digi.sbc b/BasicCargoTeleporter/Data/BlockCategories.sbc similarity index 63% rename from Advanced Gravity Collector/Data/Categories_Digi.sbc rename to BasicCargoTeleporter/Data/BlockCategories.sbc index 4dacf837..53935e29 100644 --- a/Advanced Gravity Collector/Data/Categories_Digi.sbc +++ b/BasicCargoTeleporter/Data/BlockCategories.sbc @@ -2,14 +2,15 @@ - + GuiBlockCategoryDefinition - + Digi - z-mod-digi + DisplayName_Category_ConveyorBlocks + Conveyors - GravityCollector + ConveyorSorter/LargeBlockSmallSorterTeleport + ConveyorSorter/SmallBlockMediumSorterTeleport diff --git a/BasicCargoTeleporter/Data/CubeBlocks_TeleportCargo.sbc b/BasicCargoTeleporter/Data/CubeBlocks_TeleportCargo.sbc new file mode 100644 index 00000000..c0365eb2 --- /dev/null +++ b/BasicCargoTeleporter/Data/CubeBlocks_TeleportCargo.sbc @@ -0,0 +1,128 @@ + + + + + + + 13 + TeleUnlock + + + + + + 13 + TeleUnlock + + + + + + + + + + + + + + + + + + + + + + ConveyorSorter + LargeBlockSmallSorterTeleport + + Cargo Teleporter + Textures\GUI\Icons\Cubes\ConveyorSorterLarge.dds + Large + TriangleMesh + + + Models\Cubes\Large\ConveyorSorter.mwm + + + + + + + + + + + + + + + + + + + + + + SorterCargoTeleport + Z + Y + Light + Utility + 1 + 45 + + 2.5 + 2.5 + 2.5 + + Wireless conveyor sorter to any container on the same grid or connected grids (via antenna). Requires 1 MW of power. + + + + + ConveyorSorter + SmallBlockMediumSorterTeleport + + Cargo Teleporter + Textures\GUI\Icons\Cubes\ConveyorSorterMedium.dds + Small + TriangleMesh + + + Models\Cubes\Small\ConveyorSorterMedium.mwm + + + + + + + + + + + + + + + + + + SorterCargoTeleport + Z + Y + Light + Utility + 0.5 + 25 + + 1.5 + 1.5 + 1.5 + + Wireless conveyor sorter to any container on the same grid or connected grids (via antenna). Requires 500 KW of power. + + + + \ No newline at end of file diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/.gitignore b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/.gitignore new file mode 100644 index 00000000..bb8936c0 --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/contentModel.xml +/modules.xml +/.idea.CargoTeleporter.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/indexLayout.xml b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/indexLayout.xml new file mode 100644 index 00000000..7b08163c --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/vcs.xml b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/vcs.xml new file mode 100644 index 00000000..c2365ab1 --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/.idea/.idea.CargoTeleporter/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/CargoTeleporterSorterServer.cs b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/CargoTeleporterSorterServer.cs new file mode 100644 index 00000000..3f95c54b --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/CargoTeleporterSorterServer.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sandbox.Common.ObjectBuilders; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using Sandbox.ModAPI.Ingame; +using VRage.Game.Components; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRage.ObjectBuilders; +using VRageMath; +using IMyFunctionalBlock = Sandbox.ModAPI.IMyFunctionalBlock; +using IMyLaserAntenna = Sandbox.ModAPI.Ingame.IMyLaserAntenna; +using IMyRadioAntenna = Sandbox.ModAPI.Ingame.IMyRadioAntenna; +using IMyTerminalBlock = Sandbox.ModAPI.IMyTerminalBlock; + +namespace CargoTeleporter +{ + [MyEntityComponentDescriptor(typeof(MyObjectBuilder_ConveyorSorter), false, "LargeBlockSmallSorterTeleport", + "SmallBlockMediumSorterTeleport")] + public class CargoTeleporterSorterServer : MyGameLogicComponent + { + private MyCubeBlock _cargoTeleporter; + private IMyInventory _inventory; + private MyObjectBuilder_EntityBase _objectBuilder; + private string _status; + private string _targetBlockName; + private string _targetGridName = ""; + private bool _sending; + private string _oldName = ""; + + public override void Init(MyObjectBuilder_EntityBase objectBuilder) + { + Entity.NeedsUpdate |= MyEntityUpdateEnum.EACH_100TH_FRAME; + NeedsUpdate |= MyEntityUpdateEnum.EACH_100TH_FRAME; + NeedsUpdate |= MyEntityUpdateEnum.BEFORE_NEXT_FRAME; + _objectBuilder = objectBuilder; + _cargoTeleporter = Entity as MyCubeBlock; + base.Init(objectBuilder); + (_cargoTeleporter as IMyTerminalBlock).AppendingCustomInfo += AppendingCustomInfo; + (_cargoTeleporter as IMyTerminalBlock).CustomNameChanged += CustomNameChange; + } + + public override MyObjectBuilder_EntityBase GetObjectBuilder(bool copy = false) + { + return copy ? _objectBuilder.Clone() as MyObjectBuilder_EntityBase : _objectBuilder; + } + + public override void Close() + { + Logging.Close(); + } + + public override void UpdateBeforeSimulation100() + { + if (_cargoTeleporter == null) return; + if (!((IMyFunctionalBlock) _cargoTeleporter).Enabled) return; + + try + { + if (MyAPIGateway.Session == null) + { + Write("MyAPIGateway.Session is null"); + return; + } + + ParseName(); + + if (_targetBlockName == "") return; + + if (_inventory == null) _inventory = _cargoTeleporter.GetInventory(); + if (_sending && _inventory.Empty()) + { + UpdateStatus("Status: Source Empty"); + return; + } + + //We use true, if it goes through the ref; (which I treat like an out) it will be changed. + //If it goes through the linq; then we are unchanged and true is the correct value. (we have found the grid, it is this one) + bool gridFound = true; + IMyEntity targetBlock; + if (_targetGridName.Length > 2 && _targetGridName != _cargoTeleporter.CubeGrid.Name) + { + targetBlock = GetTarget(ref gridFound); + } + else + { + var cubeBlocks = _cargoTeleporter.CubeGrid.GetFatBlocks(); + targetBlock = cubeBlocks.First(x => x?.DisplayNameText == _targetBlockName && OwnershipUtils.IsSameFactionOrOwner(_cargoTeleporter, x)); + } + + var disonnectedStatus = "Status: Disconnected"; + //If grid not found, + if (!gridFound) + { + Write("Grid Not Found"); + } + disonnectedStatus += $"\nGrid: {(gridFound ? "" : "Not ")}Found"; + if (targetBlock == null) + { + Write("Target Not Found"); + } + //This message should only ever say T/S Not Found, but in case I flubbed it; it can say found + disonnectedStatus += $"\n{(_sending ? "Target" : "Source")}: {(targetBlock != null ? "" : "Not ")}Found"; + + if (targetBlock == null || !gridFound) + { + UpdateStatus(disonnectedStatus); + return; + } + + var targetInventory = targetBlock.GetInventory(); + var status = "Status: Connected\nTarget: "; + var targetStatus = targetInventory.IsFull ? "Full" : targetInventory.Empty() ? "Empty" : "Some"; + var inventoryStatus = _inventory.IsFull ? "Full" : _inventory.Empty() ? "Empty" : "Some"; + status += _sending ? targetStatus : inventoryStatus; + status += "\nSource: "; + status += !_sending ? targetStatus : inventoryStatus; + + UpdateStatus(status); + + if (!targetInventory.IsFull && _sending) + targetInventory.TransferItemFrom(_inventory, 0, null, true, null, false); + else if (!targetInventory.Empty() && !_inventory.IsFull && !_sending) + _inventory.TransferItemFrom(targetInventory, 0, null, true, null, false); + } + catch (Exception ex) + { + Logging.WriteLine(ex.ToString()); + } + } + + private void AppendingCustomInfo(IMyTerminalBlock block, StringBuilder sb) + { + sb.Clear(); + sb.Append(_status); + } + + private void CustomNameChange(IMyTerminalBlock block) + { + if (!ParseName()) return; + + if (_targetBlockName.Length < 2) + { + Write("Name too small"); + UpdateStatus("Status: No filters\n\nPlease add [T:Block Name] or [F:Block Name].\nPlease add [G:Grid Name] if using antennas."); + return; + } + } + + private void UpdateStatus(string status) + { + if (status == _status) return; + _status = status; + (_cargoTeleporter as IMyTerminalBlock).RefreshCustomInfo(); + } + + private void Write(string v) + { + if (Config.enableDebug) Logging.WriteLine(_cargoTeleporter.CubeGrid.DisplayName + " - " + _cargoTeleporter.DisplayNameText + ": " + v); + } + + private bool ParseName() + { + var displayName = _cargoTeleporter.DisplayNameText; + if (displayName == _oldName) return false; + + //For ease of reading + const int NotFound = -1; + const char StartBracket = '['; + const char StopBracket = ']'; + const char ModeDelimiter = ':'; + const char ToMode = 'T'; + const char ToModeLower = 't'; + const char FromMode = 'F'; + const char FromModeLower = 'f'; + const char GlobalMode = 'G'; + const char GlobalModeLower = 'g'; + + _targetBlockName = ""; + _targetGridName = ""; + + var workingIndex = 0; + while (true) + { + if (workingIndex >= displayName.Length) + { + Write("Parsing Name - End Of String"); + break; + } + + var start = displayName.IndexOf(StartBracket, workingIndex); + if (start == NotFound) + { + Write("Parsing Name - '[' Not Found"); + break; + } + + var stop = displayName.IndexOf(StopBracket, start); + if (stop == NotFound) + { + Write("Parsing Name - Closing ']' Not Found"); + break; + } + var delimiter = displayName.IndexOf(ModeDelimiter, start, stop - start); + if (delimiter == NotFound) + { + workingIndex = stop + 1; //We jump to stop instead of start because the delimiter does not exist; not because it is invalid + Write("Parsing Name - Missing ':' in [] pair"); + continue; + } + + //Trims [mode:name] to just mode and name + var modePart = displayName.Substring(start + 1, delimiter - start - 1); + var namePart = displayName.Substring(delimiter + 1, stop - delimiter - 1); + + //Parses name + namePart = namePart.Trim(); + + //Parses mode + //First strip whitespace + modePart = modePart.Trim(); + + if (modePart.Length != 1) + { + //Mode is invalid, advance start and resume search + workingIndex = start + 1; + Write($"Parsing Name - '{modePart}' is not 1 character."); + continue; + } + var modeChar = modePart[0]; + switch (modeChar) + { + case ToMode: + case ToModeLower: + _targetBlockName = namePart; + _sending = true; + break; + case FromMode: + case FromModeLower: + _targetBlockName = namePart; + _sending = false; + break; + case GlobalMode: + case GlobalModeLower: + _targetGridName = namePart; + break; + default: + //Mode is invalid char, advance start and resume search + workingIndex = start + 1; + Write($"Parsing Name - '{modePart}' is not a valid character."); + continue; + } + workingIndex = stop + 1; + + if (_targetBlockName != "" && _targetGridName != "") break; + } + Write("Parsed: " + _targetGridName + ", " + _targetBlockName + ", " + _sending); + _oldName = displayName; + return true; + } + + private IMyEntity GetTarget(ref bool foundGrid) + { + Write("Looking for [" + _targetGridName + "] " + _targetBlockName); + + var gridsToProcess = new List(); + var gridsProcessed = new HashSet(); + IMyEntity target = null; + foundGrid = false; + + gridsToProcess.Add(_cargoTeleporter.CubeGrid); + + while (target == null && gridsToProcess.Count > 0) + { + var processing = gridsToProcess.Pop(); + if (gridsProcessed.Contains(processing)) continue; + Write("Processing Grid: \"" + processing.DisplayName + "\""); + + var gridBlocks = processing.GetFatBlocks().ToArray(); + + if (processing.DisplayName == _targetGridName) + { + try + { + target = gridBlocks.First(x => x?.DisplayNameText == _targetBlockName); + foundGrid = true; + break; + } + catch (InvalidOperationException e) + { + Write(e.ToString()); + } + } + + foreach (var antenna in gridBlocks.Where(x => x is IMyRadioAntenna && OwnershipUtils.IsSameFactionOrOwner(processing, x)).Cast().Where(x => x.Enabled && x.IsFunctional && x.IsBroadcasting)) + { + var sphere = new BoundingSphereD(antenna.GetPosition(), antenna.Radius); + var toCheck = MyAPIGateway.Entities.GetEntitiesInSphere(ref sphere).Where(x => x is IMyCubeGrid).Cast().Where(x => !gridsProcessed.Contains(x) && !gridsToProcess.Contains(x) && OwnershipUtils.IsSameFactionOrOwner(processing, x)).ToArray(); + if (toCheck.Length != 0) Write("Found grids to check: " + string.Join(", ", toCheck.Select(x => "\"" + x.DisplayName + "\""))); + gridsToProcess.AddRange(toCheck); + } + + foreach (var antenna in gridBlocks.Where(x => x is IMyLaserAntenna && OwnershipUtils.IsSameFactionOrOwner(processing, x)).Cast().Where(x => x.Status == MyLaserAntennaStatus.Connected)) + { + var sphere = new BoundingSphereD(antenna.TargetCoords, 1); + var toCheck = MyAPIGateway.Entities.GetEntitiesInSphere(ref sphere).Where(x => x is IMyCubeGrid).Cast().Where(x => !gridsProcessed.Contains(x) && !gridsToProcess.Contains(x) && OwnershipUtils.IsSameFactionOrOwner(processing, x)).ToArray(); + if (toCheck.Length != 0) Write("Found grids to check: " + string.Join(", ", toCheck.Select(x => "\"" + x.DisplayName + "\""))); + gridsToProcess.AddRange(toCheck); + } + + gridsProcessed.Add(processing); + } + + return target; + } + } +} \ No newline at end of file diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Config.cs b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Config.cs new file mode 100644 index 00000000..5b548a2f --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Config.cs @@ -0,0 +1,7 @@ +namespace CargoTeleporter +{ + public static class Config + { + public const bool enableDebug = false; + } +} diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Logging.cs b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Logging.cs new file mode 100644 index 00000000..815d5570 --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/Logging.cs @@ -0,0 +1,45 @@ +using Sandbox.ModAPI; +using System; +using System.IO; + +namespace CargoTeleporter +{ + public static class Logging + { + private static TextWriter writer = null; + public static void Setup() + { + try + { + writer = MyAPIGateway.Utilities.WriteFileInLocalStorage("CargoTeleport" + ".log", typeof(Logging)); + } + catch { } + } + + public static void WriteLine(string s) + { + try + { + if (writer == null) + Setup(); + writer.WriteLine(DateTime.Now.ToString("[HH:mm:ss] ") + s); + writer.Flush(); + } + catch { } + } + + public static void Close() + { + try + { + if (writer != null) + { + writer.Flush(); + writer.Close(); + } + } + catch { } + + } + } +} diff --git a/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/OwnershipUtils.cs b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/OwnershipUtils.cs new file mode 100644 index 00000000..1998bff2 --- /dev/null +++ b/BasicCargoTeleporter/Data/Scripts/CargoTeleporter/OwnershipUtils.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using VRage.Game.ModAPI; + +namespace CargoTeleporter +{ + public static class OwnershipUtils + { + + //Faction + public static string GetFaction(IMyCubeGrid grid) + { + var blocks = grid.GetFatBlocks(); + return !blocks.Any() ? "" : blocks.First().GetOwnerFactionTag(); + } + + public static bool IsSameFaction(IMyCubeBlock blockA, IMyCubeBlock blockB) + { + return blockA.GetOwnerFactionTag() == blockB.GetOwnerFactionTag(); + } + + public static bool IsSameFaction(IMyCubeGrid gridA, IMyCubeBlock blockB) + { + return GetFaction(gridA) == blockB.GetOwnerFactionTag(); + } + + public static bool IsSameFaction(IMyCubeGrid gridA, IMyCubeGrid gridB) + { + return GetFaction(gridA) == GetFaction(gridB); + } + + //Ownership + public static bool IsSameOwner(IMyCubeBlock blockA, IMyCubeBlock blockB) + { + return blockA.OwnerId == blockB.OwnerId; + } + + public static bool IsSameOwner(long playerID, IMyCubeBlock blockB) + { + return playerID == blockB.OwnerId; + } + + public static bool IsSameFactionOrOwner(IMyCubeBlock blockA, IMyCubeBlock blockB) + { + return IsSameOwner(blockA, blockB) || IsSameFaction(blockA, blockB); + } + + internal static bool IsSameFactionOrOwner(IMyCubeGrid gridA, IMyCubeBlock blockB) + { + return IsSameFaction(gridA, blockB) || gridA.BigOwners.Any(x => IsSameOwner(x, blockB)); + } + + internal static bool IsSameFactionOrOwner(IMyCubeGrid gridA, IMyCubeGrid gridB) + { + return IsSameFaction(gridA, gridB) || gridA.BigOwners.Any(x => gridB.BigOwners.Contains(x)); + } + } +} diff --git a/ConfigurableGridPoints/Audio/MW4_Enemy_Target_Destroyed.wav b/ConfigurableGridPoints/Audio/MW4_Enemy_Target_Destroyed.wav new file mode 100644 index 00000000..1be193aa Binary files /dev/null and b/ConfigurableGridPoints/Audio/MW4_Enemy_Target_Destroyed.wav differ diff --git a/ConfigurableGridPoints/Audio/MW4_Friendly_Unit_Destroyed.wav b/ConfigurableGridPoints/Audio/MW4_Friendly_Unit_Destroyed.wav new file mode 100644 index 00000000..95aced01 Binary files /dev/null and b/ConfigurableGridPoints/Audio/MW4_Friendly_Unit_Destroyed.wav differ diff --git a/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Captured.wav b/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Captured.wav new file mode 100644 index 00000000..d20532b3 Binary files /dev/null and b/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Captured.wav differ diff --git a/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Contested.wav b/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Contested.wav new file mode 100644 index 00000000..182aa7c5 Binary files /dev/null and b/ConfigurableGridPoints/Audio/MW4_V_Betty_MULTIPLAYER_Hill_Contested.wav differ diff --git a/ConfigurableGridPoints/Data/CaptureZoneSound.sbc b/ConfigurableGridPoints/Data/CaptureZoneSound.sbc new file mode 100644 index 00000000..d9e92779 --- /dev/null +++ b/ConfigurableGridPoints/Data/CaptureZoneSound.sbc @@ -0,0 +1,81 @@ + + + + + + MyObjectBuilder_AudioDefinition + Zone_Captured + + SHOT + 400 + 2 + HeavyFight + 3 + 4 + false + + + Audio/MW4_V_Betty_MULTIPLAYER_Hill_Captured.wav + + + + + + + + MyObjectBuilder_AudioDefinition + Zone_Lost + + SHOT + 400 + 2 + HeavyFight + 3 + 4 + false + + + Audio/MW4_V_Betty_MULTIPLAYER_Hill_Contested.wav + + + + + + + MyObjectBuilder_AudioDefinition + Enemy_Destroyed + + SHOT + 400 + 2 + HeavyFight + 3 + 4 + false + + + Audio/MW4_Enemy_Target_Destroyed.wav + + + + + + + MyObjectBuilder_AudioDefinition + Team_Destroyed + + SHOT + 400 + 2 + HeavyFight + 3 + 4 + false + + + Audio/MW4_Friendly_Unit_Destroyed.wav + + + + + \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBase.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBase.cs new file mode 100644 index 00000000..b832a4b2 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBase.cs @@ -0,0 +1,942 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRage.Collections; +using VRage.Game; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRageMath; + +//using CoreSystems.Platform; + +namespace CGP.ShareTrack.API.CoreSystem +{ + /// + /// https://github.com/sstixrud/CoreSystems/blob/master/BaseData/Scripts/CoreSystems/Api/CoreSystemsApiBase.cs + /// + public partial class WcApi + { + public enum ChangeMode + { + Add, + Release, + Lock + } + + public enum EventTriggers + { + Reloading, + Firing, + Tracking, + Overheated, + TurnOn, + TurnOff, + BurstReload, + NoMagsToLoad, + PreFire, + EmptyOnGameLoad, + StopFiring, + StopTracking, + LockDelay, + Init, + Homing, + TargetAligned, + WhileOn, + TargetRanged100, + TargetRanged75, + TargetRanged50, + TargetRanged25 + } + + public enum ShootState + { + EventStart, + EventEnd, + Preceding, + Canceled + } + + private const long Channel = 67549756549; + + /// + /// Only filled if giving true to . + /// + public readonly List WeaponDefinitions = new List(); + + private Action> _addProjectileMonitor; + private bool _apiInit; + private Func _canShootTarget; + private Func _currentPowerConsumption; + private Action _disableRequiredPower; + private Action _fireWeaponOnce; + private Func _forceReload; + private Func _getActiveAmmo; + private Func _getAiFocus; + + private Action>>>> + _getAllNpcSafeWeaponMagazines; + + private Action> _getAllWeaponDefinitions; + + private Action>>>> + _getAllWeaponMagazines; + + private Func _getConstructEffectiveDps; + private Action> _getCoreArmors; + private Action> _getCorePhantoms; + private Action> _getCoreRifles; + + private Action> _getCoreStaticLaunchers; + private Action> _getCoreTurrets; + private Action> _getCoreWeapons; + private Func _getHeatLevel; + private Func> _getMagazineMap; + private Func _getMaxPower; + private Func _getMaxWeaponRange; + + private Action>> + _getMuzzleInfo; + + private Action> _getNpcSafeWeapons; + private Action> _getObstructions; + + private Func _getOptimalDps; + + private Func _getPlayerController; + private Func _getPredictedTargetPos; + private Func> _getProjectilesLockedOn; + private Action> _getProjectilesLockedOnPos; + + private Func> _getProjectileState; + private Func _getShotsFired; + + private Action>> _getSortedThreats; + private Func, int, bool> _getTurretTargetTypes; + + private Func _getWeaponAzimuthMatrix; + private bool _getWeaponDefinitions; + private Func _getWeaponElevationMatrix; + private Func> _getWeaponScope; + + private Func> _getWeaponTarget; + private Func _hasAi; + private Func _hasCoreWeapon; + private Func, bool> _hudHandler; + private Func> _isInRange; + private bool _isRegistered; + private Func _isTargetAligned; + private Func> _isTargetAlignedExtended; + private Func _isTargetValid; + private Func _isWeaponReadyToFire; + private Func _isWeaponShooting; + private Action> _monitorEvents; + private Action _readyCallback; + + private Action>>>>> + _registerDamageEvent; + + private Func _releaseAiFocus; + private Action> _removeProjectileMonitor; + private Action _setActiveAmmo; + private Func _setAiFocus; + private Action _setBlockTrackingRange; + private Func _setMagazine; + private Action> _setProjectileState; + private Action, int> _setTurretTargetTypes; + private Action _setWeaponTarget; + private Func, bool> _shootHandler; + + private Func _shootRequest; + private Func, bool> _targetFocusHandler; + private Action _toggleWeaponFire; + + private Func _toggoleInfiniteResources; + private Action> _unmonitorEvents; + + /// + /// True if CoreSystems replied when got called. + /// + public bool IsReady { get; private set; } + + public void SetWeaponTarget(MyEntity weapon, MyEntity target, int weaponId = 0) + { + _setWeaponTarget?.Invoke(weapon, target, weaponId); + } + + public void FireWeaponOnce(MyEntity weapon, bool allWeapons = true, int weaponId = 0) + { + _fireWeaponOnce?.Invoke(weapon, allWeapons, weaponId); + } + + public void ToggleWeaponFire(MyEntity weapon, bool on, bool allWeapons, int weaponId = 0) + { + _toggleWeaponFire?.Invoke(weapon, on, allWeapons, weaponId); + } + + public bool IsWeaponReadyToFire(MyEntity weapon, int weaponId = 0, bool anyWeaponReady = true, + bool shootReady = false) + { + return _isWeaponReadyToFire?.Invoke(weapon, weaponId, anyWeaponReady, shootReady) ?? false; + } + + public float GetMaxWeaponRange(MyEntity weapon, int weaponId) + { + return _getMaxWeaponRange?.Invoke(weapon, weaponId) ?? 0f; + } + + public bool GetTurretTargetTypes(MyEntity weapon, IList collection, int weaponId = 0) + { + return _getTurretTargetTypes?.Invoke(weapon, collection, weaponId) ?? false; + } + + public void SetTurretTargetTypes(MyEntity weapon, IList collection, int weaponId = 0) + { + _setTurretTargetTypes?.Invoke(weapon, collection, weaponId); + } + + public void SetBlockTrackingRange(MyEntity weapon, float range) + { + _setBlockTrackingRange?.Invoke(weapon, range); + } + + public bool IsTargetAligned(MyEntity weapon, MyEntity targetEnt, int weaponId) + { + return _isTargetAligned?.Invoke(weapon, targetEnt, weaponId) ?? false; + } + + public MyTuple IsTargetAlignedExtended(MyEntity weapon, MyEntity targetEnt, int weaponId) + { + return _isTargetAlignedExtended?.Invoke(weapon, targetEnt, weaponId) ?? new MyTuple(); + } + + public bool CanShootTarget(MyEntity weapon, MyEntity targetEnt, int weaponId) + { + return _canShootTarget?.Invoke(weapon, targetEnt, weaponId) ?? false; + } + + public Vector3D? GetPredictedTargetPosition(MyEntity weapon, MyEntity targetEnt, int weaponId) + { + return _getPredictedTargetPos?.Invoke(weapon, targetEnt, weaponId) ?? null; + } + + public float GetHeatLevel(MyEntity weapon) + { + return _getHeatLevel?.Invoke(weapon) ?? 0f; + } + + public float GetCurrentPower(MyEntity weapon) + { + return _currentPowerConsumption?.Invoke(weapon) ?? 0f; + } + + public void DisableRequiredPower(MyEntity weapon) + { + _disableRequiredPower?.Invoke(weapon); + } + + public bool HasCoreWeapon(MyEntity weapon) + { + return _hasCoreWeapon?.Invoke(weapon) ?? false; + } + + public string GetActiveAmmo(MyEntity weapon, int weaponId) + { + return _getActiveAmmo?.Invoke(weapon, weaponId) ?? null; + } + + public void SetActiveAmmo(MyEntity weapon, int weaponId, string ammoType) + { + _setActiveAmmo?.Invoke(weapon, weaponId, ammoType); + } + + public long GetPlayerController(MyEntity weapon) + { + return _getPlayerController?.Invoke(weapon) ?? -1; + } + + public Matrix GetWeaponAzimuthMatrix(MyEntity weapon, int weaponId) + { + return _getWeaponAzimuthMatrix?.Invoke(weapon, weaponId) ?? Matrix.Zero; + } + + public Matrix GetWeaponElevationMatrix(MyEntity weapon, int weaponId) + { + return _getWeaponElevationMatrix?.Invoke(weapon, weaponId) ?? Matrix.Zero; + } + + public bool IsTargetValid(MyEntity weapon, MyEntity target, bool onlyThreats, bool checkRelations) + { + return _isTargetValid?.Invoke(weapon, target, onlyThreats, checkRelations) ?? false; + } + + public void GetAllWeaponDefinitions(IList collection) + { + _getAllWeaponDefinitions?.Invoke(collection); + } + + public void GetAllCoreWeapons(ICollection collection) + { + _getCoreWeapons?.Invoke(collection); + } + + public void GetNpcSafeWeapons(ICollection collection) + { + _getNpcSafeWeapons?.Invoke(collection); + } + + public void GetAllCoreStaticLaunchers(ICollection collection) + { + _getCoreStaticLaunchers?.Invoke(collection); + } + + public void GetAllWeaponMagazines( + IDictionary>>> collection) + { + _getAllWeaponMagazines?.Invoke(collection); + } + + public void GetAllNpcSafeWeaponMagazines( + IDictionary>>> collection) + { + _getAllNpcSafeWeaponMagazines?.Invoke(collection); + } + + public void GetAllCoreTurrets(ICollection collection) + { + _getCoreTurrets?.Invoke(collection); + } + + public void GetAllCorePhantoms(ICollection collection) + { + _getCorePhantoms?.Invoke(collection); + } + + public void GetAllCoreRifles(ICollection collection) + { + _getCoreRifles?.Invoke(collection); + } + + public void GetAllCoreArmors(IList collection) + { + _getCoreArmors?.Invoke(collection); + } + + public MyTuple GetProjectilesLockedOn(MyEntity victim) + { + return _getProjectilesLockedOn?.Invoke(victim) ?? new MyTuple(); + } + + public void GetProjectilesLockedOnPos(MyEntity victim, ICollection collection) + { + _getProjectilesLockedOnPos?.Invoke(victim, collection); + } + + public void GetSortedThreats(MyEntity shooter, ICollection> collection) + { + _getSortedThreats?.Invoke(shooter, collection); + } + + public void GetObstructions(MyEntity shooter, ICollection collection) + { + _getObstructions?.Invoke(shooter, collection); + } + + public MyEntity GetAiFocus(MyEntity shooter, int priority = 0) + { + return _getAiFocus?.Invoke(shooter, priority); + } + + public bool SetAiFocus(MyEntity shooter, MyEntity target, int priority = 0) + { + return _setAiFocus?.Invoke(shooter, target, priority) ?? false; + } + + public bool ReleaseAiFocus(MyEntity shooter, long playerId) + { + return _releaseAiFocus?.Invoke(shooter, playerId) ?? false; + } + + public MyTuple GetWeaponTarget(MyEntity weapon, int weaponId = 0) + { + return _getWeaponTarget?.Invoke(weapon, weaponId) ?? new MyTuple(); + } + + public float GetMaxPower(MyDefinitionId weaponDef) + { + return _getMaxPower?.Invoke(weaponDef) ?? 0f; + } + + public bool HasAi(MyEntity entity) + { + return _hasAi?.Invoke(entity) ?? false; + } + + public float GetOptimalDps(MyEntity entity) + { + return _getOptimalDps?.Invoke(entity) ?? 0f; + } + + public MyTuple GetProjectileState(ulong projectileId) + { + return _getProjectileState?.Invoke(projectileId) ?? + new MyTuple(); + } + + public float GetConstructEffectiveDps(MyEntity entity) + { + return _getConstructEffectiveDps?.Invoke(entity) ?? 0f; + } + + public MyTuple GetWeaponScope(MyEntity weapon, int weaponId) + { + return _getWeaponScope?.Invoke(weapon, weaponId) ?? new MyTuple(); + } + + public void AddProjectileCallback(MyEntity entity, int weaponId, + Action action) + { + _addProjectileMonitor?.Invoke(entity, weaponId, action); + } + + public void RemoveProjectileCallback(MyEntity entity, int weaponId, + Action action) + { + _removeProjectileMonitor?.Invoke(entity, weaponId, action); + } + + + // block/grid/player, Threat, Other + public MyTuple IsInRange(MyEntity entity) + { + return _isInRange?.Invoke(entity) ?? new MyTuple(); + } + + /// + /// Set projectile values *Warning* be sure to pass in Vector3D.MinValue or float.MinValue to NOT set that value. + /// bool = EndNow + /// Vector3D Position + /// Vector3D Additive velocity + /// float BaseDamagePool + /// + /// + /// + public void SetProjectileState(ulong projectileId, MyTuple values) + { + _setProjectileState?.Invoke(projectileId, values); + } + + /// + /// Gets whether the weapon is shooting, used by Hakerman's Beam Logic + /// Unexpected behavior may occur when using this method + /// + /// + /// + /// + internal bool IsWeaponShooting(MyEntity weaponBlock, int weaponId) + { + return _isWeaponShooting?.Invoke(weaponBlock, weaponId) ?? false; + } + + /// + /// Gets how many shots the weapon fired, used by Hakerman's Beam Logic + /// Unexpected behavior may occur when using this method + /// + /// + /// + /// + internal int GetShotsFired(MyEntity weaponBlock, int weaponId) + { + return _getShotsFired?.Invoke(weaponBlock, weaponId) ?? -1; + } + + /// + /// Gets the info of the weapon's all muzzles, used by Hakerman's Beam Logic + /// returns: A list that contains every muzzle's Position, LocalPosition, Direction, UpDirection, ParentMatrix, + /// DummyMatrix + /// Unexpected behavior may occur when using this method + /// + /// + /// + /// + internal void GetMuzzleInfo(MyEntity weaponBlock, int weaponId, + List> output) + { + _getMuzzleInfo?.Invoke(weaponBlock, weaponId, output); + } + + /// + /// Entity can be a weapon or a grid/player (enables on all subgrids as well) + /// + /// + /// + public bool ToggleInfiniteResources(MyEntity entity) + { + return _toggoleInfiniteResources?.Invoke(entity) ?? false; + } + + /// + /// Monitor various kind of events, see WcApiDef.WeaponDefinition.AnimationDef.PartAnimationSetDef.EventTriggers for + /// int mapping, bool is for active/inactive + /// + /// + /// + /// + /// + public void MonitorEvents(MyEntity entity, int partId, Action action) + { + _monitorEvents?.Invoke(entity, partId, action); + } + + /// + /// Monitor various kind of events, see WcApiDef.WeaponDefinition.AnimationDef.PartAnimationSetDef.EventTriggers for + /// int mapping, bool is for active/inactive + /// + /// + /// + /// + /// + public void UnMonitorEvents(MyEntity entity, int partId, Action action) + { + _unmonitorEvents?.Invoke(entity, partId, action); + } + + /// + /// Monitor all weaponcore damage + /// + /// + /// + /// 0 unregister, 1 register + /// + /// object casts (ulong = projectileId, IMySlimBlock, MyFloatingObject, IMyCharacter, MyVoxelBase, MyPlanet, MyEntity Shield see next line) + /// You can detect the shield entity in a performant way by creating a hash check ShieldHash = MyStringHash.GetOrCompute("DefenseShield"); + /// then use it by Session.ShieldApiLoaded && Session.ShieldHash == ent.DefinitionId?.SubtypeId && ent.Render.Visible; Visible means shield online + public void RegisterDamageEvent(long modId, int type, + Action>>>> callback) + { + _registerDamageEvent?.Invoke(modId, type, callback); + } + + /// + /// This allows you to determine when and if a player can modify the current target focus on a player/grid/phantonm. + /// Use only on server + /// + /// + /// is the player/grid/phantom you want to control the target focus for, applies to subgrids + /// as well + /// + /// be sure to unregister when you no longer want to receive callbacks + public void TargetFocushandler(long handledEntityId, bool unregister) + { + _targetFocusHandler(handledEntityId, unregister, TargetFocusCallback); + } + + /// + /// This callback fires whenever a player attempts to modify the target focus + /// + /// + /// + /// + /// + /// + private bool TargetFocusCallback(MyEntity target, IMyCharacter requestingCharacter, long handledEntityId, + int modeCode) + { + var mode = (ChangeMode)modeCode; + + return true; + } + + /// + /// Enables you to allow/deny hud draw requests. Do not use this on dedicated server. + /// + /// + /// + public void Hudhandler(long handledEntityId, bool unregister) + { + _hudHandler?.Invoke(handledEntityId, unregister, HudCallback); + } + + /// + /// This callback fires whenever the hud tries to update + /// + /// + /// + /// + /// + private bool HudCallback(IMyCharacter requestingCharacter, long handledEntityId, int modeCode) + { + var mode = (HudMode)modeCode; + + return true; + } + + /// + /// + /// + /// MyEntity, ulong (projectile) or Vector3D + /// Most weapons have only id 0, but some are multi-weapon entities + /// + /// + public bool ShootRequest(MyEntity weaponEntity, object target, int weaponId = 0, + double additionalDeviateShotAngle = 0) + { + return _shootRequest?.Invoke(weaponEntity, target, weaponId, additionalDeviateShotAngle) ?? false; + } + + /// + /// Enables you to monitor and approve shoot requests for this weapon/construct/player/grid network + /// + /// + /// + /// + public void ShootRequestHandler(long handledEntityId, bool unregister, + Func callback) + { + _shootHandler?.Invoke(handledEntityId, unregister, callback); // see example callback below + } + + /// + /// This callback fires whenever a shoot request is being evaluated for against a success criteria or is pending some + /// action + /// + /// + /// + /// This is the state of your request, state 0 means proceeding as requested + /// + /// This is false if wc thinks the target is occluded, you can choose to allow it to proceed anyway or + /// not + /// + /// valid objects to cast too are MyEntity, ulong (projectile ids) and target Vector3Ds + /// + /// + /// + /// The number of times this callback will fire will depend on the relevant firing stages for + /// this weapon/ammo and how far it gets + /// + /// + private bool ShootCallBack(Vector3D scopePos, Vector3D scopeDirection, int requestState, bool hasLos, + object target, int currentAmmo, int remainingMags, int requestStage) + { + var stage = (EventTriggers)requestStage; + var state = (ShootState)requestState; + var targetAsEntity = target as MyEntity; + var targetAsProjectileId = target as ulong? ?? 0; + var targetAsPosition = target as Vector3D? ?? Vector3D.Zero; + + return true; + } + + /// + /// Get active ammo Mag map from weapon + /// + /// + /// + /// Mag definitionId, mag name, ammoRound name, weapon must aim (not manual aim) true/false + public MyTuple GetMagazineMap(MyEntity weapon, int weaponId) + { + return _getMagazineMap?.Invoke(weapon, weaponId) ?? new MyTuple(); + } + + /// + /// Set the active ammo type via passing Mag DefinitionId + /// + /// + /// + /// + /// + /// + public bool SetMagazine(MyEntity weapon, int weaponId, MyDefinitionId id, bool forceReload) + { + return _setMagazine?.Invoke(weapon, weaponId, id, forceReload) ?? false; + } + + /// + /// + /// + /// + /// + public bool ForceReload(MyEntity weapon, int weaponId) + { + return _forceReload?.Invoke(weapon, weaponId) ?? false; + } + + /// + /// Ask CoreSystems to send the API methods. + /// Throws an exception if it gets called more than once per session without . + /// + /// Method to be called when CoreSystems replies. + /// Set to true to fill . + public void Load(Action readyCallback = null, bool getWeaponDefinitions = false) + { + if (_isRegistered) + throw new Exception($"{GetType().Name}.Load() should not be called multiple times!"); + + _readyCallback = readyCallback; + _getWeaponDefinitions = getWeaponDefinitions; + _isRegistered = true; + MyAPIGateway.Utilities.RegisterMessageHandler(Channel, HandleMessage); + MyAPIGateway.Utilities.SendModMessage(Channel, "ApiEndpointRequest"); + } + + public void Unload() + { + MyAPIGateway.Utilities.UnregisterMessageHandler(Channel, HandleMessage); + + ApiAssign(null); + + _isRegistered = false; + _apiInit = false; + IsReady = false; + } + + private void HandleMessage(object obj) + { + if (_apiInit || obj is string + ) // the sent "ApiEndpointRequest" will also be received here, explicitly ignoring that + return; + + var dict = obj as IReadOnlyDictionary; + + if (dict == null) + return; + + ApiAssign(dict, _getWeaponDefinitions); + + IsReady = true; + _readyCallback?.Invoke(); + } + + public void ApiAssign(IReadOnlyDictionary delegates, bool getWeaponDefinitions = false) + { + _apiInit = delegates != null; + /// base methods + AssignMethod(delegates, "GetAllWeaponDefinitions", ref _getAllWeaponDefinitions); + AssignMethod(delegates, "GetCoreWeapons", ref _getCoreWeapons); + AssignMethod(delegates, "GetNpcSafeWeapons", ref _getNpcSafeWeapons); + + AssignMethod(delegates, "GetAllWeaponMagazines", ref _getAllWeaponMagazines); + AssignMethod(delegates, "GetAllNpcSafeWeaponMagazines", ref _getAllNpcSafeWeaponMagazines); + + AssignMethod(delegates, "GetCoreStaticLaunchers", ref _getCoreStaticLaunchers); + AssignMethod(delegates, "GetCoreTurrets", ref _getCoreTurrets); + AssignMethod(delegates, "GetCorePhantoms", ref _getCorePhantoms); + AssignMethod(delegates, "GetCoreRifles", ref _getCoreRifles); + AssignMethod(delegates, "GetCoreArmors", ref _getCoreArmors); + + AssignMethod(delegates, "GetBlockWeaponMap", ref _getBlockWeaponMap); + AssignMethod(delegates, "GetSortedThreatsBase", ref _getSortedThreats); + AssignMethod(delegates, "GetObstructionsBase", ref _getObstructions); + AssignMethod(delegates, "GetMaxPower", ref _getMaxPower); + AssignMethod(delegates, "GetProjectilesLockedOnBase", ref _getProjectilesLockedOn); + AssignMethod(delegates, "GetProjectilesLockedOnPos", ref _getProjectilesLockedOnPos); + AssignMethod(delegates, "GetAiFocusBase", ref _getAiFocus); + AssignMethod(delegates, "SetAiFocusBase", ref _setAiFocus); + AssignMethod(delegates, "ReleaseAiFocusBase", ref _releaseAiFocus); + AssignMethod(delegates, "HasGridAiBase", ref _hasAi); + AssignMethod(delegates, "GetOptimalDpsBase", ref _getOptimalDps); + AssignMethod(delegates, "GetConstructEffectiveDpsBase", ref _getConstructEffectiveDps); + AssignMethod(delegates, "IsInRangeBase", ref _isInRange); + AssignMethod(delegates, "GetProjectileState", ref _getProjectileState); + AssignMethod(delegates, "SetProjectileState", ref _setProjectileState); + + AssignMethod(delegates, "AddMonitorProjectile", ref _addProjectileMonitor); + AssignMethod(delegates, "RemoveMonitorProjectile", ref _removeProjectileMonitor); + + AssignMethod(delegates, "TargetFocusHandler", ref _targetFocusHandler); + AssignMethod(delegates, "HudHandler", ref _hudHandler); + AssignMethod(delegates, "ShootHandler", ref _shootHandler); + AssignMethod(delegates, "ShootRequest", ref _shootRequest); + + /// block methods + AssignMethod(delegates, "GetWeaponTargetBase", ref _getWeaponTarget); + AssignMethod(delegates, "SetWeaponTargetBase", ref _setWeaponTarget); + AssignMethod(delegates, "FireWeaponOnceBase", ref _fireWeaponOnce); + AssignMethod(delegates, "ToggleWeaponFireBase", ref _toggleWeaponFire); + AssignMethod(delegates, "IsWeaponReadyToFireBase", ref _isWeaponReadyToFire); + AssignMethod(delegates, "GetMaxWeaponRangeBase", ref _getMaxWeaponRange); + AssignMethod(delegates, "GetTurretTargetTypesBase", ref _getTurretTargetTypes); + AssignMethod(delegates, "SetTurretTargetTypesBase", ref _setTurretTargetTypes); + AssignMethod(delegates, "SetBlockTrackingRangeBase", ref _setBlockTrackingRange); + AssignMethod(delegates, "IsTargetAlignedBase", ref _isTargetAligned); + AssignMethod(delegates, "IsTargetAlignedExtendedBase", ref _isTargetAlignedExtended); + AssignMethod(delegates, "CanShootTargetBase", ref _canShootTarget); + AssignMethod(delegates, "GetPredictedTargetPositionBase", ref _getPredictedTargetPos); + AssignMethod(delegates, "GetHeatLevelBase", ref _getHeatLevel); + AssignMethod(delegates, "GetCurrentPowerBase", ref _currentPowerConsumption); + AssignMethod(delegates, "DisableRequiredPowerBase", ref _disableRequiredPower); + AssignMethod(delegates, "HasCoreWeaponBase", ref _hasCoreWeapon); + AssignMethod(delegates, "GetActiveAmmoBase", ref _getActiveAmmo); + AssignMethod(delegates, "SetActiveAmmoBase", ref _setActiveAmmo); + AssignMethod(delegates, "GetPlayerControllerBase", ref _getPlayerController); + AssignMethod(delegates, "GetWeaponAzimuthMatrixBase", ref _getWeaponAzimuthMatrix); + AssignMethod(delegates, "GetWeaponElevationMatrixBase", ref _getWeaponElevationMatrix); + AssignMethod(delegates, "IsTargetValidBase", ref _isTargetValid); + AssignMethod(delegates, "GetWeaponScopeBase", ref _getWeaponScope); + + //Phantom methods + AssignMethod(delegates, "GetTargetAssessment", ref _getTargetAssessment); + //AssignMethod(delegates, "GetPhantomInfo", ref _getPhantomInfo); + AssignMethod(delegates, "SetTriggerState", ref _setTriggerState); + AssignMethod(delegates, "AddMagazines", ref _addMagazines); + AssignMethod(delegates, "SetAmmo", ref _setAmmo); + AssignMethod(delegates, "ClosePhantom", ref _closePhantom); + AssignMethod(delegates, "SpawnPhantom", ref _spawnPhantom); + AssignMethod(delegates, "SetFocusTarget", ref _setPhantomFocusTarget); + + //Hakerman's Beam Logic + AssignMethod(delegates, "IsWeaponShootingBase", ref _isWeaponShooting); + AssignMethod(delegates, "GetShotsFiredBase", ref _getShotsFired); + AssignMethod(delegates, "GetMuzzleInfoBase", ref _getMuzzleInfo); + AssignMethod(delegates, "ToggleInfiniteAmmoBase", ref _toggoleInfiniteResources); + AssignMethod(delegates, "RegisterEventMonitor", ref _monitorEvents); + AssignMethod(delegates, "UnRegisterEventMonitor", ref _unmonitorEvents); + AssignMethod(delegates, "GetMagazineMap", ref _getMagazineMap); + + AssignMethod(delegates, "SetMagazine", ref _setMagazine); + AssignMethod(delegates, "ForceReload", ref _forceReload); + + // Damage handler + AssignMethod(delegates, "DamageHandler", ref _registerDamageEvent); + + if (getWeaponDefinitions) + { + var byteArrays = new List(); + GetAllWeaponDefinitions(byteArrays); + foreach (var byteArray in byteArrays) + WeaponDefinitions.Add( + MyAPIGateway.Utilities.SerializeFromBinary(byteArray)); + } + } + + private void AssignMethod(IReadOnlyDictionary delegates, string name, ref T field) + where T : class + { + if (delegates == null) + { + field = null; + return; + } + + Delegate del; + if (!delegates.TryGetValue(name, out del)) + throw new Exception($"{GetType().Name} :: Couldn't find {name} delegate of type {typeof(T)}"); + + field = del as T; + + if (field == null) + throw new Exception( + $"{GetType().Name} :: Delegate {name} is not type {typeof(T)}, instead it's: {del.GetType()}"); + } + + internal enum HudMode + { + Selector, + Reload, + TargetInfo, + Lead, + Drone, + PainterMarks + } + + public class DamageHandlerHelper + { + public enum EventType + { + Unregister, + SystemWideDamageEvents + } + + private readonly List _convertedObjects = new List(); + + private readonly Stack> _hitPool = + new Stack>(256); + + + private readonly WcApi _wcApi; + + public DamageHandlerHelper(WcApi wcApi) + { + _wcApi = wcApi; + } + + public void YourCallBackFunction(List list) + { + // Your code goes here + // + // Once this function completes the data in the list will be deleted... if you need to use the data in this list + // after this function completes make a copy of it. + // + // This is setup to be easy to use. If you need more performance modify the Default Callback for your purposes and avoid + // copying callbacks into new lists with ProjectileDamageEvent structs. Note that the ListReader will remain usable for only 1 tick, then it will be cleared by wc. + // + } + + + /// Don't touch anything below this line + public void RegisterForDamage(long modId, EventType type) + { + _wcApi.RegisterDamageEvent(modId, (int)type, DefaultCallBack); + } + + private void DefaultCallBack( + ListReader>>> + listReader) + { + YourCallBackFunction(ProcessEvents(listReader)); + CleanUpEvents(); + } + + private List ProcessEvents( + ListReader>>> + projectiles) + { + foreach (var p in projectiles) + { + var hits = _hitPool.Count > 0 ? _hitPool.Pop() : new List(); + + foreach (var hitObj in p.Item6) + hits.Add(new ProjectileDamageEvent.ProHit + { HitPosition = hitObj.Item1, ObjectHit = hitObj.Item2, Damage = hitObj.Item3 }); + _convertedObjects.Add(new ProjectileDamageEvent + { + ProId = p.Item1, PlayerId = p.Item2, WeaponId = p.Item3, WeaponEntity = p.Item4, + WeaponParent = p.Item5, ObjectsHit = hits + }); + } + + return _convertedObjects; + } + + private void CleanUpEvents() + { + foreach (var p in _convertedObjects) + { + p.ObjectsHit.Clear(); + _hitPool.Push(p.ObjectsHit); + } + + _convertedObjects.Clear(); + } + + public struct ProjectileDamageEvent + { + public ulong ProId; + public long PlayerId; + public int WeaponId; + public MyEntity WeaponEntity; + public MyEntity WeaponParent; + public List ObjectsHit; + + public struct ProHit + { + public Vector3D HitPosition; // To == first hit, From = projectile start position this frame + public object ObjectHit; // block, player, etc... + public float Damage; + } + } + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBlocks.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBlocks.cs new file mode 100644 index 00000000..ba5ddfb7 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiBlocks.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; + +namespace CGP.ShareTrack.API.CoreSystem +{ + /// + /// https://github.com/sstixrud/CoreSystems/blob/master/BaseData/Scripts/CoreSystems/Api/CoreSystemsApiBlocks.cs + /// + public partial class WcApi + { + private Func, bool> _getBlockWeaponMap; + + public bool GetBlockWeaponMap(IMyTerminalBlock weaponBlock, IDictionary collection) + { + return _getBlockWeaponMap?.Invoke(weaponBlock, collection) ?? false; + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiDefs.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiDefs.cs new file mode 100644 index 00000000..6aa82bf2 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiDefs.cs @@ -0,0 +1,1541 @@ +using System.Collections.Generic; +using ProtoBuf; +using VRageMath; + +//using static CoreSystems.Support.WeaponDefinition.AmmoDef.GraphicDef.LineDef; +//using static CoreSystems.Support.WeaponDefinition.AmmoDef.TrajectoryDef.ApproachDef; +//using static CoreSystems.Support.WeaponDefinition.AnimationDef.PartAnimationSetDef; + +namespace CGP.ShareTrack.API.CoreSystem +{ + public static class WcApiDef + { + [ProtoContract] + public class ContainerDefinition + { + [ProtoMember(2)] internal ArmorDefinition[] ArmorDefs; + [ProtoMember(4)] internal SupportDefinition[] SupportDefs; + [ProtoMember(3)] internal UpgradeDefinition[] UpgradeDefs; + [ProtoMember(1)] internal WeaponDefinition[] WeaponDefs; + } + + [ProtoContract] + public class ConsumeableDef + { + [ProtoMember(5)] internal float EnergyCost; + [ProtoMember(4)] internal bool Hybrid; + [ProtoMember(2)] internal string InventoryItem; + [ProtoMember(1)] internal string ItemName; + [ProtoMember(3)] internal int ItemsNeeded; + [ProtoMember(6)] internal float Strength; + } + + [ProtoContract] + public class UpgradeDefinition + { + [ProtoMember(3)] internal WeaponDefinition.AnimationDef Animations; + [ProtoMember(1)] internal ModelAssignmentsDef Assignments; + [ProtoMember(5)] internal ConsumeableDef[] Consumable; + [ProtoMember(2)] internal HardPointDef HardPoint; + [ProtoMember(4)] internal string ModPath; + + [ProtoContract] + public struct ModelAssignmentsDef + { + [ProtoMember(1)] internal MountPointDef[] MountPoints; + + [ProtoContract] + public struct MountPointDef + { + [ProtoMember(1)] internal string SubtypeId; + [ProtoMember(2)] internal float DurabilityMod; + [ProtoMember(3)] internal string IconName; + } + } + + [ProtoContract] + public struct HardPointDef + { + [ProtoMember(1)] internal string PartName; + [ProtoMember(2)] internal HardwareDef HardWare; + [ProtoMember(3)] internal UiDef Ui; + [ProtoMember(4)] internal OtherDef Other; + + [ProtoContract] + public struct UiDef + { + [ProtoMember(1)] internal bool StrengthModifier; + } + + [ProtoContract] + public struct HardwareDef + { + public enum HardwareType + { + Default + } + + [ProtoMember(1)] internal float InventorySize; + [ProtoMember(2)] internal HardwareType Type; + [ProtoMember(3)] internal int BlockDistance; + } + + [ProtoContract] + public struct OtherDef + { + [ProtoMember(1)] internal int ConstructPartCap; + [ProtoMember(2)] internal int EnergyPriority; + [ProtoMember(3)] internal bool Debug; + [ProtoMember(4)] internal double RestrictionRadius; + [ProtoMember(5)] internal bool CheckInflatedBox; + [ProtoMember(6)] internal bool CheckForAnySupport; + [ProtoMember(7)] internal bool StayCharged; + } + } + } + + [ProtoContract] + public class SupportDefinition + { + [ProtoMember(3)] internal WeaponDefinition.AnimationDef Animations; + [ProtoMember(1)] internal ModelAssignmentsDef Assignments; + [ProtoMember(5)] internal ConsumeableDef[] Consumable; + [ProtoMember(6)] internal SupportEffect Effect; + [ProtoMember(2)] internal HardPointDef HardPoint; + [ProtoMember(4)] internal string ModPath; + + [ProtoContract] + public struct ModelAssignmentsDef + { + [ProtoMember(1)] internal MountPointDef[] MountPoints; + + [ProtoContract] + public struct MountPointDef + { + [ProtoMember(1)] internal string SubtypeId; + [ProtoMember(2)] internal float DurabilityMod; + [ProtoMember(3)] internal string IconName; + } + } + + [ProtoContract] + public struct HardPointDef + { + [ProtoMember(1)] internal string PartName; + [ProtoMember(2)] internal HardwareDef HardWare; + [ProtoMember(3)] internal UiDef Ui; + [ProtoMember(4)] internal OtherDef Other; + + [ProtoContract] + public struct UiDef + { + [ProtoMember(1)] internal bool ProtectionControl; + } + + [ProtoContract] + public struct HardwareDef + { + [ProtoMember(1)] internal float InventorySize; + } + + [ProtoContract] + public struct OtherDef + { + [ProtoMember(1)] internal int ConstructPartCap; + [ProtoMember(2)] internal int EnergyPriority; + [ProtoMember(3)] internal bool Debug; + [ProtoMember(4)] internal double RestrictionRadius; + [ProtoMember(5)] internal bool CheckInflatedBox; + [ProtoMember(6)] internal bool CheckForAnySupport; + [ProtoMember(7)] internal bool StayCharged; + } + } + + [ProtoContract] + public struct SupportEffect + { + public enum AffectedBlocks + { + Armor, + ArmorPlus, + PlusFunctional, + All + } + + public enum Protections + { + KineticProt, + EnergeticProt, + GenericProt, + Regenerate, + Structural + } + + [ProtoMember(1)] internal Protections Protection; + [ProtoMember(2)] internal AffectedBlocks Affected; + [ProtoMember(3)] internal int BlockRange; + [ProtoMember(4)] internal int MaxPoints; + [ProtoMember(5)] internal int PointsPerCharge; + [ProtoMember(6)] internal int UsablePerSecond; + [ProtoMember(7)] internal int UsablePerMinute; + [ProtoMember(8)] internal float Overflow; + [ProtoMember(9)] internal float Effectiveness; + [ProtoMember(10)] internal float ProtectionMin; + [ProtoMember(11)] internal float ProtectionMax; + } + } + + [ProtoContract] + public class ArmorDefinition + { + [ProtoMember(4)] internal double EnergeticResistance; + [ProtoMember(2)] internal ArmorType Kind; + [ProtoMember(3)] internal double KineticResistance; + + [ProtoMember(1)] internal string[] SubtypeIds; + + internal enum ArmorType + { + Light, + Heavy, + NonArmor + } + } + + [ProtoContract] + public class WeaponDefinition + { + [ProtoMember(5)] internal AmmoDef[] Ammos; + [ProtoMember(3)] internal AnimationDef Animations; + [ProtoMember(1)] internal ModelAssignmentsDef Assignments; + [ProtoMember(4)] internal HardPointDef HardPoint; + [ProtoMember(6)] internal string ModPath; + [ProtoMember(2)] internal TargetingDef Targeting; + [ProtoMember(7)] internal Dictionary Upgrades; + + [ProtoContract] + public struct ModelAssignmentsDef + { + [ProtoMember(1)] internal MountPointDef[] MountPoints; + [ProtoMember(2)] internal string[] Muzzles; + [ProtoMember(3)] internal string Ejector; + [ProtoMember(4)] internal string Scope; + + [ProtoContract] + public struct MountPointDef + { + [ProtoMember(1)] internal string SubtypeId; + [ProtoMember(2)] internal string SpinPartId; + [ProtoMember(3)] internal string MuzzlePartId; + [ProtoMember(4)] internal string AzimuthPartId; + [ProtoMember(5)] internal string ElevationPartId; + [ProtoMember(6)] internal float DurabilityMod; + [ProtoMember(7)] internal string IconName; + } + } + + [ProtoContract] + public struct TargetingDef + { + public enum Threat + { + Projectiles, + Characters, + Grids, + Neutrals, + Meteors, + Other, + ScanNeutralGrid, + ScanFriendlyGrid, + ScanFriendlyCharacter, + ScanRoid, + ScanPlanet, + ScanEnemyCharacter, + ScanEnemyGrid, + ScanNeutralCharacter, + ScanUnOwnedGrid, + ScanOwnersGrid + } + + public enum BlockTypes + { + Any, + Offense, + Utility, + Power, + Production, + Thrust, + Jumping, + Steering + } + + [ProtoMember(1)] internal int TopTargets; + [ProtoMember(2)] internal int TopBlocks; + [ProtoMember(3)] internal double StopTrackingSpeed; + [ProtoMember(4)] internal float MinimumDiameter; + [ProtoMember(5)] internal float MaximumDiameter; + [ProtoMember(6)] internal bool ClosestFirst; + [ProtoMember(7)] internal BlockTypes[] SubSystems; + [ProtoMember(8)] internal Threat[] Threats; + [ProtoMember(9)] internal float MaxTargetDistance; + [ProtoMember(10)] internal float MinTargetDistance; + [ProtoMember(11)] internal bool IgnoreDumbProjectiles; + [ProtoMember(12)] internal bool LockedSmartOnly; + [ProtoMember(13)] internal bool UniqueTargetPerWeapon; + [ProtoMember(14)] internal int MaxTrackingTime; + [ProtoMember(15)] internal bool ShootBlanks; + [ProtoMember(19)] internal CommunicationDef Communications; + [ProtoMember(20)] internal bool FocusOnly; + [ProtoMember(21)] internal bool EvictUniqueTargets; + [ProtoMember(22)] internal int CycleTargets; + [ProtoMember(23)] internal int CycleBlocks; + + [ProtoContract] + public struct CommunicationDef + { + public enum Comms + { + NoComms, + BroadCast, + Relay, + Jamming, + RelayAndBroadCast + } + + public enum SecurityMode + { + Public, + Private, + Secure + } + + [ProtoMember(1)] internal bool StoreTargets; + [ProtoMember(2)] internal int StorageLimit; + [ProtoMember(3)] internal string StorageLocation; + [ProtoMember(4)] internal Comms Mode; + [ProtoMember(5)] internal SecurityMode Security; + [ProtoMember(6)] internal string BroadCastChannel; + [ProtoMember(7)] internal double BroadCastRange; + [ProtoMember(8)] internal double JammingStrength; + [ProtoMember(9)] internal string RelayChannel; + [ProtoMember(10)] internal double RelayRange; + [ProtoMember(11)] internal bool TargetPersists; + [ProtoMember(12)] internal bool StoreLimitPerBlock; + [ProtoMember(13)] internal int MaxConnections; + } + } + + + [ProtoContract] + public struct AnimationDef + { + [ProtoMember(1)] internal PartAnimationSetDef[] AnimationSets; + [ProtoMember(2)] internal PartEmissive[] Emissives; + [ProtoMember(3)] internal string[] HeatingEmissiveParts; + [ProtoMember(4)] internal Dictionary EventParticles; + + [ProtoContract(IgnoreListHandling = true)] + public struct PartAnimationSetDef + { + public enum EventTriggers + { + Reloading, + Firing, + Tracking, + Overheated, + TurnOn, + TurnOff, + BurstReload, + NoMagsToLoad, + PreFire, + EmptyOnGameLoad, + StopFiring, + StopTracking, + LockDelay + } + + public enum ResetConditions + { + None, + Home, + Off, + On, + Reloaded + } + + [ProtoMember(1)] internal string[] SubpartId; + [ProtoMember(2)] internal string BarrelId; + [ProtoMember(3)] internal uint StartupFireDelay; + [ProtoMember(4)] internal Dictionary AnimationDelays; + [ProtoMember(5)] internal EventTriggers[] Reverse; + [ProtoMember(6)] internal EventTriggers[] Loop; + [ProtoMember(7)] internal Dictionary EventMoveSets; + [ProtoMember(8)] internal EventTriggers[] TriggerOnce; + [ProtoMember(9)] internal EventTriggers[] ResetEmissives; + [ProtoMember(10)] internal ResetConditions Resets; + } + + [ProtoContract] + public struct PartEmissive + { + [ProtoMember(1)] internal string EmissiveName; + [ProtoMember(2)] internal string[] EmissivePartNames; + [ProtoMember(3)] internal bool CycleEmissivesParts; + [ProtoMember(4)] internal bool LeavePreviousOn; + [ProtoMember(5)] internal Vector4[] Colors; + [ProtoMember(6)] internal float[] IntensityRange; + } + + [ProtoContract] + public struct EventParticle + { + [ProtoMember(1)] internal string[] EmptyNames; + [ProtoMember(2)] internal string[] MuzzleNames; + [ProtoMember(3)] internal ParticleDef Particle; + [ProtoMember(4)] internal uint StartDelay; + [ProtoMember(5)] internal uint LoopDelay; + [ProtoMember(6)] internal bool ForceStop; + } + + [ProtoContract] + internal struct RelMove + { + public enum MoveType + { + Linear, + ExpoDecay, + ExpoGrowth, + Delay, + Show, //instant or fade + Hide //instant or fade + } + + [ProtoMember(1)] internal MoveType MovementType; + [ProtoMember(2)] internal XYZ[] LinearPoints; + [ProtoMember(3)] internal XYZ Rotation; + [ProtoMember(4)] internal XYZ RotAroundCenter; + [ProtoMember(5)] internal uint TicksToMove; + [ProtoMember(6)] internal string CenterEmpty; + [ProtoMember(7)] internal bool Fade; + [ProtoMember(8)] internal string EmissiveName; + + [ProtoContract] + internal struct XYZ + { + [ProtoMember(1)] internal double x; + [ProtoMember(2)] internal double y; + [ProtoMember(3)] internal double z; + } + } + } + + [ProtoContract] + public struct UpgradeValues + { + [ProtoMember(1)] internal string[] Ammo; + [ProtoMember(2)] internal Dependency[] Dependencies; + [ProtoMember(3)] internal int RateOfFireMod; + [ProtoMember(4)] internal int BarrelsPerShotMod; + [ProtoMember(5)] internal int ReloadMod; + [ProtoMember(6)] internal int MaxHeatMod; + [ProtoMember(7)] internal int HeatSinkRateMod; + [ProtoMember(8)] internal int ShotsInBurstMod; + [ProtoMember(9)] internal int DelayAfterBurstMod; + [ProtoMember(10)] internal int AmmoPriority; + + [ProtoContract] + public struct Dependency + { + internal string SubtypeId; + internal int Quanity; + } + } + + [ProtoContract] + public struct HardPointDef + { + public enum Prediction + { + Off, + Basic, + Accurate, + Advanced + } + + [ProtoMember(1)] internal string PartName; + [ProtoMember(2)] internal int DelayCeaseFire; + [ProtoMember(3)] internal float DeviateShotAngle; + [ProtoMember(4)] internal double AimingTolerance; + [ProtoMember(5)] internal Prediction AimLeadingPrediction; + [ProtoMember(6)] internal LoadingDef Loading; + [ProtoMember(7)] internal AiDef Ai; + [ProtoMember(8)] internal HardwareDef HardWare; + [ProtoMember(9)] internal UiDef Ui; + [ProtoMember(10)] internal HardPointAudioDef Audio; + [ProtoMember(11)] internal HardPointParticleDef Graphics; + [ProtoMember(12)] internal OtherDef Other; + [ProtoMember(13)] internal bool AddToleranceToTracking; + [ProtoMember(14)] internal bool CanShootSubmerged; + [ProtoMember(15)] internal bool NpcSafe; + [ProtoMember(16)] internal bool ScanTrackOnly; + + [ProtoContract] + public struct LoadingDef + { + [ProtoMember(1)] internal int ReloadTime; + [ProtoMember(2)] internal int RateOfFire; + [ProtoMember(3)] internal int BarrelsPerShot; + [ProtoMember(4)] internal int SkipBarrels; + [ProtoMember(5)] internal int TrajectilesPerBarrel; + [ProtoMember(6)] internal int HeatPerShot; + [ProtoMember(7)] internal int MaxHeat; + [ProtoMember(8)] internal int HeatSinkRate; + [ProtoMember(9)] internal float Cooldown; + [ProtoMember(10)] internal int DelayUntilFire; + [ProtoMember(11)] internal int ShotsInBurst; + [ProtoMember(12)] internal int DelayAfterBurst; + [ProtoMember(13)] internal bool DegradeRof; + [ProtoMember(14)] internal int BarrelSpinRate; + [ProtoMember(15)] internal bool FireFull; + [ProtoMember(16)] internal bool GiveUpAfter; + [ProtoMember(17)] internal bool DeterministicSpin; + [ProtoMember(18)] internal bool SpinFree; + [ProtoMember(19)] internal bool StayCharged; + [ProtoMember(20)] internal int MagsToLoad; + [ProtoMember(21)] internal int MaxActiveProjectiles; + [ProtoMember(22)] internal int MaxReloads; + [ProtoMember(23)] internal bool GoHomeToReload; + [ProtoMember(24)] internal bool DropTargetUntilLoaded; + } + + + [ProtoContract] + public struct UiDef + { + [ProtoMember(1)] internal bool RateOfFire; + [ProtoMember(2)] internal bool DamageModifier; + [ProtoMember(3)] internal bool ToggleGuidance; + [ProtoMember(4)] internal bool EnableOverload; + [ProtoMember(5)] internal bool AlternateUi; + [ProtoMember(6)] internal bool DisableStatus; + } + + + [ProtoContract] + public struct AiDef + { + [ProtoMember(1)] internal bool TrackTargets; + [ProtoMember(2)] internal bool TurretAttached; + [ProtoMember(3)] internal bool TurretController; + [ProtoMember(4)] internal bool PrimaryTracking; + [ProtoMember(5)] internal bool LockOnFocus; + [ProtoMember(6)] internal bool SuppressFire; + [ProtoMember(7)] internal bool OverrideLeads; + [ProtoMember(8)] internal int DefaultLeadGroup; + [ProtoMember(9)] internal bool TargetGridCenter; + } + + [ProtoContract] + public struct HardwareDef + { + public enum HardwareType + { + BlockWeapon = 0, + HandWeapon = 1, + Phantom = 6 + } + + [ProtoMember(1)] internal float RotateRate; + [ProtoMember(2)] internal float ElevateRate; + [ProtoMember(3)] internal Vector3D Offset; + [ProtoMember(4)] internal bool FixedOffset; + [ProtoMember(5)] internal int MaxAzimuth; + [ProtoMember(6)] internal int MinAzimuth; + [ProtoMember(7)] internal int MaxElevation; + [ProtoMember(8)] internal int MinElevation; + [ProtoMember(9)] internal float InventorySize; + [ProtoMember(10)] internal HardwareType Type; + [ProtoMember(11)] internal int HomeAzimuth; + [ProtoMember(12)] internal int HomeElevation; + [ProtoMember(13)] internal CriticalDef CriticalReaction; + [ProtoMember(14)] internal float IdlePower; + + [ProtoContract] + public struct CriticalDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal int DefaultArmedTimer; + [ProtoMember(3)] internal bool PreArmed; + [ProtoMember(4)] internal bool TerminalControls; + [ProtoMember(5)] internal string AmmoRound; + } + } + + [ProtoContract] + public struct HardPointAudioDef + { + [ProtoMember(1)] internal string ReloadSound; + [ProtoMember(2)] internal string NoAmmoSound; + [ProtoMember(3)] internal string HardPointRotationSound; + [ProtoMember(4)] internal string BarrelRotationSound; + [ProtoMember(5)] internal string FiringSound; + [ProtoMember(6)] internal bool FiringSoundPerShot; + [ProtoMember(7)] internal string PreFiringSound; + [ProtoMember(8)] internal uint FireSoundEndDelay; + [ProtoMember(9)] internal bool FireSoundNoBurst; + } + + [ProtoContract] + public struct OtherDef + { + [ProtoMember(1)] internal int ConstructPartCap; + [ProtoMember(2)] internal int EnergyPriority; + [ProtoMember(3)] internal int RotateBarrelAxis; + [ProtoMember(4)] internal bool MuzzleCheck; + [ProtoMember(5)] internal bool Debug; + [ProtoMember(6)] internal double RestrictionRadius; + [ProtoMember(7)] internal bool CheckInflatedBox; + [ProtoMember(8)] internal bool CheckForAnyWeapon; + [ProtoMember(9)] internal bool DisableLosCheck; + [ProtoMember(10)] internal bool NoVoxelLosCheck; + } + + [ProtoContract] + public struct HardPointParticleDef + { + [ProtoMember(1)] internal ParticleDef Effect1; + [ProtoMember(2)] internal ParticleDef Effect2; + } + } + + [ProtoContract] + public class AmmoDef + { + [ProtoMember(17)] internal AmmoAudioDef AmmoAudio; + [ProtoMember(16)] internal GraphicDef AmmoGraphics; + [ProtoMember(1)] internal string AmmoMagazine; + [ProtoMember(2)] internal string AmmoRound; + [ProtoMember(13)] internal AreaDamageDef AreaEffect; + [ProtoMember(24)] internal AreaOfDamageDef AreaOfDamage; + [ProtoMember(8)] internal float BackKickForce; + [ProtoMember(5)] internal float BaseDamage; + [ProtoMember(14)] internal BeamDef Beams; + [ProtoMember(9)] internal DamageScaleDef DamageScales; + [ProtoMember(21)] internal float DecayPerShot; + [ProtoMember(22)] internal EjectionDef Ejection; + [ProtoMember(4)] internal float EnergyCost; + [ProtoMember(20)] internal int EnergyMagazineSize; + [ProtoMember(25)] internal EwarDef Ewar; + [ProtoMember(15)] internal FragmentDef Fragment; + [ProtoMember(18)] internal bool HardPointUsable; + [ProtoMember(7)] internal float Health; + [ProtoMember(28)] internal double HeatModifier; + [ProtoMember(3)] internal bool HybridRound; + [ProtoMember(26)] internal bool IgnoreVoxels; + [ProtoMember(23)] internal bool IgnoreWater; + [ProtoMember(6)] internal float Mass; + [ProtoMember(31)] internal bool NoGridOrArmorScaling; + [ProtoMember(29)] internal bool NpcSafe; + [ProtoMember(11)] internal ObjectsHitDef ObjectsHit; + [ProtoMember(19)] internal PatternDef Pattern; + [ProtoMember(10)] internal ShapeDef Shape; + [ProtoMember(30)] internal SynchronizeDef Sync; + [ProtoMember(27)] internal bool Synchronize; + [ProtoMember(12)] internal TrajectoryDef Trajectory; + + [ProtoContract] + public struct SynchronizeDef + { + [ProtoMember(1)] internal bool Full; + [ProtoMember(2)] internal bool PointDefense; + [ProtoMember(3)] internal bool OnHitDeath; + } + + [ProtoContract] + public struct DamageScaleDef + { + [ProtoMember(1)] internal float MaxIntegrity; + [ProtoMember(2)] internal bool DamageVoxels; + [ProtoMember(3)] internal float Characters; + [ProtoMember(4)] internal bool SelfDamage; + [ProtoMember(5)] internal GridSizeDef Grids; + [ProtoMember(6)] internal ArmorDef Armor; + [ProtoMember(7)] internal CustomScalesDef Custom; + [ProtoMember(8)] internal ShieldDef Shields; + [ProtoMember(9)] internal FallOffDef FallOff; + [ProtoMember(10)] internal double HealthHitModifier; + [ProtoMember(11)] internal double VoxelHitModifier; + [ProtoMember(12)] internal DamageTypes DamageType; + [ProtoMember(13)] internal DeformDef Deform; + + [ProtoContract] + public struct FallOffDef + { + [ProtoMember(1)] internal float Distance; + [ProtoMember(2)] internal float MinMultipler; + } + + [ProtoContract] + public struct GridSizeDef + { + [ProtoMember(1)] internal float Large; + [ProtoMember(2)] internal float Small; + } + + [ProtoContract] + public struct ArmorDef + { + [ProtoMember(1)] internal float Armor; + [ProtoMember(2)] internal float Heavy; + [ProtoMember(3)] internal float Light; + [ProtoMember(4)] internal float NonArmor; + } + + [ProtoContract] + public struct CustomScalesDef + { + internal enum SkipMode + { + NoSkip, + Inclusive, + Exclusive + } + + [ProtoMember(1)] internal CustomBlocksDef[] Types; + [ProtoMember(2)] internal bool IgnoreAllOthers; + [ProtoMember(3)] internal SkipMode SkipOthers; + } + + [ProtoContract] + public struct DamageTypes + { + internal enum Damage + { + Energy, + Kinetic + } + + [ProtoMember(1)] internal Damage Base; + [ProtoMember(2)] internal Damage AreaEffect; + [ProtoMember(3)] internal Damage Detonation; + [ProtoMember(4)] internal Damage Shield; + } + + [ProtoContract] + public struct ShieldDef + { + internal enum ShieldType + { + Default, + Heal, + Bypass, + EmpRetired + } + + [ProtoMember(1)] internal float Modifier; + [ProtoMember(2)] internal ShieldType Type; + [ProtoMember(3)] internal float BypassModifier; + } + + [ProtoContract] + public struct DeformDef + { + internal enum DeformTypes + { + HitBlock, + AllDamagedBlocks, + NoDeform + } + + [ProtoMember(1)] internal DeformTypes DeformType; + [ProtoMember(2)] internal int DeformDelay; + } + } + + [ProtoContract] + public struct ShapeDef + { + public enum Shapes + { + LineShape, + SphereShape + } + + [ProtoMember(1)] internal Shapes Shape; + [ProtoMember(2)] internal double Diameter; + } + + [ProtoContract] + public struct ObjectsHitDef + { + [ProtoMember(1)] internal int MaxObjectsHit; + [ProtoMember(2)] internal bool CountBlocks; + } + + + [ProtoContract] + public struct CustomBlocksDef + { + [ProtoMember(1)] internal string SubTypeId; + [ProtoMember(2)] internal float Modifier; + } + + [ProtoContract] + public struct GraphicDef + { + [ProtoMember(1)] internal bool ShieldHitDraw; + [ProtoMember(2)] internal float VisualProbability; + [ProtoMember(3)] internal string ModelName; + [ProtoMember(4)] internal AmmoParticleDef Particles; + [ProtoMember(5)] internal LineDef Lines; + [ProtoMember(6)] internal DecalDef Decals; + + [ProtoContract] + public struct AmmoParticleDef + { + [ProtoMember(1)] internal ParticleDef Ammo; + [ProtoMember(2)] internal ParticleDef Hit; + [ProtoMember(3)] internal ParticleDef Eject; + } + + [ProtoContract] + public struct LineDef + { + internal enum Texture + { + Normal, + Cycle, + Chaos, + Wave + } + + public enum FactionColor + { + DontUse, + Foreground, + Background + } + + [ProtoMember(1)] internal TracerBaseDef Tracer; + [ProtoMember(2)] internal string TracerMaterial; + [ProtoMember(3)] internal Randomize ColorVariance; + [ProtoMember(4)] internal Randomize WidthVariance; + [ProtoMember(5)] internal TrailDef Trail; + [ProtoMember(6)] internal OffsetEffectDef OffsetEffect; + [ProtoMember(7)] internal bool DropParentVelocity; + + [ProtoContract] + public struct OffsetEffectDef + { + [ProtoMember(1)] internal double MaxOffset; + [ProtoMember(2)] internal double MinLength; + [ProtoMember(3)] internal double MaxLength; + } + + [ProtoContract] + public struct TracerBaseDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal float Length; + [ProtoMember(3)] internal float Width; + [ProtoMember(4)] internal Vector4 Color; + [ProtoMember(5)] internal uint VisualFadeStart; + [ProtoMember(6)] internal uint VisualFadeEnd; + [ProtoMember(7)] internal SegmentDef Segmentation; + [ProtoMember(8)] internal string[] Textures; + [ProtoMember(9)] internal Texture TextureMode; + [ProtoMember(10)] internal bool AlwaysDraw; + [ProtoMember(11)] internal FactionColor FactionColor; + + [ProtoContract] + public struct SegmentDef + { + [ProtoMember(1)] internal string Material; //retired + [ProtoMember(2)] internal double SegmentLength; + [ProtoMember(3)] internal double SegmentGap; + [ProtoMember(4)] internal double Speed; + [ProtoMember(5)] internal Vector4 Color; + [ProtoMember(6)] internal double WidthMultiplier; + [ProtoMember(7)] internal bool Reverse; + [ProtoMember(8)] internal bool UseLineVariance; + [ProtoMember(9)] internal Randomize ColorVariance; + [ProtoMember(10)] internal Randomize WidthVariance; + [ProtoMember(11)] internal string[] Textures; + [ProtoMember(12)] internal bool Enable; + [ProtoMember(13)] internal FactionColor FactionColor; + } + } + + [ProtoContract] + public struct TrailDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal string Material; + [ProtoMember(3)] internal int DecayTime; + [ProtoMember(4)] internal Vector4 Color; + [ProtoMember(5)] internal bool Back; + [ProtoMember(6)] internal float CustomWidth; + [ProtoMember(7)] internal bool UseWidthVariance; + [ProtoMember(8)] internal bool UseColorFade; + [ProtoMember(9)] internal string[] Textures; + [ProtoMember(10)] internal Texture TextureMode; + [ProtoMember(11)] internal bool AlwaysDraw; + [ProtoMember(12)] internal FactionColor FactionColor; + } + } + + [ProtoContract] + public struct DecalDef + { + [ProtoMember(1)] internal int MaxAge; + [ProtoMember(2)] internal TextureMapDef[] Map; + + [ProtoContract] + public struct TextureMapDef + { + [ProtoMember(1)] internal string HitMaterial; + [ProtoMember(2)] internal string DecalMaterial; + } + } + } + + [ProtoContract] + public struct BeamDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal bool ConvergeBeams; + [ProtoMember(3)] internal bool VirtualBeams; + [ProtoMember(4)] internal bool RotateRealBeam; + [ProtoMember(5)] internal bool OneParticle; + [ProtoMember(6)] internal bool FakeVoxelHits; + } + + [ProtoContract] + public struct FragmentDef + { + [ProtoMember(1)] internal string AmmoRound; + [ProtoMember(2)] internal int Fragments; + [ProtoMember(3)] internal float Radial; + [ProtoMember(4)] internal float BackwardDegrees; + [ProtoMember(5)] internal float Degrees; + [ProtoMember(6)] internal bool Reverse; + [ProtoMember(7)] internal bool IgnoreArming; + [ProtoMember(8)] internal bool DropVelocity; + [ProtoMember(9)] internal float Offset; + [ProtoMember(10)] internal int MaxChildren; + [ProtoMember(11)] internal TimedSpawnDef TimedSpawns; + [ProtoMember(12)] internal bool FireSound; + [ProtoMember(13)] internal Vector3D AdvOffset; + [ProtoMember(14)] internal bool ArmWhenHit; + + [ProtoContract] + public struct TimedSpawnDef + { + public enum PointTypes + { + Direct, + Lead, + Predict + } + + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal int Interval; + [ProtoMember(3)] internal int StartTime; + [ProtoMember(4)] internal int MaxSpawns; + [ProtoMember(5)] internal double Proximity; + [ProtoMember(6)] internal bool ParentDies; + [ProtoMember(7)] internal bool PointAtTarget; + [ProtoMember(8)] internal int GroupSize; + [ProtoMember(9)] internal int GroupDelay; + [ProtoMember(10)] internal PointTypes PointType; + } + } + + [ProtoContract] + public struct PatternDef + { + public enum PatternModes + { + Never, + Weapon, + Fragment, + Both + } + + + [ProtoMember(1)] internal string[] Patterns; + [ProtoMember(2)] internal bool Enable; + [ProtoMember(3)] internal float TriggerChance; + [ProtoMember(4)] internal bool SkipParent; + [ProtoMember(5)] internal bool Random; + [ProtoMember(6)] internal int RandomMin; + [ProtoMember(7)] internal int RandomMax; + [ProtoMember(8)] internal int PatternSteps; + [ProtoMember(9)] internal PatternModes Mode; + } + + [ProtoContract] + public struct EjectionDef + { + public enum SpawnType + { + Item, + Particle + } + + [ProtoMember(1)] internal float Speed; + [ProtoMember(2)] internal float SpawnChance; + [ProtoMember(3)] internal SpawnType Type; + [ProtoMember(4)] internal ComponentDef CompDef; + + [ProtoContract] + public struct ComponentDef + { + [ProtoMember(1)] internal string ItemName; + [ProtoMember(2)] internal int ItemLifeTime; + [ProtoMember(3)] internal int Delay; + } + } + + [ProtoContract] + public struct AreaOfDamageDef + { + public enum Falloff + { + Legacy, + NoFalloff, + Linear, + Curve, + InvCurve, + Squeeze, + Pooled, + Exponential + } + + public enum AoeShape + { + Round, + Diamond + } + + [ProtoMember(1)] internal ByBlockHitDef ByBlockHit; + [ProtoMember(2)] internal EndOfLifeDef EndOfLife; + + [ProtoContract] + public struct ByBlockHitDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal double Radius; + [ProtoMember(3)] internal float Damage; + [ProtoMember(4)] internal float Depth; + [ProtoMember(5)] internal float MaxAbsorb; + [ProtoMember(6)] internal Falloff Falloff; + [ProtoMember(7)] internal AoeShape Shape; + } + + [ProtoContract] + public struct EndOfLifeDef + { + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal double Radius; + [ProtoMember(3)] internal float Damage; + [ProtoMember(4)] internal float Depth; + [ProtoMember(5)] internal float MaxAbsorb; + [ProtoMember(6)] internal Falloff Falloff; + [ProtoMember(7)] internal bool ArmOnlyOnHit; + [ProtoMember(8)] internal int MinArmingTime; + [ProtoMember(9)] internal bool NoVisuals; + [ProtoMember(10)] internal bool NoSound; + [ProtoMember(11)] internal float ParticleScale; + [ProtoMember(12)] internal string CustomParticle; + [ProtoMember(13)] internal string CustomSound; + [ProtoMember(14)] internal AoeShape Shape; + } + } + + [ProtoContract] + public struct EwarDef + { + public enum EwarType + { + AntiSmart, + JumpNull, + EnergySink, + Anchor, + Emp, + Offense, + Nav, + Dot, + Push, + Pull, + Tractor + } + + public enum EwarMode + { + Effect, + Field + } + + [ProtoMember(1)] internal bool Enable; + [ProtoMember(2)] internal EwarType Type; + [ProtoMember(3)] internal EwarMode Mode; + [ProtoMember(4)] internal float Strength; + [ProtoMember(5)] internal double Radius; + [ProtoMember(6)] internal int Duration; + [ProtoMember(7)] internal bool StackDuration; + [ProtoMember(8)] internal bool Depletable; + [ProtoMember(9)] internal int MaxStacks; + [ProtoMember(10)] internal bool NoHitParticle; + [ProtoMember(11)] internal PushPullDef Force; + [ProtoMember(12)] internal FieldDef Field; + + + [ProtoContract] + public struct FieldDef + { + [ProtoMember(1)] internal int Interval; + [ProtoMember(2)] internal int PulseChance; + [ProtoMember(3)] internal int GrowTime; + [ProtoMember(4)] internal bool HideModel; + [ProtoMember(5)] internal bool ShowParticle; + [ProtoMember(6)] internal double TriggerRange; + [ProtoMember(7)] internal ParticleDef Particle; + } + + [ProtoContract] + public struct PushPullDef + { + public enum Force + { + ProjectileLastPosition, + ProjectileOrigin, + HitPosition, + TargetCenter, + TargetCenterOfMass + } + + [ProtoMember(1)] internal Force ForceFrom; + [ProtoMember(2)] internal Force ForceTo; + [ProtoMember(3)] internal Force Position; + [ProtoMember(4)] internal bool DisableRelativeMass; + [ProtoMember(5)] internal double TractorRange; + [ProtoMember(6)] internal bool ShooterFeelsForce; + } + } + + + [ProtoContract] + public struct AreaDamageDef + { + public enum AreaEffectType + { + Disabled, + Explosive, + Radiant, + AntiSmart, + JumpNullField, + EnergySinkField, + AnchorField, + EmpField, + OffenseField, + NavField, + DotField, + PushField, + PullField, + TractorField + } + + [ProtoMember(1)] internal double AreaEffectRadius; + [ProtoMember(2)] internal float AreaEffectDamage; + [ProtoMember(3)] internal AreaEffectType AreaEffect; + [ProtoMember(4)] internal PulseDef Pulse; + [ProtoMember(5)] internal DetonateDef Detonation; + [ProtoMember(6)] internal ExplosionDef Explosions; + [ProtoMember(7)] internal EwarFieldsDef EwarFields; + [ProtoMember(8)] internal AreaInfluence Base; + + [ProtoContract] + public struct AreaInfluence + { + [ProtoMember(1)] internal double Radius; + [ProtoMember(2)] internal float EffectStrength; + } + + + [ProtoContract] + public struct PulseDef + { + [ProtoMember(1)] internal int Interval; + [ProtoMember(2)] internal int PulseChance; + [ProtoMember(3)] internal int GrowTime; + [ProtoMember(4)] internal bool HideModel; + [ProtoMember(5)] internal bool ShowParticle; + [ProtoMember(6)] internal ParticleDef Particle; + } + + [ProtoContract] + public struct EwarFieldsDef + { + [ProtoMember(1)] internal int Duration; + [ProtoMember(2)] internal bool StackDuration; + [ProtoMember(3)] internal bool Depletable; + [ProtoMember(4)] internal double TriggerRange; + [ProtoMember(5)] internal int MaxStacks; + [ProtoMember(6)] internal PushPullDef Force; + [ProtoMember(7)] internal bool DisableParticleEffect; + + [ProtoContract] + public struct PushPullDef + { + public enum Force + { + ProjectileLastPosition, + ProjectileOrigin, + HitPosition, + TargetCenter, + TargetCenterOfMass + } + + [ProtoMember(1)] internal Force ForceFrom; + [ProtoMember(2)] internal Force ForceTo; + [ProtoMember(3)] internal Force Position; + [ProtoMember(4)] internal bool DisableRelativeMass; + [ProtoMember(5)] internal double TractorRange; + [ProtoMember(6)] internal bool ShooterFeelsForce; + } + } + + [ProtoContract] + public struct DetonateDef + { + [ProtoMember(1)] internal bool DetonateOnEnd; + [ProtoMember(2)] internal bool ArmOnlyOnHit; + [ProtoMember(3)] internal float DetonationRadius; + [ProtoMember(4)] internal float DetonationDamage; + [ProtoMember(5)] internal int MinArmingTime; + } + + [ProtoContract] + public struct ExplosionDef + { + [ProtoMember(1)] internal bool NoVisuals; + [ProtoMember(2)] internal bool NoSound; + [ProtoMember(3)] internal float Scale; + [ProtoMember(4)] internal string CustomParticle; + [ProtoMember(5)] internal string CustomSound; + [ProtoMember(6)] internal bool NoShrapnel; + [ProtoMember(7)] internal bool NoDeformation; + } + } + + [ProtoContract] + public struct AmmoAudioDef + { + [ProtoMember(1)] internal string TravelSound; + [ProtoMember(2)] internal string HitSound; + [ProtoMember(3)] internal float HitPlayChance; + [ProtoMember(4)] internal bool HitPlayShield; + [ProtoMember(5)] internal string VoxelHitSound; + [ProtoMember(6)] internal string PlayerHitSound; + [ProtoMember(7)] internal string FloatingHitSound; + [ProtoMember(8)] internal string ShieldHitSound; + [ProtoMember(9)] internal string ShotSound; + } + + [ProtoContract] + public struct TrajectoryDef + { + internal enum GuidanceType + { + None, + Remote, + TravelTo, + Smart, + DetectTravelTo, + DetectSmart, + DetectFixed, + DroneAdvanced + } + + [ProtoMember(1)] internal float MaxTrajectory; + [ProtoMember(2)] internal float AccelPerSec; + [ProtoMember(3)] internal float DesiredSpeed; + [ProtoMember(4)] internal float TargetLossDegree; + [ProtoMember(5)] internal int TargetLossTime; + [ProtoMember(6)] internal int MaxLifeTime; + [ProtoMember(7)] internal int DeaccelTime; + [ProtoMember(8)] internal Randomize SpeedVariance; + [ProtoMember(9)] internal Randomize RangeVariance; + [ProtoMember(10)] internal GuidanceType Guidance; + [ProtoMember(11)] internal SmartsDef Smarts; + [ProtoMember(12)] internal MinesDef Mines; + [ProtoMember(13)] internal float GravityMultiplier; + [ProtoMember(14)] internal uint MaxTrajectoryTime; + [ProtoMember(15)] internal ApproachDef[] Approaches; + [ProtoMember(16)] internal double TotalAcceleration; + + [ProtoContract] + public struct SmartsDef + { + [ProtoMember(1)] internal double Inaccuracy; + [ProtoMember(2)] internal double Aggressiveness; + [ProtoMember(3)] internal double MaxLateralThrust; + [ProtoMember(4)] internal double TrackingDelay; + [ProtoMember(5)] internal int MaxChaseTime; + [ProtoMember(6)] internal bool OverideTarget; + [ProtoMember(7)] internal int MaxTargets; + [ProtoMember(8)] internal bool NoTargetExpire; + [ProtoMember(9)] internal bool Roam; + [ProtoMember(10)] internal bool KeepAliveAfterTargetLoss; + [ProtoMember(11)] internal float OffsetRatio; + [ProtoMember(12)] internal int OffsetTime; + [ProtoMember(13)] internal bool CheckFutureIntersection; + [ProtoMember(14)] internal double NavAcceleration; + [ProtoMember(15)] internal bool AccelClearance; + [ProtoMember(16)] internal double SteeringLimit; + [ProtoMember(17)] internal bool FocusOnly; + [ProtoMember(18)] internal double OffsetMinRange; + [ProtoMember(19)] internal bool FocusEviction; + [ProtoMember(20)] internal double ScanRange; + [ProtoMember(21)] internal bool NoSteering; + [ProtoMember(22)] internal double FutureIntersectionRange; + [ProtoMember(23)] internal double MinTurnSpeed; + [ProtoMember(24)] internal bool NoTargetApproach; + [ProtoMember(25)] internal bool AltNavigation; + } + + [ProtoContract] + public struct ApproachDef + { + public enum ReInitCondition + { + Wait, + MoveToPrevious, + MoveToNext, + ForceRestart + } + + public enum Conditions + { + Ignore, + Spawn, + DistanceFromPositionC, + Lifetime, + DesiredElevation, + MinTravelRequired, + MaxTravelRequired, + Deadtime, + DistanceToPositionC, + NextTimedSpawn, + RelativeLifetime, + RelativeDeadtime, + SinceTimedSpawn, + RelativeSpawns, + EnemyTargetLoss, + RelativeHealthLost, + HealthRemaining, + DistanceFromPositionB, + DistanceToPositionB, + DistanceFromTarget, + DistanceToTarget, + DistanceFromEndTrajectory, + DistanceToEndTrajectory + } + + public enum UpRelativeTo + { + UpRelativeToBlock, + UpRelativeToGravity, + UpTargetDirection, + UpTargetVelocity, + UpStoredStartDontUse, + UpStoredEndDontUse, + UpStoredStartPosition, + UpStoredEndPosition, + UpStoredStartLocalPosition, + UpStoredEndLocalPosition, + UpRelativeToShooter, + UpOriginDirection, + UpElevationDirection + } + + public enum FwdRelativeTo + { + ForwardElevationDirection, + ForwardRelativeToBlock, + ForwardRelativeToGravity, + ForwardTargetDirection, + ForwardTargetVelocity, + ForwardStoredStartDontUse, + ForwardStoredEndDontUse, + ForwardStoredStartPosition, + ForwardStoredEndPosition, + ForwardStoredStartLocalPosition, + ForwardStoredEndLocalPosition, + ForwardRelativeToShooter, + ForwardOriginDirection + } + + public enum RelativeTo + { + Origin, + Shooter, + Target, + Surface, + MidPoint, + PositionA, + Nothing, + StoredStartDontUse, + StoredEndDontUse, + StoredStartPosition, + StoredEndPosition, + StoredStartLocalPosition, + StoredEndLocalPosition + } + + public enum ConditionOperators + { + StartEnd_And, + StartEnd_Or, + StartAnd_EndOr, + StartOr_EndAnd + } + + public enum StageEvents + { + DoNothing, + EndProjectile, + EndProjectileOnRestart, + StoreDontUse, + StorePositionDontUse, + Refund, + StorePositionA, + StorePositionB, + StorePositionC + } + + [ProtoContract] + public struct WeightedIdListDef + { + [ProtoMember(1)] public int ApproachId; + [ProtoMember(2)] public Randomize Weight; + [ProtoMember(3)] public double End1WeightMod; + [ProtoMember(4)] public double End2WeightMod; + [ProtoMember(5)] public int MaxRuns; + [ProtoMember(6)] public double End3WeightMod; + } + + [ProtoMember(1)] internal ReInitCondition RestartCondition; + [ProtoMember(2)] internal Conditions StartCondition1; + [ProtoMember(3)] internal Conditions EndCondition1; + [ProtoMember(4)] internal UpRelativeTo Up; + [ProtoMember(5)] internal RelativeTo PositionB; + [ProtoMember(6)] internal double AngleOffset; + [ProtoMember(7)] internal double Start1Value; + [ProtoMember(8)] internal double End1Value; + [ProtoMember(9)] internal double LeadDistance; + [ProtoMember(10)] internal double DesiredElevation; + [ProtoMember(11)] internal double AccelMulti; + [ProtoMember(12)] internal double SpeedCapMulti; + [ProtoMember(13)] internal bool AdjustPositionC; + [ProtoMember(14)] internal bool CanExpireOnceStarted; + [ProtoMember(15)] internal ParticleDef AlternateParticle; + [ProtoMember(16)] internal string AlternateSound; + [ProtoMember(17)] internal string AlternateModel; + [ProtoMember(18)] internal int OnRestartRevertTo; + [ProtoMember(19)] internal ParticleDef StartParticle; + [ProtoMember(20)] internal bool AdjustPositionB; + [ProtoMember(21)] internal bool AdjustUp; + [ProtoMember(22)] internal bool PushLeadByTravelDistance; + [ProtoMember(23)] internal double TrackingDistance; + [ProtoMember(24)] internal Conditions StartCondition2; + [ProtoMember(25)] internal double Start2Value; + [ProtoMember(26)] internal Conditions EndCondition2; + [ProtoMember(27)] internal double End2Value; + [ProtoMember(28)] internal RelativeTo Elevation; + [ProtoMember(29)] internal double ElevationTolerance; + [ProtoMember(30)] internal ConditionOperators Operators; + [ProtoMember(31)] internal StageEvents StartEvent; + [ProtoMember(32)] internal StageEvents EndEvent; + [ProtoMember(33)] internal double TotalAccelMulti; + [ProtoMember(34)] internal double DeAccelMulti; + [ProtoMember(35)] internal bool Orbit; + [ProtoMember(36)] internal double OrbitRadius; + [ProtoMember(37)] internal int OffsetTime; + [ProtoMember(38)] internal double OffsetMinRadius; + [ProtoMember(39)] internal bool NoTimedSpawns; + [ProtoMember(40)] internal double OffsetMaxRadius; + [ProtoMember(41)] internal bool ForceRestart; + [ProtoMember(42)] internal RelativeTo PositionC; + [ProtoMember(43)] internal bool DisableAvoidance; + [ProtoMember(44)] internal int StoredStartId; + [ProtoMember(45)] internal int StoredEndId; + [ProtoMember(46)] internal WeightedIdListDef[] RestartList; + [ProtoMember(47)] internal RelativeTo StoredStartType; + [ProtoMember(48)] internal RelativeTo StoredEndType; + [ProtoMember(49)] internal bool LeadRotateElevatePositionB; + [ProtoMember(50)] internal bool LeadRotateElevatePositionC; + [ProtoMember(51)] internal bool NoElevationLead; + [ProtoMember(52)] internal bool IgnoreAntiSmart; + [ProtoMember(53)] internal double HeatRefund; + [ProtoMember(54)] internal Randomize AngleVariance; + [ProtoMember(55)] internal bool ReloadRefund; + [ProtoMember(56)] internal int ModelRotateTime; + [ProtoMember(57)] internal FwdRelativeTo Forward; + [ProtoMember(58)] internal bool AdjustForward; + [ProtoMember(59)] internal bool ToggleIngoreVoxels; + [ProtoMember(60)] internal bool SelfAvoidance; + [ProtoMember(61)] internal bool TargetAvoidance; + [ProtoMember(62)] internal bool SelfPhasing; + [ProtoMember(63)] internal bool TrajectoryRelativeToB; + [ProtoMember(64)] internal Conditions EndCondition3; + [ProtoMember(65)] internal double End3Value; + [ProtoMember(66)] internal bool SwapNavigationType; + [ProtoMember(67)] internal bool ElevationRelativeToC; + } + + [ProtoContract] + public struct MinesDef + { + [ProtoMember(1)] internal double DetectRadius; + [ProtoMember(2)] internal double DeCloakRadius; + [ProtoMember(3)] internal int FieldTime; + [ProtoMember(4)] internal bool Cloak; + [ProtoMember(5)] internal bool Persist; + } + } + + [ProtoContract] + public struct Randomize + { + [ProtoMember(1)] internal float Start; + [ProtoMember(2)] internal float End; + } + } + + [ProtoContract] + public struct ParticleOptionDef + { + [ProtoMember(1)] internal float Scale; + [ProtoMember(2)] internal float MaxDistance; + [ProtoMember(3)] internal float MaxDuration; + [ProtoMember(4)] internal bool Loop; + [ProtoMember(5)] internal bool Restart; + [ProtoMember(6)] internal float HitPlayChance; + } + + + [ProtoContract] + public struct ParticleDef + { + [ProtoMember(1)] internal string Name; + [ProtoMember(2)] internal Vector4 Color; + [ProtoMember(3)] internal Vector3D Offset; + [ProtoMember(4)] internal ParticleOptionDef Extras; + [ProtoMember(5)] internal bool ApplyToShield; + [ProtoMember(6)] internal bool DisableCameraCulling; + } + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiPhantoms.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiPhantoms.cs new file mode 100644 index 00000000..09135359 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/CoreSystem/CoreSystemsApiPhantoms.cs @@ -0,0 +1,144 @@ +using System; +using VRage; +using VRage.Game.Entity; +using VRageMath; + +namespace CGP.ShareTrack.API.CoreSystem +{ + /// + /// https://github.com/sstixrud/CoreSystems/blob/master/BaseData/Scripts/CoreSystems/Api/CoreSystemsApiPhantoms.cs + /// + public partial class WcApi + { + public enum TriggerActions + { + TriggerOff, + TriggerOn, + TriggerOnce + } + + private Action _addMagazines; + private Func _closePhantom; + + private Func> _getTargetAssessment; + private Action _setAmmo; + + private Func _setPhantomFocusTarget; + + //private Action>> _getPhantomInfo; + private Action _setTriggerState; + + private Func + _spawnPhantom; + + /// + /// Get information about a particular target relative to this phantom + /// + /// + /// + /// + /// + /// + internal void GetTargetAssessment(MyEntity phantom, MyEntity target, int weapon = 0, bool mustBeInrange = false, + bool checkTargetOrb = false) + { + _getTargetAssessment?.Invoke(phantom, target, weapon, mustBeInrange, checkTargetOrb); + } + + /// + /// + /// + /// + /// + //internal void GetPhantomInfo(string phantomSubtypeId, ICollection> collection) => _getPhantomInfo?.Invoke(phantomSubtypeId, collection); + + /// + /// Change the phantoms current fire state + /// + /// + /// + internal void SetTriggerState(MyEntity phantom, TriggerActions trigger) + { + _setTriggerState?.Invoke(phantom, (int)trigger); + } + + /// + /// Add additional magazines + /// + /// + /// + /// + internal void AddMagazines(MyEntity phantom, int weapon, long quanity) + { + _addMagazines?.Invoke(phantom, weapon, quanity); + } + + /// + /// Set/switch ammo + /// + /// + /// + /// + internal void SetAmmo(MyEntity phantom, int weapon, string ammoName) + { + _setAmmo?.Invoke(phantom, weapon, ammoName); + } + + /// + /// Close phantoms, required for phantoms that do not auto close + /// + /// + /// + internal bool ClosePhantom(MyEntity phantom) + { + return _closePhantom?.Invoke(phantom) ?? false; + } + + /// + /// string: weapon subtypeId + /// uint: max age, defaults to never die, you must issue close request! Max duration is 14400 ticks (4 minutes) + /// bool: close when phantom runs out of ammo + /// long: Number of ammo reloads phantom has per default, prior to you adding more, defaults to long.MaxValue + /// string: name of the ammo you want the phantom to start with, if different than default + /// TriggerActions: TriggerOff, TriggerOn, TriggerOnce + /// float?: scales the model if defined in the WeaponDefinition for this subtypeId + /// MyEntity: Parent's this phantom to another world entity. + /// StringerBuilder: Assign a name to this phantom + /// bool: Add this phantom to the world PrunningStructure + /// bool: Enable shadows for the model. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal MyEntity SpawnPhantom(string phantomType, uint maxAge = 0, bool closeWhenOutOfAmmo = false, + long defaultReloads = long.MaxValue, string ammoOverideName = null, + TriggerActions trigger = TriggerActions.TriggerOff, float? modelScale = null, MyEntity parnet = null, + bool addToPrunning = false, bool shadows = false, long identityId = 0, bool sync = false) + { + return _spawnPhantom?.Invoke(phantomType, maxAge, closeWhenOutOfAmmo, defaultReloads, ammoOverideName, + (int)trigger, modelScale, parnet, addToPrunning, shadows, identityId, sync) ?? null; + } + + /// + /// Set/switch ammo + /// focusId is a value between 0 and 1, can have two active focus targets. + /// + /// + /// + /// + internal bool SetPhantomFocusTarget(MyEntity phantom, MyEntity target, int focusId) + { + return _setPhantomFocusTarget?.Invoke(phantom, target, focusId) ?? false; + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/HudAPIv2.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/HudAPIv2.cs new file mode 100644 index 00000000..f2653b0c --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/HudAPIv2.cs @@ -0,0 +1,2217 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ProtoBuf; +using Sandbox.ModAPI; +using VRage; +using VRage.Input; +using VRage.ModAPI; +using VRage.Utils; +using VRageMath; +using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; + +namespace CGP.ShareTrack.API +{ + public class HudAPIv2 + { + public enum TextOrientation : byte + { + ltr = 1, + center = 2, + rtl = 3 + } + + public const string DefaultFont = "white"; + public const BlendTypeEnum DefaultHUDBlendType = BlendTypeEnum.PostPP; + public const BlendTypeEnum DefaultWorldBlendType = BlendTypeEnum.Standard; + private const long REGISTRATIONID = 573804956; + + private static HudAPIv2 instance; + private Action m_onRegisteredAction; + + private Func MessageFactory; + private Func MessageGetter; + private Action MessageSetter; + private Action RemoveMessage; + + /// + /// Create a HudAPI Instance. Please only create one per mod. + /// + /// Callback once the HudAPI is active. You can Instantiate HudAPI objects in this Action + public HudAPIv2(Action onRegisteredAction = null) + { + if (instance != null) return; + instance = this; + m_onRegisteredAction = onRegisteredAction; + MyAPIGateway.Utilities.RegisterMessageHandler(REGISTRATIONID, RegisterComponents); + } + + public Action OnScreenDimensionsChanged { get; set; } + + /// + /// If Heartbeat is true you may call any constructor in this class. Do not call any constructor or set properties if + /// this is false. + /// + public bool Heartbeat { get; private set; } + + public void Close() + { + Unload(); + } + + /// + /// Unregisters mod and frees references. + /// + public void Unload() + { + MyAPIGateway.Utilities.UnregisterMessageHandler(REGISTRATIONID, RegisterComponents); + MessageFactory = null; + MessageSetter = null; + MessageGetter = null; + RemoveMessage = null; + Heartbeat = false; + m_onRegisteredAction = null; + if (instance == this) + instance = null; + } + + private void RegisterComponents(object obj) + { + if (Heartbeat) + return; + if (obj is MyTuple, Action, Func, + Action>) + { + var Handlers = + (MyTuple, Action, Func, Action>) + obj; + MessageFactory = Handlers.Item1; + MessageSetter = Handlers.Item2; + MessageGetter = Handlers.Item3; + RemoveMessage = Handlers.Item4; + + Heartbeat = true; + if (m_onRegisteredAction != null) + m_onRegisteredAction(); + APIDialog.GetDialogMethods(MessageGetter); + MessageSet(null, (int)RegistrationEnum.OnScreenUpdate, new MyTuple(ScreenChangedHandle)); + } + } + + private enum RegistrationEnum + { + OnScreenUpdate = 2000 + } + + private enum MessageTypes + { + HUDMessage = 0, + BillBoardHUDMessage, + EntityMessage, + SpaceMessage, + BillboardTriHUDMessage, + + MenuItem = 20, + MenuSubCategory, + MenuRootCategory, + MenuScreenInput, + MenuSliderItem, + MenuTextInput, + MenuKeybindInput, + MenuColorPickerInput, + + BoxUIContainer = 40, + BoxUIText, + BoxUIImage, + + UIDefinition = 60, + UIBehaviourDefinition + } + + #region CustomDialogs + + public static class APIDialog + { + private static Func, Action, Action, bool, bool, bool> + ColorPickerDialogDelagete; + + private static Func, StringBuilder, bool> TextDialogDelagete; + private static Func, StringBuilder, bool> KeybindDialogDelagete; + + private static Func, Action, Action, bool> + ScreenInputDialogDelagete; + + private static Func, float, Func, Action, bool> + SliderDialogDelagete; + + internal static void GetDialogMethods(Func messageGetter) + { + ColorPickerDialogDelagete = messageGetter.Invoke((int)APIinfo.APIinfoMembers.GetDialog, + (int)APIDialogs.ColorPickerDialog) + as Func, Action, Action, bool, bool, bool>; + TextDialogDelagete = + messageGetter.Invoke((int)APIinfo.APIinfoMembers.GetDialog, (int)APIDialogs.TextDialog) + as Func, StringBuilder, bool>; + KeybindDialogDelagete = + messageGetter.Invoke((int)APIinfo.APIinfoMembers.GetDialog, (int)APIDialogs.KeybindDialog) + as Func, StringBuilder, bool>; + ScreenInputDialogDelagete = messageGetter.Invoke((int)APIinfo.APIinfoMembers.GetDialog, + (int)APIDialogs.ScreenInputDialog) + as Func, Action, Action, bool>; + SliderDialogDelagete = + messageGetter.Invoke((int)APIinfo.APIinfoMembers.GetDialog, (int)APIDialogs.SliderDialog) + as Func, float, Func, Action, bool>; + } + + public static bool ColorPickerDialog(StringBuilder Title, Color initialColor, Action onSubmit, + Action onUpdate, Action onCancel, bool showAlpha, bool usehsv = false) + { + return ColorPickerDialogDelagete?.Invoke(Title, initialColor, onSubmit, onUpdate, onCancel, showAlpha, + usehsv) ?? false; + } + + public static bool TextDialog(Action onSubmit, StringBuilder Title) + { + return TextDialogDelagete?.Invoke(onSubmit, Title) ?? false; + } + + public static bool KeybindDialog(Action onSubmit, StringBuilder Title) + { + return KeybindDialogDelagete?.Invoke(onSubmit, Title) ?? false; + } + + public static bool ScreenInputDialog(StringBuilder title, Vector2D origin, Vector2D size, + Action onSubmit, Action onUpdate, Action onCancel) + { + return ScreenInputDialogDelagete?.Invoke(title, origin, size, onSubmit, onUpdate, onCancel) ?? false; + } + + public static bool SliderDialog(StringBuilder title, Action onSubmit, float initialvalue, + Func SliderPercentToValue, Action onCancel) + { + return SliderDialogDelagete?.Invoke(title, onSubmit, initialvalue, SliderPercentToValue, onCancel) ?? + false; + } + + private enum APIDialogs + { + ColorPickerDialog = 1100, + TextDialog, + KeybindDialog, + ScreenInputDialog, + SliderDialog + } + } + + #endregion + + #region Info + + public static class APIinfo + { + /// + /// Returns the distance for one pixel in x and y directions, can be multiplied and fed into Origin, Offset, and Size + /// parameters for precise manipulation of HUD objects. + /// + public static Vector2D ScreenPositionOnePX => + (Vector2D)instance.MessageGet(null, (int)APIinfoMembers.ScreenPositionOnePX); + + /// + /// Available definitions: None, Default, Square + /// + /// + /// + public static BoxUIDefinition GetBoxUIDefinition(MyStringId definitionName) + { + return new BoxUIDefinition(instance.MessageGet(definitionName, (int)APIinfoMembers.GetBoxUIDefinition)); + } + + public static BoxUIBehaviourDef GetBoxUIBehaviour(MyStringId definitionName) + { + return new BoxUIBehaviourDef(instance.MessageGet(definitionName, + (int)APIinfoMembers.GetBoxUIBehaviour)); + } + + public static FontDefinition GetFontDefinition(MyStringId DefinitionName) + { + var retval = instance.MessageGet(DefinitionName, (int)APIinfoMembers.GetFontDefinition); + return new FontDefinition(retval); + } + + /// + /// Gives a list of fonts currently available in the TextHudAPI + /// + /// Fonts will be added to the collection, if null a new collection will be allocated + public static void GetFonts(List collection) + { + instance.MessageGet(collection, (int)APIinfoMembers.GetFonts); + } + + internal enum APIinfoMembers + { + ScreenPositionOnePX = 1000, + OnScreenUpdate, + GetBoxUIDefinition, + GetBoxUIBehaviour, + GetFontDefinition, + GetFonts, + GetDialog + } + } + + #endregion + + + /// + /// Class to allow adding fonts to the TextHUDApi + /// + public class FontDefinition + { + public object BackingDefinition; + + public FontDefinition(object BackingObject) + { + BackingDefinition = BackingObject; + } + + /// + /// Checks to see if the object is readonly. Once the definition is read only none of its properties can be modified or + /// character definitions replaced. New character definitions can still be added if none exist. + /// + public bool ReadOnly + { + get { return (bool)instance.MessageGet(BackingDefinition, (int)FontDefinitionMembers.ReadOnly); } + set { instance.MessageSet(BackingDefinition, (int)FontDefinitionMembers.ReadOnly, value); } + } + + /// + /// Sets the global parameters for a font + /// + public void DefineFont(int fontbase, int lineheight, int fontsize) + { + var data = new MyTuple(fontbase, lineheight, fontsize); + instance.MessageSet(BackingDefinition, (int)FontDefinitionMembers.DefineFont, data); + } + + /// + /// Adds a character glyph + /// + /// TransparentMaterial definitions subtype id + /// must be square + /// code in hex + /// origin x + /// origin y + /// size x + /// size y + /// advance width + /// left side bearing + /// force character to grayscale in render + public void AddCharacter(char character, MyStringId material, int materialtexturesize, string charactercode, + int uv1x, int uv1Y, int sizex, int sizey, int aw, int lsb, bool forcewhite = false) + { + var data = new FontCharacterDefinitionData + { + character = character, MaterialId = material, texturesize = materialtexturesize, + charactercode = charactercode, uv1x = uv1x, uv1y = uv1Y, sizex = sizex, sizey = sizey, aw = aw, + lsb = lsb, forcewhite = forcewhite + }; + instance.MessageSet(BackingDefinition, (int)FontDefinitionMembers.AddCharacter, + MyAPIGateway.Utilities.SerializeToBinary(data)); + } + + /// + /// Sets the kerning parameters. Right character must be defined first! + /// + /// how many pixels to move the right char + /// char that will be moved by the kerning + /// + public void AddKerning(int adjust, char right, char left) + { + var data = new MyTuple(adjust, right, left); + instance.MessageSet(BackingDefinition, (int)FontDefinitionMembers.AddKerning, data); + } + + private enum FontDefinitionMembers + { + AddCharacter = 0, + DefineFont, + AddKerning, + ReadOnly + } + + [ProtoContract] + public struct FontCharacterDefinitionData + { + [ProtoMember(1)] public char character; + [ProtoMember(2)] public int texturesize; + [ProtoMember(3)] public string charactercode; + [ProtoMember(4)] public int uv1x; + [ProtoMember(5)] public int uv1y; + [ProtoMember(6)] public int sizex; + [ProtoMember(7)] public int sizey; + [ProtoMember(8)] public int aw; + [ProtoMember(9)] public int lsb; + [ProtoMember(10)] public bool forcewhite; + [ProtoMember(11)] public MyStringId MaterialId; + } + } + + + #region Intercomm + + private void DeleteMessage(object BackingObject) + { + if (BackingObject != null) + RemoveMessage(BackingObject); + } + + private object CreateMessage(MessageTypes type) + { + return MessageFactory((int)type); + } + + private object MessageGet(object BackingObject, int member) + { + return MessageGetter(BackingObject, member); + } + + private void MessageSet(object BackingObject, int member, object value) + { + MessageSetter(BackingObject, member, value); + } + + private void RegisterCheck() + { + if (instance.Heartbeat == false) + throw new InvalidOperationException( + "HudAPI: Failed to create backing object. Do not instantiate without checking if heartbeat is true."); + } + + private void ScreenChangedHandle() + { + if (OnScreenDimensionsChanged != null) OnScreenDimensionsChanged(); + } + + #endregion + + #region Messages + + public enum Options : byte + { + None = 0x0, + HideHud = 0x1, + Shadowing = 0x2, + Fixed = 0x4, + FOVScale = 0x8, + Pixel = 0x10 + } + + private enum MessageBaseMembers + { + Message = 0, + Visible, + TimeToLive, + Scale, + TextLength, + Offset, + BlendType, + Draw, + Flush, + SkipLinearRGB + } + + public abstract class MessageBase + { + internal object BackingObject; + + public abstract void DeleteMessage(); + + /// + /// Gets the offset of the lower right corner of the text element from the upper left. The value returned is a local + /// translation. Screen space for screen messages, world space for world messages. Please note that the Y value is + /// negative in screen space. + /// + /// Lower Right Corner + public Vector2D GetTextLength() + { + return (Vector2D)instance.MessageGet(BackingObject, (int)MessageBaseMembers.TextLength); + } + + /// + /// Manual draw method + /// + public void Draw() + { + instance.MessageGet(BackingObject, (int)MessageBaseMembers.Draw); + } + + /// + /// Clears the object cache + /// + public void Flush() + { + instance.MessageGet(BackingObject, (int)MessageBaseMembers.Flush); + } + + #region Properties + + /// + /// Note that if you update the stringbuilder anywhere it will update the message automatically. Use this property to + /// set the stringbuilder object to your own or use the one generated by the constructor. + /// + public StringBuilder Message + { + get { return (StringBuilder)instance.MessageGet(BackingObject, (int)MessageBaseMembers.Message); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.Message, value); } + } + + + /// + /// True if HUD Element is visible, note that this will still be true if the player has their hud activated and HideHud + /// option is set. + /// + public bool Visible + { + get { return (bool)instance.MessageGet(BackingObject, (int)MessageBaseMembers.Visible); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.Visible, value); } + } + + /// + /// Time to live in Draw ticks. At 0 class will close itself and will no longer update. + /// + public int TimeToLive + { + get { return (int)instance.MessageGet(BackingObject, (int)MessageBaseMembers.TimeToLive); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.TimeToLive, value); } + } + + + /// + /// Scale of the text elements or billboard + /// + public double Scale + { + get { return (double)instance.MessageGet(BackingObject, (int)MessageBaseMembers.Scale); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.Scale, value); } + } + + + /// + /// Offset the text element by this amount. Note this takes the result of GetTextLength, be sure to clear Offset.Y if + /// you do not want to start at the lower left corner of the previous element + /// + public Vector2D Offset + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)MessageBaseMembers.Offset); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.Offset, value); } + } + + /// + /// put using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; on top of your script to use this property. + /// + public BlendTypeEnum Blend + { + get { return (BlendTypeEnum)instance.MessageGet(BackingObject, (int)MessageBaseMembers.BlendType); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.BlendType, value); } + } + + /// + /// Skips LinearRGB call in TextHUDAPI + /// + public bool SkipLinearRGB + { + get { return (bool)instance.MessageGet(BackingObject, (int)MessageBaseMembers.SkipLinearRGB); } + set { instance.MessageSet(BackingObject, (int)MessageBaseMembers.SkipLinearRGB, value); } + } + + #endregion + } + + public class EntityMessage : MessageBase + { + public EntityMessage(StringBuilder Message, IMyEntity entity, MatrixD transformMatrix, int timeToLive = -1, + double scale = 1, TextOrientation orientation = TextOrientation.ltr, Vector2D? offset = null, + Vector2D? max = null, string font = DefaultFont) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.EntityMessage); + if (BackingObject != null) + { + if (max.HasValue) + Max = max.Value; + this.Message = Message; + Entity = entity; + TransformMatrix = transformMatrix; + TimeToLive = timeToLive; + Scale = scale; + Visible = true; + Orientation = orientation; + Blend = DefaultWorldBlendType; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Font = font; + } + } + + public EntityMessage(StringBuilder Message, IMyEntity entity, Vector3D localPosition, Vector3D forward, + Vector3D up, int timeToLive = -1, double scale = 1, TextOrientation orientation = TextOrientation.ltr, + Vector2D? offset = null, Vector2D? max = null, BlendTypeEnum blend = DefaultWorldBlendType, + string font = DefaultFont) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.EntityMessage); + if (BackingObject != null) + { + if (max.HasValue) + Max = max.Value; + this.Message = Message; + Entity = entity; + LocalPosition = localPosition; + Forward = forward; + Up = up; + TimeToLive = timeToLive; + Scale = scale; + Visible = true; + Orientation = orientation; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Font = font; + } + } + + public EntityMessage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.EntityMessage); + } + + /// + /// Do not use this class after deleting it. + /// + public override void DeleteMessage() + { + instance.DeleteMessage(BackingObject); + BackingObject = null; + } + + private enum EntityMembers + { + Entity = 10, + LocalPosition, + Up, + Forward, + Orientation, + Max, + TransformMatrix, + Font + } + + #region Properties + + /// + /// Entity text will be centered on / attached to. + /// + public IMyEntity Entity + { + get { return instance.MessageGet(BackingObject, (int)EntityMembers.Entity) as IMyEntity; } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Entity, value); } + } + + + /// + /// Local translation of where the text will be in relation to the Entity it is attached to. Used to construct the + /// TransformMatrix + /// + public Vector3D LocalPosition + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.LocalPosition); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.LocalPosition, value); } + } + + /// + /// Up, value used to construct the TransformMatrix + /// + public Vector3D Up + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.Up); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Up, value); } + } + + /// + /// Forward, value used to construct the TransformMatrix + /// + public Vector3D Forward + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.Forward); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Forward, value); } + } + + /// + /// Flag that sets from what direction text is written + /// + public TextOrientation Orientation + { + get { return (TextOrientation)instance.MessageGet(BackingObject, (int)EntityMembers.Orientation); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Orientation, (byte)value); } + } + + + /// + /// World Boundries + /// + public Vector2D Max + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)EntityMembers.Max); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Max, value); } + } + + /// + /// Sets the transformation matrix directly, use instead of LocalPosition, Up, Forward + /// + public MatrixD TransformMatrix + { + get { return (MatrixD)instance.MessageGet(BackingObject, (int)EntityMembers.TransformMatrix); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.TransformMatrix, value); } + } + + /// + /// Font, default is "white", "monospace" is also included. + /// + public string Font + { + get { return (string)instance.MessageGet(BackingObject, (int)EntityMembers.Font); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Font, value); } + } + + #endregion + } + + public class HUDMessage : MessageBase + { + public HUDMessage(StringBuilder Message, Vector2D origin, Vector2D? offset = null, int timeToLive = -1, + double scale = 1.0d, bool hideHud = true, bool shadowing = false, Color? shadowColor = null, + BlendTypeEnum blend = DefaultHUDBlendType, string font = DefaultFont) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.HUDMessage); + if (BackingObject != null) + { + TimeToLive = timeToLive; + Origin = origin; + Options = Options.None; + if (hideHud) + Options |= Options.HideHud; + if (shadowing) + Options |= Options.Shadowing; + var blackshadow = Color.Black; + if (shadowColor.HasValue) + shadowColor = shadowColor.Value; + Scale = scale; + this.Message = Message; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Font = font; + } + } + + public HUDMessage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.HUDMessage); + } + + public override void DeleteMessage() + { + instance.DeleteMessage(BackingObject); + BackingObject = null; + } + + private enum EntityMembers + { + Origin = 10, + Options, + ShadowColor, + Font, + InitalColor + } + + #region Properties + + /// + /// top left is -1, 1, bottom right is 1 -1 + /// + public Vector2D Origin + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)EntityMembers.Origin); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Origin, value); } + } + + + /// + /// HideHud - hides when hud is hidden, shadow draw a shadow behind the text. + /// + public Options Options + { + get { return (Options)instance.MessageGet(BackingObject, (int)EntityMembers.Options); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Options, (byte)value); } + } + + /// + /// Color of shadow behind the text + /// + public Color ShadowColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)EntityMembers.ShadowColor); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.ShadowColor, value); } + } + + /// + /// Font, default is "white", "monospace" also supported, modded fonts will be supported in the future. + /// + public string Font + { + get { return (string)instance.MessageGet(BackingObject, (int)EntityMembers.Font); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Font, value); } + } + + /// + /// Sets the initial color of the text, Default: White + /// + public Color InitialColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)EntityMembers.InitalColor); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.InitalColor, value); } + } + + #endregion + } + + public class BillBoardHUDMessage : MessageBase + { + public BillBoardHUDMessage(MyStringId Material, Vector2D origin, Color billBoardColor, + Vector2D? offset = null, int timeToLive = -1, double scale = 1d, float width = 1f, float height = 1f, + float rotation = 0, bool hideHud = true, bool shadowing = true, + BlendTypeEnum blend = DefaultHUDBlendType) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BillBoardHUDMessage); + + if (BackingObject != null) + { + TimeToLive = timeToLive; + Origin = origin; + Options = Options.None; + if (hideHud) + Options |= Options.HideHud; + if (shadowing) + Options |= Options.Shadowing; + BillBoardColor = billBoardColor; + Scale = scale; + this.Material = Material; + Rotation = rotation; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Width = width; + Height = height; + } + } + + public BillBoardHUDMessage(MyStringId Material, Vector2D origin, Color billBoardColor, Vector2 uvOffset, + Vector2 uvSize, float textureSize, Vector2D? offset = null, int timeToLive = -1, double scale = 1d, + float width = 1f, float height = 1f, float rotation = 0, bool hideHud = true, bool shadowing = true, + BlendTypeEnum blend = DefaultHUDBlendType) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BillBoardHUDMessage); + + if (BackingObject != null) + { + uvEnabled = true; + this.uvOffset = uvOffset; + this.uvSize = uvSize; + TextureSize = textureSize; + TimeToLive = timeToLive; + Origin = origin; + Options = Options.None; + if (hideHud) + Options |= Options.HideHud; + if (shadowing) + Options |= Options.Shadowing; + BillBoardColor = billBoardColor; + Scale = scale; + this.Material = Material; + Rotation = rotation; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Width = width; + Height = height; + } + } + + public BillBoardHUDMessage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BillBoardHUDMessage); + } + + public override void DeleteMessage() + { + instance.DeleteMessage(BackingObject); + BackingObject = null; + } + + private enum EntityMembers + { + Origin = 10, + Options, + BillBoardColor, + Material, + Rotation, + Width, + Height, + uvOffset, + uvSize, + TextureSize, + uvEnabled + } + + #region Properties + + /// + /// top left is -1, 1, bottom right is 1 -1 + /// + public Vector2D Origin + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)EntityMembers.Origin); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Origin, value); } + } + + /// + /// Use MyStringId.GetOrCompute to turn a string into a MyStringId. + /// + public MyStringId Material + { + get { return (MyStringId)instance.MessageGet(BackingObject, (int)EntityMembers.Material); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Material, value); } + } + + + /// + /// Set Options, HideHud to true will hide billboard when hud is hidden. Shadowing will draw the element on the shadow + /// layer (behind the text layer) + /// + public Options Options + { + get { return (Options)instance.MessageGet(BackingObject, (int)EntityMembers.Options); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Options, (byte)value); } + } + + + /// + /// Sets the color mask of the billboard, not all billboards support this parameter. + /// + public Color BillBoardColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)EntityMembers.BillBoardColor); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.BillBoardColor, value); } + } + + /// + /// Rotate billboard in radians. + /// + public float Rotation + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Rotation); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Rotation, value); } + } + + + /// + /// Multiplies the width of the billboard by this amount. Set Scale to 1 if you want to use this to finely control the + /// width of the billboard, such as a value from GetTextLength + /// You might need to multiply the result of GetTextLength by 250 or maybe 500 if Scale is 1. Will need experiementing + /// + public float Width + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Width); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Width, value); } + } + + + /// + /// Multiplies the height of the billboard by this amount. Set Scale to 1 if you want to use this to finely control the + /// height of the billboard, such as a value from GetTextLength + /// + public float Height + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Height); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Height, value); } + } + + /// + /// UV offset in pixels + /// + public Vector2 uvOffset + { + get { return (Vector2)instance.MessageGet(BackingObject, (int)EntityMembers.uvOffset); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.uvOffset, value); } + } + + /// + /// Size in pixels + /// + public Vector2 uvSize + { + get { return (Vector2)instance.MessageGet(BackingObject, (int)EntityMembers.uvSize); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.uvSize, value); } + } + + /// + /// Size of image in pixels (please note the height and width of the image must be the same) + /// + public float TextureSize + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.TextureSize); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.TextureSize, value); } + } + + /// + /// Use uv parameters. Default is false. + /// + public bool uvEnabled + { + get { return (bool)instance.MessageGet(BackingObject, (int)EntityMembers.uvEnabled); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.uvEnabled, value); } + } + + #endregion + } + + public class BillBoardTriHUDMessage : MessageBase + { + public BillBoardTriHUDMessage(MyStringId Material, Vector2D origin, Color billBoardColor, Vector2 p0, + Vector2 p1, Vector2 p2, Vector2D? offset = null, int timeToLive = -1, double scale = 1d, + float width = 1f, float height = 1f, float rotation = 0, bool hideHud = true, bool shadowing = true, + BlendTypeEnum blend = DefaultHUDBlendType) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BillboardTriHUDMessage); + + if (BackingObject != null) + { + TimeToLive = timeToLive; + Origin = origin; + Options = Options.None; + if (hideHud) + Options |= Options.HideHud; + if (shadowing) + Options |= Options.Shadowing; + BillBoardColor = billBoardColor; + Scale = scale; + this.Material = Material; + Rotation = rotation; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Width = width; + Height = height; + P0 = p0; + P1 = p1; + P2 = p2; + } + } + + public BillBoardTriHUDMessage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BillboardTriHUDMessage); + } + + public override void DeleteMessage() + { + instance.DeleteMessage(BackingObject); + BackingObject = null; + } + + private enum EntityMembers + { + Message = 0, + Origin = 10, + Options, + BillBoardColor, + Material, + Rotation, + Width, + Height, + p0, + p1, + p2 + } + + #region Properties + + /// + /// top left is -1, 1, bottom right is 1 -1 + /// + public Vector2D Origin + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)EntityMembers.Origin); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Origin, value); } + } + + /// + /// Use MyStringId.GetOrCompute to turn a string into a MyStringId. + /// + public MyStringId Material + { + get { return (MyStringId)instance.MessageGet(BackingObject, (int)EntityMembers.Material); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Material, value); } + } + + + /// + /// Set Options, HideHud to true will hide billboard when hud is hidden. Shadowing will draw the element on the shadow + /// layer (behind the text layer) + /// + public Options Options + { + get { return (Options)instance.MessageGet(BackingObject, (int)EntityMembers.Options); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Options, (byte)value); } + } + + + /// + /// Sets the color mask of the billboard, not all billboards support this parameter. + /// + public Color BillBoardColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)EntityMembers.BillBoardColor); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.BillBoardColor, value); } + } + + /// + /// Rotate billboard in radians. + /// + public float Rotation + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Rotation); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Rotation, value); } + } + + + /// + /// Multiplies the width of the billboard by this amount. Set Scale to 1 if you want to use this to finely control the + /// width of the billboard, such as a value from GetTextLength + /// You might need to multiply the result of GetTextLength by 250 or maybe 500 if Scale is 1. Will need experiementing + /// + public float Width + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Width); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Width, value); } + } + + + /// + /// Multiplies the height of the billboard by this amount. Set Scale to 1 if you want to use this to finely control the + /// height of the billboard, such as a value from GetTextLength + /// + public float Height + { + get { return (float)instance.MessageGet(BackingObject, (int)EntityMembers.Height); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Height, value); } + } + + /// + /// UV P0 (note this is percentage based between 0-1 for X,Y) + /// + public Vector2 P0 + { + get { return (Vector2)instance.MessageGet(BackingObject, (int)EntityMembers.p0); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.p0, value); } + } + + /// + /// UV P1 (note this is percentage based between 0-1 for X,Y) + /// + public Vector2 P1 + { + get { return (Vector2)instance.MessageGet(BackingObject, (int)EntityMembers.p1); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.p1, value); } + } + + /// + /// UV P2 (note this is percentage based between 0-1 for X,Y) + /// + public Vector2 P2 + { + get { return (Vector2)instance.MessageGet(BackingObject, (int)EntityMembers.p2); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.p2, value); } + } + + #endregion + } + + public class SpaceMessage : MessageBase + { + public SpaceMessage(StringBuilder Message, Vector3D worldPosition, Vector3D up, Vector3D left, + double scale = 1, Vector2D? offset = null, int timeToLive = -1, + TextOrientation txtOrientation = TextOrientation.ltr, BlendTypeEnum blend = DefaultWorldBlendType, + string font = DefaultFont) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.SpaceMessage); + if (BackingObject != null) + { + TimeToLive = timeToLive; + Scale = scale; + WorldPosition = worldPosition; + Up = up; + Left = left; + TxtOrientation = txtOrientation; + this.Message = Message; + Blend = blend; + if (offset.HasValue) + Offset = offset.Value; + else + Offset = Vector2D.Zero; + Font = font; + } + } + + public SpaceMessage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.SpaceMessage); + } + + public override void DeleteMessage() + { + instance.DeleteMessage(BackingObject); + BackingObject = null; + } + + private enum EntityMembers + { + WorldPosition = 10, + Up, + Left, + TxtOrientation, + Font + } + + #region Properties + + /// + /// Position + /// + public Vector3D WorldPosition + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.WorldPosition); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.WorldPosition, value); } + } + + + /// + /// Up vector for textures + /// + public Vector3D Up + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.Up); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Up, value); } + } + + + /// + /// Left Vector for Textures + /// + public Vector3D Left + { + get { return (Vector3D)instance.MessageGet(BackingObject, (int)EntityMembers.Left); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Left, value); } + } + + /// + /// Font, default is "white", "monospace" also supported, modded fonts will be supported in the future. + /// + public string Font + { + get { return (string)instance.MessageGet(BackingObject, (int)EntityMembers.Font); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.Font, value); } + } + + /// + /// Text orientation, from what edge text is aligned. + /// + public TextOrientation TxtOrientation + { + get { return (TextOrientation)instance.MessageGet(BackingObject, (int)EntityMembers.TxtOrientation); } + set { instance.MessageSet(BackingObject, (int)EntityMembers.TxtOrientation, (byte)value); } + } + + #endregion + } + + #endregion + + #region Menu + + public abstract class MenuItemBase + { + internal object BackingObject; + + /// + /// Text displayed in the category list + /// + public string Text + { + get { return (string)instance.MessageGet(BackingObject, (int)MenuItemBaseMembers.Text); } + set { instance.MessageSet(BackingObject, (int)MenuItemBaseMembers.Text, value); } + } + + /// + /// User can select this item. true by default + /// + public bool Interactable + { + get { return (bool)instance.MessageGet(BackingObject, (int)MenuItemBaseMembers.Interactable); } + set { instance.MessageSet(BackingObject, (int)MenuItemBaseMembers.Interactable, value); } + } + + private enum MenuItemBaseMembers + { + Text = 0, + Interactable + } + } + + public class MenuItem : MenuItemBase + { + /// + /// Basic toggle. You can use this to create on/off toggles, checkbox lists or option lists. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// On click event that will be fired if the user selects this item. + /// User can select this item. true by default + public MenuItem(string Text, MenuCategoryBase parent, Action onClick = null, bool interactable = true) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuItem); + + this.Text = Text; + Parent = parent; + OnClick = onClick; + Interactable = interactable; + } + + /// + /// On click event that will be fired if the user selects this item. + /// + public Action OnClick + { + get { return (Action)instance.MessageGet(BackingObject, (int)MenuItemMembers.OnClickAction); } + set { instance.MessageSet(BackingObject, (int)MenuItemMembers.OnClickAction, value); } + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuItemMembers.Parent, value.BackingObject); } + } + + private enum MenuItemMembers + { + OnClickAction = 100, + Parent + } + } + + public abstract class MenuCategoryBase : MenuItemBase + { + /// + /// Header text of the menu list. + /// + public string Header + { + get { return (string)instance.MessageGet(BackingObject, (int)MenuBaseCategoryMembers.Header); } + set { instance.MessageSet(BackingObject, (int)MenuBaseCategoryMembers.Header, value); } + } + + private enum MenuBaseCategoryMembers + { + Header = 100 + } + } + + public class MenuRootCategory : MenuCategoryBase + { + public enum MenuFlag + { + None = 0, + PlayerMenu = 1, + AdminMenu = 2 + } + + /// + /// Create only one of these per mod. Automatically attaches to parent lists. + /// + /// Text displayed in the root menu list + /// Which menu to attach to, either Player or Admin menus. + /// Header text of this menu list. + public MenuRootCategory(string Text, MenuFlag attachedMenu = MenuFlag.None, + string headerText = "Default Header") + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuRootCategory); + this.Text = Text; + Header = headerText; + Menu = attachedMenu; + } + + /// + /// Which menu to attach to, either Player or Admin menus. + /// + public MenuFlag Menu + { + get { return (MenuFlag)instance.MessageGet(BackingObject, (int)MenuRootCategoryMembers.MenuFlag); } + set { instance.MessageSet(BackingObject, (int)MenuRootCategoryMembers.MenuFlag, (int)value); } + } + + private enum MenuRootCategoryMembers + { + MenuFlag = 200 + } + } + + public class MenuSubCategory : MenuCategoryBase + { + /// + /// Creates a sub category, must attach to either Root or another Sub Category. + /// + /// Text displayed in the category list + /// + /// Must be either a MenuRootCategory or MenuSubCategory objectMust be either a MenuRootCategory or + /// MenuSubCategory object + /// + /// Header text of this menu list. + public MenuSubCategory(string Text, MenuCategoryBase parent, string headerText = "Default Header") + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuSubCategory); + this.Text = Text; + Header = headerText; + Parent = parent; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory objectMust be either a MenuRootCategory or MenuSubCategory + /// object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuSubCategoryMembers.Parent, value.BackingObject); } + } + + private enum MenuSubCategoryMembers + { + Parent = 200 + } + } + + public class MenuColorPickerInput : MenuItemBase + { + /// + /// Summons a dialog box that allows the user to specify a color. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// Initial color set in the dialog box + /// Dialog Title + /// On Submit Callback, returns color in the dialog + /// Update callback, will call per tick with the current selected color in the dialog + /// User canceled the dialog + /// Shows alpha slider if true + /// Have Sliders Represent HSV Values + public MenuColorPickerInput(string Text, MenuCategoryBase parent, Color initialColor, + string inputDialogTitle = "Enter text value", Action onSubmit = null, + Action onUpdate = null, Action onCancel = null, bool showAlpha = true, bool useHsv = false) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuColorPickerInput); + this.Text = Text; + InputDialogTitle = inputDialogTitle; + OnSubmitAction = onSubmit; + Parent = parent; + InitialColor = initialColor; + OnUpdateAction = onUpdate; + OnCancelAction = onCancel; + ShowAlpha = showAlpha; + UseHSV = useHsv; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set + { + instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.Parent, value.BackingObject); + } + } + + /// + /// Titlebar of the Dialog window. + /// + public string InputDialogTitle + { + get + { + return (string)instance.MessageGet(BackingObject, + (int)MenuColorPickerInputMembers.InputDialogTitle); + } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.InputDialogTitle, value); } + } + + /// + /// Returns inputted color on submit. + /// + public Action OnSubmitAction + { + get + { + return (Action)instance.MessageGet(BackingObject, + (int)MenuColorPickerInputMembers.OnSubmitAction); + } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.OnSubmitAction, value); } + } + + /// + /// Returns color as client is manipulating the dialog. + /// + public Action OnUpdateAction + { + get + { + return (Action)instance.MessageGet(BackingObject, + (int)MenuColorPickerInputMembers.OnUpdateAction); + } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.OnUpdateAction, value); } + } + + /// + /// Canceled the dialog + /// + public Action OnCancelAction + { + get + { + return (Action)instance.MessageGet(BackingObject, (int)MenuColorPickerInputMembers.OnCancelAction); + } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.OnCancelAction, value); } + } + + /// + /// Initial color in the dialog box + /// + public Color InitialColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)MenuColorPickerInputMembers.InitialColor); } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.InitialColor, value); } + } + + /// + /// Shows alpha slider if true (default true) + /// + public bool ShowAlpha + { + get { return (bool)instance.MessageGet(BackingObject, (int)MenuColorPickerInputMembers.ShowAlpha); } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.ShowAlpha, value); } + } + + /// + /// UseHSV Values + /// + public bool UseHSV + { + get { return (bool)instance.MessageGet(BackingObject, (int)MenuColorPickerInputMembers.UseHSV); } + set { instance.MessageSet(BackingObject, (int)MenuColorPickerInputMembers.UseHSV, value); } + } + + private enum MenuColorPickerInputMembers + { + OnSubmitAction = 100, + Parent, + InputDialogTitle, + OnUpdateAction, + OnCancelAction, + InitialColor, + ShowAlpha, + UseHSV + } + } + + public class MenuTextInput : MenuItemBase + { + /// + /// Opens a text input dialog box when user selects this item. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// Titlebar of the Dialog window. + /// Returns inputted string on submit. + public MenuTextInput(string Text, MenuCategoryBase parent, string inputDialogTitle = "Enter text value", + Action onSubmit = null) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuTextInput); + this.Text = Text; + InputDialogTitle = inputDialogTitle; + OnSubmitAction = onSubmit; + Parent = parent; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuTextInputMembers.Parent, value.BackingObject); } + } + + /// + /// Titlebar of the Dialog window. + /// + public string InputDialogTitle + { + get { return (string)instance.MessageGet(BackingObject, (int)MenuTextInputMembers.InputDialogTitle); } + set { instance.MessageSet(BackingObject, (int)MenuTextInputMembers.InputDialogTitle, value); } + } + + /// + /// Returns inputted string on submit. + /// + public Action OnSubmitAction + { + get + { + return (Action)instance.MessageGet(BackingObject, (int)MenuTextInputMembers.OnSubmitAction); + } + set { instance.MessageSet(BackingObject, (int)MenuTextInputMembers.OnSubmitAction, value); } + } + + private enum MenuTextInputMembers + { + OnSubmitAction = 100, + Parent, + InputDialogTitle + } + } + + public class MenuKeybindInput : MenuItemBase + { + /// + /// Opens up a keybind dialog box which lets the user submit a Key + Modifiers. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// Titlebar of the Dialog window. + /// Called with Key pressed, Shift Pressed, Ctrl Pressed, Alt Pressed when user Submits the dialog. + public MenuKeybindInput(string Text, MenuCategoryBase parent, + string inputDialogTitle = "Keybind - Press any key", Action onSubmit = null) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuKeybindInput); + this.Text = Text; + InputDialogTitle = inputDialogTitle; + OnSubmitAction = onSubmit; + Parent = parent; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuKeybindInputMembers.Parent, value.BackingObject); } + } + + /// + /// Titlebar of the Dialog window. + /// + public string InputDialogTitle + { + get + { + return (string)instance.MessageGet(BackingObject, (int)MenuKeybindInputMembers.InputDialogTitle); + } + set { instance.MessageSet(BackingObject, (int)MenuKeybindInputMembers.InputDialogTitle, value); } + } + + /// + /// Called with Key pressed, Shift Pressed, Ctrl Pressed, Alt Pressed when user Submits the dialog. + /// + public Action OnSubmitAction + { + get + { + return (Action)instance.MessageGet(BackingObject, + (int)MenuKeybindInputMembers.OnSubmitAction); + } + set { instance.MessageSet(BackingObject, (int)MenuKeybindInputMembers.OnSubmitAction, value); } + } + + private enum MenuKeybindInputMembers + { + OnSubmitAction = 100, + Parent, + InputDialogTitle + } + } + + public class MenuScreenInput : MenuItemBase + { + /// + /// Summons a dialog box that gives you a screen position when completed. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// Screen position origin of the dialog box. + /// + /// Size of the dialog box. Use GetTextLength() on a Hud Object to manipulate this. Or you can specify a + /// manual width and height APIinfo can get you the width and height of a single PX. + /// + /// Titlebar of the Dialog window. + /// Called when user lets go of the dialog box with the final position. + /// Called every tick while the user is manipulating the dialog. + /// + /// Called when user does not click the dialog box window to move it and cancels out of the dialog + /// box. + /// + /// Called when user invokes this dialog box use to refresh the Size property + public MenuScreenInput(string Text, MenuCategoryBase parent, Vector2D origin, Vector2D size, + string inputDialogTitle = "Move this element", Action onSubmit = null, + Action update = null, Action cancel = null, Action onSelect = null) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuScreenInput); + this.Text = Text; + InputDialogTitle = inputDialogTitle; + OnSubmitAction = onSubmit; + UpdateAction = update; + Origin = origin; + Size = size; + OnCancel = cancel; + OnSelect = onSelect; + Parent = parent; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.Parent, value.BackingObject); } + } + + /// + /// Titlebar of the Dialog window. + /// + public string InputDialogTitle + { + get { return (string)instance.MessageGet(BackingObject, (int)MenuScreenInputMembers.InputDialogTitle); } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.InputDialogTitle, value); } + } + + /// + /// Called when user does not click the dialog box window to move it and cancels out of the dialog box. + /// + public Action OnCancel + { + get { return (Action)instance.MessageGet(BackingObject, (int)MenuScreenInputMembers.Cancel); } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.Cancel, value); } + } + + /// + /// Screen position origin of the dialog box. + /// + public Vector2D Origin + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)MenuScreenInputMembers.Origin); } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.Origin, value); } + } + + /// + /// Size of the dialog box. Use GetTextLength() on a Hud Object to manipulate this. Or you can specify a manual width + /// and height APIinfo can get you the width and height of a single PX. + /// + public Vector2D Size + { + get { return (Vector2D)instance.MessageGet(BackingObject, (int)MenuScreenInputMembers.Size); } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.Size, value); } + } + + /// + /// Called when user lets go of the dialog box with the final position. Please note that the result may be off the + /// screen. Recommend clamping between -1 and 1 on each axis. + /// + public Action OnSubmitAction + { + get + { + return (Action)instance.MessageGet(BackingObject, + (int)MenuScreenInputMembers.OnSubmitAction); + } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.OnSubmitAction, value); } + } + + /// + /// Called every tick while the user is manipulating the dialog. + /// + public Action UpdateAction + { + get + { + return (Action)instance.MessageGet(BackingObject, + (int)MenuScreenInputMembers.OnUpdateAction); + } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.OnUpdateAction, value); } + } + + public Action OnSelect + { + get { return (Action)instance.MessageGet(BackingObject, (int)MenuScreenInputMembers.OnSelect); } + set { instance.MessageSet(BackingObject, (int)MenuScreenInputMembers.OnSelect, value); } + } + + private enum MenuScreenInputMembers + { + OnSubmitAction = 100, + Parent, + InputDialogTitle, + Origin, + Size, + OnUpdateAction, + Cancel, + OnSelect + } + } + + public class MenuSliderInput : MenuItemBase + { + /// + /// Creates a dialog object and adds it to the Parent list. + /// + /// Text displayed in the category list + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + /// When the dialog box first opens set the position as a percentage based on this number. + /// Expected value between 0 and 1. + /// + /// Titlebar of the Dialog window. + /// Percentage value of the slider when the user submits the dialog + /// + /// Returned value calls toString to print the text in the dialog box. Value fed to this + /// function is the slider percentage value. + /// + /// + /// Called when the user cancels the dialog window or otherwise closes the dialog box without + /// confirming. + /// + public MenuSliderInput(string Text, MenuCategoryBase parent, float initialPercent, + string inputDialogTitle = "Adjust Slider to modify value", Action onSubmitAction = null, + Func sliderPercentToValue = null, Action onCancel = null) + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.MenuSliderItem); + this.Text = Text; + InputDialogTitle = inputDialogTitle; + OnSubmitAction = onSubmitAction; + SliderPercentToValue = sliderPercentToValue; + InitialPercent = initialPercent; + OnCancel = onCancel; + Parent = parent; + } + + /// + /// Must be either a MenuRootCategory or MenuSubCategory object + /// + public MenuCategoryBase Parent + { + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.Parent, value.BackingObject); } + } + + /// + /// Titlebar of the Dialog window. + /// + public string InputDialogTitle + { + get { return (string)instance.MessageGet(BackingObject, (int)MenuSliderItemMembers.InputDialogTitle); } + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.InputDialogTitle, value); } + } + + /// + /// When the dialog box first opens set the position as a percentage based on this number. Expected value between 0 and + /// 1. + /// + public float InitialPercent + { + get { return (float)instance.MessageGet(BackingObject, (int)MenuSliderItemMembers.InitialPercent); } + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.InitialPercent, value); } + } + + /// + /// Percentage value of the slider when the user submits the dialog + /// + public Action OnSubmitAction + { + get + { + return (Action)instance.MessageGet(BackingObject, (int)MenuSliderItemMembers.OnSubmitAction); + } + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.OnSubmitAction, value); } + } + + /// + /// Called when the user cancels the dialog window or otherwise closes the dialog box without confirming. + /// + public Action OnCancel + { + get { return (Action)instance.MessageGet(BackingObject, (int)MenuSliderItemMembers.OnCancel); } + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.OnCancel, value); } + } + + /// + /// Returned value calls toString to print the text in the dialog box. Value fed to this function is the slider + /// percentage value. + /// + public Func SliderPercentToValue + { + get + { + return (Func)instance.MessageGet(BackingObject, + (int)MenuSliderItemMembers.SliderPercentToValue); + } + set { instance.MessageSet(BackingObject, (int)MenuSliderItemMembers.SliderPercentToValue, value); } + } + + private enum MenuSliderItemMembers + { + OnSubmitAction = 100, + Parent, + InputDialogTitle, + InitialPercent, + SliderPercentToValue, + OnCancel + } + } + + #endregion + + + #region BoxUI + + /// + /// Creates a new BoxUIDefinition. This defines exactly how the UI texture is laid out on the screen. + /// + public class BoxUIDefinition + { + public object BackingDefinition; + + + public BoxUIDefinition() + { + BackingDefinition = instance.CreateMessage(MessageTypes.UIDefinition); + } + + public BoxUIDefinition(MyStringId Material, int imagesize, int topwidthpx, int leftwidthpx, + int bottomwidthpx, int rightwidthpx, int margin = 0, int padding = 0) + { + BackingDefinition = instance.CreateMessage(MessageTypes.UIDefinition); + var data = new BoxUIDefinitionData + { + Material = Material, + imagesize = imagesize, + topwidthpx = topwidthpx, + leftwidthpx = leftwidthpx, + bottomwidthpx = bottomwidthpx, + rightwidthpx = rightwidthpx, + margin = margin, + padding = padding + }; + BoxUIDef = data; + } + + public BoxUIDefinition(object BackingObject) + { + BackingDefinition = BackingObject; + } + + public BoxUIDefinitionData BoxUIDef + { + set + { + instance.MessageSet(BackingDefinition, (int)BoxUIDefinitionMembers.Definition, + MyAPIGateway.Utilities.SerializeToBinary(value)); + } + } + + /// + /// Returns the margin + padding + border values. subtract this from the total size to get the size of the content area + /// of the object. + /// + public Vector2I Min => (Vector2I)instance.MessageGet(BackingDefinition, (int)BoxUIDefinitionMembers.Min); + + [ProtoContract] + public struct BoxUIDefinitionData + { + [ProtoMember(1)] public MyStringId Material; + [ProtoMember(2)] public int imagesize; + [ProtoMember(3)] public int topwidthpx; + [ProtoMember(4)] public int leftwidthpx; + [ProtoMember(5)] public int bottomwidthpx; + [ProtoMember(6)] public int rightwidthpx; + [ProtoMember(7)] public int margin; + [ProtoMember(8)] public int padding; + } + + private enum BoxUIDefinitionMembers + { + Definition = 0, + Min + } + } + + + /// + /// Unused at the moment, but will be expanded on in the future. + /// + public class BoxUIBehaviourDef + { + public object BackingDefinition; + + public BoxUIBehaviourDef() + { + BackingDefinition = instance.CreateMessage(MessageTypes.UIBehaviourDefinition); + } + + public BoxUIBehaviourDef(object BackingObject) + { + BackingDefinition = BackingObject; + } + } + + public abstract class BoxUIBase + { + internal object BackingObject; + internal BoxUIBase m_Parent; + + /// + /// Sets the BoxUI's position in PX values. + /// + public Vector2I Origin + { + get { return (Vector2I)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Origin); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Origin, value); } + } + + /// + /// Sets the width of the box in PX values + /// + public int Width + { + get { return (int)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Width); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Width, value); } + } + + /// + /// Sets the Height in PX values + /// + public int Height + { + get { return (int)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Height); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Height, value); } + } + + /// + /// Sets the background color, default White + /// + public Color BackgroundColor + { + get { return (Color)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.BackgroundColor); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.BackgroundColor, value); } + } + + /// + /// Element and subelements visible + /// + public bool Visible + { + get { return (bool)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Visible); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Visible, value); } + } + + /// + /// Sets the backing UI definition, please set using SetDefinition + /// + public object DefinitionObject + { + get { return instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Definition); } + set + { + if (value is BoxUIDefinition) + { + instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Definition, + (value as BoxUIDefinition).BackingDefinition); + return; + } + + instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Definition, value); + } + } + + /// + /// Sets the backing behaviour object, please set using SetBehaviour + /// + public object BehaviourObject + { + get { return instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.Behaviour); } + set + { + if (value is BoxUIBehaviourDef) + { + instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Behaviour, + (value as BoxUIDefinition).BackingDefinition); + return; + } + + instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Behaviour, value); + } + } + + /// + /// Defaults to true. + /// + public bool HideHud + { + get { return (bool)instance.MessageGet(BackingObject, (int)BoxUIBaseMembers.HideHud); } + set { instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.HideHud, value); } + } + + /// + /// Gets or sets the parent object, please be careful not to create a circular reference. Sub objects are automatically + /// offset by the top left corner of the parent object. + /// + public BoxUIBase Parent + { + get { return m_Parent; } + set + { + m_Parent = value; + instance.MessageSet(BackingObject, (int)BoxUIBaseMembers.Parent, m_Parent.BackingObject); + } + } + + + public void SetDefinition(BoxUIDefinition def) + { + DefinitionObject = def.BackingDefinition; + } + + public void SetBehaviour(BoxUIBehaviourDef def) + { + BehaviourObject = def.BackingDefinition; + } + + private enum BoxUIBaseMembers + { + Origin = 0, + BackgroundColor, + Width, + Height, + Definition, + Behaviour, + Visible, + Parent, + HideHud + } + } + + public class BoxUIContainer : BoxUIBase + { + public BoxUIContainer() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BoxUIContainer); + } + } + + public class BoxUIText : BoxUIBase + { + public BoxUIText() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BoxUIText); + } + + /// + /// Automatically sets the message to use the pixel offset types. Please note that the BoxUI will control the .Origin + /// of the Message, you can use the offset value in PX to move it. Please note Scale is now the pt size of the font. + /// + /// + public void SetTextContent(HUDMessage Message) + { + instance.MessageSet(BackingObject, (int)BoxUITextMembers.SetTextContent, Message.BackingObject); + } + + private enum BoxUITextMembers + { + SetTextContent = 100 + } + } + + public class BoxUIImage : BoxUIBase + { + public BoxUIImage() + { + instance.RegisterCheck(); + BackingObject = instance.CreateMessage(MessageTypes.BoxUIImage); + } + + /// + /// Sets the image, please note that image parameters will now be forced to the Pixel setting. Height and Width + /// parameters are measured in px + /// + /// + public void SetImageContent(BillBoardHUDMessage Message) + { + instance.MessageSet(BackingObject, (int)BoxUIImageMembers.SetImageContent, Message.BackingObject); + } + + private enum BoxUIImageMembers + { + SetImageContent = 100 + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/RtsApi.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/RtsApi.cs new file mode 100644 index 00000000..5a56f89e --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/RtsApi.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; + +namespace CGP.ShareTrack.API +{ + public class RtsApi + { + private const long ChannelId = 2772681332; + private Func _GetAcceleration; + private Func _GetAccelerationByDirection; + private Func _GetBoost; + private Func _GetCruiseSpeed; + private Func _GetMaxSpeed; + private Func _GetNegativeInfluence; + private Func _GetReducedAngularSpeed; + + private bool isRegistered; + + private Action ReadyCallback; + public bool IsReady { get; private set; } + + public void Load(Action readyCallback = null) + { + if (isRegistered) + throw new Exception($"{GetType().Name}.Load() should not be called multiple times!"); + + isRegistered = true; + ReadyCallback = readyCallback; + MyAPIGateway.Utilities.RegisterMessageHandler(ChannelId, HandleMessage); + MyAPIGateway.Utilities.SendModMessage(ChannelId, "ApiEndpointRequest"); + } + + public void Unload() + { + MyAPIGateway.Utilities.UnregisterMessageHandler(ChannelId, HandleMessage); + IsReady = false; + isRegistered = false; + } + + private void HandleMessage(object obj) + { + if (obj is string) // the sent "ApiEndpointRequest" will also be received here, explicitly ignoring that + return; + + var dict = obj as IReadOnlyDictionary; + + if (dict == null) + return; + + AssignMethod(dict, "GetCruiseSpeed", ref _GetCruiseSpeed); + AssignMethod(dict, "GetMaxSpeed", ref _GetMaxSpeed); + AssignMethod(dict, "GetBoost", ref _GetBoost); + AssignMethod(dict, "GetAcceleration", ref _GetAcceleration); + AssignMethod(dict, "GetAccelerationByDirection", ref _GetAccelerationByDirection); + AssignMethod(dict, "GetNegativeInfluence", ref _GetNegativeInfluence); + AssignMethod(dict, "GetReducedAngularSpeed", ref _GetReducedAngularSpeed); + + IsReady = true; + ReadyCallback?.Invoke(); + } + + private void AssignMethod(IReadOnlyDictionary delegates, string name, ref T field) + where T : class + { + if (delegates == null) + { + field = null; + return; + } + + Delegate del; + if (!delegates.TryGetValue(name, out del)) + throw new Exception($"{GetType().Name} :: Couldn't find {name} delegate of type {typeof(T)}"); + + field = del as T; + + if (field == null) + throw new Exception( + $"{GetType().Name} :: Delegate {name} is not type {typeof(T)}, instead it's: {del.GetType()}"); + } + + /// + /// Returns the cruising speed of the grid. + /// + public float GetCruiseSpeed(IMyCubeGrid grid) + { + return _GetCruiseSpeed.Invoke(grid); + } + + /// + /// Gets the maximum possible speed (cruise speed + max boost) + /// + public float GetMaxSpeed(IMyCubeGrid grid) + { + return _GetMaxSpeed.Invoke(grid); + } + + /// + /// Returns 4 values: forward boost, min, average, max + /// + public float[] GetBoost(IMyCubeGrid grid) + { + return _GetBoost.Invoke(grid); + } + + /// + /// Returns 4 values: forward accel, min, average, max + /// + public float[] GetAcceleration(IMyCubeGrid grid) + { + return _GetAcceleration.Invoke(grid); + } + + /// + /// Uses Base6Directions.Direction + /// forward = reverse accel + /// backward = forward accel + /// left = right accel + /// ... + /// + public float[] GetAccelerationByDirection(IMyCubeGrid grid) + { + return _GetAccelerationByDirection.Invoke(grid); + } + + /// + /// Returns the negative influence for the specified grid. + /// + public float GetNegativeInfluence(IMyCubeGrid grid) + { + return _GetNegativeInfluence.Invoke(grid); + } + + /// + /// Returns the reduced angular speed for the specified grid. + /// + public float GetReducedAngularSpeed(IMyCubeGrid grid) + { + return _GetReducedAngularSpeed.Invoke(grid); + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/ShieldApi.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/ShieldApi.cs new file mode 100644 index 00000000..2fd15c10 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/API/ShieldApi.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Sandbox.ModAPI; +using VRage; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRageMath; + +namespace CGP.ShareTrack.API +{ + public class ShieldApi + { + private const long Channel = 1365616918; + + private Action _addAtacker; + private bool _apiInit; + private Func _entityBypass; + private Func _getCharge; + private Func _getChargeRate; + private Func _getClosestShield; + private Func _getClosestShieldPoint; + private Func _getDistanceToShield; + private Func> _getFaceInfo; + + private Func> + _getFaceInfoAndPenChance; + + private Func> _getFacesFast; + private Action>> _getLastAttackers; + private Func _getMaxCharge; + private Func _getMaxHpCap; + private Func> _getModulationInfo; + private Func _getPowerCap; + private Func _getPowerUsed; + private Func _getShieldBlock; + private Func _getShieldHeat; + private Func> _getShieldInfo; + private Func _getShieldPercent; + private Func _gridHasShield; + private Func _gridShieldOnline; + private Func _hpToChargeRatio; + + private Func, RayD, bool, bool, long, float, MyTuple> + _intersectEntToShieldFast; // fast check of entities for shield + + private Func _isBlockProtected; + private Func _isFortified; + + private bool _isRegistered; + private Func _isShieldBlock; + private Func _isShieldUp; + + private Func + _lineAttackShield; // negative damage values heal + + private Func _lineIntersectShield; + private Func _matchEntToShieldFast; + + private Func, + MyTuple, MyTuple>?> _matchEntToShieldFastDetails; + + private Func, + MyTuple>?> _matchEntToShieldFastExt; + + private Action _overLoad; + + private Func + _pointAttackShield; // negative damage values heal + + private Func + _pointAttackShieldCon; // negative damage values heal, conditional secondary damage + + private Func + _pointAttackShieldExt; // negative damage values heal + + private Func + _pointAttackShieldHeat; // negative damage values heal, conditional secondary damage + + private Func _pointInShield; + private Func _protectedByShield; + + private Func + _rayAttackShield; // negative damage values heal + + private Func _rayIntersectShield; + private Action _setCharge; + private Action _setShieldHeat; + private Func _shieldStatus; + + public bool IsReady { get; private set; } + public bool Compromised { get; private set; } + + private void HandleMessage(object o) + { + if (_apiInit) return; + var dict = o as IReadOnlyDictionary; + var message = o as string; + + if (message != null && message == "Compromised") + Compromised = true; + + if (dict == null || dict is ImmutableDictionary) + return; + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var pair in dict) + builder.Add(pair.Key, pair.Value); + + MyAPIGateway.Utilities.SendModMessage(Channel, builder.ToImmutable()); + + ApiLoad(dict); + IsReady = true; + } + + public bool Load() + { + if (!_isRegistered) + { + _isRegistered = true; + MyAPIGateway.Utilities.RegisterMessageHandler(Channel, HandleMessage); + } + + if (!IsReady) + MyAPIGateway.Utilities.SendModMessage(Channel, "ApiEndpointRequest"); + return IsReady; + } + + public void Unload() + { + if (_isRegistered) + { + _isRegistered = false; + MyAPIGateway.Utilities.UnregisterMessageHandler(Channel, HandleMessage); + } + + IsReady = false; + } + + public void ApiLoad(IReadOnlyDictionary delegates) + { + _apiInit = true; + _rayAttackShield = + (Func)delegates["RayAttackShield"]; + _lineAttackShield = + (Func)delegates["LineAttackShield"]; + _intersectEntToShieldFast = + (Func, RayD, bool, bool, long, float, MyTuple>)delegates[ + "IntersectEntToShieldFast"]; + _pointAttackShield = + (Func)delegates["PointAttackShield"]; + _pointAttackShieldExt = + (Func)delegates[ + "PointAttackShieldExt"]; + _pointAttackShieldCon = + (Func)delegates[ + "PointAttackShieldCon"]; + _pointAttackShieldHeat = + (Func)delegates[ + "PointAttackShieldHeat"]; + _setShieldHeat = (Action)delegates["SetShieldHeat"]; + _overLoad = (Action)delegates["OverLoadShield"]; + _setCharge = (Action)delegates["SetCharge"]; + _rayIntersectShield = (Func)delegates["RayIntersectShield"]; + _lineIntersectShield = (Func)delegates["LineIntersectShield"]; + _pointInShield = (Func)delegates["PointInShield"]; + _getShieldPercent = (Func)delegates["GetShieldPercent"]; + _getShieldHeat = (Func)delegates["GetShieldHeat"]; + _getChargeRate = (Func)delegates["GetChargeRate"]; + _hpToChargeRatio = (Func)delegates["HpToChargeRatio"]; + _getMaxCharge = (Func)delegates["GetMaxCharge"]; + _getCharge = (Func)delegates["GetCharge"]; + _getPowerUsed = (Func)delegates["GetPowerUsed"]; + _getPowerCap = (Func)delegates["GetPowerCap"]; + _getMaxHpCap = (Func)delegates["GetMaxHpCap"]; + _isShieldUp = (Func)delegates["IsShieldUp"]; + _shieldStatus = (Func)delegates["ShieldStatus"]; + _entityBypass = (Func)delegates["EntityBypass"]; + _gridHasShield = (Func)delegates["GridHasShield"]; + _gridShieldOnline = (Func)delegates["GridShieldOnline"]; + _protectedByShield = (Func)delegates["ProtectedByShield"]; + _getShieldBlock = (Func)delegates["GetShieldBlock"]; + _matchEntToShieldFast = (Func)delegates["MatchEntToShieldFast"]; + _matchEntToShieldFastExt = + (Func, + MyTuple>?>)delegates["MatchEntToShieldFastExt"]; + _matchEntToShieldFastDetails = + (Func, + MyTuple, MyTuple>?>)delegates[ + "MatchEntToShieldFastDetails"]; + _isShieldBlock = (Func)delegates["IsShieldBlock"]; + _getClosestShield = (Func)delegates["GetClosestShield"]; + _getDistanceToShield = (Func)delegates["GetDistanceToShield"]; + _getClosestShieldPoint = (Func)delegates["GetClosestShieldPoint"]; + _getShieldInfo = (Func>)delegates["GetShieldInfo"]; + _getModulationInfo = (Func>)delegates["GetModulationInfo"]; + _getFaceInfo = + (Func>)delegates["GetFaceInfo"]; + _getFaceInfoAndPenChance = + (Func>)delegates[ + "GetFaceInfoAndPenChance"]; + _addAtacker = (Action)delegates["AddAttacker"]; + _isBlockProtected = (Func)delegates["IsBlockProtected"]; + _getFacesFast = (Func>)delegates["GetFacesFast"]; + _getLastAttackers = + (Action>>)delegates["GetLastAttackers"]; + _isFortified = (Func)delegates["IsFortified"]; + } + + public Vector3D? RayAttackShield(IMyTerminalBlock block, RayD ray, long attackerId, float damage, bool energy, + bool drawParticle) + { + return _rayAttackShield?.Invoke(block, ray, attackerId, damage, energy, drawParticle) ?? null; + } + + public Vector3D? LineAttackShield(IMyTerminalBlock block, LineD line, long attackerId, float damage, + bool energy, bool drawParticle) + { + return _lineAttackShield?.Invoke(block, line, attackerId, damage, energy, drawParticle) ?? null; + } + + public MyTuple IntersectEntToShieldFast(List entities, RayD ray, bool onlyIfOnline, + bool enemyOnly, long requesterId, float maxRange) + { + return _intersectEntToShieldFast?.Invoke(entities, ray, onlyIfOnline, enemyOnly, requesterId, maxRange) ?? + new MyTuple(false, float.MaxValue); + } + + public bool PointAttackShield(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, bool energy, + bool drawParticle, bool posMustBeInside = false) + { + return _pointAttackShield?.Invoke(block, pos, attackerId, damage, energy, drawParticle, posMustBeInside) ?? + false; + } + + public float? PointAttackShieldExt(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, + bool energy, bool drawParticle, bool posMustBeInside = false) + { + return _pointAttackShieldExt?.Invoke(block, pos, attackerId, damage, energy, drawParticle, + posMustBeInside) ?? null; + } + + public float? PointAttackShieldCon(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, + float optionalDamage, bool energy, bool drawParticle, bool posMustBeInside = false) + { + return _pointAttackShieldCon?.Invoke(block, pos, attackerId, damage, optionalDamage, energy, drawParticle, + posMustBeInside) ?? null; + } + + public float? PointAttackShieldHeat(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, + float optionalDamage, bool energy, bool drawParticle, bool posMustBeInside = false, float heatScaler = 1) + { + return _pointAttackShieldHeat?.Invoke(block, pos, attackerId, damage, optionalDamage, energy, drawParticle, + posMustBeInside, heatScaler) ?? null; + } + + public void SetShieldHeat(IMyTerminalBlock block, int value) + { + _setShieldHeat?.Invoke(block, value); + } + + public void OverLoadShield(IMyTerminalBlock block) + { + _overLoad?.Invoke(block); + } + + public void SetCharge(IMyTerminalBlock block, float value) + { + _setCharge.Invoke(block, value); + } + + public Vector3D? RayIntersectShield(IMyTerminalBlock block, RayD ray) + { + return _rayIntersectShield?.Invoke(block, ray) ?? null; + } + + public Vector3D? LineIntersectShield(IMyTerminalBlock block, LineD line) + { + return _lineIntersectShield?.Invoke(block, line) ?? null; + } + + public bool PointInShield(IMyTerminalBlock block, Vector3D pos) + { + return _pointInShield?.Invoke(block, pos) ?? false; + } + + public float GetShieldPercent(IMyTerminalBlock block) + { + return _getShieldPercent?.Invoke(block) ?? -1; + } + + public int GetShieldHeat(IMyTerminalBlock block) + { + return _getShieldHeat?.Invoke(block) ?? -1; + } + + public float GetChargeRate(IMyTerminalBlock block) + { + return _getChargeRate?.Invoke(block) ?? -1; + } + + public float HpToChargeRatio(IMyTerminalBlock block) + { + return _hpToChargeRatio?.Invoke(block) ?? -1; + } + + public float GetMaxCharge(IMyTerminalBlock block) + { + return _getMaxCharge?.Invoke(block) ?? -1; + } + + public float GetCharge(IMyTerminalBlock block) + { + return _getCharge?.Invoke(block) ?? -1; + } + + public float GetPowerUsed(IMyTerminalBlock block) + { + return _getPowerUsed?.Invoke(block) ?? -1; + } + + public float GetPowerCap(IMyTerminalBlock block) + { + return _getPowerCap?.Invoke(block) ?? -1; + } + + public float GetMaxHpCap(IMyTerminalBlock block) + { + return _getMaxHpCap?.Invoke(block) ?? -1; + } + + public bool IsShieldUp(IMyTerminalBlock block) + { + return _isShieldUp?.Invoke(block) ?? false; + } + + public string ShieldStatus(IMyTerminalBlock block) + { + return _shieldStatus?.Invoke(block) ?? string.Empty; + } + + public bool EntityBypass(IMyTerminalBlock block, IMyEntity entity, bool remove = false) + { + return _entityBypass?.Invoke(block, entity, remove) ?? false; + } + + public bool GridHasShield(IMyCubeGrid grid) + { + return _gridHasShield?.Invoke(grid) ?? false; + } + + public bool GridShieldOnline(IMyCubeGrid grid) + { + return _gridShieldOnline?.Invoke(grid) ?? false; + } + + public bool ProtectedByShield(IMyEntity entity) + { + return _protectedByShield?.Invoke(entity) ?? false; + } + + public IMyTerminalBlock GetShieldBlock(IMyEntity entity) + { + return _getShieldBlock?.Invoke(entity) ?? null; + } + + public IMyTerminalBlock MatchEntToShieldFast(IMyEntity entity, bool onlyIfOnline) + { + return _matchEntToShieldFast?.Invoke(entity, onlyIfOnline) ?? null; + } + + public MyTuple, MyTuple>? + MatchEntToShieldFastExt(MyEntity entity, bool onlyIfOnline) + { + return _matchEntToShieldFastExt?.Invoke(entity, onlyIfOnline) ?? null; + } + + public MyTuple, MyTuple, + MyTuple>? MatchEntToShieldFastDetails(MyEntity entity, bool onlyIfOnline) + { + return _matchEntToShieldFastDetails?.Invoke(entity, onlyIfOnline) ?? null; + } + + public bool IsShieldBlock(IMyTerminalBlock block) + { + return _isShieldBlock?.Invoke(block) ?? false; + } + + public IMyTerminalBlock GetClosestShield(Vector3D pos) + { + return _getClosestShield?.Invoke(pos) ?? null; + } + + public double GetDistanceToShield(IMyTerminalBlock block, Vector3D pos) + { + return _getDistanceToShield?.Invoke(block, pos) ?? -1; + } + + public Vector3D? GetClosestShieldPoint(IMyTerminalBlock block, Vector3D pos) + { + return _getClosestShieldPoint?.Invoke(block, pos) ?? null; + } + + public MyTuple GetShieldInfo(MyEntity entity) + { + return _getShieldInfo?.Invoke(entity) ?? new MyTuple(); + } + + public MyTuple GetModulationInfo(MyEntity entity) + { + return _getModulationInfo?.Invoke(entity) ?? new MyTuple(); + } + + public MyTuple GetFaceInfo(IMyTerminalBlock block, Vector3D pos, + bool posMustBeInside = false) + { + return _getFaceInfo?.Invoke(block, pos, posMustBeInside) ?? new MyTuple(); + } + + public MyTuple TAPI_GetFaceInfoAndPenChance(IMyTerminalBlock block, + Vector3D pos, bool posMustBeInside = false) + { + return _getFaceInfoAndPenChance?.Invoke(block, pos, posMustBeInside) ?? + new MyTuple(); + } + + public void AddAttacker(long attacker) + { + _addAtacker?.Invoke(attacker); + } + + public bool IsBlockProtected(IMySlimBlock block) + { + return _isBlockProtected?.Invoke(block) ?? false; + } + + public MyTuple GetFacesFast(MyEntity entity) + { + return _getFacesFast?.Invoke(entity) ?? new MyTuple(); + } + + public void GetLastAttackers(MyEntity entity, ICollection> collection) + { + _getLastAttackers?.Invoke(entity, collection); + } + + public bool IsFortified(IMyTerminalBlock block) + { + return _isFortified?.Invoke(block) ?? false; + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/AllGridsList.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/AllGridsList.cs new file mode 100644 index 00000000..813112fe --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/AllGridsList.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using CGP.ShareTrack.API.CoreSystem; +using CGP.ShareTrack.HeartNetworking; +using CGP.ShareTrack.HeartNetworking.Custom; +using CGP.ShareTrack.ShipTracking; +using VRage; +using VRage.Game.ModAPI; +using VRage.Input; +using VRageMath; +using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; + +namespace CGP.ShareTrack +{ + /// + /// Shift-M menu + /// + public class AllGridsList + { + + public static AllGridsList I; + + public static Dictionary PointValues = new Dictionary(); + + + private static readonly Dictionary AllPlayers = new Dictionary(); + + public static HudAPIv2.HUDMessage + IntegretyMessage, + TimerMessage, + Ticketmessage; + + public static ShipTracker.NametagSettings NametagViewState = ShipTracker.NametagSettings.PlayerName; + + private readonly Dictionary _bp = new Dictionary(); + + private readonly Dictionary _keyAndActionPairs = new Dictionary + { + //[MyKeys.M] = () => + //{ + // var castGrid = RaycastGridFromCamera(); + // if (castGrid == null) + // return; + // + // if (!TrackingManager.I.IsGridTracked(castGrid)) + // TrackingManager.I.TrackGrid(castGrid); + // else + // TrackingManager.I.UntrackGrid(castGrid); + //}, + //[MyKeys.N] = () => + //{ + // IntegretyMessage.Visible = !IntegretyMessage.Visible; + // MyAPIGateway.Utilities.ShowNotification("ShipTracker: Hud visibility set to " + + // IntegretyMessage.Visible); + //}, + //[MyKeys.B] = () => + //{ + // TimerMessage.Visible = !TimerMessage.Visible; + // Ticketmessage.Visible = !Ticketmessage.Visible; + // MyAPIGateway.Utilities.ShowNotification( + // "ShipTracker: Timer visibility set to " + TimerMessage.Visible); + //}, + //[MyKeys.J] = () => + //{ + // NametagViewState++; + // if (NametagViewState > (ShipTracker.NametagSettings)3) + // NametagViewState = 0; + // MyAPIGateway.Utilities.ShowNotification( + // "ShipTracker: Nameplate visibility set to " + NametagViewState); + //}, + [MyKeys.T] = () => + { + I?._hudPointsList?.CycleViewState(); + }, + }; + + private readonly List _listPlayers = new List(); + private readonly Dictionary _m = new Dictionary(); + private readonly Dictionary _mbp = new Dictionary(); + private readonly Dictionary _mobp = new Dictionary(); + private readonly Dictionary _obp = new Dictionary(); + + private readonly Dictionary _pbp = new Dictionary(); + + // todo: remove this and replace with old solution for just combining BP and mass + private readonly Dictionary> _ts = new Dictionary>(); + + // Get the sphere model based on the given cap color + + private bool _awaitingTrackRequest = true; + private Func> _climbingCostFunction; + + private void ParsePointsDict(object message) + { + try + { + var dict = message as Dictionary; + if (dict != null) + { + PointValues = new Dictionary(); + foreach (var kvp in dict) + { + AddFuzzyPointValue(kvp.Key, kvp.Value); + } + return; + } + var climbCostFunc = message as Func>; + if (climbCostFunc != null) + { + _climbingCostFunction = climbCostFunc; + } + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + private void AddFuzzyPointValue(string key, double value) + { + foreach (var existingKey in PointValues.Keys.ToList()) + { + if (existingKey.StartsWith(key, StringComparison.OrdinalIgnoreCase)) + { + PointValues[existingKey] = value; + } + } + PointValues[key] = value; + } + + private void HudRegistered() + { + // Avoid bootlock when opening world with autotracked grids. + TrackingManager.Init(); + + _hudPointsList = new HudPointsList(); + + IntegretyMessage = new HudAPIv2.HUDMessage(scale: 1.15f, font: "BI_SEOutlined", + Message: new StringBuilder(""), origin: new Vector2D(.51, .95), hideHud: false, + blend: BlendTypeEnum.PostPP) + { + Visible = true + }; + TimerMessage = new HudAPIv2.HUDMessage(scale: 1.2f, font: "BI_SEOutlined", Message: new StringBuilder(""), + origin: new Vector2D(0.35, .99), hideHud: false, shadowing: true, blend: BlendTypeEnum.PostPP) + { + Visible = false, //defaulted off? + InitialColor = Color.White + }; + Ticketmessage = new HudAPIv2.HUDMessage(scale: 1f, font: "BI_SEOutlined", Message: new StringBuilder(""), + origin: new Vector2D(0.51, .99), hideHud: false, shadowing: true, blend: BlendTypeEnum.PostPP) + { + Visible = false, //defaulted off? + InitialColor = Color.White + }; + } + + private void HandleKeyInputs() + { + if (!MyAPIGateway.Input.IsAnyShiftKeyPressed()) + return; + + foreach (var pair in _keyAndActionPairs) + if (MyAPIGateway.Input.IsNewKeyPressed(pair.Key)) + pair.Value.Invoke(); + } + + private void UpdateTrackingData() + { + if (MasterSession.I.Ticks % 59 != 0) + return; + + lock (TrackingManager.I.TrackedGrids) + { + foreach (var shipTracker in TrackingManager.I.TrackedGrids.Values) + { + shipTracker.Update(); + } + } + + if (IntegretyMessage == null || !MasterSession.I.TextHudApi.Heartbeat) + return; + + var tt = new StringBuilder(); + + // Clear the dictionaries to remove old data + _ts.Clear(); + _m.Clear(); + _bp.Clear(); + _mbp.Clear(); + _pbp.Clear(); + _obp.Clear(); + _mobp.Clear(); + + MainTrackerUpdate(_ts, _m, _bp, _mbp, _pbp, _obp, _mobp); + + TeamBpCalc(tt, _ts, _m, _bp, _mbp, _pbp, _obp, _mobp); + + if (!IntegretyMessage.Message.Equals(tt)) + { + IntegretyMessage.Message = tt; + } + + IntegretyMessage.Origin = new Vector2D(0.975 - IntegretyMessage.GetTextLength().X, IntegretyMessage.Origin.Y); + } + + + private void MainTrackerUpdate(Dictionary> ts, Dictionary m, + Dictionary bp, Dictionary mbp, Dictionary pbp, + Dictionary obp, Dictionary mobp) + { + foreach (var shipTracker in TrackingManager.I.TrackedGrids.Values) + { + var fn = shipTracker.FactionName.Length > 6 ? shipTracker.FactionName.Substring(0, 6) : shipTracker.FactionName; + var o = shipTracker.OwnerName; + var nd = shipTracker.IsFunctional; + + if (!ts.ContainsKey(fn)) + { + ts.Add(fn, new List()); + m[fn] = 0; + bp[fn] = 0; + mbp[fn] = 0; + pbp[fn] = 0; + obp[fn] = 0; + mobp[fn] = 0; + } + + if (nd) + { + m[fn] += shipTracker.Mass; + bp[fn] += shipTracker.BattlePoints; + } + else + { + continue; + } + + mbp[fn] += shipTracker.RemainingPoints; + pbp[fn] += shipTracker.PowerPoints; + obp[fn] += shipTracker.OffensivePoints; + mobp[fn] += shipTracker.MovementPoints; + + var wep = 0; + foreach (var kvp in shipTracker.WeaponCounts) + { + wep += kvp.Value; + } + + var pwr = FormatPower(Math.Round(shipTracker.TotalPower, 1)); + var ts2 = FormatThrust(Math.Round(shipTracker.TotalThrust, 2)); + + ts[fn].Add(CreateDisplayString(o, shipTracker, wep, pwr, ts2)); + } + } + + private string FormatPower(double currentPower) + { + return currentPower > 1000 ? $"{Math.Round(currentPower / 1000, 1)}GW" : $"{currentPower}MW"; + } + + private string FormatThrust(double installedThrust) + { + var thrustInMega = Math.Round(installedThrust / 1e6, 1); + return thrustInMega > 1e2 ? $"{Math.Round(thrustInMega / 1e3, 2)}GN" : $"{thrustInMega}MN"; + } + + private string CreateDisplayString(string ownerName, ShipTracker tracker, int wep, string power, string thrust) + { + var ownerDisplay = ownerName != null + ? ownerName.Substring(0, Math.Min(ownerName.Length, 7)) + : tracker.GridName; + var integrityPercent = + (int)(tracker.GridIntegrity / tracker.OriginalGridIntegrity * + 100); // TODO fix this to use hull integrity + var shieldPercent = (int)tracker.CurrentShieldPercent; + var shieldColor = shieldPercent <= 0 + ? "red" + : $"{255},{255 - tracker.CurrentShieldHeat * 2.5f},{255 - tracker.CurrentShieldHeat * 2.5f}"; + var weaponColor = wep == 0 ? "red" : "Orange"; + + var functionalColor = tracker.IsFunctional ? "white" : "red"; + var integrityColor = integrityPercent >= 75 ? "White" : integrityPercent >= 50 ? "LightCoral" : integrityPercent >= 25 ? "IndianRed" : "FireBrick"; + + return + $"{ownerDisplay,-8}{integrityPercent,3}% P:{power,3} T:{thrust,3} W:{wep} S:{shieldPercent,3}%"; + } + + + private static void TeamBpCalc(StringBuilder tt, Dictionary> trackedShip, + Dictionary m, Dictionary bp, Dictionary mbp, + Dictionary pbp, Dictionary obp, Dictionary mobp) + { + foreach (var faction in trackedShip.Keys) + { + if (trackedShip[faction] == null || trackedShip[faction].Count == 0) continue; + + var msValue = m[faction] / 1e6; + var tbi = 100f / bp[faction]; + + tt.Append("---- ") + .Append(faction) + .Append(" : ") + .AppendFormat("{0:0.00}M : {1}bp [", msValue, bp[faction]); + + tt.AppendFormat("{0}%|", (int)(obp[faction] * tbi + 0.5f)) + .AppendFormat("{0}%|", (int)(pbp[faction] * tbi + 0.5f)) + .AppendFormat("{0}%|", (int)(mobp[faction] * tbi + 0.5f)) + .AppendFormat("{0}%]", (int)(mbp[faction] * tbi + 0.5f)) + .AppendLine(" ---------"); + + foreach (var y in trackedShip[faction]) tt.AppendLine(y); + } + } + + public static IMyPlayer GetOwner(long v) + { + IMyPlayer owner; + return AllPlayers.TryGetValue(v, out owner) ? owner : null; + } + + public static IMyCubeGrid RaycastGridFromCamera() + { + var camMat = MyAPIGateway.Session.Camera.WorldMatrix; + var hits = new List(); + MyAPIGateway.Physics.CastRay(camMat.Translation, camMat.Translation + camMat.Forward * 500, hits); + foreach (var hit in hits) + { + var grid = hit.HitEntity as IMyCubeGrid; + + if (grid?.Physics != null) + return grid; + } + + return null; + } + + #region API Fields + + public WcApi WcApi { get; private set; } + public ShieldApi ShieldApi { get; private set; } + public RtsApi RtsApi { get; private set; } + + private HudPointsList _hudPointsList; + + #endregion + + + #region Public Methods + + public void Init() + { + I = this; + MasterSession.I.HudRegistered += HudRegistered; + + MyAPIGateway.Utilities.ShowMessage("ShareTrack", + "Aim at a grid and press:" + + "\n- Shift+T to show grid stats." + //"\n- Shift+M to track a grid." + + //"\n- Shift+J to cycle nametag style." + ); + + MyAPIGateway.Utilities.RegisterMessageHandler(2546247, ParsePointsDict); + + // Check if the current instance is not a dedicated server + if (MyAPIGateway.Utilities.IsDedicated) + TrackingManager.Init(); + + // Initialize the WC_api and load it if it's not null + + WcApi = new WcApi(); + WcApi?.Load(); + + // Initialize the SH_api and load it if it's not null + ShieldApi = new ShieldApi(); + ShieldApi?.Load(); + + // Initialize the RTS_api and load it if it's not null + RtsApi = new RtsApi(); + RtsApi?.Load(); + } + + public void Close() + { + Log.Info("Start PointCheck.UnloadData()"); + + WcApi?.Unload(); + ShieldApi?.Unload(); + if (PointValues != null) + { + PointValues.Clear(); + AllPlayers.Clear(); + } + + MyAPIGateway.Utilities.UnregisterMessageHandler(2546247, ParsePointsDict); + + I = null; + } + + public void UpdateAfterSimulation() + { + // Send request to server for tracked grids. + if (_awaitingTrackRequest && !MyAPIGateway.Session.IsServer) + { + HeartNetwork.I.SendToServer(new SyncRequestPacket()); + _awaitingTrackRequest = false; + } + + try + { + UpdateTrackingData(); + } + catch (Exception e) + { + Log.Error($"Exception in UpdateAfterSimulation TryCatch 01: {e}"); + } + + try + { + if (MasterSession.I.Ticks % 61 == 0) + { + AllPlayers.Clear(); + MyAPIGateway.Multiplayer.Players.GetPlayers(_listPlayers, delegate(IMyPlayer p) + { + AllPlayers.Add(p.IdentityId, p); + return false; + } + ); + } + } + catch (Exception e) + { + Log.Error($"Exception in UpdateAfterSimulation TryCatch 02: {e}"); + } + } + + public void Draw() + { + //if you are the server do nothing here + if (MyAPIGateway.Utilities.IsDedicated || !MasterSession.I.TextHudApi.Heartbeat) + return; + try + { + if (MyAPIGateway.Session?.Camera != null && MyAPIGateway.Session.CameraController != null && + !MyAPIGateway.Gui.ChatEntryVisible && + !MyAPIGateway.Gui.IsCursorVisible && MyAPIGateway.Gui.GetCurrentScreen == MyTerminalPageEnum.None) + HandleKeyInputs(); + + foreach (var tracker in TrackingManager.I.TrackedGrids.Values) + tracker.UpdateHud(); + + _hudPointsList?.UpdateDraw(); + } + catch (Exception e) + { + Log.Error($"Exception in Draw: {e}"); + } + } + + public static void ClimbingCostRename(ref string blockDisplayName, ref float climbingCostMultiplier) // Double instead of float. + { + if (I._climbingCostFunction == null) return; + var results = I._climbingCostFunction.Invoke(blockDisplayName); + blockDisplayName = results.Item1; + climbingCostMultiplier = results.Item2; + //MyAPIGateway.Utilities.ShowNotification($"Processing: {blockDisplayName}, Multiplier: {climbingCostMultiplier}", 3000); + } + + + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/BuildingBlockPoints.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/BuildingBlockPoints.cs new file mode 100644 index 00000000..aa748c13 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/BuildingBlockPoints.cs @@ -0,0 +1,61 @@ +using System.Text; +using Sandbox.Game.Gui; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using VRageMath; +using VRageRender; + +namespace CGP.ShareTrack +{ + internal class BuildingBlockPoints + { + internal string LastHeldSubtype; + private HudAPIv2.HUDMessage _pointsMessage; + + public BuildingBlockPoints() + { + MasterSession.I.HudRegistered += () => + { + _pointsMessage = new HudAPIv2.HUDMessage(scale: 1f, font: "BI_SEOutlined", Message: new StringBuilder(""), + origin: new Vector2D(-0.969, 0.57), blend: MyBillboard.BlendTypeEnum.PostPP); + }; + } + + private int _ticks; + public void Update() + { + if (_ticks++ % 10 != 0) + return; + + if (LastHeldSubtype != MyHud.BlockInfo?.DefinitionId.SubtypeName) + { + LastHeldSubtype = MyHud.BlockInfo?.DefinitionId.SubtypeName; + UpdateHud(MyHud.BlockInfo); + } + } + + private void UpdateHud(MyHudBlockInfo blockInfo) + { + if (_pointsMessage == null) + return; + + double blockPoints; + if (blockInfo == null || !AllGridsList.PointValues.TryGetValue(blockInfo.DefinitionId.SubtypeName, out blockPoints)) + { + _pointsMessage.Visible = false; + return; + } + + string blockDisplayName = blockInfo.BlockName; + + float thisClimbingCostMult = 0; + AllGridsList.ClimbingCostRename(ref blockDisplayName, ref thisClimbingCostMult); + + _pointsMessage.Message.Clear(); + _pointsMessage.Message.Append($"{blockDisplayName}:\n{blockPoints}bp"); + if (thisClimbingCostMult != 0) + _pointsMessage.Message.Append($" +{(int)(blockPoints*thisClimbingCostMult)}bp/b"); + _pointsMessage.Visible = true; + } + } +} diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/SyncRequestPacket.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/SyncRequestPacket.cs new file mode 100644 index 00000000..109cbf84 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/SyncRequestPacket.cs @@ -0,0 +1,26 @@ +using System; +using ProtoBuf; +using Sandbox.ModAPI; +using CGP.ShareTrack.ShipTracking; + +namespace CGP.ShareTrack.HeartNetworking.Custom +{ + [ProtoContract] + internal class SyncRequestPacket : PacketBase + { + public override void Received(ulong SenderSteamId) + { + if (!MyAPIGateway.Session.IsServer) + return; + + if (AllGridsList.I == null) + throw new Exception("Null PointCheck instance!"); + if (TrackingManager.I == null) + throw new Exception("Null TrackingManager instance!"); + + Log.Info("Received join-sync request from " + SenderSteamId); + + TrackingManager.I.ServerDoSync(); + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/TrackingSyncPacket.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/TrackingSyncPacket.cs new file mode 100644 index 00000000..08093ebc --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/Custom/TrackingSyncPacket.cs @@ -0,0 +1,55 @@ +using System; +using ProtoBuf; +using Sandbox.ModAPI; +using CGP.ShareTrack.ShipTracking; + +namespace CGP.ShareTrack.HeartNetworking.Custom +{ + /// + /// Packet used for syncing tracked grids. + /// + [ProtoContract] + internal class TrackingSyncPacket : PacketBase + { + [ProtoMember(22)] public bool? IsAddingReference; + [ProtoMember(21)] public long[] TrackedGrids; + + public TrackingSyncPacket() + { + } + + public TrackingSyncPacket(long[] trackedGrids) + { + TrackedGrids = trackedGrids; + } + + public TrackingSyncPacket(long referenceGrid, bool isAddingReference) + { + TrackedGrids = new[] { referenceGrid }; + IsAddingReference = isAddingReference; + } + + public override void Received(ulong SenderSteamId) + { + if (TrackingManager.I == null) + { + Log.Info("TrackingManager is null!"); + return; + } + + if (TrackedGrids == null) + { + Log.Info("Null TrackedGrids!"); + TrackedGrids = Array.Empty(); + } + + if (IsAddingReference == null) + TrackingManager.I.BulkTrackGrids(TrackedGrids); + else if ((bool)IsAddingReference) + TrackingManager.I.TrackGrid(TrackedGrids[0], MyAPIGateway.Session.IsServer); + else + TrackingManager.I.UntrackGrid(TrackedGrids[0], MyAPIGateway.Session.IsServer); + Log.Info("Receive track request! " + (IsAddingReference == null)); + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/HeartNetwork.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/HeartNetwork.cs new file mode 100644 index 00000000..eb42fd02 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/HeartNetwork.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; + +namespace CGP.ShareTrack.HeartNetworking +{ + public class HeartNetwork + { + public static HeartNetwork I; + + private int _networkLoadUpdate; + + public int NetworkLoadTicks = 240; + + + private readonly List TempPlayers = new List(); + public Dictionary TypeNetworkLoad = new Dictionary(); + + public ushort NetworkId { get; private set; } + public int TotalNetworkLoad { get; private set; } + + public void LoadData(ushort networkId) { + if (I != null) + return; + + I = this; + NetworkId = networkId; + + TypeNetworkLoad.Clear(); + foreach (var type in PacketBase.Types) + TypeNetworkLoad.Add(type, 0); + + MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(NetworkId, ReceivedPacket); + } + + public void UnloadData() { + if (NetworkId > 0) + MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(NetworkId, ReceivedPacket); + + TypeNetworkLoad?.Clear(); + NetworkId = 0; + I = null; + } + + public void Update() { + if (MasterSession.I == null || Log._unloaded) + return; + + _networkLoadUpdate--; + if (_networkLoadUpdate <= 0) { + _networkLoadUpdate = NetworkLoadTicks; + TotalNetworkLoad = 0; + + foreach (var networkLoadArray in TypeNetworkLoad.Keys.ToArray()) { + TotalNetworkLoad += TypeNetworkLoad[networkLoadArray]; + TypeNetworkLoad[networkLoadArray] = 0; + } + + TotalNetworkLoad /= NetworkLoadTicks / 60; // Average per-second + } + } + + private void ReceivedPacket(ushort channelId, byte[] serialized, ulong senderSteamId, bool isSenderServer) { + // Add check if mod is unloaded + if (MasterSession.I == null || Log._unloaded) + return; + + try { + var packet = MyAPIGateway.Utilities.SerializeFromBinary(serialized); + if (packet == null) + return; + + if (TypeNetworkLoad.ContainsKey(packet.GetType())) + TypeNetworkLoad[packet.GetType()] += serialized.Length; + + HandlePacket(packet, senderSteamId); + } + catch (Exception ex) { + if (!Log._unloaded) + Log.Error(ex); + } + } + + private void HandlePacket(PacketBase packet, ulong senderSteamId) { + if (packet == null || MasterSession.I == null) + return; + + try { + packet.Received(senderSteamId); + } + catch (Exception ex) { + if (!Log._unloaded) + Log.Error(ex); + } + } + + + public KeyValuePair HighestNetworkLoad() + { + Type highest = null; + + foreach (var networkLoadArray in TypeNetworkLoad) + if (highest == null || networkLoadArray.Value > TypeNetworkLoad[highest]) + highest = networkLoadArray.Key; + + return new KeyValuePair(highest, TypeNetworkLoad[highest]); + } + + public void SendToPlayer(PacketBase packet, ulong playerSteamId, byte[] serialized = null) + { + RelayToClient(packet, playerSteamId, MyAPIGateway.Session?.Player?.SteamUserId ?? 0, serialized); + } + + public void SendToEveryone(PacketBase packet, byte[] serialized = null) + { + RelayToClients(packet, MyAPIGateway.Session?.Player?.SteamUserId ?? 0, serialized); + } + + public void SendToServer(PacketBase packet, byte[] serialized = null) + { + RelayToServer(packet, MyAPIGateway.Session?.Player?.SteamUserId ?? 0, serialized); + } + + private void RelayToClients(PacketBase packet, ulong senderSteamId = 0, byte[] serialized = null) + { + if (!MyAPIGateway.Multiplayer.IsServer) + return; + + TempPlayers.Clear(); + MyAPIGateway.Players.GetPlayers(TempPlayers); + + foreach (var p in TempPlayers) + { + // skip sending to self (server player) or back to sender + if (p.SteamUserId == MyAPIGateway.Multiplayer.ServerId || p.SteamUserId == senderSteamId) + continue; + + if (serialized == null) // only serialize if necessary, and only once. + serialized = MyAPIGateway.Utilities.SerializeToBinary(packet); + + MyAPIGateway.Multiplayer.SendMessageTo(NetworkId, serialized, p.SteamUserId); + } + + TempPlayers.Clear(); + } + + private void RelayToClient(PacketBase packet, ulong playerSteamId, ulong senderSteamId, + byte[] serialized = null) + { + if (playerSteamId == MyAPIGateway.Multiplayer.ServerId || playerSteamId == senderSteamId) + return; + + if (serialized == null) // only serialize if necessary, and only once. + serialized = MyAPIGateway.Utilities.SerializeToBinary(packet); + + MyAPIGateway.Multiplayer.SendMessageTo(NetworkId, serialized, playerSteamId); + } + + private void RelayToServer(PacketBase packet, ulong senderSteamId = 0, byte[] serialized = null) + { + if (senderSteamId == MyAPIGateway.Multiplayer.ServerId) + return; + + if (serialized == null) // only serialize if necessary, and only once. + serialized = MyAPIGateway.Utilities.SerializeToBinary(packet); + + MyAPIGateway.Multiplayer.SendMessageToServer(NetworkId, serialized); + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/PacketBase.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/PacketBase.cs new file mode 100644 index 00000000..52797d88 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HeartNetworking/PacketBase.cs @@ -0,0 +1,25 @@ +using System; +using ProtoBuf; +using CGP.ShareTrack.HeartNetworking.Custom; + +namespace CGP.ShareTrack.HeartNetworking +{ + [ProtoInclude(91, typeof(TrackingSyncPacket))] + [ProtoInclude(92, typeof(SyncRequestPacket))] + [ProtoContract(UseProtoMembersOnly = true)] + public abstract class PacketBase + { + public static Type[] Types = + { + typeof(PacketBase), + typeof(TrackingSyncPacket), + typeof(SyncRequestPacket), + }; + + /// + /// Called whenever your packet is recieved. + /// + /// + public abstract void Received(ulong SenderSteamId); + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/HudPointsList.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HudPointsList.cs new file mode 100644 index 00000000..4ca7d00a --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/HudPointsList.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sandbox.Definitions; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using CGP.ShareTrack.API.CoreSystem; +using CGP.ShareTrack.ShipTracking; +using VRage.Game; +using VRage.Game.ModAPI; +using VRageMath; +using static VRageRender.MyBillboard; + +namespace CGP.ShareTrack +{ + /// + /// Shift-T screen + /// + internal class HudPointsList + { + private readonly StringBuilder _gunTextBuilder = new StringBuilder(); + private readonly StringBuilder _speedTextBuilder = new StringBuilder(); + + private ShipTracker _shipTracker = null; + + private int _currentPage = 0; + private const int ItemsPerPage = 10; // Number of items to display per page + + private int _pageCounter = 0; // Counter for automatic page change + private const int PageChangeInterval = 5; // Number of ticks between page changes (5 seconds at 60 ticks per second) + + + + private readonly HudAPIv2.HUDMessage + _statMessage = new HudAPIv2.HUDMessage(scale: 1f, font: "BI_SEOutlined", Message: new StringBuilder(""), + origin: new Vector2D(-.99, .99), hideHud: false, blend: BlendTypeEnum.PostPP) + { + Visible = false, + InitialColor = Color.Orange + }, + _statMessageBattleWeaponCountsist = new HudAPIv2.HUDMessage(scale: 1.25f, font: "BI_SEOutlined", + Message: new StringBuilder(""), origin: new Vector2D(-.99, .99), hideHud: false, shadowing: true, + blend: BlendTypeEnum.PostPP) + { + Visible = false + }, + _statMessageBattle = new HudAPIv2.HUDMessage(scale: 1.25f, font: "BI_SEOutlined", + Message: new StringBuilder(""), origin: new Vector2D(-.54, -0.955), hideHud: false, + blend: BlendTypeEnum.PostPP) + { + Visible = false + }; + + private ViewState _viewState = ViewState.None; + private Queue _executionTimes = new Queue(); + private const int _sampleSize = 1; // Number of samples to consider for the average + private double _executionTimeSum = 0; // Running total of execution times + private static IMyCubeGrid GetFocusedGrid() + { + var cockpit = MyAPIGateway.Session.ControlledObject?.Entity as IMyCockpit; + if (cockpit == null || MyAPIGateway.Session.IsCameraUserControlledSpectator) + return AllGridsList.RaycastGridFromCamera(); + return cockpit.CubeGrid?.Physics != null + ? // user is in cockpit + cockpit.CubeGrid + : null; + } + + private void ShiftTHandling() + { + var focusedGrid = GetFocusedGrid(); + if (focusedGrid != null) + { + ShiftTCalcs(focusedGrid); + } + else if (_statMessage.Visible) + { + _shipTracker = null; + _statMessage.Message.Clear(); + _statMessage.Visible = false; + } + } + + private void BattleShiftTHandling() + { + if (_statMessage.Visible) + { + _statMessage.Message.Clear(); + _statMessage.Visible = false; + } + + var focusedGrid = GetFocusedGrid(); + if (focusedGrid != null) + { + BattleShiftTCalcs(focusedGrid); + } + else if (_statMessageBattle.Visible) + { + _shipTracker = null; + _statMessageBattle.Message.Clear(); + _statMessageBattle.Visible = false; + _statMessageBattleWeaponCountsist.Message.Clear(); + _statMessageBattleWeaponCountsist.Visible = false; + } + } + + private void ShiftTCalcs(IMyCubeGrid focusedGrid) + { + // Update once per second + if (MasterSession.I.Ticks % 59 != 0) + return; + var stopwatch = new System.Diagnostics.Stopwatch(); + stopwatch.Start(); + + if (_shipTracker?.Grid?.GetGridGroup(GridLinkTypeEnum.Physical) != focusedGrid.GetGridGroup(GridLinkTypeEnum.Physical) + && !TrackingManager.I.TrackedGrids.TryGetValue(focusedGrid, out _shipTracker)) + { + _shipTracker = new ShipTracker(focusedGrid, false); + Log.Info($"ShiftTCalcs Tracked grid {focusedGrid.DisplayName}. Visible: false"); + } + + _shipTracker.Update(); + + var totalShieldString = "None"; + + if (_shipTracker.MaxShieldHealth > 100) + totalShieldString = $"{_shipTracker.MaxShieldHealth / 100f:F2} M"; + else if (_shipTracker.MaxShieldHealth > 1 && _shipTracker.MaxShieldHealth < 100) + totalShieldString = $"{_shipTracker.MaxShieldHealth:F0}0 K"; + + var gunTextBuilder = new StringBuilder(); + foreach (var x in _shipTracker.WeaponCounts.Keys) + gunTextBuilder.AppendFormat("{0} x {1}\n", _shipTracker.WeaponCounts[x], x); + var gunText = gunTextBuilder.ToString(); + + // Count the number of each type of FatBlock and get their PCU + var blockCountDict = new Dictionary(); + var fatBlocks = new List(); + focusedGrid.GetBlocks(fatBlocks, block => block.FatBlock != null); + + foreach (var block in fatBlocks) + { + var fatBlock = block.FatBlock; + var blockDef = MyDefinitionManager.Static.GetCubeBlockDefinition(fatBlock.BlockDefinition); + + if (blockDef != null) + { + int blockPCU = blockDef.PCU; // Get the PCU value from the block definition + string blockType = $"{blockDef.DisplayNameText}"; + + if (blockCountDict.ContainsKey(blockType)) + { + blockCountDict[blockType] += blockPCU; + } + else + { + blockCountDict[blockType] = blockPCU; + } + } + } + + // Update the page periodically + _pageCounter++; + if (_pageCounter >= PageChangeInterval) + { + _pageCounter = 0; // Reset the counter + _currentPage++; // Move to the next page + } + + // Handle page wrapping + var blockList = blockCountDict.ToList(); + blockList.Sort((x, y) => string.Compare(x.Key, y.Key, StringComparison.Ordinal)); // Sort alphabetically + int totalPages = (int)Math.Ceiling((double)blockList.Count / ItemsPerPage); + if (totalPages > 0) + { + _currentPage = _currentPage % totalPages; // Wrap around if we exceed the total pages + } + else + { + _currentPage = 0; // Reset if there are no pages + } + + // Build the special block text with pagination + var specialBlockTextBuilder = new StringBuilder(); + int startIndex = _currentPage * ItemsPerPage; + int endIndex = Math.Min(startIndex + ItemsPerPage, blockList.Count); + + for (int i = startIndex; i < endIndex; i++) + { + var kvp = blockList[i]; + specialBlockTextBuilder.AppendFormat("{0}: {1} total PCU\n", kvp.Key, kvp.Value); + } + + // Add page navigation info + specialBlockTextBuilder.AppendFormat("\nPage {0}/{1}", _currentPage + 1, totalPages); + + var specialBlockText = specialBlockTextBuilder.ToString(); + + var massString = $"{_shipTracker.Mass}"; + + var thrustInKilograms = focusedGrid.GetMaxThrustInDirection(Base6Directions.Direction.Backward) / 9.81f; + var mass = _shipTracker.Mass; + var twr = (float)Math.Round(thrustInKilograms / mass, 1); + + if (_shipTracker.Mass > 1000000) + massString = $"{Math.Round(_shipTracker.Mass / 1000000f, 1):F2}m"; + + var twRs = $"{twr:F3}"; + var thrustString = $"{Math.Round(_shipTracker.TotalThrust, 1)}"; + + if (_shipTracker.TotalThrust > 1000000) + thrustString = $"{Math.Round(_shipTracker.TotalThrust / 1000000f, 1):F2}M"; + + var playerName = _shipTracker.Owner == null ? _shipTracker.GridName : _shipTracker.Owner.DisplayName; + var factionName = _shipTracker.Owner == null + ? "" + : MyAPIGateway.Session?.Factions?.TryGetPlayerFaction(_shipTracker.OwnerId)?.Name; + + var speed = focusedGrid.GridSizeEnum == MyCubeSize.Large + ? MyDefinitionManager.Static.EnvironmentDefinition.LargeShipMaxSpeed + : MyDefinitionManager.Static.EnvironmentDefinition.SmallShipMaxSpeed; + var reducedAngularSpeed = 0f; + + if (RtsApi != null && RtsApi.IsReady) + { + speed = (float)Math.Round(RtsApi.GetMaxSpeed(focusedGrid), 2); + reducedAngularSpeed = RtsApi.GetReducedAngularSpeed(focusedGrid); + } + + var pwrNotation = _shipTracker.TotalPower > 1000 ? "GW" : "MW"; + var tempPwr = _shipTracker.TotalPower > 1000 + ? $"{Math.Round(_shipTracker.TotalPower / 1000, 1):F1}" + : Math.Round(_shipTracker.TotalPower, 1).ToString(); + var pwr = tempPwr + pwrNotation; + + var gyroString = $"{Math.Round(_shipTracker.TotalTorque, 1)}"; + + if (_shipTracker.TotalTorque >= 1000000) + { + var tempGyro2 = Math.Round(_shipTracker.TotalTorque / 1000000f, 1); + gyroString = tempGyro2 > 1000 + ? $"{Math.Round(tempGyro2 / 1000, 1):F1}G" + : $"{Math.Round(tempGyro2, 1):F1}M"; + } + + var sb = new StringBuilder(); + double lastExecutionTime = _executionTimes.Count > 0 ? _executionTimes.Last() : 0; + + sb.AppendLine($"Last Update took: {lastExecutionTime:F2} ms"); + // Basic Info + sb.AppendLine("----Basic Info----"); + sb.AppendFormat("{0} ", focusedGrid.DisplayName); + sb.AppendFormat("Owner: {0} ", playerName); + sb.AppendFormat("Faction: {0}\n", factionName); + sb.AppendFormat("Mass: {0} kg\n", massString); + sb.AppendFormat("Heavy blocks: {0}\n", _shipTracker.HeavyArmorCount); + sb.AppendFormat("Total blocks: {0}\n", _shipTracker.BlockCount); + sb.AppendFormat("PCU: {0}\n", _shipTracker.PCU); + sb.AppendFormat("Size: {0}\n", + (focusedGrid.Max + Vector3.Abs(focusedGrid.Min)).ToString()); + sb.AppendFormat("Max Speed: {0}\n", speed); + //sb.AppendFormat("Reduced Angular Speed (rad/s):: {0}\n", reducedAngularSpeed); + sb.AppendLine(); //blank line + + // Battle Stats + sb.AppendLine("----Battle Stats----"); + //sb.AppendFormat("Battle Points: {0}\n", _shipTracker.BattlePoints); + sb.AppendFormat("Thrust: {0}N\n", thrustString); + sb.AppendFormat("Power: {0}\n", pwr); + sb.AppendLine(); //blank line + + // Armament Info + sb.AppendLine("----Armament----"); + sb.Append(gunText); + sb.AppendLine(); //blank line + + // Blocks Info + sb.AppendLine("----Blocks----"); + sb.AppendLine(specialBlockText); + + if (!_statMessage.Message.Equals(sb)) + _statMessage.Message = sb; + _statMessage.Visible = true; + stopwatch.Stop(); + UpdateExecutionTimes(stopwatch.Elapsed.TotalMilliseconds); + } + + private void BattleShiftTCalcs(IMyCubeGrid focusedGrid) + { + if (MasterSession.I.Ticks % 59 != 0) + return; + + ShipTracker tracked; + TrackingManager.I.TrackedGrids.TryGetValue(focusedGrid, out tracked); + if (tracked == null) + { + tracked = new ShipTracker(focusedGrid, false); + Log.Info($"BattleShiftTCalcs Tracked grid {focusedGrid.DisplayName}. Visible: false"); + } + + var totalShieldString = "None"; + + if (tracked.MaxShieldHealth > 100) + totalShieldString = $"{tracked.MaxShieldHealth / 100f:F2} M"; + else if (tracked.MaxShieldHealth > 1 && tracked.MaxShieldHealth < 100) + totalShieldString = $"{tracked.MaxShieldHealth:F0}0 K"; + + var maxSpeed = focusedGrid.GridSizeEnum == MyCubeSize.Large + ? MyDefinitionManager.Static.EnvironmentDefinition.LargeShipMaxSpeed + : MyDefinitionManager.Static.EnvironmentDefinition.SmallShipMaxSpeed; + var reducedAngularSpeed = 0f; + var negativeInfluence = 0f; + + if (RtsApi != null && RtsApi.IsReady) + { + maxSpeed = (float)Math.Round(RtsApi.GetMaxSpeed(focusedGrid), 2); + reducedAngularSpeed = RtsApi.GetReducedAngularSpeed(focusedGrid); + negativeInfluence = RtsApi.GetNegativeInfluence(focusedGrid); + } + + _speedTextBuilder.Clear(); + _speedTextBuilder.Append($"\nMax Speed: {maxSpeed:F2} m/s"); + _speedTextBuilder.Append( + $"\nReduced Angular Speed: {reducedAngularSpeed:F2} rad/s"); + _speedTextBuilder.Append($"\nNegative Influence: {negativeInfluence:F2}"); + + _gunTextBuilder.Clear(); + foreach (var x in tracked.WeaponCounts) + _gunTextBuilder.Append($"{x.Value} x {x.Key}\n"); + + var thrustString = $"{Math.Round(tracked.TotalThrust, 1)}"; + if (tracked.TotalThrust > 1000000) + thrustString = $"{Math.Round(tracked.TotalThrust / 1000000f, 1):F2}M"; + + var gyroString = $"{Math.Round(tracked.TotalTorque, 1)}"; + double tempGyro2; + if (tracked.TotalTorque >= 1000000) + { + tempGyro2 = Math.Round(tracked.TotalTorque / 1000000f, 1); + if (tempGyro2 > 1000) + gyroString = $"{Math.Round(tempGyro2 / 1000, 1):F1}G"; + else + gyroString = $"{Math.Round(tempGyro2, 1):F1}M"; + } + + var pwrNotation = tracked.TotalPower > 1000 ? "GW" : "MW"; + var tempPwr = tracked.TotalPower > 1000 + ? $"{Math.Round(tracked.TotalPower / 1000, 1):F1}" + : Math.Round(tracked.TotalPower, 1).ToString(); + var pwr = tempPwr + pwrNotation; + + _gunTextBuilder.Append($"\nThrust: {thrustString} N") + .Append($"\nGyro: {gyroString} N") + .Append($"\nPower: {pwr}") + .Append(_speedTextBuilder); + + _statMessageBattleWeaponCountsist.Message.Length = 0; + _statMessageBattleWeaponCountsist.Message.Append(_gunTextBuilder); + + _statMessageBattle.Message.Length = 0; + _statMessageBattle.Message.Append($"{totalShieldString} ({(int)tracked.CurrentShieldPercent}%)"); + + _statMessageBattle.Visible = true; + _statMessageBattleWeaponCountsist.Visible = true; + } + private void UpdateExecutionTimes(double elapsedTime) + { + if (_executionTimes.Count >= _sampleSize) + _executionTimes.Dequeue(); // Remove the oldest time if at capacity + _executionTimes.Enqueue(elapsedTime); + } + private enum ViewState + { + None, + InView, + InView2 + } + + #region APIs + + private WcApi WcApi => AllGridsList.I.WcApi; + private ShieldApi ShApi => AllGridsList.I.ShieldApi; + private RtsApi RtsApi => AllGridsList.I.RtsApi; + private HudAPIv2 TextHudApi => MasterSession.I.TextHudApi; + + #endregion + + #region Public Methods + + public void CycleViewState() + { + _viewState++; + if (_viewState > ViewState.InView2) + { + _statMessageBattle.Message.Clear(); + _statMessageBattleWeaponCountsist.Message.Clear(); + _statMessageBattle.Visible = false; + _statMessageBattleWeaponCountsist.Visible = false; + + _viewState = ViewState.None; + } + } + + public void UpdateDraw() + { + if (!TextHudApi.Heartbeat) + return; + + switch (_viewState) + { + case ViewState.InView: + ShiftTHandling(); + break; + case ViewState.InView2: + BattleShiftTHandling(); + break; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/Logger.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/Logger.cs new file mode 100644 index 00000000..347787d7 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/Logger.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ParallelTasks; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.ModAPI; +using VRage.Utils; + +namespace CGP.ShareTrack { + /// + /// Standalone logger, does not require any setup. + /// + /// Mod name is automatically set from workshop name or folder name. Can also be manually defined using + /// . + /// + /// Version 1.52 by Digi + /// + [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate, int.MaxValue)] + public class Log : MySessionComponentBase { + private const int DefaultTimeInfo = 3000; + private const int DefaultTimeError = 10000; + + /// + /// Print the generic error info. + /// (For use in 's 2nd arg) + /// + public const string PrintError = ""; + + /// + /// Prints the message instead of the generic generated error info. + /// (For use in 's 2nd arg) + /// + public const string PrintMsg = ""; + + private static Log _instance; + private static Handler _handler; + public static bool _unloaded; + + public static readonly string File = GenerateTimestampedFileName(); + + private static string GenerateTimestampedFileName() { + var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); + return $"[{timestamp}]-Sharetrack.log"; + } + + private class Handler { + private readonly StringBuilder _sb = new StringBuilder(64); + private string _errorPrintText; + private int _indent; + private string _modName = string.Empty; + private IMyHudNotification _notifyError; + + private IMyHudNotification _notifyInfo; + + private List _preInitMessages; + private Log _sessionComp; + + private TextWriter _writer; + + public bool AutoClose { get; set; } = true; + + public ulong WorkshopId { get; private set; } + + public string ModName { + get { return _modName; } + set { + _modName = value; + ComputeErrorPrintText(); + } + } + + public void Init(Log sessionComp) { + if (_writer != null) + return; // already initialized + + if (MyAPIGateway.Utilities == null) { + Error("MyAPIGateway.Utilities is NULL !"); + return; + } + + _sessionComp = sessionComp; + + if (string.IsNullOrWhiteSpace(ModName)) + ModName = sessionComp.ModContext.ModName; + + WorkshopId = GetWorkshopId(sessionComp.ModContext.ModId); + + _writer = MyAPIGateway.Utilities.WriteFileInLocalStorage(File, typeof(Log)); + + #region Pre-init messages + + if (_preInitMessages != null) { + var warning = $"{_modName} WARNING: there are log messages before the mod initialized!"; + + Info("--- pre-init messages ---"); + + foreach (var msg in _preInitMessages) Info(msg, warning); + + Info("--- end pre-init messages ---"); + + _preInitMessages = null; + } + + #endregion + + #region Init message + + _sb.Clear(); + _sb.Append("Initialized"); + _sb.Append("\nGameMode=").Append(MyAPIGateway.Session.SessionSettings.GameMode); + _sb.Append("\nOnlineMode=").Append(MyAPIGateway.Session.SessionSettings.OnlineMode); + _sb.Append("\nServer=").Append(MyAPIGateway.Session.IsServer); + _sb.Append("\nDS=").Append(MyAPIGateway.Utilities.IsDedicated); + _sb.Append("\nDefined="); + +#if STABLE + _sb.Append("STABLE, "); +#endif + +#if UNOFFICIAL + _sb.Append("UNOFFICIAL, "); +#endif + +#if DEBUG + _sb.Append("DEBUG, "); +#endif + +#if BRANCH_STABLE + _sb.Append("BRANCH_STABLE, "); +#endif + +#if BRANCH_DEVELOP + _sb.Append("BRANCH_DEVELOP, "); +#endif + +#if BRANCH_UNKNOWN + _sb.Append("BRANCH_UNKNOWN, "); +#endif + + Info(_sb.ToString()); + _sb.Clear(); + + #endregion + } + + public void Close() { + if (_writer != null) { + Info("Unloaded."); + + _writer.Flush(); + _writer.Close(); + _writer = null; + } + } + + private void ComputeErrorPrintText() { + _errorPrintText = + $"[ {_modName} ERROR, report contents of: %AppData%/SpaceEngineers/Storage/{MyAPIGateway.Utilities.GamePaths.ModScopeName}/{File} ]"; + } + + public void IncreaseIndent() { + _indent++; + } + + public void DecreaseIndent() { + if (_indent > 0) + _indent--; + } + + public void ResetIndent() { + _indent = 0; + } + + public void Error(string message, string printText = PrintError, int printTime = DefaultTimeError) { + MyLog.Default.WriteLineAndConsole(_modName + " error/exception: " + message); // write to game's log + + LogMessage(message, "ERROR: "); // write to custom log + + if (printText != null) // printing to HUD is optional + ShowHudMessage(ref _notifyError, message, printText, printTime, MyFontEnum.Red); + } + + public void Info(string message, string printText = null, int printTime = DefaultTimeInfo) { + LogMessage(message); // write to custom log + + if (printText != null) // printing to HUD is optional + ShowHudMessage(ref _notifyInfo, message, printText, printTime, MyFontEnum.White); + } + + private void ShowHudMessage(ref IMyHudNotification notify, string message, string printText, int printTime, + string font) { + if (printText == null) + return; + + try { + if (MyAPIGateway.Utilities != null && !MyAPIGateway.Utilities.IsDedicated) { + if (printText == PrintError) + printText = _errorPrintText; + else if (printText == PrintMsg) + printText = $"[ {_modName} ERROR: {message} ]"; + + if (notify == null) { + notify = MyAPIGateway.Utilities.CreateNotification(printText, printTime, font); + } + else { + notify.Text = printText; + notify.AliveTime = printTime; + notify.ResetAliveTime(); + } + + notify.Show(); + } + } + catch (Exception e) { + Info("ERROR: Could not send notification to local client: " + e); + MyLog.Default.WriteLineAndConsole(_modName + + " logger error/exception: Could not send notification to local client: " + + e); + } + } + + private void LogMessage(string message, string prefix = null) { + try { + _sb.Clear(); + _sb.Append(DateTime.Now.ToString("[HH:mm:ss] ")); + + if (_writer == null) + _sb.Append("(PRE-INIT) "); + + for (var i = 0; i < _indent; i++) + _sb.Append(' ', 4); + + if (prefix != null) + _sb.Append(prefix); + + _sb.Append(message); + + if (_writer == null) { + if (_preInitMessages == null) + _preInitMessages = new List(); + + _preInitMessages.Add(_sb.ToString()); + } + else { + _writer.WriteLine(_sb); + _writer.Flush(); + } + + _sb.Clear(); + } + catch (Exception e) { + MyLog.Default.WriteLineAndConsole( + $"{_modName} had an error while logging message = '{message}'\nLogger error: {e.Message}\n{e.StackTrace}"); + } + } + + private ulong GetWorkshopId(string modId) { + // NOTE workaround for MyModContext not having the actual workshop ID number. + foreach (var mod in MyAPIGateway.Session.Mods) + if (mod.Name == modId) + return mod.PublishedFileId; + + return 0; + } + } + + #region Handling of handler + + public override void LoadData() { + _instance = this; + EnsureHandlerCreated(); + _handler.Init(this); + } + + protected override void UnloadData() { + _unloaded = true; + _instance = null; + if (_handler != null && _handler.AutoClose) + Unload(); + } + + + private void Unload() { + try { + if (_unloaded) + return; + + _unloaded = true; + _handler?.Close(); + _handler = null; + } + catch (Exception e) { + MyLog.Default.WriteLine($"Error in {ModContext.ModName} ({ModContext.ModId}): {e.Message}\n{e.StackTrace}"); + throw new ModCrashedException(e, ModContext); + } + } + + + private static void EnsureHandlerCreated() { + if (_unloaded) + throw new Exception("Digi.Log accessed after it was unloaded!"); + + if (_handler == null) + _handler = new Handler(); + } + + #endregion + + #region Publicly accessible properties and methods + + /// + /// Manually unload the logger. Works regardless of , but if that property is false then this + /// method must be called! + /// + public static void Close() { + _instance?.Unload(); + } + + /// + /// Defines if the component self-unloads next tick or after . + /// If set to false, you must call manually! + /// + public static bool AutoClose { + get { + EnsureHandlerCreated(); + return _handler.AutoClose; + } + set { + EnsureHandlerCreated(); + _handler.AutoClose = value; + } + } + + /// + /// Sets/gets the mod name. + /// This is optional as the mod name is generated from the folder/workshop name, but those can be weird or long. + /// + public static string ModName { + get { + EnsureHandlerCreated(); + return _handler.ModName; + } + set { + EnsureHandlerCreated(); + _handler.ModName = value; + } + } + + /// + /// Gets the workshop id of the mod. + /// Will return 0 if it's a local mod or if it's called before LoadData() executes on the logger. + /// + public static ulong WorkshopId => _handler?.WorkshopId ?? 0; + + /// + /// Increases indentation by 4 spaces. + /// Each indent adds 4 space characters before each of the future messages. + /// + public static void IncreaseIndent() { + EnsureHandlerCreated(); + _handler.IncreaseIndent(); + } + + /// + /// Decreases indentation by 4 space characters down to 0 indentation. + /// See + /// + public static void DecreaseIndent() { + EnsureHandlerCreated(); + _handler.DecreaseIndent(); + } + + /// + /// Resets the indentation to 0. + /// See + /// + public static void ResetIndent() { + EnsureHandlerCreated(); + _handler.ResetIndent(); + } + + /// + /// Writes an exception to custom log file, game's log file and by default writes a generic error message to player's + /// HUD. + /// + /// The exception to write to custom log and game's log. + /// + /// HUD notification text, can be set to null to disable, to to use the + /// exception message, to use the predefined error message, or any other custom string. + /// + /// How long to show the HUD notification for, in miliseconds. + public static void Error(Exception exception, string printText = PrintError, + int printTimeMs = DefaultTimeError) { + EnsureHandlerCreated(); + _handler.Error(exception.ToString(), printText, printTimeMs); + } + + /// + /// Writes a message to custom log file, game's log file and by default writes a generic error message to player's HUD. + /// + /// The message printed to custom log and game log. + /// + /// HUD notification text, can be set to null to disable, to to use the + /// message arg, to use the predefined error message, or any other custom string. + /// + /// How long to show the HUD notification for, in miliseconds. + public static void Error(string message, string printText = PrintError, int printTimeMs = DefaultTimeError) { + EnsureHandlerCreated(); + _handler.Error(message, printText, printTimeMs); + } + + /// + /// Writes a message in the custom log file. + /// Optionally prints a different message (or same message) in player's HUD. + /// + /// The text that's written to log. + /// + /// HUD notification text, can be set to null to disable, to to use the + /// message arg or any other custom string. + /// + /// How long to show the HUD notification for, in miliseconds. + public static void Info(string message, string printText = null, int printTimeMs = DefaultTimeInfo) { + EnsureHandlerCreated(); + _handler.Info(message, printText, printTimeMs); + } + + /// + /// Iterates task errors and reports them, returns true if any errors were found. + /// + /// The task to check for errors. + /// Used in the reports. + /// true if errors found, false otherwise. + public static bool TaskHasErrors(Task task, string taskName) { + EnsureHandlerCreated(); + + if (task.Exceptions != null && task.Exceptions.Length > 0) { + foreach (var e in task.Exceptions) Error($"Error in {taskName} thread!\n{e}"); + + return true; + } + + return false; + } + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/MasterSession.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/MasterSession.cs new file mode 100644 index 00000000..4e3c8084 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/MasterSession.cs @@ -0,0 +1,128 @@ +using System; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using CGP.ShareTrack.HeartNetworking; +using CGP.ShareTrack.ShipTracking; +using CGP.ShareTrack.TrackerApi; +using VRage.Game.Components; +using VRageMath; + +namespace CGP.ShareTrack +{ + [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] + internal class MasterSession : MySessionComponentBase + { + /// + /// API version, Mod version + /// + public static readonly Vector2I ModVersion = new Vector2I(3, 2); + + public static MasterSession I; + + public HudAPIv2 TextHudApi; + public Action HudRegistered = () => { }; + + private readonly AllGridsList _allGridsList = new AllGridsList(); + private BuildingBlockPoints _buildingBlockPoints; + private ApiProvider _apiProvider; + public int Ticks { get; private set; } = 0; + + private const int DelayTicks = 600; // 10 seconds at 60 ticks per second + private bool _trackingStarted = false; + + + public override void LoadData() + { + I = this; + + try + { + HeartNetwork.I = new HeartNetwork(); + HeartNetwork.I.LoadData(42521); + _allGridsList.Init(); + _apiProvider = new ApiProvider(); + _buildingBlockPoints = new BuildingBlockPoints(); + TrackingManager.Init(); // Initialize TrackingManager, but don't start tracking yet + + if (!MyAPIGateway.Utilities.IsDedicated) + // Initialize the sphere entities + // Initialize the text_api with the HUDRegistered callback + TextHudApi = new HudAPIv2(HudRegistered); + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + protected override void UnloadData() + { + try + { + TextHudApi?.Unload(); + _apiProvider.Unload(); + _allGridsList.Close(); + TrackingManager.Close(); + HeartNetwork.I.UnloadData(); + } + catch (Exception ex) + { + Log.Error(ex); + } + + I = null; + } + + public override void UpdateAfterSimulation() + { + try + { + Ticks++; + HeartNetwork.I.Update(); + + if (!_trackingStarted) + { + if (Ticks >= DelayTicks) + { + _trackingStarted = true; + TrackingManager.I.StartTracking(); // New method to start tracking + } + } + else + { + TrackingManager.UpdateAfterSimulation(); + } + + _allGridsList.UpdateAfterSimulation(); + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + public override void Draw() + { + try + { + _allGridsList.Draw(); + _buildingBlockPoints?.Update(); + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + public override void HandleInput() + { + try + { + } + catch (Exception ex) + { + Log.Error(ex); + } + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/GridStats.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/GridStats.cs new file mode 100644 index 00000000..3ebfd0c3 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/GridStats.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sandbox.Definitions; +using Sandbox.Game.Entities; +using Sandbox.Game.Weapons; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using CGP.ShareTrack.API.CoreSystem; +using VRage.Game.Entity; +using VRage.Game.ModAPI; + +namespace CGP.ShareTrack.ShipTracking +{ + internal class GridStats // TODO convert this to be event-driven. OnBlockPlace, etc. Keep a queue. + { + private readonly HashSet _fatBlocks = new HashSet(); + + private readonly HashSet _slimBlocks; + private ShieldApi ShieldApi => AllGridsList.I.ShieldApi; + private WcApi WcApi => AllGridsList.I.WcApi; + + public bool NeedsUpdate { get; private set; } = true; + public bool IsPrimaryGrid = false; + + #region Public Methods + + public GridStats(IMyCubeGrid grid) + { + if (grid == null) + { + Log.Error("GridStats constructor called with null grid"); + throw new ArgumentNullException(nameof(grid)); + } + + Grid = grid; + var allSlimBlocks = new List(); + Grid.GetBlocks(allSlimBlocks); + _slimBlocks = allSlimBlocks.ToHashSet(); + + foreach (var block in _slimBlocks) + { + if (block?.FatBlock != null) + { + _fatBlocks.Add(block.FatBlock); + GridIntegrity += block.Integrity; + } + } + + OriginalGridIntegrity = GridIntegrity; + Grid.OnBlockAdded += OnBlockAdd; + Grid.OnBlockRemoved += OnBlockRemove; + Update(); + } + + public void Close() + { + Grid.OnBlockAdded -= OnBlockAdd; + Grid.OnBlockRemoved -= OnBlockRemove; + + _slimBlocks.Clear(); + _fatBlocks.Clear(); + } + + public void UpdateAfterSim() + { + UpdateCounter++; + + if (Grid != null) + { + int updateCount = (int)(Grid.EntityId % UpdateInterval); + + if (UpdateCounter % UpdateInterval == updateCount) + { + float tempGridInteg = 0; + + foreach (var block in _slimBlocks) + { + if (block.FatBlock != null) // Remove To Count All Blocks + { + tempGridInteg += block.Integrity; + } + } + + GridIntegrity = tempGridInteg; + } + } + + if (UpdateCounter >= int.MaxValue - UpdateInterval) + { + UpdateCounter = 0; + } + } + + public void Update() + { + if (!NeedsUpdate) // Modscripts changing the output of a block (i.e. Fusion Systems) without adding/removing blocks will not be updated properly. + return; // I am willing to make this sacrifice. + + BattlePoints = 0; + OffensivePoints = 0; + PowerPoints = 0; + MovementPoints = 0; + PointDefensePoints = 0; + CockpitCount = 0; + + // Setting battlepoints first so that calcs can do calc stuff + foreach (var block in + _fatBlocks) // If slimblock points become necessary in the future, change this to _slimBlock + CalculateCost(block); + + UpdateGlobalStats(); + UpdateWeaponStats(); + NeedsUpdate = false; + } + + #endregion + + #region Public Fields + + public readonly IMyCubeGrid Grid; + + // Global Stats + public int BlockCount { get; private set; } + public int HeavyArmorCount { get; private set; } + public int CockpitCount { get; private set; } + public int PCU { get; private set; } + public readonly Dictionary BlockCounts = new Dictionary(); + public readonly Dictionary SpecialBlockCounts = new Dictionary(); + public float TotalThrust { get; private set; } + public float TotalTorque { get; private set; } + + public float GridIntegrity { get; private set; } + public float OriginalGridIntegrity { get; private set; } + + // BattlePoint Stats + public double BattlePoints { get; private set; } + public double OffensivePoints { get; private set; } + public double PowerPoints { get; private set; } + public double MovementPoints { get; private set; } + public double PointDefensePoints { get; private set; } + + // Weapon Stats + public readonly Dictionary WeaponCounts = new Dictionary(); + + #endregion + + #region Private Actions + + private void OnBlockAdd(IMySlimBlock block) + { + if (block == null) + return; + + _slimBlocks.Add(block); + if (block.FatBlock != null) + _fatBlocks.Add(block.FatBlock); + + NeedsUpdate = true; + } + + private void OnBlockRemove(IMySlimBlock block) + { + if (block == null) + return; + + _slimBlocks.Remove(block); + if (block.FatBlock != null) + _fatBlocks.Remove(block.FatBlock); + + NeedsUpdate = true; + } + + #endregion + + #region Private Fields + + private int UpdateCounter = 0; + private int UpdateInterval = 100; + // TODO + + #endregion + + #region Private Methods + + private void UpdateGlobalStats() + { + BlockCounts.Clear(); + SpecialBlockCounts.Clear(); + + TotalThrust = 0; + TotalTorque = 0; + + foreach (var block in _fatBlocks) + { + if (block is IMyCockpit && block.IsFunctional) + CockpitCount++; + + if (block is IMyThrust && block.IsFunctional) + { + TotalThrust += ((IMyThrust)block).MaxEffectiveThrust; + } + + else if (block is IMyGyro && block.IsFunctional) + { + TotalTorque += + ((MyGyroDefinition)MyDefinitionManager.Static.GetDefinition((block as IMyGyro).BlockDefinition)) + .ForceMagnitude * (block as IMyGyro).GyroStrengthMultiplier; + } + + if (!(block is IMyConveyorSorter) || !WcApi.HasCoreWeapon((MyEntity)block)) + { + var blockDisplayName = block.DefinitionDisplayNameText; + if (blockDisplayName + .Contains("Armor") && !blockDisplayName.StartsWith("Armor Laser")) // This is a bit stupid. TODO find a better way to sort out armor blocks. + continue; + + if (!AllGridsList.PointValues.ContainsKey(block.BlockDefinition.SubtypeName)) + continue; + + float ignored = 0; + AllGridsList.ClimbingCostRename(ref blockDisplayName, ref ignored); + ShipTracker.SpecialBlockRename(ref blockDisplayName, block); + if (!SpecialBlockCounts.ContainsKey(blockDisplayName)) + SpecialBlockCounts.Add(blockDisplayName, 0); + SpecialBlockCounts[blockDisplayName]++; + } + } + + BlockCount = ((MyCubeGrid)Grid).BlocksCount; + PCU = ((MyCubeGrid)Grid).BlocksPCU; + HeavyArmorCount = 0; + foreach (var slimBlock in _slimBlocks) + { + if (slimBlock.FatBlock != null) + continue; + + if (slimBlock.BlockDefinition.Id.SubtypeName.Contains("Heavy")) + HeavyArmorCount++; + } + } + + private void UpdateWeaponStats() + { + WeaponCounts.Clear(); + foreach (var block in _fatBlocks) + { + double weaponPoints; + var weaponDisplayName = block.DefinitionDisplayNameText; + + // Check for WeaponCore weapons + if (AllGridsList.PointValues.TryGetValue(block.BlockDefinition.SubtypeName, out weaponPoints) && WcApi.HasCoreWeapon((MyEntity)block)) + { + float thisClimbingCostMult = 0; + AllGridsList.ClimbingCostRename(ref weaponDisplayName, ref thisClimbingCostMult); + AddWeaponCount(weaponDisplayName); + continue; + } + + // Check for vanilla and modded weapons using IMyGunObject + var gunObject = block as IMyGunObject; + if (gunObject != null && block is IMyCubeBlock) // Ensure it's a block, not a hand weapon + { + AddWeaponCount(weaponDisplayName); + } + } + } + + private string GetWeaponDisplayName(IMyCubeBlock block, IMyGunObject gunObject) + { + // You can customize this method to categorize weapons as needed + if (block is IMyLargeTurretBase) + return $"{block.DefinitionDisplayNameText} Turret"; + else + return block.DefinitionDisplayNameText; + } + + private void AddWeaponCount(string weaponDisplayName) + { + if (!WeaponCounts.ContainsKey(weaponDisplayName)) + WeaponCounts.Add(weaponDisplayName, 0); + WeaponCounts[weaponDisplayName]++; + } + + private void CalculateCost(IMyCubeBlock block) + { + double blockPoints = 0; + foreach (var kvp in AllGridsList.PointValues) + { + if (block.BlockDefinition.SubtypeName.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase)) + { + blockPoints = kvp.Value; + break; + } + } + if (blockPoints == 0) return; + + var blockDisplayName = block.DefinitionDisplayNameText; + float climbingCostMultiplier = 0; // Updated name + AllGridsList.ClimbingCostRename(ref blockDisplayName, ref climbingCostMultiplier); + + // DEBUG: Check multiplier + //MyAPIGateway.Utilities.ShowNotification($"Block: {blockDisplayName}, Multiplier: {climbingCostMultiplier}", 3000); + + + if (!BlockCounts.ContainsKey(blockDisplayName)) + BlockCounts.Add(blockDisplayName, 0); + + var thisSpecialBlocksCount = BlockCounts[blockDisplayName]++; + + if (climbingCostMultiplier > 0) // Use consistent variable name + blockPoints += (blockPoints * thisSpecialBlocksCount * climbingCostMultiplier); + + if (block is IMyThrust || block is IMyGyro) + MovementPoints += blockPoints; + if (block is IMyPowerProducer) + PowerPoints += blockPoints; + + if (WcApi.HasCoreWeapon((MyEntity)block)) + { + // Weapons on subgrids have an extra 20% cost applied (this is disabled) + //if (!IsPrimaryGrid) + // blockPoints = (int)(blockPoints * 1.2f); + + var validTargetTypes = new List(); + WcApi.GetTurretTargetTypes((MyEntity)block, validTargetTypes); + if (validTargetTypes.Contains("Projectiles")) + PointDefensePoints += blockPoints; + else + OffensivePoints += blockPoints; + } + + BattlePoints += blockPoints; + + //MyAPIGateway.Utilities.ShowNotification($"EEEEEEEEE{blockPoints}", 3000); + } + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/ShipTracker.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/ShipTracker.cs new file mode 100644 index 00000000..5a164c2c --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/ShipTracker.cs @@ -0,0 +1,670 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sandbox.Game.Entities; +using Sandbox.Game.EntityComponents; +using Sandbox.ModAPI; +using CGP.ShareTrack.API; +using VRage.Game; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRageMath; +using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; + +namespace CGP.ShareTrack.ShipTracking +{ + public class ShipTracker + { + [Flags] + public enum NametagSettings + { + None = 0, + PlayerName = 1, + GridName = 2 + } + + private readonly Dictionary _gridStats = new Dictionary(); + + + private HudAPIv2.HUDMessage _nametag; + + + private ShipTracker() + { + } + + public ShipTracker(IMyCubeGrid grid, bool showOnHud = true) + { + TransferToGrid(grid, showOnHud); + Update(); + if (!showOnHud || MyAPIGateway.Utilities.IsDedicated) return; + _nametag = new HudAPIv2.HUDMessage(new StringBuilder("Initializing..."), Vector2D.Zero, font: "BI_SEOutlined", blend: BlendTypeEnum.PostPP, hideHud: false, shadowing: true); + UpdateHud(); + } + + private void TransferToGrid(IMyCubeGrid newGrid, bool showOnHud = true) + { + Log.Info($"TransferToGrid called for grid {newGrid?.DisplayName ?? "null"}"); + + if (newGrid == null) + { + Log.Error("TransferToGrid called with null newGrid"); + return; + } + + if (Grid != null) + { + Grid.OnClose -= OnClose; + Grid.OnGridSplit -= OnGridSplit; + var oldGridGroup = Grid.GetGridGroup(GridLinkTypeEnum.Physical); + if (oldGridGroup != null) + { + oldGridGroup.OnGridAdded -= OnGridAdd; + oldGridGroup.OnGridRemoved -= OnGridRemove; + } + foreach (var gridStat in _gridStats) + { + if (gridStat.Key != newGrid) + gridStat.Value.Close(); + } + _gridStats.Clear(); + TrackingManager.I.TrackedGrids.Remove(Grid); + } + + Grid = newGrid; + var allAttachedGrids = new List(); + var newGridGroup = Grid.GetGridGroup(GridLinkTypeEnum.Physical); + if (newGridGroup != null) + { + newGridGroup.GetGrids(allAttachedGrids); + } + else + { + Log.Error($"Grid {Grid.DisplayName} has no physical grid group"); + allAttachedGrids.Add(Grid); + } + + OriginalGridIntegrity = 0; + foreach (var attachedGrid in allAttachedGrids) + { + if (attachedGrid != null) + { + try + { + var stats = new GridStats(attachedGrid); + _gridStats.Add(attachedGrid, stats); + OriginalGridIntegrity += stats.OriginalGridIntegrity; + if (((MyCubeGrid)attachedGrid).BlocksCount > ((MyCubeGrid)Grid).BlocksCount) + Grid = attachedGrid; + } + catch (Exception ex) + { + Log.Error($"Error processing attached grid {attachedGrid.DisplayName}: {ex}"); + } + } + } + + Grid.OnClose += OnClose; + Grid.OnGridSplit += OnGridSplit; + var gridGroup = Grid.GetGridGroup(GridLinkTypeEnum.Physical); + if (gridGroup != null) + { + gridGroup.OnGridAdded += OnGridAdd; + gridGroup.OnGridRemoved += OnGridRemove; + } + + if (!TrackingManager.I.TrackedGrids.ContainsKey(Grid) && showOnHud) + TrackingManager.I.TrackedGrids.Add(Grid, this); + + Log.Info($"TransferToGrid completed for grid {Grid.DisplayName}"); + } + + private ShieldApi ShieldApi => AllGridsList.I.ShieldApi; + + + public IMyCubeGrid Grid { get; private set; } + public IMyPlayer Owner => MyAPIGateway.Players.GetPlayerControllingEntity(Grid) ?? AllGridsList.GetOwner(OwnerId); + public long OwnerId => Grid?.BigOwners.Count > 0 ? Grid?.BigOwners[0] ?? -1 : -1; + + + public string GridName => Grid?.DisplayName; + public float Mass => ((MyCubeGrid)Grid).GetCurrentMass(); + public Vector3 Position => Grid.Physics.CenterOfMassWorld; + public IMyFaction OwnerFaction => MyAPIGateway.Session?.Factions?.TryGetPlayerFaction(OwnerId); + public string FactionName => OwnerFaction?.Name ?? "None"; + public Vector3 FactionColor => ColorMaskToRgb(OwnerFaction?.CustomColor ?? Vector3.Zero); + public string OwnerName => Owner?.DisplayName ?? GridName; + + public void OnClose(IMyEntity e) + { + if (Grid != null) + { + Grid.OnClose -= OnClose; + var gridGroup = Grid.GetGridGroup(GridLinkTypeEnum.Physical); + if (gridGroup != null) + { + gridGroup.OnGridAdded -= OnGridAdd; + gridGroup.OnGridRemoved -= OnGridRemove; + } + } + + TrackingManager.I.TrackedGrids.Remove(Grid); + + DisposeHud(); + } + + /// + /// Ensures that the tracker will follow the correct grid on split. + /// + /// + /// + public void OnGridSplit(IMyCubeGrid originalGrid, IMyCubeGrid newGrid) + { + // sorry this is really laggy + int originalCockpitsCount = originalGrid.GetFatBlocks().Count(); + int newCockpitsCount = newGrid.GetFatBlocks().Count(); + + if (newCockpitsCount > originalCockpitsCount) + { + TransferToGrid(newGrid); + return; + } + + if (newCockpitsCount < originalCockpitsCount) + { + return; + } + + if (((MyCubeGrid)newGrid).BlocksCount > ((MyCubeGrid)originalGrid).BlocksCount) + { + TransferToGrid(newGrid); + } + } + + public void Update() + { + if (Grid?.Physics == null) // TODO transfer to a different grid + return; + + var shieldController = ShieldApi.GetShieldBlock(Grid); + if (shieldController == null) + OriginalMaxShieldHealth = -1; + if (OriginalMaxShieldHealth == -1 && !ShieldApi.IsFortified(shieldController)) + OriginalMaxShieldHealth = MaxShieldHealth; + + // TODO: Update pilots + foreach (var gridStat in _gridStats.Values) + { + gridStat.IsPrimaryGrid = gridStat.Grid == Grid; + gridStat.Update(); + } + + bool bufferIsFunctional = IsFunctional; + IsFunctional = TotalPower > 0 && TotalTorque > 0 && CockpitCount > 0; + if (bufferIsFunctional != IsFunctional) + { + TrackingManager.I.OnShipAliveChanged?.Invoke(Grid, IsFunctional); + } + } + + public void UpdateAfterSim() + { + foreach (var gridStat in _gridStats.Values) + { + gridStat.UpdateAfterSim(); + } + } + + private void OnGridAdd(IMyGridGroupData groupData, IMyCubeGrid grid, IMyGridGroupData previousGroupData) + { + if (_gridStats.ContainsKey(grid)) + return; + var stats = new GridStats(grid); + _gridStats.Add(grid, stats); + OriginalGridIntegrity += stats.OriginalGridIntegrity; + } + + private void OnGridRemove(IMyGridGroupData groupData, IMyCubeGrid grid, IMyGridGroupData newGroupData) + { + if (!_gridStats.ContainsKey(grid) || grid == Grid) + return; + OriginalGridIntegrity -= _gridStats[grid].OriginalGridIntegrity; + _gridStats[grid].Close(); + _gridStats.Remove(grid); + } + + public static void SpecialBlockRename(ref string blockDisplayName, IMyCubeBlock block) + { + var subtype = block.BlockDefinition.SubtypeName; + + // Dictionary for grouping blocks by subtype or display name + var groupings = new Dictionary + { + { "Suspension", "Wheel Suspension" }, + { "Wing_", "Wing" }, + { "DieselEngine", "Engine" }, + //{ "Weapon_", "Weapon" }, + //{ "Buster", "Buster Block" }, + //{ "Armor Laser", "Laser Armor" } + }; + + // Check if the block should be grouped + foreach (var group in groupings) + { + if (subtype.StartsWith(group.Key, StringComparison.OrdinalIgnoreCase) || + blockDisplayName.StartsWith(group.Key, StringComparison.OrdinalIgnoreCase)) + { + blockDisplayName = group.Value; + return; + } + } + + // Type-specific renaming + if (block is IMyGasTank) + blockDisplayName = "HydrogenTank"; + else if (block is IMyLightingBlock && !(block is IMyReflectorLight)) + blockDisplayName = "Light"; + else if (block is IMyConveyor || block is IMyConveyorTube) + blockDisplayName = "Conveyor"; + else if (block is IMyMotorStator && subtype == "SubgridBase") + blockDisplayName = "Invincible Subgrid"; + else if (block is IMyUpgradeModule) + { + switch (subtype) + { + case "LargeEnhancer": + blockDisplayName = "Shield Enhancer"; + break; + case "EmitterL": + case "EmitterLA": + blockDisplayName = "Shield Emitter"; + break; + case "LargeShieldModulator": + blockDisplayName = "Shield Modulator"; + break; + case "DSControlLarge": + case "DSControlTable": + blockDisplayName = "Shield Controller"; + break; + case "AQD_LG_GyroBooster": + blockDisplayName = "Gyro Booster"; + break; + case "AQD_LG_GyroUpgrade": + blockDisplayName = "Large Gyro Booster"; + break; + } + } + else if (block is IMyReactor) + { + switch (subtype) + { + case "LargeBlockLargeGenerator": + case "LargeBlockLargeGeneratorWarfare2": + blockDisplayName = "Large Reactor"; + break; + case "LargeBlockSmallGenerator": + case "LargeBlockSmallGeneratorWarfare2": + blockDisplayName = "Small Reactor"; + break; + } + } + else if (block is IMyGyro) + { + switch (subtype) + { + case "LargeBlockGyro": + blockDisplayName = "Small Gyro"; + break; + case "AQD_LG_LargeGyro": + blockDisplayName = "Large Gyro"; + break; + } + } + else if (block is IMyCameraBlock) + { + switch (subtype) + { + case "MA_Buster_Camera": + blockDisplayName = "Buster Camera"; + break; + case "LargeCameraBlock": + blockDisplayName = "Camera"; + break; + } + } + + //else if (!(block is IMyTerminalBlock)) blockDisplayName = "CubeBlock"; // If this is ever an issue, look here. + + //if (blockDisplayName.Contains("Letter")) blockDisplayName = "Letter"; + //else if (blockDisplayName.Contains("Beam Block")) blockDisplayName = "Beam Block"; + //else if (blockDisplayName.Contains("Window")) + // blockDisplayName = "Window"; + //else if (blockDisplayName.Contains("Neon")) + // blockDisplayName = "Neon Tube"; + } + + private static Vector3 ColorMaskToRgb(Vector3 colorMask) + { + return MyColorPickerConstants.HSVOffsetToHSV(colorMask).HSVtoColor(); + } + + /// + /// Updates the nametag display. + /// + public void UpdateHud() + { + if (_nametag == null || MyAPIGateway.Utilities.IsDedicated) + return; + + try + { + var camera = MyAPIGateway.Session.Camera; + const int distanceThreshold = 20000; + const int maxAngle = 60; // Adjust this angle as needed + + Vector3D gridPosition = Position; + + var targetHudPos = camera.WorldToScreen(ref gridPosition); + var newOrigin = new Vector2D(targetHudPos.X, targetHudPos.Y); + + _nametag.InitialColor = new Color(FactionColor); + var fov = camera.FieldOfViewAngle; + var angle = GetAngleBetweenDegree(gridPosition - camera.WorldMatrix.Translation, + camera.WorldMatrix.Forward); + + var stealthed = ((uint)Grid.Flags & 0x1000000) > 0; + var visible = !(newOrigin.X > 1 || newOrigin.X < -1 || newOrigin.Y > 1 || newOrigin.Y < -1) && + angle <= fov && !stealthed; + + var distance = Vector3D.Distance(camera.WorldMatrix.Translation, gridPosition); + _nametag.Scale = 1 - MathHelper.Clamp(distance / distanceThreshold, 0, 1) + + 30 / Math.Max(maxAngle, angle * angle * angle); + _nametag.Origin = new Vector2D(targetHudPos.X, + targetHudPos.Y + MathHelper.Clamp(-0.000125 * distance + 0.25, 0.05, 0.25)); + _nametag.Visible = visible && AllGridsList.NametagViewState != NametagSettings.None; + + _nametag.Message.Clear(); + + var nameTagText = ""; + + if ((AllGridsList.NametagViewState & NametagSettings.PlayerName) > 0) + nameTagText += OwnerName; + if ((AllGridsList.NametagViewState & NametagSettings.GridName) > 0) + nameTagText += "\n" + GridName; + if (!IsFunctional) + nameTagText += ":[Dead]"; + + _nametag.Message.Append(nameTagText.TrimStart('\n')); + _nametag.Offset = -_nametag.GetTextLength() / 2; + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + private double GetAngleBetweenDegree(Vector3D vectorA, Vector3D vectorB) + { + vectorA.Normalize(); + vectorB.Normalize(); + return Math.Acos(MathHelper.Clamp(vectorA.Dot(vectorB), -1, 1)) * (180.0 / Math.PI); + } + + public void DisposeHud() + { + if (_nametag != null) + { + _nametag.Visible = false; + _nametag.Message.Clear(); + _nametag.DeleteMessage(); + } + + _nametag = null; + } + + #region GridStats Pointers + + #region Global Stats + + public bool IsFunctional = false; + + public int BlockCount + { + get + { + var total = 0; + foreach (var stats in _gridStats.Values) + total += stats.BlockCount; + return total; + } + } + + public float GridIntegrity + { + get + { + float total = 0; + foreach (var stats in _gridStats.Values) + total += stats.GridIntegrity; + return total; + } + } + + public float OriginalGridIntegrity; + + public int HeavyArmorCount + { + get + { + var total = 0; + foreach (var stats in _gridStats.Values) + total += stats.HeavyArmorCount; + return total; + } + } + + public int CockpitCount + { + get + { + var total = 0; + foreach (var stats in _gridStats.Values) + total += stats.CockpitCount; + return total; + } + } + + public int PCU + { + get + { + var total = 0; + foreach (var stats in _gridStats.Values) + total += stats.PCU; + return total; + } + } + + public float TotalThrust + { + get + { + float total = 0; + foreach (var stats in _gridStats.Values) + total += stats.TotalThrust; + return total; + } + } + + public float TotalTorque + { + get + { + float total = 0; + foreach (var stats in _gridStats.Values) + total += stats.TotalTorque; + return total; + } + } + + public float TotalPower => Grid?.ResourceDistributor?.MaxAvailableResourceByType(MyResourceDistributorComponent.ElectricityId) ?? 0; + + public Dictionary SpecialBlockCounts + { + get + { + var blockCounts = new Dictionary(); + foreach (var stats in _gridStats.Values) + foreach (var kvp in stats.SpecialBlockCounts) + { + if (!blockCounts.ContainsKey(kvp.Key)) + blockCounts.Add(kvp.Key, 0); + blockCounts[kvp.Key] += kvp.Value; + } + + return blockCounts; + } + } + + #endregion + + #region BattlePoint Stats + + public double BattlePoints + { + get + { + double total = 0; + foreach (var stats in _gridStats.Values) + total += stats.BattlePoints; + return total; + } + } + + public double OffensivePoints + { + get + { + double total = 0; + foreach (var stats in _gridStats.Values) + total += stats.OffensivePoints; + return total; + } + } + + public float OffensivePointsRatio => BattlePoints == 0 ? 0 : (float)OffensivePoints / (float)BattlePoints; + + public double PowerPoints + { + get + { + double total = 0; + foreach (var stats in _gridStats.Values) + total += stats.PowerPoints; + return total; + } + } + + public float PowerPointsRatio => BattlePoints == 0 ? 0 : (float)PowerPoints / (float)BattlePoints; + + public double MovementPoints + { + get + { + double total = 0; + foreach (var stats in _gridStats.Values) + total += stats.MovementPoints; + return total; + } + } + + public float MovementPointsRatio => BattlePoints == 0 ? 0 : (float)MovementPoints / (float)BattlePoints; + + public double PointDefensePoints + { + get + { + double total = 0; + foreach (var stats in _gridStats.Values) + total += stats.PointDefensePoints; + return total; + } + } + + public float PointDefensePointsRatio => BattlePoints == 0 ? 0 : (float)PointDefensePoints / (float)BattlePoints; + + + public double RemainingPoints => + BattlePoints - OffensivePoints - PowerPoints - MovementPoints - PointDefensePoints; + + public double RemainingPointsRatio => BattlePoints == 0 ? 0 : RemainingPoints / BattlePoints; + + #endregion + + #region Shield Stats + + public float OriginalMaxShieldHealth = -1; + + public float MaxShieldHealth + { + get + { + var shieldController = ShieldApi.GetShieldBlock(Grid); + if (shieldController == null) + return -1; + return ShieldApi.GetMaxHpCap(shieldController); + } + } + + public float CurrentShieldPercent + { + get + { + var shieldController = ShieldApi.GetShieldBlock(Grid); + if (shieldController == null) + return -1; + return ShieldApi.GetShieldPercent(shieldController); + } + } + + public float CurrentShieldHeat + { + get + { + var shieldController = ShieldApi.GetShieldBlock(Grid); + if (shieldController == null) + return -1; + return ShieldApi.GetShieldHeat(shieldController); + } + } + + #endregion + + #region Weapon Stats + + public Dictionary WeaponCounts + { + get + { + var blockCounts = new Dictionary(); + foreach (var stats in _gridStats.Values) + foreach (var kvp in stats.WeaponCounts) + { + if (!blockCounts.ContainsKey(kvp.Key)) + blockCounts.Add(kvp.Key, 0); + blockCounts[kvp.Key] += kvp.Value; + } + + return blockCounts; + } + } + + public float DamagePerSecond => Math.Abs(AllGridsList.I.WcApi.GetConstructEffectiveDps((MyEntity)Grid)); + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/TrackingManager.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/TrackingManager.cs new file mode 100644 index 00000000..0767a5cb --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/ShipTracking/TrackingManager.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using CGP.ShareTrack.HeartNetworking; +using CGP.ShareTrack.HeartNetworking.Custom; +using VRage.Game.ModAPI; +using VRage.ModAPI; + +namespace CGP.ShareTrack.ShipTracking +{ + internal class TrackingManager + { + public static TrackingManager I; + private static readonly string[] AutoTrackSubtypes = { "" }; + private readonly HashSet _queuedGridTracks = new HashSet(); + public HashSet AllGrids = new HashSet(); + public Dictionary TrackedGrids = new Dictionary(); + + #region Public Actions + + public Action OnShipTracked; + public Action OnShipAliveChanged; + + #endregion + + private bool _isTracking = false; + + private TrackingManager() + { + //MyAPIGateway.Entities.OnEntityAdd += OnEntityAdd; + //MyAPIGateway.Entities.OnEntityRemove += OnEntityRemove; + } + + public void StartTracking() + { + //Log.Info("Starting grid tracking"); + //_isTracking = true; + //var entities = new HashSet(); + //MyAPIGateway.Entities.GetEntities(entities); + //foreach (var entity in entities) + // ProcessEntity(entity); + } + + private void ProcessEntity(IMyEntity entity) + { + var grid = entity as IMyCubeGrid; + if (grid?.Physics == null) return; + + AllGrids.Add(grid); + grid.GetBlocks(null, block => + { + CheckAutotrack(block); + return false; + }); + + if (_queuedGridTracks.Contains(grid.EntityId)) + { + _queuedGridTracks.Remove(grid.EntityId); + if (!TrackedGrids.ContainsKey(grid)) + { + TrackGrid(grid, false); + } + } + } + + private void Update() + { + if (!_isTracking) return; + + foreach (var tracker in TrackedGrids.Values) + { + tracker.UpdateAfterSim(); + } + } + + private void Unload() + { + AllGrids.Clear(); + foreach (var tracker in TrackedGrids.Values) + tracker.DisposeHud(); + TrackedGrids.Clear(); + MyAPIGateway.Entities.OnEntityAdd -= OnEntityAdd; + MyAPIGateway.Entities.OnEntityRemove -= OnEntityRemove; + } + + private void OnEntityAdd(IMyEntity entity) + { + if (!_isTracking) return; + ProcessEntity(entity); + } + + private void OnEntityRemove(IMyEntity entity) + { + if (!(entity is IMyCubeGrid) || entity.Physics == null) + return; + + var grid = (IMyCubeGrid)entity; + AllGrids.Remove(grid); + if (TrackedGrids.ContainsKey(grid)) + { + TrackedGrids[grid].DisposeHud(); + TrackedGrids.Remove(grid); + } + _queuedGridTracks.Remove(grid.EntityId); + } + + private long[] GetGridIds() + { + var gridIds = new List(); + foreach (var grid in TrackedGrids.Keys) + gridIds.Add(grid.EntityId); + return gridIds.ToArray(); + } + + private void CheckAutotrack(IMySlimBlock block) + { + if (block == null) + { + Log.Error("CheckAutotrack called with null block"); + return; + } + if (block.FatBlock == null || !AutoTrackSubtypes.Contains(block.BlockDefinition.Id.SubtypeName)) + return; + + if (block.CubeGrid == null) + { + Log.Error($"Block {block.BlockDefinition.Id.SubtypeName} has null CubeGrid"); + return; + } + + TrackGrid(block.CubeGrid, false); + } + + #region Public Methods + + public static void Init() + { + I = new TrackingManager(); + } + + public static void UpdateAfterSimulation() + { + I?.Update(); + } + + public static void Close() + { + I?.Unload(); + I = null; + } + + public void BulkTrackGrids(long[] gridIds) + { + if (!_isTracking) + { + Log.Info($"Queuing bulk track request with {gridIds.Length} items!"); + _queuedGridTracks.UnionWith(gridIds); + return; + } + + Log.Info($"Processing bulk track request with {gridIds.Length} items!"); + var gridIds_List = new List(gridIds); + foreach (var grid in TrackedGrids.Keys.ToArray()) + { + if (gridIds.Contains(grid.EntityId)) + { + gridIds_List.Remove(grid.EntityId); + continue; + } + UntrackGrid(grid, false); + } + foreach (var gridId in gridIds_List) + TrackGrid(gridId, false); + } + + public void TrackGrid(IMyCubeGrid grid, bool share = true) + { + // if (!_isTracking) + // { + // _queuedGridTracks.Add(grid.EntityId); + // return; + // } + // + // if (grid == null) + // { + // Log.Error("TrackGrid called with null grid"); + // return; + // } + // + // if (((MyCubeGrid)grid).IsStatic || TrackedGrids.ContainsKey(grid)) + // return; + // + // // Don't allow tracking grids that are already tracked in the group. + // var allAttachedGrids = new List(); + // var gridGroup = grid.GetGridGroup(GridLinkTypeEnum.Physical); + // if (gridGroup != null) + // { + // gridGroup.GetGrids(allAttachedGrids); + // } + // else + // { + // Log.Error($"Grid {grid.DisplayName} has no physical grid group"); + // allAttachedGrids.Add(grid); + // } + // + // foreach (var attachedGrid in allAttachedGrids) + // if (TrackedGrids.ContainsKey(attachedGrid)) + // return; + // + // try + // { + // var tracker = new ShipTracker(grid); + // TrackedGrids[grid] = tracker; + // // Automatically added to tracked grid list + // Log.Info($"TrackGrid Tracked grid {grid.DisplayName}. Visible: true"); + // OnShipTracked?.Invoke(grid, true); + // } + // catch (Exception ex) + // { + // Log.Error($"Error creating ShipTracker for grid {grid.DisplayName}: {ex}"); + // return; + // } + // + // if (!share) return; + // if (MyAPIGateway.Session.IsServer) + // { + // ServerDoSync(); + // } + // else + // { + // var packet = new TrackingSyncPacket(grid.EntityId, true); + // HeartNetwork.I.SendToServer(packet); + // } + } + + public void TrackGrid(long gridId, bool share = true) + { + if (!_isTracking) + { + _queuedGridTracks.Add(gridId); + return; + } + + var grid = MyAPIGateway.Entities.GetEntityById(gridId) as IMyCubeGrid; + if (grid == null) + { + _queuedGridTracks.Add(gridId); + return; + } + TrackGrid(grid, share); + } + + public void UntrackGrid(IMyCubeGrid grid, bool share = true) + { + // Untrack all grids in group. + var allAttachedGrids = new List(); + grid.GetGridGroup(GridLinkTypeEnum.Physical).GetGrids(allAttachedGrids); + foreach (var attachedGrid in allAttachedGrids.Where(attachedGrid => TrackedGrids.ContainsKey(attachedGrid))) + { + TrackedGrids[attachedGrid].DisposeHud(); + TrackedGrids.Remove(attachedGrid); + OnShipTracked?.Invoke(attachedGrid, false); + } + + if (!share) return; + if (MyAPIGateway.Session.IsServer) + { + ServerDoSync(); + } + else + { + var packet = new TrackingSyncPacket(grid.EntityId, false); + HeartNetwork.I.SendToServer(packet); + } + } + + public void UntrackGrid(long gridId, bool share = true) + { + var grid = MyAPIGateway.Entities.GetEntityById(gridId) as IMyCubeGrid; + _queuedGridTracks.Remove(gridId); + if (grid != null) + UntrackGrid(grid, share); + } + + public bool IsGridTracked(IMyCubeGrid grid) + { + var allAttachedGrids = new List(); + grid.GetGridGroup(GridLinkTypeEnum.Physical).GetGrids(allAttachedGrids); + foreach (var attachedGrid in allAttachedGrids.Where(attachedGrid => TrackedGrids.ContainsKey(attachedGrid))) + return true; + return false; + } + + public void ServerDoSync() + { + var packet = new TrackingSyncPacket(GetGridIds()); + HeartNetwork.I.SendToEveryone(packet); + } + + public long[] GetQueuedGridTracks() + { + return _queuedGridTracks.ToArray(); + } + + #endregion + } +} diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiMethods.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiMethods.cs new file mode 100644 index 00000000..0aecd39a --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiMethods.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CGP.ShareTrack.ShipTracking; +using VRage.Game.ModAPI; + +namespace CGP.ShareTrack.TrackerApi +{ + internal class ApiMethods + { + internal readonly Dictionary ModApiMethods; + + internal ApiMethods() + { + ModApiMethods = new Dictionary + { + ["GetTrackedGrids"] = new Func(GetTrackedGrids), + ["IsGridAlive"] = new Func(IsGridAlive), + ["RegisterOnTrack"] = new Action>(RegisterOnTrack), + ["UnregisterOnTrack"] = new Action>(UnregisterOnTrack), + ["RegisterOnAliveChanged"] = new Action>(RegisterOnAliveChanged), + ["UnregisterOnAliveChanged"] = new Action>(UnregisterOnAliveChanged), + ["AreTrackedGridsLoaded"] = new Func(AreTrackedGridsLoaded), + ["GetGridPoints"] = new Func(GetGridPoints), + ["TrackGrid"] = new Action(TrackGrid), + ["UnTrackGrid"] = new Action(UnTrackGrid), + }; + } + + private IMyCubeGrid[] GetTrackedGrids() + { + return TrackingManager.I?.TrackedGrids.Keys.ToArray(); + } + + private bool IsGridAlive(IMyCubeGrid grid) + { + return TrackingManager.I?.TrackedGrids.GetValueOrDefault(grid, null)?.IsFunctional ?? false; + } + + private void RegisterOnTrack(Action action) + { + if (TrackingManager.I != null) + TrackingManager.I.OnShipTracked += action; + } + + private void UnregisterOnTrack(Action action) + { + if (TrackingManager.I != null) + TrackingManager.I.OnShipTracked -= action; + } + + private void RegisterOnAliveChanged(Action action) + { + if (TrackingManager.I != null) + TrackingManager.I.OnShipAliveChanged += action; + } + + private void UnregisterOnAliveChanged(Action action) + { + if (TrackingManager.I != null) + TrackingManager.I.OnShipAliveChanged -= action; + } + + private bool AreTrackedGridsLoaded() + { + if (TrackingManager.I == null) + return false; + + return TrackingManager.I.GetQueuedGridTracks().Length == 0; + } + + private double GetGridPoints(IMyCubeGrid grid) + { + if (TrackingManager.I == null || !TrackingManager.I.TrackedGrids.ContainsKey(grid)) + return -1; + + return TrackingManager.I.TrackedGrids[grid].BattlePoints; + } + + private void TrackGrid(IMyCubeGrid grid, bool share) + { + //TrackingManager.I?.TrackGrid(grid, share); + } + + private void UnTrackGrid(IMyCubeGrid grid, bool share) + { + //TrackingManager.I?.UntrackGrid(grid, share); + } + } +} diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiProvider.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiProvider.cs new file mode 100644 index 00000000..270cc91a --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ApiProvider.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRageMath; + +namespace CGP.ShareTrack.TrackerApi +{ + internal class ApiProvider + { + private const long Channel = 3033234540; + private readonly IReadOnlyDictionary _apiDefinitions; + private readonly MyTuple> _endpointTuple; + + /// + /// Registers for API requests and updates any pre-existing clients. + /// + public ApiProvider() + { + _apiDefinitions = new ApiMethods().ModApiMethods; + _endpointTuple = + new MyTuple>(MasterSession.ModVersion, + _apiDefinitions); + + MyAPIGateway.Utilities.RegisterMessageHandler(Channel, HandleMessage); + + IsReady = true; + try + { + MyAPIGateway.Utilities.SendModMessage(Channel, _endpointTuple); + } + catch (Exception ex) + { + Log.Info($"Exception in Api Load: {ex}"); + } + + Log.Info($"ShareTrackAPI v{MasterSession.ModVersion.Y} initialized."); + } + + /// + /// Is the API ready? + /// + public bool IsReady { get; private set; } + + private void HandleMessage(object o) + { + if (o as string == "ApiEndpointRequest") + { + MyAPIGateway.Utilities.SendModMessage(Channel, _endpointTuple); + Log.Info("ShareTrackAPI sent definitions."); + } + } + + + /// + /// Unloads all API endpoints and detaches events. + /// + public void Unload() + { + MyAPIGateway.Utilities.UnregisterMessageHandler(Channel, HandleMessage); + + IsReady = false; + // Clear API client's endpoints + MyAPIGateway.Utilities.SendModMessage(Channel, + new MyTuple>(MasterSession.ModVersion, null)); + + Log.Info("ShareTrackAPI unloaded."); + } + } +} diff --git a/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ShareTrackApi.cs b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ShareTrackApi.cs new file mode 100644 index 00000000..467d80f4 --- /dev/null +++ b/ConfigurableGridPoints/Data/Scripts/ShipPoints/TrackerApi/ShareTrackApi.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRage.Game.ModAPI; +using VRage.Utils; +using VRageMath; + +namespace CGP.ShareTrack.TrackerApi +{ + // Aristeas? Reuse code? NEVER... + public class ShareTrackApi + { + /// + /// The expected API version. + /// + public const int ApiVersion = 3; + + /// + /// Triggered whenever the API is ready - added to by the constructor or manually. + /// + public Action OnReady; + + /// + /// The currently loaded ShareTrack version. + /// + /// Not the API version; see + /// + /// + public int FrameworkVersion { get; private set; } = -1; + + /// + /// Displays whether endpoints are loaded and the API is ready for use. + /// + public bool IsReady { get; private set; } + + /// + /// Call this to initialize the ShareTrackApi.
+ /// + /// API methods will be unusable until the endpoints are populated. Check or utilize + /// for safety. + /// + ///
+ /// + /// Method to be triggered when the API is ready. + /// + public void Init(IMyModContext modContext, Action onLoad = null) + { + if (_isRegistered) + throw new Exception($"{GetType().Name}.Load() should not be called multiple times!"); + + _modContext = modContext; + OnReady = onLoad; + _isRegistered = true; + MyAPIGateway.Utilities.RegisterMessageHandler(ApiChannel, HandleMessage); + MyAPIGateway.Utilities.SendModMessage(ApiChannel, "ApiEndpointRequest"); + MyLog.Default.WriteLineAndConsole( + $"{_modContext.ModName}: ShareTrackAPI listening for API methods..."); + } + + /// + /// Call this to unload the ShareTrackApi; i.e. in case of instantiating a new API or for freeing up resources. + /// + /// This method will also be called automatically when ShareTrack is + /// closed. + /// + /// + public void UnloadData() + { + MyAPIGateway.Utilities.UnregisterMessageHandler(ApiChannel, HandleMessage); + + if (_apiInit) + ApiAssign(); // Clear API methods if the API is currently inited. + + _isRegistered = false; + _apiInit = false; + IsReady = false; + OnReady = null; + MyLog.Default.WriteLineAndConsole($"{_modContext.ModName}: ShareTrackAPI unloaded."); + } + + // These sections are what the user can actually see when referencing the API, and can be used freely. // + // Note the null checks. // + + #region API Methods + + public IMyCubeGrid[] GetTrackedGrids() + { + return _getTrackedGrids?.Invoke(); + } + + public bool IsGridAlive(IMyCubeGrid grid) + { + return _isGridAlive?.Invoke(grid) ?? false; + } + + public void RegisterOnTrack(Action action) + { + _registerOnTrack?.Invoke(action); + } + + public void UnregisterOnTrack(Action action) + { + _unRegisterOnTrack?.Invoke(action); + } + + public void RegisterOnAliveChanged(Action action) + { + _registerOnAliveChanged?.Invoke(action); + } + + public void UnregisterOnAliveChanged(Action action) + { + _unregisterOnAliveChanged?.Invoke(action); + } + + public bool AreTrackedGridsLoaded() + { + return _areTrackedGridsLoaded?.Invoke() ?? false; + } + + public int GetGridPoints(IMyCubeGrid grid) + { + return _getGridPoints?.Invoke(grid) ?? -1; + } + + public void TrackGrid(IMyCubeGrid grid, bool share = true) + { + _trackGrid?.Invoke(grid, share); + } + + public void UnTrackGrid(IMyCubeGrid grid, bool share = true) + { + _unTrackGrid?.Invoke(grid, share); + } + + #endregion + + + // This section lists all the delegates that will be assigned and utilized below. // + + #region Delegates + + // Global methods + private Func _getTrackedGrids; + private Func _isGridAlive; + private Action> _registerOnTrack; + private Action> _unRegisterOnTrack; + private Action> _registerOnAliveChanged; + private Action> _unregisterOnAliveChanged; + private Func _areTrackedGridsLoaded; + private Func _getGridPoints; + private Action _trackGrid; + private Action _unTrackGrid; + + #endregion + + + // This section is the 'guts' of the API; it assigns out all the API endpoints internally and registers with the main mod. // + + #region API Initialization + + private bool _isRegistered; + private bool _apiInit; + private const long ApiChannel = 3033234540; + private IReadOnlyDictionary _methodMap; + private IMyModContext _modContext; + + /// + /// Assigns all API methods. Internal function, avoid editing. + /// + /// + public bool ApiAssign() + { + _apiInit = _methodMap != null; + + // Global methods + SetApiMethod("GetTrackedGrids", ref _getTrackedGrids); + SetApiMethod("IsGridAlive", ref _isGridAlive); + SetApiMethod("RegisterOnTrack", ref _registerOnTrack); + SetApiMethod("UnregisterOnTrack", ref _unRegisterOnTrack); + SetApiMethod("RegisterOnAliveChanged", ref _registerOnAliveChanged); + SetApiMethod("UnregisterOnAliveChanged", ref _unregisterOnAliveChanged); + SetApiMethod("AreTrackedGridsLoaded", ref _areTrackedGridsLoaded); + SetApiMethod("GetGridPoints", ref _getGridPoints); + SetApiMethod("TrackGrid", ref _trackGrid); + SetApiMethod("UnTrackGrid", ref _unTrackGrid); + + // Unload data if told to, otherwise notify that the API is ready. + if (_methodMap == null) + { + UnloadData(); + return false; + } + + _methodMap = null; + OnReady?.Invoke(); + return true; + } + + /// + /// Assigns a single API endpoint. + /// + /// + /// Shared endpoint name; matches with the mod. + /// Method to assign. + /// + private void SetApiMethod(string name, ref T method) where T : class + { + if (_methodMap == null) + { + method = null; + return; + } + + if (!_methodMap.ContainsKey(name)) + throw new Exception("Method Map does not contain method " + name); + var del = _methodMap[name]; + if (del.GetType() != typeof(T)) + throw new Exception( + $"Method {name} type mismatch! [MapMethod: {del.GetType().Name} | ApiMethod: {typeof(T).Name}]"); + method = _methodMap[name] as T; + } + + /// + /// Triggered whenever the API receives a message from the mod. + /// + /// + private void HandleMessage(object obj) + { + try + { + if (_apiInit || obj is string || + obj == null) // the "ApiEndpointRequest" message will also be received here, we're ignoring that + return; + + var tuple = (MyTuple>)obj; + var receivedVersion = tuple.Item1; + var dict = tuple.Item2; + + if (dict == null) + { + MyLog.Default.WriteLineAndConsole( + $"{_modContext.ModName}: ShareTrackAPI ERR: Received null dictionary!"); + return; + } + + if (receivedVersion.Y != ApiVersion) + Log.Info( + $"Expected API version ({ApiVersion}) differs from received API version {receivedVersion}; errors may occur."); + + _methodMap = dict; + + if (!ApiAssign()) // If we're unassigning the API, don't notify when ready + return; + + FrameworkVersion = receivedVersion.X; + IsReady = true; + Log.Info($"ShareTrackApi v{ApiVersion} loaded!"); + } + catch (Exception ex) + { + // We really really want to notify the player if something goes wrong here. + MyLog.Default.WriteLineAndConsole($"{_modContext.ModName}: Exception in ShareTrackAPI! " + ex); + MyAPIGateway.Utilities.ShowMessage(_modContext.ModName, "Exception in ShareTrackAPI!\n" + ex); + } + } + + #endregion + } +} diff --git a/ConfigurableGridPoints/Data/TransparentMaterials.sbc b/ConfigurableGridPoints/Data/TransparentMaterials.sbc new file mode 100644 index 00000000..674458d7 --- /dev/null +++ b/ConfigurableGridPoints/Data/TransparentMaterials.sbc @@ -0,0 +1,226 @@ + + + + + + TransparentMaterialDefinition + Inner + + false + 1 + true + false + 1 + Textures\Particles\SquareWindowDirtInside_ca.dds + + false + true + + 0 + 0 + + + 1 + 1 + + + 0 + 0 + 0 + 0.5 + + + 0.05 + 0.05 + 0.05 + 0.01 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + 0.5 + 5 + 1 + 0.4 + 0.8 + 255 + false + + + + + + + TransparentMaterialDefinition + ShieldPassiveGlass08 + + false + 1 + true + false + 1 + Textures\Particles\SquareWindowDirtInside_ca.dds + + false + true + + 0 + 0 + + + 1 + 1 + + + 0 + 0 + 0 + 0 + + + 0.00 + 0.00 + 0.4 + 0.01 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + 0.5 + 5 + 1 + 0.4 + 0.8 + 255 + false + + + + + TransparentMaterialDefinition + ShieldPassiveGlass09 + + false + 1 + true + false + 1 + Textures\Particles\SquareWindowDirtInside_ca.dds + + false + true + + 0 + 0 + + + 1 + 1 + + + 0 + 0 + 0 + 0 + + + 0.4 + 0.00 + 0.00 + 0.005 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + 0.5 + 5 + 1 + 0.4 + 0.8 + 255 + false + + + + + TransparentMaterialDefinition + ShieldPassiveGlass07 + + false + 1 + true + false + 1 + Textures\Particles\SquareWindowDirtInside_ca.dds + + false + true + + 0 + 0 + + + 1 + 1 + + + 0 + 0 + 0 + 0 + + + 0.00 + 0.4 + 0.00 + 0.005 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + 0.5 + 5 + 1 + 0.4 + 0.8 + 255 + false + + + diff --git a/ConfigurableGridPoints/Data/concatrecursivelyandminify.bat b/ConfigurableGridPoints/Data/concatrecursivelyandminify.bat new file mode 100644 index 00000000..9bbffa34 --- /dev/null +++ b/ConfigurableGridPoints/Data/concatrecursivelyandminify.bat @@ -0,0 +1,60 @@ +@echo off +setlocal enabledelayedexpansion +chcp 65001 +REM ^^ this is to change the encoding to UTF-8, apparently + +echo Starting operation... + +set TEMP_OUTPUT=temp_concatenated.txt +set FINAL_OUTPUT=minified_output.txt +set /a COUNT=0 +set /a ERRORS=0 + +if exist "%TEMP_OUTPUT%" ( + echo Existing temporary file found. Deleting... + del "%TEMP_OUTPUT%" +) + +if exist "%FINAL_OUTPUT%" ( + echo Existing output file found. Deleting... + del "%FINAL_OUTPUT%" +) + +for /r %%i in (*.cs) do ( + echo Processing: "%%i" + type "%%i" >> "%TEMP_OUTPUT%" + if errorlevel 1 ( + echo Error processing "%%i". + set /a ERRORS+=1 + ) else ( + set /a COUNT+=1 + ) +) + +echo Concatenation completed. +echo Total files processed: %COUNT% +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the concatenation. +) else ( + echo No errors encountered during concatenation. +) + +echo Minifying concatenated file... +csmin < "%TEMP_OUTPUT%" > "%FINAL_OUTPUT%" +if errorlevel 1 ( + echo Error occurred during minification. + set /a ERRORS+=1 +) else ( + echo Minification completed successfully. +) + +echo Cleaning up temporary file... +del "%TEMP_OUTPUT%" + +echo Operation completed. +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the entire operation. +) else ( + echo No errors encountered during the entire operation. +) +pause \ No newline at end of file diff --git a/ConfigurableGridPoints/Models/Cubes/InnerShield.mwm b/ConfigurableGridPoints/Models/Cubes/InnerShield.mwm new file mode 100644 index 00000000..f288b3aa Binary files /dev/null and b/ConfigurableGridPoints/Models/Cubes/InnerShield.mwm differ diff --git a/ConfigurableGridPoints/Models/Cubes/ShieldPassive07.mwm b/ConfigurableGridPoints/Models/Cubes/ShieldPassive07.mwm new file mode 100644 index 00000000..5d9d6b0d Binary files /dev/null and b/ConfigurableGridPoints/Models/Cubes/ShieldPassive07.mwm differ diff --git a/ConfigurableGridPoints/Models/Cubes/ShieldPassive08.mwm b/ConfigurableGridPoints/Models/Cubes/ShieldPassive08.mwm new file mode 100644 index 00000000..02f40260 Binary files /dev/null and b/ConfigurableGridPoints/Models/Cubes/ShieldPassive08.mwm differ diff --git a/ConfigurableGridPoints/Models/Cubes/ShieldPassive09.mwm b/ConfigurableGridPoints/Models/Cubes/ShieldPassive09.mwm new file mode 100644 index 00000000..4a1ff231 Binary files /dev/null and b/ConfigurableGridPoints/Models/Cubes/ShieldPassive09.mwm differ diff --git a/ConfigurableGridPoints/metadata.mod b/ConfigurableGridPoints/metadata.mod new file mode 100644 index 00000000..0a020fdf --- /dev/null +++ b/ConfigurableGridPoints/metadata.mod @@ -0,0 +1,4 @@ + + + 1.0 + \ No newline at end of file diff --git a/ConfigurableGridPoints/modinfo.sbmi b/ConfigurableGridPoints/modinfo.sbmi new file mode 100644 index 00000000..93efe5ed --- /dev/null +++ b/ConfigurableGridPoints/modinfo.sbmi @@ -0,0 +1,11 @@ + + + 76561198071098415 + 0 + + + 3346579329 + Steam + + + \ No newline at end of file diff --git a/ConfigurableGridPoints/tldr.txt b/ConfigurableGridPoints/tldr.txt new file mode 100644 index 00000000..7fb65fba --- /dev/null +++ b/ConfigurableGridPoints/tldr.txt @@ -0,0 +1,7 @@ +- use new sharetrack API +- make {thing} happen when grid that contains {block subtype} +- i.e. make the points turn red in shift + T when over limit +- probably should port the new CGP grid point features over to SC sharetrack +- fork the slimmed down ish CGPGridPoints + +- can make into base mod + addon later for public-facing release \ No newline at end of file diff --git a/ConfigurableGridPointsAddon/Data/Scripts/Additions/PointAdditions.cs b/ConfigurableGridPointsAddon/Data/Scripts/Additions/PointAdditions.cs new file mode 100644 index 00000000..2d12eef8 --- /dev/null +++ b/ConfigurableGridPointsAddon/Data/Scripts/Additions/PointAdditions.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using Sandbox.Definitions; +using Sandbox.ModAPI; +using VRage; +using VRage.Game; +using VRage.Game.Components; + +namespace ShipPoints +{ + [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] + internal class PointAdditions : MySessionComponentBase + { + private readonly Dictionary PointValues = new Dictionary + { + // to show up on the HUD, it needs to be listed here + //["SmallBlockBatteryBlock"] = 0, + //["TinyDieselEngine"] = 0, + //["SmallDieselEngine"] = 0, + //["MediumDieselEngine"] = 0, + + + }; + + private readonly Dictionary FuzzyPoints = new Dictionary(); + private readonly Func> _climbingCostRename = ClimbingCostRename; + + private static MyTuple ClimbingCostRename(string blockDisplayName) + { + float costMultiplier = 0f; + + switch (blockDisplayName) + { + case "test": + blockDisplayName = "test"; + costMultiplier = 0f; + break; + } + + return new MyTuple(blockDisplayName, costMultiplier); + } + + public override void Init(MyObjectBuilder_SessionComponent sessionComponent) + { + // Add fuzzy rules (can be displayname or subtype) + //FuzzyPoints.Add("aero-wing", 0.33); + //FuzzyPoints.Add("Control Surface", 0.33); + //FuzzyPoints.Add("suspension2x2", 0.125); + //FuzzyPoints.Add("suspension3x3", 0.25); + //FuzzyPoints.Add("suspension5x5", 0.5); + //FuzzyPoints.Add("SmallDragWheel", 0.33); + + // Process fuzzy rules + foreach (var kvp in FuzzyPoints) + { + foreach (var block in MyDefinitionManager.Static.GetAllDefinitions()) + { + var cubeBlock = block as MyCubeBlockDefinition; + if (cubeBlock != null) + { + // Check if the subtype contains the fuzzy rule key (case-insensitive) + if (cubeBlock.Id.SubtypeName.IndexOf(kvp.Key, StringComparison.OrdinalIgnoreCase) >= 0) + { + if (!PointValues.ContainsKey(cubeBlock.Id.SubtypeName)) + { + PointValues[cubeBlock.Id.SubtypeName] = kvp.Value; + } + } + else if (cubeBlock.DisplayNameString != null && cubeBlock.DisplayNameString.Contains(kvp.Key)) + { + if (!PointValues.ContainsKey(cubeBlock.Id.SubtypeName)) + { + PointValues[cubeBlock.Id.SubtypeName] = kvp.Value; + } + + } + } + } + } + + MyAPIGateway.Utilities.SendModMessage(2546247, PointValues); + MyAPIGateway.Utilities.SendModMessage(2546247, _climbingCostRename); + } + } +} \ No newline at end of file diff --git a/ConfigurableGridPointsAddon/metadata.mod b/ConfigurableGridPointsAddon/metadata.mod new file mode 100644 index 00000000..0a020fdf --- /dev/null +++ b/ConfigurableGridPointsAddon/metadata.mod @@ -0,0 +1,4 @@ + + + 1.0 + \ No newline at end of file diff --git a/ConfigurableGridPointsAddon/modinfo.sbmi b/ConfigurableGridPointsAddon/modinfo.sbmi new file mode 100644 index 00000000..3494c489 --- /dev/null +++ b/ConfigurableGridPointsAddon/modinfo.sbmi @@ -0,0 +1,11 @@ + + + 76561198071098415 + 0 + + + 3346579398 + Steam + + + \ No newline at end of file diff --git a/DeltaVBaR/Data/BlockCategories.sbc b/DeltaVBaR/Data/BlockCategories.sbc new file mode 100644 index 00000000..929fa51b --- /dev/null +++ b/DeltaVBaR/Data/BlockCategories.sbc @@ -0,0 +1,18 @@ + + + + + + GuiBlockCategoryDefinition + + + DisplayName_Category_Tools + Section1_Position3_ShipTools + + ShipWelder/SELtdLargeNanobotBuildAndRepairSystem + ShipWelder/SELtdSmallNanobotBuildAndRepairSystem + + + + + diff --git a/DeltaVBaR/Data/BlueprintClasses.sbc b/DeltaVBaR/Data/BlueprintClasses.sbc new file mode 100644 index 00000000..611c9cb2 --- /dev/null +++ b/DeltaVBaR/Data/BlueprintClasses.sbc @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DeltaVBaR/Data/CubeBlocks.sbc b/DeltaVBaR/Data/CubeBlocks.sbc new file mode 100644 index 00000000..ed852a8a --- /dev/null +++ b/DeltaVBaR/Data/CubeBlocks.sbc @@ -0,0 +1,154 @@ + + + + + + + ShipWelder + SELtdLargeNanobotBuildAndRepairSystem + + BuildAndRepairSystem + Textures\Icons\SELtdNanobotBuildAndRepairSystem.dds + A Build and Repair System that will help construct and repair your grids! + Large + TriangleMesh + + +
+ Models\NanobotBuildAndRepairSystem\SELtdLargeNanobotBuildAndRepairSystem.mwm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 60 + SELtdNanobotBuildAndRepairSystem + None + None + None + Light + BlockModuleEfficiency + 212 + ParticleHeavyMech + 0.00001 + 0.0 + 200 + + + + + ShipWelder + SELtdSmallNanobotBuildAndRepairSystem + + BuildAndRepairSystem + Textures\Icons\SELtdNanobotBuildAndRepairSystem.dds + A Build and Repair System that will help construct and repair your grids! + Small + TriangleMesh + + + Models\NanobotBuildAndRepairSystem\SELtdSmallNanobotBuildAndRepairSystem.mwm + + + + + + + + + + + + + + + + + + + + + + + + + + 30 + SELtdNanobotBuildAndRepairSystem + None + None + None + Light + BlockModuleEfficiency + 212 + ParticleHeavyMech + 0.00001 + 0.0 + 200 + + + + + + + MyObjectBuilder_AudioDefinition + ArcBaRUnable + + TOOL + 0.1 + 35 + true + + + Sounds\ArcBaRUnable2d.xwm + + + Sounds\ArcBaRUnable3d.xwm + + + + + + + MyObjectBuilder_AudioDefinition + RealBaRUnable + + TOOL_R + 5 + 0.1 + -1 + true + + + Sounds\RealBaRUnable2d.wav + + + Sounds\RealBaRUnable3d.wav + + + + + \ No newline at end of file diff --git a/DeltaVBaR/Data/EntityComponents.sbc b/DeltaVBaR/Data/EntityComponents.sbc new file mode 100644 index 00000000..cb64ef9d --- /dev/null +++ b/DeltaVBaR/Data/EntityComponents.sbc @@ -0,0 +1,14 @@ + + + + + + ModStorageComponent + NanobotBuildAndRepairSystemMod + + + 8B57046C-DA20-4DE1-8E35-513FD21E3B9F + + + + diff --git a/DeltaVBaR/Data/EntityContainers.sbc b/DeltaVBaR/Data/EntityContainers.sbc new file mode 100644 index 00000000..592fcbda --- /dev/null +++ b/DeltaVBaR/Data/EntityContainers.sbc @@ -0,0 +1,23 @@ + + + + + + ShipWelder + SELtdLargeNanobotBuildAndRepairSystem + + + + + + + + ShipWelder + SELtdSmallNanobotBuildAndRepairSystem + + + + + + + diff --git a/DeltaVBaR/Data/Particles.sbc b/DeltaVBaR/Data/Particles.sbc new file mode 100644 index 00000000..70a02fb4 --- /dev/null +++ b/DeltaVBaR/Data/Particles.sbc @@ -0,0 +1,987 @@ + + + + + + ParticleEffect + WeldNanobotTrace1 + + 0 + 20000 + 30 + 1 + 0 + 600 + true + + + GPU + + + + 16 + 16 + 0 + + + + 144 + + + 1 + + + + + + + + + + + 0.00060784 + 1.0000 + 1.0000 + 0.2235 + + + + + + 0.1960784 + 1.0000 + 1.0000 + 0.2235 + + + + + + 0.0060784 + 1.0000 + 1.0000 + 0.2235 + + + + + + 0 + 1.0000 + 1.0000 + 0.2235 + + + + + + + + + + + + + + + + 50 + + + + 20 + + + + 10 + + + + 4 + + + + + + + + 0.5 + + + + + + + 0.05 + 0.05 + 0.05 + + + + + + + + + 1 + + + + + + 0 + 0 + -1 + + + + + + + 0.3 + + + + + + + + 0.5 + + + + + + + + 0 + + + + + + + + 360 + + + + + + 0 + 0 + 2 + + + + + + + + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + + + + + 0 + + + + + + + + + + 0 + + + + 6 + + + + 4 + + + + 2 + + + + + + + + 1 + + + 10 + + + 4 + + + 0.08 + + + true + + + + + + 64 + + + + 32 + + + + 16 + + + + 64 + + + + 32 + + + + + Atlas_E_01 + + + 1 + + + false + + + false + + + true + + + true + + + 1 + + + 0 + + + + 0 + 0 + 0 + + + + 3 + + + 0 + + + true + + + 0.5 + + + 0 + + + false + + + 0 + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + + + + + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + 1 + + + + 4 + + + + 2 + + + + + + + + 5 + + + false + + + false + + + 1 + + + 0 + + + 1 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + 1 + 1 + 0.8277258 + 0.5542271 + + + + + + + + + + + + 20 + + + + + + + + 2 + + + + + + + + 20 + + + + + + + + false + + + 0 + + + 1 + + + 0.1 + + + + + + + + + + ParticleEffect + GrindNanobotTrace1 + + 0 + 20000 + 30 + 1 + 0 + 600 + true + + + GPU + + + + 16 + 16 + 0 + + + + 144 + + + 1 + + + + + + + + + + + 0.00060784 + 0.0050 + 0.0050 + 1.0000 + + + + + + 0.1960784 + 0.0050 + 0.0050 + 1.0000 + + + + + + 0.0060784 + 0.0050 + 0.0050 + 1.0000 + + + + + + 0 + 0 + 0 + 0 + + + + + + + + + + + + + + + + 50 + + + + 20 + + + + 10 + + + + 4 + + + + + + + + 0.5 + + + + + + + 0.05 + 0.05 + 0.05 + + + + + + + + + 1 + + + + + + 0 + 0 + -1 + + + + + + + 0.3 + + + + + + + + 0.5 + + + + + + + + 0 + + + + + + + + 360 + + + + + + 0 + 0 + 2 + + + + + + + + + + + 0 + + + + 0 + + + + 0 + + + + 0 + + + + + + + + 0 + + + + + + + + + + 0 + + + + 6 + + + + 4 + + + + 2 + + + + + + + + 1 + + + 10 + + + 4 + + + 0.08 + + + true + + + + + + 64 + + + + 32 + + + + 16 + + + + 64 + + + + 32 + + + + + Atlas_E_01 + + + 1 + + + false + + + false + + + true + + + true + + + 1 + + + 0 + + + + 0 + 0 + 0 + + + + 3 + + + 0 + + + true + + + 0.5 + + + 0 + + + false + + + 0 + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + + + + + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + 1 + + + + 4 + + + + 2 + + + + + + + + 5 + + + false + + + false + + + 1 + + + 0 + + + 1 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + 1 + 1 + 0.8277258 + 0.5542271 + + + + + + + + + + + + 20 + + + + + + + + 2 + + + + + + + + 20 + + + + + + + + false + + + 0 + + + 1 + + + 0.1 + + + + + + + + + \ No newline at end of file diff --git a/DeltaVBaR/Data/ResearchBlocks.sbc b/DeltaVBaR/Data/ResearchBlocks.sbc new file mode 100644 index 00000000..4b97b217 --- /dev/null +++ b/DeltaVBaR/Data/ResearchBlocks.sbc @@ -0,0 +1,17 @@ + + + + + + + 16 + + + + + + 16 + + + + \ No newline at end of file diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Deb.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Deb.cs new file mode 100644 index 00000000..5cb1b223 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Deb.cs @@ -0,0 +1,19 @@ +using Sandbox.ModAPI; +using VRage.Utils; + +namespace SKONanobotBuildAndRepairSystem +{ + internal class Deb + { + private static readonly bool EnableDebug = false; + + public static void Write(string msg) + { + if (EnableDebug) + { + MyAPIGateway.Utilities.ShowMessage("Nanobot", msg); + MyLog.Default.WriteLineAndConsole($"Nanobot: {msg}"); + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Localization.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Localization.cs new file mode 100644 index 00000000..1a005c8d --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Localization.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Sandbox.ModAPI; +using VRage; +using VRage.Utils; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class LocalizationHelper + { + public static Dictionary GetTexts(MyLanguagesEnum language, Dictionary> translations, Logging log = null) + { + var texts = new Dictionary(); + + var fallbackTranslation = translations[MyLanguagesEnum.English]; //Should be a complete set (all possible entries) + Dictionary requestedTranslation; + if (language == MyLanguagesEnum.English || !translations.TryGetValue(language, out requestedTranslation)) requestedTranslation = null; + + foreach (var kv in fallbackTranslation) + { + string translation; + if (requestedTranslation == null || !requestedTranslation.TryGetValue(kv.Key, out translation)) + { + if (language != MyLanguagesEnum.English && log != null && log.ShouldLog(Logging.Level.Error)) log.Write(Logging.Level.Error, "Missing translation in language={0} for key={1}", language, kv.Key); + translation = kv.Value; + } + texts.Add(kv.Key, translation); + } + return texts; + } + + public static MyStringId GetStringId(Dictionary texts, string key) + { + try + { + return MyStringId.GetOrCompute(texts[key]); + } + catch (Exception ex) + { + throw new Exception($"GetStringId Failed for Key={key}", ex); + } + } + + public static void ExportDictionary(string destFileName, Dictionary texts) + { + using (var writer = MyAPIGateway.Utilities.WriteFileInLocalStorage(destFileName, typeof(LocalizationHelper))) + { + foreach (var kv in texts) + { + writer.WriteLine($"\"{kv.Key}\",\"{kv.Value}\""); + } + } + } + + public static void ImportDictionary(string srcFileName, Dictionary texts) + { + using (var reader = MyAPIGateway.Utilities.ReadFileInLocalStorage(srcFileName, typeof(LocalizationHelper))) + { + var regexObj = new Regex(@"""[^""]*"); + string line; + while (!string.IsNullOrEmpty(line = reader.ReadLine())) + { + var matchResults = regexObj.Matches(line); + if (matchResults.Count == 2) + { + texts.Add(matchResults[0].Value.Replace("\"", ""), matchResults[1].Value.Replace("\"", "")); + } + } + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Logging.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Logging.cs new file mode 100644 index 00000000..a6c915f4 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Logging.cs @@ -0,0 +1,281 @@ +using System; +using System.Text; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRage.Utils; + +namespace SKONanobotBuildAndRepairSystem +{ + public class Logging + { + private readonly string _ModName; + private int _WorkshopId; + private readonly string _LogFilename; + private readonly Type _TypeOfMod; + + private System.IO.TextWriter _Writer = null; + private IMyHudNotification _Notify = null; + private int _Indent = 0; + private readonly StringBuilder _Cache = new StringBuilder(); + + [Flags] + public enum BlockNameOptions + { + None = 0x0000, + IncludeTypename = 0x0001 + } + + [Flags] + public enum Level + { + Error = 0x0001, + Event = 0x0002, + Info = 0x0004, + Verbose = 0x0008, + Special1 = 0x100000, + Communication = 0x1000, + All = 0xFFFF + } + + public static string BlockName(object block) + { + return BlockName(block, BlockNameOptions.IncludeTypename); + } + + public static string BlockName(object block, BlockNameOptions options) + { + 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 + { + return + $"{(slimBlock.CubeGrid != null ? slimBlock.CubeGrid.DisplayName : "Unknown Grid")}.{slimBlock.BlockDefinition.DisplayNameText}"; + } + } + + var terminalBlock = block as IMyTerminalBlock; + if (terminalBlock != null) + { + if ((options & BlockNameOptions.IncludeTypename) != 0) return + $"{(terminalBlock.CubeGrid != null ? terminalBlock.CubeGrid.DisplayName : "Unknown Grid")}.{terminalBlock.CustomName} [{terminalBlock.BlockDefinition.TypeIdString}]"; + return + $"{(terminalBlock.CubeGrid != null ? terminalBlock.CubeGrid.DisplayName : "Unknown Grid")}.{terminalBlock.CustomName}"; + } + + var cubeBlock = block as IMyCubeBlock; + if (cubeBlock != null) + { + return + $"{(cubeBlock.CubeGrid != null ? cubeBlock.CubeGrid.DisplayName : "Unknown Grid")} [{cubeBlock.BlockDefinition.TypeIdString}/{cubeBlock.BlockDefinition.SubtypeName}]"; + } + + var entity = block as IMyEntity; + if (entity != null) + { + if ((options & BlockNameOptions.IncludeTypename) != 0) return + $"{(string.IsNullOrEmpty(entity.DisplayName) ? entity.GetFriendlyName() : entity.DisplayName)} ({entity.EntityId}) [{entity.GetType().Name}]"; + return $"{entity.DisplayName} ({entity.EntityId})"; + } + + return block != null ? block.ToString() : "NULL"; + } + + public Level LogLevel { get; set; } + + public bool EnableHudNotification { get; set; } + + /// + /// + /// + public Logging(string modName, int workshopId, string logFileName, Type typeOfMod) + { + MyLog.Default.WriteLineAndConsole(_ModName + " Create Log instance Utils=" + (MyAPIGateway.Utilities != null).ToString()); + _ModName = modName; + _WorkshopId = workshopId; + _LogFilename = logFileName; + _TypeOfMod = typeOfMod; + } + + /// + /// Precheckl to avoid retriveing large amout of data, + /// that might be not needed afterwards + /// + /// + /// + public bool ShouldLog(Level level) + { + return (LogLevel & level) != 0; + } + + /// + /// + /// + public void IncreaseIndent(Level level) + { + if ((LogLevel & level) != 0) _Indent++; + } + + /// + /// + /// + public void DecreaseIndent(Level level) + { + if ((LogLevel & level) != 0) + if (_Indent > 0) _Indent--; + } + + /// + /// + /// + public void ResetIndent(Level level) + { + if ((LogLevel & level) != 0) _Indent = 0; + } + + /// + /// + /// + public void Error(Exception e) + { + Error(e.ToString()); + } + + /// + /// + /// + public void Error(string msg, params object[] args) + { + Error(string.Format(msg, args)); + } + + /// + /// + /// + private void Error(string msg) + { + if ((LogLevel & Level.Error) == 0) return; + + Write("ERROR: " + msg); + + try + { + MyLog.Default.WriteLineAndConsole(_ModName + " error: " + msg); + + var text = _ModName + " error - open %AppData%/SpaceEngineers/Storage/" + _LogFilename + " for details"; + + if (EnableHudNotification) + { + ShowOnHud(text); + } + } + catch (Exception e) + { + Write(string.Format("ERROR: Could not send notification to local client: " + e.ToString())); + } + } + + /// + /// + /// + public void Write(Level level, string msg, params Object[] args) + { + if ((LogLevel & level) == 0) return; + Write(string.Format(msg, args)); + } + + /// + /// + /// + public void Write(string msg, params Object[] args) + { + Write(string.Format(msg, args)); + } + + /// + /// + /// + private void Write(string msg) + { + try + { + lock (_Cache) + { + _Cache.Append(DateTime.Now.ToString("u") + ":"); + + for (var i = 0; i < _Indent; i++) + { + _Cache.Append(" "); + } + + _Cache.Append(msg).AppendLine(); + + if (_Writer == null && MyAPIGateway.Utilities != null) + { + _Writer = MyAPIGateway.Utilities.WriteFileInLocalStorage(_LogFilename, _TypeOfMod); + } + + if (_Writer != null) + { + _Writer.Write(_Cache); + _Writer.Flush(); + _Cache.Clear(); + } + } + } + catch (Exception e) + { + MyLog.Default.WriteLineAndConsole(_ModName + " Error while logging message='" + msg + "'\nLogger error: " + e.Message + "\n" + e.StackTrace); + } + } + + /// + /// + /// + /// + /// + public void ShowOnHud(string text, int displayms = 10000) + { + if (_Notify == null) + { + _Notify = MyAPIGateway.Utilities.CreateNotification(text, displayms, MyFontEnum.Red); + } + else + { + _Notify.Text = text; + _Notify.ResetAliveTime(); + } + + _Notify.Show(); + } + + /// + /// + /// + public void Close() + { + lock (_Cache) + { + if (_Writer != null) + { + _Writer.Flush(); + _Writer.Close(); + _Writer.Dispose(); + _Writer = null; + } + + _Indent = 0; + _Cache.Clear(); + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemBlock.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemBlock.cs new file mode 100644 index 00000000..9e64db06 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemBlock.cs @@ -0,0 +1,2990 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + using System.Threading; + using DefenseShields; + using Sandbox.Common.ObjectBuilders; + using Sandbox.Definitions; + using Sandbox.Game.Entities; + using Sandbox.Game.Lights; + using Sandbox.ModAPI; + using Sandbox.ModAPI.Ingame; + using VRage; + using VRage.Game; + using VRage.Game.Components; + using VRage.Game.ModAPI; + using VRage.ModAPI; + using VRage.ObjectBuilders; + using VRage.Utils; + using VRageMath; + using IMyShipWelder = Sandbox.ModAPI.IMyShipWelder; + using IMyTerminalBlock = Sandbox.ModAPI.IMyTerminalBlock; + using MyInventoryItem = VRage.Game.ModAPI.Ingame.MyInventoryItem; + + [MyEntityComponentDescriptor(typeof(MyObjectBuilder_ShipWelder), false, "SELtdLargeNanobotBuildAndRepairSystem", "SELtdSmallNanobotBuildAndRepairSystem")] + public class NanobotBuildAndRepairSystemBlock : MyGameLogicComponent + { + public static ShieldApi Shield; + + private enum WorkingState + { + Invalid = 0, NotReady = 1, Idle = 2, Welding = 3, NeedWelding = 4, MissingComponents = 5, Grinding = 6, NeedGrinding = 7, InventoryFull = 8, LimitsExceeded = 9 + } + + public const int WELDER_RANGE_DEFAULT_IN_M = 100; //*2 = AreaSize + public const int WELDER_RANGE_MAX_IN_M = 2000; + public const int WELDER_RANGE_MIN_IN_M = 2; + public const int WELDER_OFFSET_DEFAULT_IN_M = 0; + public const int WELDER_OFFSET_MAX_DEFAULT_IN_M = 150; + public const int WELDER_OFFSET_MAX_IN_M = 2000; + + public const float WELDING_GRINDING_MULTIPLIER_MIN = 0.001f; + public const float WELDING_GRINDING_MULTIPLIER_MAX = 1000f; + + public const float WELDER_REQUIRED_ELECTRIC_POWER_STANDBY_DEFAULT = 1.0f / 1000; // 1kW //20W, 0.02f + public const float WELDER_REQUIRED_ELECTRIC_POWER_WELDING_DEFAULT = 200.0f / 1000; // 200kW //2kW, 2.0f + public const float WELDER_REQUIRED_ELECTRIC_POWER_GRINDING_DEFAULT = 200.0f / 1000; // 200kW //1.5kW, 1.5f + public const float WELDER_REQUIRED_ELECTRIC_POWER_TRANSPORT_DEFAULT = 100.0f / 1000; // 100kW //10kW, 10.0f + public const float WELDER_TRANSPORTSPEED_METER_PER_SECOND_DEFAULT = 40f; // 20f + public const float WELDER_TRANSPORTVOLUME_DIVISOR = 10f; + public const float WELDER_TRANSPORTVOLUME_MAX_MULTIPLIER = 8f; + public const float WELDER_AMOUNT_PER_SECOND = 4f; // 2f + public const float WELDER_MAX_REPAIR_BONE_MOVEMENT_SPEED = 0.2f; + public const float GRINDER_AMOUNT_PER_SECOND = 8f; // 4f + public const float WELDER_SOUND_VOLUME = 2f; + + public static readonly int COLLECT_FLOATINGOBJECTS_SIMULTANEOUSLY = 50; + public static readonly MyDefinitionId ElectricityId = new MyDefinitionId(typeof(VRage.Game.ObjectBuilders.Definitions.MyObjectBuilder_GasProperties), "Electricity"); + private static readonly MyStringId RangeGridResourceId = MyStringId.GetOrCompute("WelderGrid"); + private static readonly Random _RandomDelay = new Random(); + + private static readonly MySoundPair[] _Sounds = new[] { null, null, null, new MySoundPair("ToolLrgWeldMetal"), new MySoundPair("BlockModuleProductivity"), new MySoundPair("BaRUnable"), new MySoundPair("ToolLrgGrindMetal"), new MySoundPair("BlockModuleProductivity"), new MySoundPair("BaRUnable"), new MySoundPair("BaRUnable") }; + private static readonly float[] _SoundLevels = new[] { 0f, 0f, 0f, 1f, 0.5f, 0.4f, 1f, 0.5f, 0.4f, 0.4f }; + + private const string PARTICLE_EFFECT_WELDING1 = MyParticleEffectsNameEnum.WelderContactPoint; + private const string PARTICLE_EFFECT_GRINDING1 = MyParticleEffectsNameEnum.ShipGrinder; + private const string PARTICLE_EFFECT_TRANSPORT1_PICK = "GrindNanobotTrace1"; + private const string PARTICLE_EFFECT_TRANSPORT1_DELIVER = "WeldNanobotTrace1"; + + private readonly Stopwatch _DelayWatch = new Stopwatch(); + private int _Delay = 0; + + private bool _AsyncUpdateSourcesAndTargetsRunning = false; + private readonly List _TempPossibleWeldTargets = new List(); + private readonly List _TempPossibleGrindTargets = new List(); + private readonly List _TempPossibleFloatingTargets = new List(); + private readonly List _TempPossibleSources = new List(); + private readonly HashSet _TempIgnore4Ingot = new HashSet(); + private readonly HashSet _TempIgnore4Items = new HashSet(); + private readonly HashSet _TempIgnore4Components = new HashSet(); + + private IMyShipWelder _Welder; + private IMyInventory _TransportInventory; + private bool _IsInit; + private readonly List _PossibleSources = new List(); + private readonly HashSet _Ignore4Ingot = new HashSet(); + private readonly HashSet _Ignore4Items = new HashSet(); + private readonly HashSet _Ignore4Components = new HashSet(); + private readonly Dictionary _TempMissingComponents = new Dictionary(); + private TimeSpan _LastFriendlyDamageCleanup; + + private static readonly int MaxTransportEffects = 50; + private static int _ActiveTransportEffects = 0; + private static readonly int MaxWorkingEffects = 80; + private static int _ActiveWorkingEffects = 0; + + private MyEntity3DSoundEmitter _SoundEmitter; + private MyEntity3DSoundEmitter _SoundEmitterWorking; + private Vector3D? _SoundEmitterWorkingPosition; + private MyParticleEffect _ParticleEffectWorking1; + private MyParticleEffect _ParticleEffectTransport1; + private bool _ParticleEffectTransport1Active; + private MyLight _LightEffect; + private MyFlareDefinition _LightEffectFlareWelding; + private MyFlareDefinition _LightEffectFlareGrinding; + private Vector3 _EmitterPosition; + + private TimeSpan _LastSourceUpdate = -NanobotBuildAndRepairSystemMod.Settings.SourcesUpdateInterval; + private TimeSpan _LastTargetsUpdate; + + private bool _CreativeModeActive; + private int _UpdateEffectsInterval; + private bool _UpdateCustomInfoNeeded; + private TimeSpan _UpdateCustomInfoLast; + private WorkingState _WorkingStateSet = WorkingState.Invalid; + private float _SoundVolumeSet; + private bool _TransportStateSet; + private float _MaxTransportVolume; + private WorkingState _WorkingState; + private int _ContinuouslyError; + private bool _PowerReady; + private bool _PowerWelding; + private bool _PowerGrinding; + private bool _PowerTransporting; + private TimeSpan _UpdatePowerSinkLast; + private TimeSpan _TryAutoPushInventoryLast; + private TimeSpan _TryPushInventoryLast; + private float CurrentPowerUsage = 0f; + + private SyncBlockSettings _Settings; + + internal SyncBlockSettings Settings + { + get + { + return _Settings ?? (_Settings = SyncBlockSettings.Load(this, NanobotBuildAndRepairSystemMod.ModGuid, BlockWeldPriority, BlockGrindPriority, ComponentCollectPriority)); + } + } + + private readonly NanobotBuildAndRepairSystemBlockPriorityHandling _BlockWeldPriority = new NanobotBuildAndRepairSystemBlockPriorityHandling(); + + internal NanobotBuildAndRepairSystemBlockPriorityHandling BlockWeldPriority + { + get + { + return _BlockWeldPriority; + } + } + + private readonly NanobotBuildAndRepairSystemBlockPriorityHandling _BlockGrindPriority = new NanobotBuildAndRepairSystemBlockPriorityHandling(); + + internal NanobotBuildAndRepairSystemBlockPriorityHandling BlockGrindPriority + { + get + { + return _BlockGrindPriority; + } + } + + private readonly NanobotBuildAndRepairSystemComponentPriorityHandling _ComponentCollectPriority = new NanobotBuildAndRepairSystemComponentPriorityHandling(); + + internal NanobotBuildAndRepairSystemComponentPriorityHandling ComponentCollectPriority + { + get + { + return _ComponentCollectPriority; + } + } + + public IMyShipWelder Welder { get { return _Welder; } } + + private readonly SyncBlockState _State = new SyncBlockState(); + public SyncBlockState State { get { return _State; } } + + /// + /// Currently friendly damaged blocks + /// + private Dictionary _FriendlyDamage; + + public Dictionary FriendlyDamage + { + get + { + return _FriendlyDamage ?? (_FriendlyDamage = new Dictionary()); + } + } + + /// + /// Initialize logical component + /// + /// + public override void Init(MyObjectBuilder_EntityBase objectBuilder) + { + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Initializing", Logging.BlockName(Entity, Logging.BlockNameOptions.None)); + + base.Init(objectBuilder); + NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME | MyEntityUpdateEnum.EACH_100TH_FRAME; + + if (Entity.GameLogic is MyCompositeGameLogicComponent) + { + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock: Init Entiy.Logic remove other mods from this entity"); + Entity.GameLogic = this; + } + + _Welder = Entity as IMyShipWelder; + _Welder.AppendingCustomInfo += AppendingCustomInfo; + + _WorkingState = WorkingState.NotReady; + + if (Settings == null) //Force load of settings (is much faster here than initial load in UpdateBeforeSimulation10_100) + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemBlock {0}: Initializing Load-Settings failed", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + }; + + if (Shield == null) + { + Shield = new ShieldApi(); + Shield.Load(); + } + + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Initialized", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + } + + /// + /// + /// + public void SettingsChanged() + { + if (NanobotBuildAndRepairSystemMod.SettingsValid) + { + //Check limits as soon but not sooner as the 'server' settings has been received, otherwise we might use the wrong limits + Settings.CheckLimits(this, false); + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedEffects & VisualAndSoundEffects.WeldingSoundEffect) == 0) _Sounds[(int)WorkingState.Welding] = null; + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedEffects & VisualAndSoundEffects.GrindingSoundEffect) == 0) _Sounds[(int)WorkingState.Grinding] = null; + } + + var resourceSink = _Welder.Components.Get(); + if (resourceSink != null) + { + var electricPowerTransport = Settings.MaximumRequiredElectricPowerTransport; + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & SearchModes.BoundingBox) == 0) electricPowerTransport /= 10; + var maxPowerWorking = Math.Max(Settings.MaximumRequiredElectricPowerWelding, Settings.MaximumRequiredElectricPowerGrinding); + resourceSink.SetMaxRequiredInputByType(ElectricityId, maxPowerWorking + electricPowerTransport + Settings.MaximumRequiredElectricPowerStandby); + resourceSink.SetRequiredInputFuncByType(ElectricityId, ComputeRequiredElectricPower); + resourceSink.Update(); + } + + var maxMultiplier = Math.Max(NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier, NanobotBuildAndRepairSystemMod.Settings.Welder.GrindingMultiplier); + if (maxMultiplier > 10) NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME | MyEntityUpdateEnum.EACH_10TH_FRAME; + else NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME | MyEntityUpdateEnum.EACH_100TH_FRAME; + + var multiplier = maxMultiplier > WELDER_TRANSPORTVOLUME_MAX_MULTIPLIER ? WELDER_TRANSPORTVOLUME_MAX_MULTIPLIER : maxMultiplier; + _MaxTransportVolume = (float)_TransportInventory.MaxVolume * multiplier / WELDER_TRANSPORTVOLUME_DIVISOR; + + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Init Inventory Volume {1}/{2} MaxTransportVolume={3} Mode={4}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), (float)_Welder.GetInventory(0).MaxVolume, _TransportInventory.MaxVolume, _MaxTransportVolume, Settings.SearchMode); + } + + /// + /// + /// + private void Init() + { + if (_IsInit) return; + if (_Welder.SlimBlock.IsProjected() || !_Welder.Synchronized) //Synchronized = !IsPreview + { + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Init Block is only projected/preview -> exit", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + NeedsUpdate = MyEntityUpdateEnum.NONE; + return; + } + + lock (NanobotBuildAndRepairSystemMod.BuildAndRepairSystems) + { + if (!NanobotBuildAndRepairSystemMod.BuildAndRepairSystems.ContainsKey(Entity.EntityId)) + { + NanobotBuildAndRepairSystemMod.BuildAndRepairSystems.Add(Entity.EntityId, this); + } + } + NanobotBuildAndRepairSystemMod.InitControls(); + + _Welder.EnabledChanged += (block) => { this.UpdateCustomInfo(true); }; + _Welder.IsWorkingChanged += (block) => { this.UpdateCustomInfo(true); }; + + var welderInventory = _Welder.GetInventory(0); + if (welderInventory == null) return; + _TransportInventory = new Sandbox.Game.MyInventory((float)welderInventory.MaxVolume / MyAPIGateway.Session.BlocksInventorySizeMultiplier, Vector3.MaxValue, MyInventoryFlags.CanSend); + //_Welder.Components.Add((Sandbox.Game.MyInventory)_TransportInventory); Won't work as the gui only could handle one inventory + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Init Block TransportInventory Added to welder MaxVolume {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), (float)welderInventory.MaxVolume / MyAPIGateway.Session.BlocksInventorySizeMultiplier); + SettingsChanged(); + + var dummies = new Dictionary(); + _Welder.Model.GetDummies(dummies); + foreach (var dummy in dummies) + { + if (dummy.Key.ToLower().Contains("detector_emitter")) + { + _EmitterPosition = dummy.Value.Matrix.Translation; + break; + } + } + + NanobotBuildAndRepairSystemMod.SyncBlockDataRequestSend(this); + UpdateCustomInfo(true); + _TryPushInventoryLast = MyAPIGateway.Session.ElapsedPlayTime.Add(TimeSpan.FromSeconds(10)); + _TryAutoPushInventoryLast = _TryPushInventoryLast; + _WorkingStateSet = WorkingState.Invalid; + _SoundVolumeSet = -1; + _IsInit = true; + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Init -> done", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + } + + /// + /// + /// + /// + private float ComputeRequiredElectricPower() + { + if (_Welder == null) return 0f; + var required = 0f; + + required += Settings.MaximumRequiredElectricPowerStandby; + required += State.Welding ? Settings.MaximumRequiredElectricPowerWelding : 0f; + required += State.Grinding ? Settings.MaximumRequiredElectricPowerGrinding : 0f; + required += State.Transporting ? Settings.SearchMode == SearchModes.Grids ? Settings.MaximumRequiredElectricPowerTransport / 10 : Settings.MaximumRequiredElectricPowerTransport : 0f; + + return required; + } + + /// + /// + /// + /// + /// + private bool HasRequiredElectricPower(bool weld, bool grind, bool transport) + { + if (_Welder == null) return false; + + //var required = Settings.MaximumRequiredElectricPowerStandby; + //required += weld ? Settings.MaximumRequiredElectricPowerWelding : 0f; + //required += grind ? Settings.MaximumRequiredElectricPowerGrinding : 0f; + //required += transport ? (Settings.SearchMode == SearchModes.Grids ? Settings.MaximumRequiredElectricPowerTransport / 10 : Settings.MaximumRequiredElectricPowerTransport) : 0f; + + //var resourceSink = _Welder.Components.Get(); + //var resourceSink2 = _Welder.CubeGrid.Components.Get(); + + //if (resourceSink != null) + //{ + // resourceSink.SetRequiredInputByType(ElectricityId, required); + //} + + return _Welder.Enabled && _Welder.IsFunctional && _Welder.IsWorking; + } + + /// + /// + /// + public override void Close() + { + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Close", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + if (_IsInit) + { + ServerEmptyTranportInventory(true); + Settings.Save(Entity, NanobotBuildAndRepairSystemMod.ModGuid); + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Close Saved Settings {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Settings.GetAsXML()); + lock (NanobotBuildAndRepairSystemMod.BuildAndRepairSystems) + { + NanobotBuildAndRepairSystemMod.BuildAndRepairSystems.Remove(Entity.EntityId); + } + + //Stop effects + State.CurrentTransportTarget = null; + State.Ready = false; + UpdateEffects(); + } + base.Close(); + } + + /// + /// + /// + public override void UpdateBeforeSimulation() + { + try + { + base.UpdateBeforeSimulation(); + + if (_Welder == null || !_IsInit) return; + + if (!MyAPIGateway.Utilities.IsDedicated) + { + if ((Settings.Flags & SyncBlockSettings.Settings.ShowArea) != 0) + { + var colorWelder = _Welder.SlimBlock.GetColorMask().HSVtoColor(); + var color = Color.FromNonPremultiplied(colorWelder.R, colorWelder.G, colorWelder.B, 255); + var areaBoundingBox = Settings.CorrectedAreaBoundingBox; + var emitterMatrix = _Welder.WorldMatrix; + emitterMatrix.Translation = Vector3D.Transform(Settings.CorrectedAreaOffset, emitterMatrix); + MySimpleObjectDraw.DrawTransparentBox(ref emitterMatrix, ref areaBoundingBox, ref color, MySimpleObjectRasterizer.Solid, 1, 0.04f, RangeGridResourceId, null, false); + } + + _UpdateEffectsInterval = ++_UpdateEffectsInterval % 2; + if (_UpdateEffectsInterval == 0) UpdateEffects(); + } + } + catch (Exception ex) + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemBlock {0}: UpdateBeforeSimulation Exception:{1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), ex); + } + } + + public override void UpdateBeforeSimulation10() + { + base.UpdateBeforeSimulation10(); + UpdateBeforeSimulation10_100(true); + } + + public override void UpdateBeforeSimulation100() + { + base.UpdateBeforeSimulation100(); + UpdateBeforeSimulation10_100(false); + } + + /// + /// + /// + public override void UpdatingStopped() + { + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: UpdatingStopped", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + if (_IsInit) + { + Settings.Save(Entity, NanobotBuildAndRepairSystemMod.ModGuid); + } + //Stop sound effects + StopSoundEffects(); + _WorkingStateSet = WorkingState.Invalid; + base.UpdatingStopped(); + } + + private void UpdateBeforeSimulation10_100(bool fast) + { + try + { + if (_Welder == null) return; + if (!_IsInit) Init(); + if (!_IsInit) return; + + if (_Delay > 0) + { + _Delay--; + return; + } + + _DelayWatch.Restart(); + if (MyAPIGateway.Session.IsServer) + { + _CreativeModeActive = MyAPIGateway.Session.CreativeMode; + if (!fast) + { + CleanupFriendlyDamage(); + } + + // Check if other BaR is powered in grid group + if (_Welder.Enabled && IsOtherBaRPoweredInGridGroup()) + { + _Welder.Enabled = false; + NotifyPlayersInRange( + "Only one Build And Repair System can be powered per grid group", + _Welder.GetPosition(), + 1000, + "Red" + ); + return; + } + + ServerTryWeldingGrindingCollecting(); + + if (!fast) + { + if ((State.Ready != _PowerReady || State.Welding != _PowerWelding || State.Grinding != _PowerGrinding || State.Transporting != _PowerTransporting) && + MyAPIGateway.Session.ElapsedPlayTime.Subtract(_UpdatePowerSinkLast).TotalSeconds > 3) + { + _UpdatePowerSinkLast = MyAPIGateway.Session.ElapsedPlayTime; + _PowerReady = State.Ready; + _PowerWelding = State.Welding; + _PowerGrinding = State.Grinding; + _PowerTransporting = State.Transporting; + + var resourceSink = _Welder.Components.Get(); + resourceSink?.Update(); + } + + Settings.TrySave(Entity, NanobotBuildAndRepairSystemMod.ModGuid); + if (State.IsTransmitNeeded()) + { + NanobotBuildAndRepairSystemMod.SyncBlockStateSend(0, this); + } + } + } + else + { + if (State.Changed) + { + UpdateCustomInfo(true); + State.ResetChanged(); + } + } + if (Settings.IsTransmitNeeded()) + { + NanobotBuildAndRepairSystemMod.SyncBlockSettingsSend(0, this); + } + if (_UpdateCustomInfoNeeded) UpdateCustomInfo(false); + + _DelayWatch.Stop(); + if (_DelayWatch.ElapsedMilliseconds > 40) + { + _Delay = _RandomDelay.Next(1, 20); //Slowdown a little bit + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: Delay {1} ({2}ms)", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _Delay, _DelayWatch.ElapsedMilliseconds); + } + } + catch (Exception ex) + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemBlock {0}: UpdateBeforeSimulation10/100 Exception:{1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), ex); + } + } + + private bool IsOtherBaRPoweredInGridGroup() + { + var grid = _Welder.CubeGrid; + var groupedGrids = new HashSet(); + MyAPIGateway.GridGroups.GetGroup(grid, GridLinkTypeEnum.Physical, groupedGrids); + + foreach (var connectedGrid in groupedGrids) + { + var blocks = new List(); + connectedGrid.GetBlocks(blocks); + + foreach (var block in blocks) + { + var bar = block.FatBlock as IMyShipWelder; + if (bar != null && bar.BlockDefinition.SubtypeName.Contains("NanobotBuildAndRepairSystem")) + { + var otherSystem = bar.GameLogic.GetAs(); + if (otherSystem != null && otherSystem != this && otherSystem.Welder.IsWorking) + { + return true; + } + } + } + } + + return false; + } + + private static void NotifyPlayersInRange(string text, Vector3D position, double radius, string font = "White") + { + BoundingSphereD bound = new BoundingSphereD(position, radius); + List nearbyEntities = MyAPIGateway.Entities.GetEntitiesInSphere(ref bound); + + foreach (var entity in nearbyEntities) + { + IMyCharacter character = entity as IMyCharacter; + if (character != null && character.IsPlayer) + { + var notification = MyAPIGateway.Utilities.CreateNotification(text, 2000, font); + notification.Show(); + } + } + } + + /// + /// Try to weld/grind/collect the possible targets + /// + private void ServerTryWeldingGrindingCollecting() + { + var inventoryFull = State.InventoryFull; + var limitsExceeded = State.LimitsExceeded; + var welding = false; + var needwelding = false; + var grinding = false; + var needgrinding = false; + var collecting = false; + var needcollecting = false; + var transporting = false; + var ready = _Welder.Enabled && _Welder.IsWorking && _Welder.IsFunctional; + IMySlimBlock currentWeldingBlock = null; + IMySlimBlock currentGrindingBlock = null; + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + if (ready) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryWeldingGrindingCollecting Welder ready: Enabled={1}, IsWorking={2}, IsFunctional={3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _Welder.Enabled, _Welder.IsWorking, _Welder.IsFunctional); + + ServerTryPushInventory(); + transporting = IsTransportRunnning(playTime); + if (transporting && State.CurrentTransportIsPick) needgrinding = true; + if ((Settings.Flags & SyncBlockSettings.Settings.ComponentCollectIfIdle) == 0 && !transporting) ServerTryCollectingFloatingTargets(out collecting, out needcollecting, out transporting); + if (!transporting) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryWeldingGrindingCollecting TryWeldGrind: Mode {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Settings.WorkMode); + State.MissingComponents.Clear(); + State.LimitsExceeded = false; + + switch (Settings.WorkMode) + { + case WorkModes.WeldBeforeGrind: + ServerTryWelding(out welding, out needwelding, out transporting, out currentWeldingBlock); + if (State.PossibleWeldTargets.CurrentCount == 0 || ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 && Settings.CurrentPickedGrindingBlock != null)) + { + ServerTryGrinding(out grinding, out needgrinding, out transporting, out currentGrindingBlock); + } + break; + + case WorkModes.GrindBeforeWeld: + ServerTryGrinding(out grinding, out needgrinding, out transporting, out currentGrindingBlock); + if (State.PossibleGrindTargets.CurrentCount == 0 || ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 && Settings.CurrentPickedWeldingBlock != null)) + { + ServerTryWelding(out welding, out needwelding, out transporting, out currentWeldingBlock); + } + break; + + case WorkModes.GrindIfWeldGetStuck: + ServerTryWelding(out welding, out needwelding, out transporting, out currentWeldingBlock); + if (!(welding || transporting) || ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 && Settings.CurrentPickedGrindingBlock != null)) + { + ServerTryGrinding(out grinding, out needgrinding, out transporting, out currentGrindingBlock); + } + break; + + case WorkModes.WeldOnly: + ServerTryWelding(out welding, out needwelding, out transporting, out currentWeldingBlock); + break; + + case WorkModes.GrindOnly: + ServerTryGrinding(out grinding, out needgrinding, out transporting, out currentGrindingBlock); + break; + } + State.MissingComponents.RebuildHash(); + } + if ((Settings.Flags & SyncBlockSettings.Settings.ComponentCollectIfIdle) != 0 && !transporting && !welding && !grinding) ServerTryCollectingFloatingTargets(out collecting, out needcollecting, out transporting); + } + else + { + transporting = IsTransportRunnning(playTime); //Finish running transport + State.MissingComponents.Clear(); + State.MissingComponents.RebuildHash(); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: TryWelding Welder not ready: Enabled={1}, IsWorking={2}, IsFunctional={3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _Welder.Enabled || _CreativeModeActive, _Welder.IsWorking, _Welder.IsFunctional); + } + + if (!(welding || grinding || collecting || transporting) && _TransportInventory.CurrentVolume > 0) + { + //Idle but not empty -> empty inventory + if (State.LastTransportTarget.HasValue) + { + State.CurrentTransportIsPick = true; + State.CurrentTransportTarget = State.LastTransportTarget; + State.CurrentTransportStartTime = playTime; + //State.TransportTime same (way back) + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: Idle but not empty started transporttime={1} CurrentVolume={2}/{3}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), State.CurrentTransportTime, _TransportInventory.CurrentVolume, _MaxTransportVolume); + transporting = true; + } + ServerEmptyTranportInventory(true); + } + + if ((State.Welding && !welding) || (State.Grinding && !(grinding || collecting))) + { + StartAsyncUpdateSourcesAndTargets(false); //Scan immediately once for new targets + } + + var readyChanged = State.Ready != ready; + State.Ready = ready; + State.Welding = welding; + State.NeedWelding = needwelding; + State.CurrentWeldingBlock = currentWeldingBlock; + + State.Grinding = grinding; + State.NeedGrinding = needgrinding; + State.CurrentGrindingBlock = currentGrindingBlock; + + State.Transporting = transporting; + + var inventoryFullChanged = State.InventoryFull != inventoryFull; + var limitsExceededChanged = State.LimitsExceeded != limitsExceeded; + + var missingComponentsChanged = State.MissingComponents.LastHash != State.MissingComponents.CurrentHash; + State.MissingComponents.LastHash = State.MissingComponents.CurrentHash; + + var possibleWeldTargetsChanged = State.PossibleWeldTargets.LastHash != State.PossibleWeldTargets.CurrentHash; + State.PossibleWeldTargets.LastHash = State.PossibleWeldTargets.CurrentHash; + + var possibleGrindTargetsChanged = State.PossibleGrindTargets.LastHash != State.PossibleGrindTargets.CurrentHash; + State.PossibleGrindTargets.LastHash = State.PossibleGrindTargets.CurrentHash; + + var possibleFloatingTargetsChanged = State.PossibleFloatingTargets.LastHash != State.PossibleFloatingTargets.CurrentHash; + State.PossibleFloatingTargets.LastHash = State.PossibleFloatingTargets.CurrentHash; + + if (missingComponentsChanged || possibleWeldTargetsChanged || possibleGrindTargetsChanged || possibleFloatingTargetsChanged) State.HasChanged(); + + if (missingComponentsChanged && Mod.Log.ShouldLog(Logging.Level.Verbose)) + { + lock (Mod.Log) + { + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: TryWelding: MissingComponents --->", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + Mod.Log.IncreaseIndent(Logging.Level.Verbose); + foreach (var missing in State.MissingComponents) + { + Mod.Log.Write(Logging.Level.Verbose, "{0}:{1}", missing.Key.SubtypeName, missing.Value); + } + Mod.Log.DecreaseIndent(Logging.Level.Verbose); + Mod.Log.Write(Logging.Level.Verbose, "<--- MissingComponents"); + } + } + + UpdateCustomInfo(missingComponentsChanged || possibleWeldTargetsChanged || possibleGrindTargetsChanged || possibleFloatingTargetsChanged || readyChanged || inventoryFullChanged || limitsExceededChanged); + } + + /// + /// Push ore/ingot out of the welder + /// + private void ServerTryPushInventory() + { + if ((Settings.Flags & (SyncBlockSettings.Settings.PushIngotOreImmediately | SyncBlockSettings.Settings.PushComponentImmediately | SyncBlockSettings.Settings.PushItemsImmediately)) == 0) return; + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(_TryAutoPushInventoryLast).TotalSeconds <= 5) return; + + var welderInventory = _Welder.GetInventory(0); + if (welderInventory != null) + { + if (welderInventory.Empty()) return; + var lastPush = MyAPIGateway.Session.ElapsedPlayTime; + + var tempInventoryItems = new List(); + welderInventory.GetItems(tempInventoryItems); + for (var srcItemIndex = tempInventoryItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var srcItem = tempInventoryItems[srcItemIndex]; + if (srcItem.Type.TypeId == typeof(MyObjectBuilder_Ore).Name || srcItem.Type.TypeId == typeof(MyObjectBuilder_Ingot).Name) + { + if ((Settings.Flags & SyncBlockSettings.Settings.PushIngotOreImmediately) != 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerTryPushInventory TryPush IngotOre: Item={1} Amount={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), srcItem.ToString(), srcItem.Amount); + welderInventory.PushComponents(_PossibleSources, (IMyInventory destInventory, IMyInventory srcInventory, ref MyInventoryItem srcItemIn) => { return _Ignore4Ingot.Contains(destInventory); }, srcItemIndex, srcItem); + _TryAutoPushInventoryLast = lastPush; + } + } + else if (srcItem.Type.TypeId == typeof(MyObjectBuilder_Component).Name) + { + if ((Settings.Flags & SyncBlockSettings.Settings.PushComponentImmediately) != 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerTryPushInventory TryPush Component: Item={1} Amount={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), srcItem.ToString(), srcItem.Amount); + welderInventory.PushComponents(_PossibleSources, (IMyInventory destInventory, IMyInventory srcInventory, ref MyInventoryItem srcItemIn) => { return _Ignore4Components.Contains(destInventory); }, srcItemIndex, srcItem); + _TryAutoPushInventoryLast = lastPush; + } + } + else + { + //Any kind of items (Tools, Weapons, Ammo, Bottles, ..) + if ((Settings.Flags & SyncBlockSettings.Settings.PushItemsImmediately) != 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerTryPushInventory TryPush Items: Item={1} Amount={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), srcItem.ToString(), srcItem.Amount); + welderInventory.PushComponents(_PossibleSources, (IMyInventory destInventory, IMyInventory srcInventory, ref MyInventoryItem srcItemIn) => { return _Ignore4Items.Contains(destInventory); }, srcItemIndex, srcItem); + _TryAutoPushInventoryLast = lastPush; + } + } + } + tempInventoryItems.Clear(); + } + } + + /// + /// + /// + /// + /// + /// + private void ServerTryCollectingFloatingTargets(out bool collecting, out bool needcollecting, out bool transporting) + { + collecting = false; + needcollecting = false; + transporting = false; + if (!HasRequiredElectricPower(false, false, true)) + { + Deb.Write("BuildAndRepairSystemBlock: ServerTryCollectingFloatingTargets: Not enough power"); + return; //-> Not enough power + } + + lock (State.PossibleFloatingTargets) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryCollectingFloatingTargets PossibleFloatingTargets={1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), State.PossibleFloatingTargets.CurrentCount); + TargetEntityData collectingFirstTarget = null; + var collectingCount = 0; + foreach (var targetData in State.PossibleFloatingTargets) + { + if (targetData.Entity != null && !targetData.Ignore) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerTryCollectingFloatingTargets: {1} distance={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Entity), targetData.Distance); + needcollecting = true; + var added = ServerDoCollectFloating(targetData, out transporting, ref collectingFirstTarget); + if (targetData.Ignore) State.PossibleFloatingTargets.ChangeHash(); + collecting |= added; + if (added) collectingCount++; + if (transporting || collectingCount >= COLLECT_FLOATINGOBJECTS_SIMULTANEOUSLY) + { + break; //Max Inventorysize reached or max simultaneously floating object reached + } + } + } + if (collecting && !transporting) ServerDoCollectFloating(null, out transporting, ref collectingFirstTarget); //Starttransport if pending + } + } + + public bool IsWelderShielded() + { + try + { + if (Welder != null && Shield != null && Shield.IsReady) + { + if (Shield.ProtectedByShield(Welder.CubeGrid)) + { + return Shield.IsBlockProtected(Welder.SlimBlock); + } + } + } + catch + { + } + + return false; + } + + public bool IsShieldProtected(IMySlimBlock block) + { + try + { + if (block != null && Shield != null && Shield.IsReady) + { + var isProtected = Shield.ProtectedByShield(block.CubeGrid); + + if (!isProtected) + { + return false; + } + else + { + // If the block is on the same grid, then we are allowed to grind it when shielded. + if (block?.CubeGrid.EntityId == this.Welder.CubeGrid.EntityId) + { + return false; + } + + // Otherwise check if it is allowed. + return Shield.IsBlockProtected(block); + } + } + } + catch { } + return false; + } + + private void ServerTryGrinding(out bool grinding, out bool needgrinding, out bool transporting, out IMySlimBlock currentGrindingBlock) + { + grinding = false; + needgrinding = false; + transporting = false; + currentGrindingBlock = null; + + if (!HasRequiredElectricPower(false, true, true)) return; //No power -> nothing to do + + lock (State.PossibleGrindTargets) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryGrinding PossibleGrindTargets={1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), State.PossibleGrindTargets.CurrentCount); + + MyCubeGrid cubeGrid = null; + + foreach (var targetData in State.PossibleGrindTargets) + { + if (cubeGrid == null) + { + cubeGrid = targetData.Block.CubeGrid as MyCubeGrid; + if (cubeGrid != null && !cubeGrid.IsPowered && !cubeGrid.IsStatic) + { + cubeGrid.Physics.ClearSpeed(); + } + } + + if ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 && targetData.Block != Settings.CurrentPickedGrindingBlock) continue; + + if (!targetData.Block.IsDestroyed) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryGrinding: {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Block)); + needgrinding = true; + grinding = ServerDoGrind(targetData, out transporting); + if (grinding) + { + currentGrindingBlock = targetData.Block; + break; //Only grind one block at once + } + } + } + } + + // Damage reputation with grinding Faction. + if (currentGrindingBlock != null) + { + var ownerId = UtilsPlayer.GetOwner(currentGrindingBlock.CubeGrid as MyCubeGrid); + if (ownerId > 0) + { + UtilsFaction.DamageReputationWithPlayerFaction(this.Welder.OwnerId, ownerId); + } + } + } + + /// + /// + /// + private void ServerTryWelding(out bool welding, out bool needwelding, out bool transporting, out IMySlimBlock currentWeldingBlock) + { + welding = false; + needwelding = false; + transporting = false; + currentWeldingBlock = null; + var power4WeldingAndTransporting = HasRequiredElectricPower(true, false, true); + var power4Welding = power4WeldingAndTransporting ? true : HasRequiredElectricPower(true, false, false); + + if (!power4Welding && !power4WeldingAndTransporting) return; //No power -> nothing to do + + lock (State.PossibleWeldTargets) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryWelding PossibleWeldTargets={1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), State.PossibleWeldTargets.CurrentCount); + foreach (var targetData in State.PossibleWeldTargets) + { + if ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 && + targetData.Block != Settings.CurrentPickedWeldingBlock) + { + continue; + } + + if ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0 || (!targetData.Ignore && Weldable(targetData))) + { + needwelding = true; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerTryWelding: {1} HasDeformation={2} (MaxDeformation={3}), IsFullIntegrity={4}, HasFatBlock={5}, IsProjected={6}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Block), targetData.Block.HasDeformation, targetData.Block.MaxDeformation, targetData.Block.IsFullIntegrity, targetData.Block.FatBlock != null, targetData.Block.IsProjected()); + + if (power4WeldingAndTransporting && !transporting) //Transport needs to be weld afterwards + { + transporting = ServerFindMissingComponents(targetData); + } + + welding = ServerDoWeld(targetData); + ServerEmptyTranportInventory(false); + if (targetData.Ignore) State.PossibleWeldTargets.ChangeHash(); + + if (welding) + { + currentWeldingBlock = targetData.Block; + break; //Only weld one block at once (do not split over all blocks as the base shipwelder does) + } + } + } + } + } + + /// + /// + /// + /// + /// + private bool Weldable(TargetBlockData targetData) + { + var target = targetData.Block; + if ((targetData.Attributes & TargetBlockData.AttributeFlags.Projected) != 0) + { + if (target.CanBuild(true)) return true; + //Is the block already created (maybe by user or an other BaR block) -> + //After creation we can't welding this projected block, we have to find the 'physical' block instead. + var cubeGridProjected = target.CubeGrid as MyCubeGrid; + if (cubeGridProjected?.Projector != null) + { + var cubeGrid = cubeGridProjected.Projector.CubeGrid; + var blockPos = cubeGrid.WorldToGridInteger(cubeGridProjected.GridIntegerToWorld(target.Position)); + target = cubeGrid.GetCubeBlock(blockPos); + if (target != null) + { + targetData.Block = target; + targetData.Attributes &= ~TargetBlockData.AttributeFlags.Projected; + return Weldable(targetData); + } + } + + targetData.Ignore = true; + return false; + } + + var weld = target.NeedRepair((Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) != 0) && !IsFriendlyDamage(target); + targetData.Ignore = !weld; + return weld; + } + + /// + /// + /// + /// + /// + private bool IsTransportRunnning(TimeSpan playTime) + { + if (State.CurrentTransportStartTime > TimeSpan.Zero) + { + //Transport started + if (State.CurrentTransportIsPick) + { + if (!ServerEmptyTranportInventory(true)) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: IsTransportRunnning transport still running transport inventory not emtpy", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + return true; + } + } + + if (playTime.Subtract(State.CurrentTransportStartTime) < State.CurrentTransportTime) + { + //Last transport still running -> wait + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: IsTransportRunnning: transport still running remaining transporttime={1}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), State.CurrentTransportTime.Subtract(MyAPIGateway.Session.ElapsedPlayTime.Subtract(State.CurrentTransportStartTime))); + return true; + } + State.CurrentTransportStartTime = TimeSpan.Zero; + State.LastTransportTarget = State.CurrentTransportTarget; + State.CurrentTransportTarget = null; + } + else State.CurrentTransportTarget = null; + return false; + } + + /// + /// + /// + private void UpdateCustomInfo(bool changed) + { + _UpdateCustomInfoNeeded |= changed; + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + if (_UpdateCustomInfoNeeded && playTime.Subtract(_UpdateCustomInfoLast).TotalSeconds >= 2) + { + _Welder.RefreshCustomInfo(); + TriggerTerminalRefresh(); + _UpdateCustomInfoLast = playTime; + _UpdateCustomInfoNeeded = false; + } + } + + /// + /// + /// + public void TriggerTerminalRefresh() + { + //Workaround as long as RaisePropertiesChanged is not public + if (_Welder != null && MyAPIGateway.Gui.InteractedEntity == _Welder) + { + var action = _Welder.GetActionWithName("helpOthers"); + if (action != null) + { + action.Apply(_Welder); + action.Apply(_Welder); + } + } + } + + /// + /// + /// + private bool ServerDoWeld(TargetBlockData targetData) + { + var welderInventory = _Welder.GetInventory(0); + var welding = false; + var created = false; + var target = targetData.Block; + var hasIgnoreColor = (Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) != 0 && IsColorNearlyEquals(Settings.IgnoreColorPacked, target.GetColorMask()); + + if ((targetData.Attributes & TargetBlockData.AttributeFlags.Projected) != 0) + { + //New Block (Projected) + var cubeGridProjected = target.CubeGrid as MyCubeGrid; + var blockDefinition = target.BlockDefinition as MyCubeBlockDefinition; + var item = _TransportInventory.FindItem(blockDefinition.Components[0].Definition.Id); + + if ((_CreativeModeActive || (item != null && item.Amount >= 1)) && cubeGridProjected?.Projector != null) + { + if (_Welder.IsWithinWorldLimits(cubeGridProjected.Projector, blockDefinition.BlockPairName, blockDefinition.PCU)) + { + var blockBuildIntegrity = target.BuildIntegrity; + + if (!cubeGridProjected.Projector.Closed && !cubeGridProjected.Projector.CubeGrid.Closed && (target.FatBlock == null || !target.FatBlock.Closed || !target.FatBlock.IsVisible())) + { + ((Sandbox.ModAPI.IMyProjector)cubeGridProjected.Projector).Build(target, _Welder.OwnerId, _Welder.EntityId, true, _Welder.SlimBlock.BuiltBy); + } + + //After creation we can't welding this projected block, we have to find the 'physical' block instead. + var cubeGrid = cubeGridProjected.Projector.CubeGrid; + var blockPos = cubeGrid.WorldToGridInteger(cubeGridProjected.GridIntegerToWorld(target.Position)); + target = cubeGrid.GetCubeBlock(blockPos); + + if (target != null) targetData.Block = target; + targetData.Attributes &= ~TargetBlockData.AttributeFlags.Projected; + created = true; + + if (!_CreativeModeActive) + { + var newIntegrity = target?.BuildIntegrity; + if (newIntegrity > blockBuildIntegrity) + { + _TransportInventory.RemoveItems(item.ItemId, 1); + } + } + + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoWeld (new): {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target)); + } + else + { + State.LimitsExceeded = true; + targetData.Ignore = true; + } + } + } + + if (!hasIgnoreColor && target != null && (targetData.Attributes & TargetBlockData.AttributeFlags.Projected) == 0) + { + //No ignore color and allready created + if (!target.IsFullIntegrity || created) + { + //Move collected/needed items to stockpile. + target.MoveItemsToConstructionStockpile(_TransportInventory); + + //Incomplete + welding = target.CanContinueBuild(_TransportInventory) || _CreativeModeActive; + + if (welding) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoWeld (incomplete): {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target)); + + target.IncreaseMountLevel(MyAPIGateway.Session.WelderSpeedMultiplier * NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier * WELDER_AMOUNT_PER_SECOND, _Welder.OwnerId, welderInventory, MyAPIGateway.Session.WelderSpeedMultiplier * NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier * WELDER_MAX_REPAIR_BONE_MOVEMENT_SPEED, _Welder.HelpOthers); + } + + if (target.IsFullIntegrity || ((Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) != 0 && target.Integrity >= target.MaxIntegrity * ((MyCubeBlockDefinition)target.BlockDefinition).CriticalIntegrityRatio)) + { + targetData.Ignore = true; + } + } + else + { + //Deformation + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoWeld (deformed): {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target)); + welding = true; + target.IncreaseMountLevel(MyAPIGateway.Session.WelderSpeedMultiplier * NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier * WELDER_AMOUNT_PER_SECOND, _Welder.OwnerId, welderInventory, MyAPIGateway.Session.WelderSpeedMultiplier * NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier * WELDER_MAX_REPAIR_BONE_MOVEMENT_SPEED, _Welder.HelpOthers); + } + } + return welding || created; + } + + /// + /// + /// + private bool ServerDoGrind(TargetBlockData targetData, out bool transporting) + { + var target = targetData.Block; + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + transporting = IsTransportRunnning(playTime); + if (transporting) return false; + + var welderInventory = _Welder.GetInventory(0); + var targetGrid = target.CubeGrid; + + if (targetGrid.Physics == null || !targetGrid.Physics.Enabled) return false; + + var criticalIntegrityRatio = ((MyCubeBlockDefinition)target.BlockDefinition).CriticalIntegrityRatio; + var ownershipIntegrityRatio = ((MyCubeBlockDefinition)target.BlockDefinition).OwnershipIntegrityRatio > 0 ? ((MyCubeBlockDefinition)target.BlockDefinition).OwnershipIntegrityRatio : criticalIntegrityRatio; + var integrityRatio = target.Integrity / target.MaxIntegrity; + + if ((targetData.Attributes & TargetBlockData.AttributeFlags.Autogrind) != 0) + { + if ((Settings.GrindJanitorOptions & AutoGrindOptions.DisableOnly) != 0 && target.FatBlock != null && integrityRatio < criticalIntegrityRatio) + { + //Block allready out of order -> stop grinding and switch to next + return false; + } + if ((Settings.GrindJanitorOptions & AutoGrindOptions.HackOnly) != 0 && target.FatBlock != null && integrityRatio < ownershipIntegrityRatio) + { + //Block allready hacked -> stop grinding and switch to next + return false; + } + } + + var disassembleRatio = target.FatBlock?.DisassembleRatio ?? ((MyCubeBlockDefinition)target.BlockDefinition).DisassembleRatio; + var integrityPointsPerSec = ((MyCubeBlockDefinition)target.BlockDefinition).IntegrityPointsPerSec; + + var damage = MyAPIGateway.Session.GrinderSpeedMultiplier * NanobotBuildAndRepairSystemMod.Settings.Welder.GrindingMultiplier * GRINDER_AMOUNT_PER_SECOND; + var grinderAmount = damage * integrityPointsPerSec / disassembleRatio; + integrityRatio = (target.Integrity - grinderAmount) / target.MaxIntegrity; + + if ((targetData.Attributes & TargetBlockData.AttributeFlags.Autogrind) != 0) + { + if ((Settings.GrindJanitorOptions & AutoGrindOptions.DisableOnly) != 0 && integrityRatio < criticalIntegrityRatio) + { + //Grind only down to critical ratio not further + grinderAmount = target.Integrity - 0.9f * criticalIntegrityRatio * target.MaxIntegrity; + damage = grinderAmount * disassembleRatio / integrityPointsPerSec; + integrityRatio = criticalIntegrityRatio; + } + else if ((Settings.GrindJanitorOptions & AutoGrindOptions.HackOnly) != 0 && integrityRatio < ownershipIntegrityRatio) + { + //Grind only down to ownership ratio not further + grinderAmount = target.Integrity - 0.9f * ownershipIntegrityRatio * target.MaxIntegrity; + damage = grinderAmount * disassembleRatio / integrityPointsPerSec; + integrityRatio = ownershipIntegrityRatio; + } + } + + var emptying = false; + var isEmpty = false; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerDoGrind {1} integrityRatio={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target), integrityRatio); + if (integrityRatio <= 0.2) + { + //Try to emtpy inventory (if any) + if (target.FatBlock != null && target.FatBlock.HasInventory) + { + emptying = EmptyBlockInventories(target.FatBlock, _TransportInventory, out isEmpty); + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerDoGrind {1} Try empty Inventory running={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target), emptying); + } + } + + if (!emptying || isEmpty) + { + var damageInfo = new MyDamageInformation(false, damage, MyDamageType.Grind, _Welder.EntityId); + + if (target.UseDamageSystem) + { + //Not available in modding + //MyAPIGateway.Session.DamageSystem.RaiseBeforeDamageApplied(target, ref damageInfo); + + foreach (var entry in NanobotBuildAndRepairSystemMod.BuildAndRepairSystems) + { + var relation = entry.Value.Welder.GetUserRelationToOwner(_Welder.OwnerId); + if (MyRelationsBetweenPlayerAndBlockExtensions.IsFriendly(relation)) + { + //A 'friendly' damage from grinder -> do not repair (for a while) + //I don't check block relation here, because if it is enemy we won't repair it in any case and it just times out + entry.Value.FriendlyDamage[target] = MyAPIGateway.Session.ElapsedPlayTime + NanobotBuildAndRepairSystemMod.Settings.FriendlyDamageTimeout; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock: Damaged Add FriendlyDamage {0} Timeout {1}", Logging.BlockName(target), entry.Value.FriendlyDamage[target]); + } + } + } + + target.DecreaseMountLevel(damageInfo.Amount, _TransportInventory); + target.MoveItemsFromConstructionStockpile(_TransportInventory); + + if (target.UseDamageSystem) + { + //Not available in modding + //MyAPIGateway.Session.DamageSystem.RaiseAfterDamageApplied(target, ref damageInfo); + } + if (target.IsFullyDismounted) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoGrind {1} FullyDismounted", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target)); + if (target.UseDamageSystem) + { + //Not available in modding + //MyAPIGateway.Session.DamageSystem.RaiseDestroyed(target, damageInfo); + } + + target.SpawnConstructionStockpile(); + target.CubeGrid.RazeBlock(target.Position); + } + } + + if ((float)_TransportInventory.CurrentVolume >= _MaxTransportVolume || target.IsFullyDismounted) + { + //Transport started + State.CurrentTransportIsPick = true; + State.CurrentTransportTarget = ComputePosition(target); + State.CurrentTransportStartTime = playTime; + State.CurrentTransportTime = TimeSpan.FromSeconds(2d * targetData.Distance / Settings.TransportSpeed); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoGrind: Target {1} transport started transporttime={2} CurrentVolume={3}/{4}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Block), State.CurrentTransportTime, _TransportInventory.CurrentVolume, _MaxTransportVolume); + ServerEmptyTranportInventory(true); + transporting = true; + } + + return true; + } + + /// + /// + /// + private bool ServerDoCollectFloating(TargetEntityData targetData, out bool transporting, ref TargetEntityData collectingFirstTarget) + { + transporting = false; + var collecting = false; + var canAdd = false; + var isEmpty = true; + + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + transporting = IsTransportRunnning(playTime); + if (transporting) return false; + if (targetData != null) + { + var target = targetData.Entity; + var floating = target as MyFloatingObject; + var floatingFirstTarget = collectingFirstTarget?.Entity as MyFloatingObject; + + canAdd = collectingFirstTarget == null || (floatingFirstTarget != null && floating != null); + if (canAdd) + { + if (floating != null) collecting = EmptyFloatingObject(floating, _TransportInventory, out isEmpty); + else + { + collecting = EmptyBlockInventories(target, _TransportInventory, out isEmpty); + if (isEmpty) + { + var character = target as IMyCharacter; + if (character != null && character.IsBot) + { + //Wolf, Spider, ... + target.Delete(); + } + } + } + + if (collecting && collectingFirstTarget == null) collectingFirstTarget = targetData; + + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerDoCollectFloating {1} Try pick floating running={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(target), collecting); + + targetData.Ignore = isEmpty; + } + } + if (collectingFirstTarget != null && ((float)_TransportInventory.CurrentVolume >= _MaxTransportVolume || (!canAdd && _TransportInventory.CurrentVolume > 0))) + { + //Transport started + State.CurrentTransportIsPick = true; + State.CurrentTransportTarget = ComputePosition(collectingFirstTarget.Entity); + State.CurrentTransportStartTime = playTime; + State.CurrentTransportTime = TimeSpan.FromSeconds(2d * collectingFirstTarget.Distance / Settings.TransportSpeed); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerDoCollectFloating: Target {1} transport started transporttime={2} CurrentVolume={3}/{4}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(collectingFirstTarget.Entity), State.CurrentTransportTime, _TransportInventory.CurrentVolume, _MaxTransportVolume); + ServerEmptyTranportInventory(true); + transporting = true; + collectingFirstTarget = null; + } + + return collecting; + } + + /// + /// Try to find an the missing components and moves them into welder inventory + /// + private bool ServerFindMissingComponents(TargetBlockData targetData) + { + try + { + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + if (IsTransportRunnning(playTime)) return true; + + var remainingVolume = _MaxTransportVolume; + _TempMissingComponents.Clear(); + var picked = false; ; + var cubeGrid = targetData.Block.CubeGrid as MyCubeGrid; + if ((targetData.Attributes & TargetBlockData.AttributeFlags.Projected) != 0) + { + targetData.Block.GetMissingComponents(_TempMissingComponents, UtilsInventory.IntegrityLevel.Create); + if (_TempMissingComponents.Count > 0) + { + picked = ServerFindMissingComponents(targetData, ref remainingVolume); + if (picked) + { + if ((Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) == 0 || !IsColorNearlyEquals(Settings.IgnoreColorPacked, targetData.Block.GetColorMask())) + { + //Block could be created and should be welded -> so retrieve the remaining material also + var keyValue = _TempMissingComponents.ElementAt(0); + _TempMissingComponents.Clear(); + targetData.Block.GetMissingComponents(_TempMissingComponents, (Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) == 0 ? UtilsInventory.IntegrityLevel.Complete : UtilsInventory.IntegrityLevel.Functional); + if (_TempMissingComponents.ContainsKey(keyValue.Key)) + { + if (_TempMissingComponents[keyValue.Key] <= keyValue.Value) _TempMissingComponents.Remove(keyValue.Key); + else _TempMissingComponents[keyValue.Key] -= keyValue.Value; + } + } + } + } + } + else + { + targetData.Block.GetMissingComponents(_TempMissingComponents, (Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) == 0 ? UtilsInventory.IntegrityLevel.Complete : UtilsInventory.IntegrityLevel.Functional); + } + + if (_TempMissingComponents.Count > 0) + { + ServerFindMissingComponents(targetData, ref remainingVolume); + } + + if (remainingVolume < _MaxTransportVolume || (_CreativeModeActive && _TempMissingComponents.Count > 0)) + { + //Transport startet + State.CurrentTransportIsPick = false; + State.CurrentTransportTarget = ComputePosition(targetData.Block); + State.CurrentTransportStartTime = playTime; + State.CurrentTransportTime = TimeSpan.FromSeconds(2d * targetData.Distance / Settings.TransportSpeed); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: FindMissingComponents: Target {1} transport started volume={2} (max {3}) transporttime={4}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Block), _MaxTransportVolume - remainingVolume, _MaxTransportVolume, State.CurrentTransportTime); + return true; + } + return false; + } + finally + { + _TempMissingComponents.Clear(); + } + } + + /// + /// + /// + /// + /// + private bool ServerFindMissingComponents(TargetBlockData targetData, ref float remainingVolume) + { + var picked = false; + foreach (var keyValue in _TempMissingComponents) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: FindMissingComponents: Target {1} missing {2}={3} remainingVolume={4}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(targetData.Block), keyValue.Key, keyValue.Value, remainingVolume); + var neededAmount = 0; + + var componentId = new MyDefinitionId(typeof(MyObjectBuilder_Component), keyValue.Key); + int allreadyMissingAmount; + if (!State.MissingComponents.TryGetValue(componentId, out allreadyMissingAmount)) + { + var definition = MyDefinitionManager.Static.GetPhysicalItemDefinition(componentId); + neededAmount = keyValue.Value; + picked = ServerPickFromWelder(componentId, definition.Volume, ref neededAmount, ref remainingVolume) || picked; + if (neededAmount > 0 && remainingVolume > 0) picked = PullComponents(componentId, definition.Volume, ref neededAmount, ref remainingVolume) || picked; + } + else + { + neededAmount = keyValue.Value; + } + + if (neededAmount > 0 && remainingVolume > 0) AddToMissingComponents(componentId, neededAmount); + if (remainingVolume <= 0) break; + } + return picked; + } + + /// + /// Try to pick needed material from own inventory, if successfull material is moved into transport inventory + /// + private bool ServerPickFromWelder(MyDefinitionId componentId, float volume, ref int neededAmount, ref float remainingVolume) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerPickFromWelder Try: {1}={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, neededAmount); + + var picked = false; + + var welderInventory = _Welder.GetInventory(0); + if (welderInventory == null || welderInventory.Empty()) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerPickFromWelder welder empty: {1}={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, neededAmount); + return picked; + } + + var tempInventoryItems = new List(); + welderInventory.GetItems(tempInventoryItems); + for (var i1 = tempInventoryItems.Count - 1; i1 >= 0; i1--) + { + var srcItem = tempInventoryItems[i1]; + if (srcItem != null && (MyDefinitionId)srcItem.Type == componentId && srcItem.Amount > 0) + { + var maxpossibleAmount = Math.Min(neededAmount, (int)Math.Floor(remainingVolume / volume)); + var pickedAmount = MyFixedPoint.Min(maxpossibleAmount, srcItem.Amount); + if (pickedAmount > 0) + { + welderInventory.RemoveItems(srcItem.ItemId, pickedAmount); + var physicalObjBuilder = (MyObjectBuilder_PhysicalObject)MyObjectBuilderSerializer.CreateNewObject((MyDefinitionId)srcItem.Type); + _TransportInventory.AddItems(pickedAmount, physicalObjBuilder); + + neededAmount -= (int)pickedAmount; + remainingVolume -= (float)pickedAmount * volume; + + picked = true; + } + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerPickFromWelder: {1}: missingAmount={2} pickedAmount={3} maxpossibleAmount={4} remainingVolume={5} transportVolumeTotal={6}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, neededAmount, pickedAmount, maxpossibleAmount, remainingVolume, _TransportInventory.CurrentVolume); + } + if (neededAmount <= 0 || remainingVolume <= 0) break; + } + tempInventoryItems.Clear(); + return picked; + } + + /// + /// Check if the transport inventory is empty after delivering/grinding/collecting, if not move items back to welder inventory + /// + private bool ServerEmptyTranportInventory(bool push) + { + var empty = _TransportInventory.Empty(); + if (!empty) + { + if (!_CreativeModeActive) + { + var welderInventory = _Welder.GetInventory(0); + if (welderInventory != null) + { + if (push && !welderInventory.Empty()) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: ServerEmptyTranportInventory: push={1}: MaxVolume={2} CurrentVolume={3} Timeout={4}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), push, welderInventory.MaxVolume, welderInventory.CurrentVolume, MyAPIGateway.Session.ElapsedPlayTime.Subtract(_TryPushInventoryLast).TotalSeconds); + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(_TryPushInventoryLast).TotalSeconds > 3 && welderInventory.MaxVolume - welderInventory.CurrentVolume < _TransportInventory.CurrentVolume * 1.5f) + { + if (!welderInventory.PushComponents(_PossibleSources, null)) + { + //Failed retry after timeout + _TryPushInventoryLast = MyAPIGateway.Session.ElapsedPlayTime; + } + } + } + + var tempInventoryItems = new List(); + _TransportInventory.GetItems(tempInventoryItems); + for (var srcItemIndex = tempInventoryItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var item = tempInventoryItems[srcItemIndex]; + if (item == null) continue; + + //Try to move as much as possible + var amount = item.Amount; + var moveableAmount = welderInventory.MaxItemsAddable(amount, item.Type); + if (moveableAmount > 0) + { + if (welderInventory.TransferItemFrom(_TransportInventory, srcItemIndex, null, true, moveableAmount, false)) + { + amount -= moveableAmount; + } + } + if (moveableAmount > 0 && Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerEmptyTranportInventory move to welder Item {1} amount={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), item.Type, moveableAmount); + if (amount > 0 && Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: ServerEmptyTranportInventory (no more room in welder) Item {1} amount={2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), item.Type, amount); + } + tempInventoryItems.Clear(); + } + } + else + { + _TransportInventory.Clear(); + } + empty = _TransportInventory.Empty(); + } + State.InventoryFull = !empty; + return empty; + } + + /// + /// + /// + /// + /// + private bool EmptyBlockInventories(IMyEntity entity, IMyInventory dstInventory, out bool isEmpty) + { + var running = false; + var remainingVolume = _MaxTransportVolume - (float)dstInventory.CurrentVolume; + + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: EmptyBlockInventories remainingVolume={1} Entity={2}, InventoryCount={3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), remainingVolume, Logging.BlockName(entity, Logging.BlockNameOptions.None), entity.InventoryCount); + + isEmpty = true; + for (var i1 = 0; i1 < entity.InventoryCount; i1++) + { + var srcInventory = entity.GetInventory(i1); + if (srcInventory.Empty()) continue; + + if (remainingVolume <= 0) return true; //No more transport volume + + var tempInventoryItems = new List(); + srcInventory.GetItems(tempInventoryItems); + for (var srcItemIndex = tempInventoryItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var srcItem = srcInventory.GetItemByID(tempInventoryItems[srcItemIndex].ItemId); + if (srcItem == null) continue; + + var definition = MyDefinitionManager.Static.GetPhysicalItemDefinition(srcItem.Content.GetId()); + + var maxpossibleAmountFP = Math.Min((float)srcItem.Amount, remainingVolume / definition.Volume); + //Real Transport Volume is always bigger than logical _MaxTransportVolume so ceiling is no problem + var maxpossibleAmount = (MyFixedPoint)(definition.HasIntegralAmounts ? Math.Ceiling(maxpossibleAmountFP) : maxpossibleAmountFP); + if (dstInventory.TransferItemFrom(srcInventory, srcItemIndex, null, true, maxpossibleAmount, false)) + { + remainingVolume -= (float)maxpossibleAmount * definition.Volume; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: EmptyBlockInventories Transfered Item {1} amount={2} remainingVolume={3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), srcItem.Content.GetId(), maxpossibleAmount, remainingVolume); + running = true; + if (remainingVolume <= 0) + { + isEmpty = false; + return true; //No more transport volume + } + } + else + { + isEmpty = false; + return running; //No more space + } + } + tempInventoryItems.Clear(); + } + return running; + } + + /// + /// + /// + private bool EmptyFloatingObject(MyFloatingObject floating, IMyInventory dstInventory, out bool isEmpty) + { + var running = false; + isEmpty = floating.WasRemovedFromWorld || floating.MarkedForClose; + if (!isEmpty) + { + var remainingVolume = _MaxTransportVolume - (double)dstInventory.CurrentVolume; + + var definition = MyDefinitionManager.Static.GetPhysicalItemDefinition(floating.Item.Content.GetId()); + var startAmount = floating.Item.Amount; + + var maxremainAmount = (MyFixedPoint)(remainingVolume / definition.Volume); + var maxpossibleAmount = maxremainAmount > floating.Item.Amount ? floating.Item.Amount : maxremainAmount; //Do not use MyFixedPoint.Min !Wrong Implementation could cause overflow! + if (definition.HasIntegralAmounts) maxpossibleAmount = MyFixedPoint.Floor(maxpossibleAmount); + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: EmptyFloatingObject remainingVolume={1}, Item={2}, ItemAmount={3}, MaxPossibleAmount={4}, ItemVolume={5})", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), remainingVolume, floating.Item.Content.GetId(), floating.Item.Amount, maxpossibleAmount, definition.Volume); + if (maxpossibleAmount > 0) + { + if (maxpossibleAmount >= floating.Item.Amount) + { + MyFloatingObjects.RemoveFloatingObject(floating); + isEmpty = true; + } + else + { + floating.Item.Amount = floating.Item.Amount - maxpossibleAmount; + floating.RefreshDisplayName(); + } + + dstInventory.AddItems(maxpossibleAmount, floating.Item.Content); + remainingVolume -= (float)maxpossibleAmount * definition.Volume; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: EmptyFloatingObject Removed Item {1} amount={2} remainingVolume={3} remainingItemAmount={4}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), floating.Item.Content.GetId(), maxpossibleAmount, remainingVolume, floating.Item.Amount); + running = true; + } + } + return running; + } + + /// + /// + /// + /// + /// + private void AddToMissingComponents(MyDefinitionId componentId, int neededAmount) + { + int missingAmount; + if (State.MissingComponents.TryGetValue(componentId, out missingAmount)) + { + State.MissingComponents[componentId] = missingAmount + neededAmount; + } + else + { + State.MissingComponents.Add(componentId, neededAmount); + } + } + + /// + /// Pull components into welder + /// + private bool PullComponents(MyDefinitionId componentId, float volume, ref int neededAmount, ref float remainingVolume) + { + var availAmount = 0; + var welderInventory = _Welder.GetInventory(0); + var maxpossibleAmount = Math.Min(neededAmount, (int)Math.Ceiling(remainingVolume / volume)); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: PullComponents start: {1}={2} maxpossibleAmount={3} volume={4}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, neededAmount, maxpossibleAmount, volume); + if (maxpossibleAmount <= 0) return false; + var picked = false; + lock (_PossibleSources) + { + foreach (var srcInventory in _PossibleSources) + { + //Pre Test is 10 timers faster then get the whole list (as copy!) and iterate for nothing + if (srcInventory.FindItem(componentId) != null && srcInventory.CanTransferItemTo(welderInventory, componentId)) + { + var tempInventoryItems = new List(); + srcInventory.GetItems(tempInventoryItems); + for (var srcItemIndex = tempInventoryItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var srcItem = tempInventoryItems[srcItemIndex]; + if (srcItem != null && (MyDefinitionId)srcItem.Type == componentId && srcItem.Amount > 0) + { + var moved = false; + var amountMoveable = 0; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: PullComponents Found: {1}={2} in {3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, srcItem.Amount, Logging.BlockName(srcInventory)); + var amountPossible = Math.Min(maxpossibleAmount, (int)srcItem.Amount); + if (amountPossible > 0) + { + amountMoveable = (int)welderInventory.MaxItemsAddable(amountPossible, componentId); + if (amountMoveable > 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: PullComponents Try to move: {1}={2} from {3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, amountMoveable, Logging.BlockName(srcInventory)); + moved = welderInventory.TransferItemFrom(srcInventory, srcItemIndex, null, true, amountMoveable); + if (moved) + { + maxpossibleAmount -= amountMoveable; + availAmount += amountMoveable; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: PullComponents Moved: {1}={2} from {3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId, amountMoveable, Logging.BlockName(srcInventory)); + picked = ServerPickFromWelder(componentId, volume, ref neededAmount, ref remainingVolume) || picked; + } + } + else + { + //No (more) space in welder + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: PullComponents no more space in welder: {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), componentId); + neededAmount -= availAmount; + remainingVolume -= availAmount * volume; + return picked; + } + } + } + if (maxpossibleAmount <= 0) return picked; + } + tempInventoryItems.Clear(); + } + if (maxpossibleAmount <= 0) return picked; + } + } + + return picked; + } + + /// + /// + /// + public void UpdateSourcesAndTargetsTimer() + { + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + var updateTargets = playTime.Subtract(_LastTargetsUpdate) >= NanobotBuildAndRepairSystemMod.Settings.TargetsUpdateInterval; + var updateSources = updateTargets && playTime.Subtract(_LastSourceUpdate) >= NanobotBuildAndRepairSystemMod.Settings.SourcesUpdateInterval; + if (updateTargets) + { + StartAsyncUpdateSourcesAndTargets(updateSources); + } + } + + /// + /// Parse all the connected blocks and find the possible targets and sources of components + /// + private void StartAsyncUpdateSourcesAndTargets(bool updateSource) + { + if (!_Welder.UseConveyorSystem) + { + lock (_PossibleSources) + { + _PossibleSources.Clear(); + } + } + + if (!_Welder.Enabled || !_Welder.IsFunctional || State.Ready == false) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Enabled={1} IsFunctional={2} ---> not ready don't search for targets", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _Welder.Enabled, _Welder.IsFunctional); + lock (State.PossibleWeldTargets) + { + State.PossibleWeldTargets.Clear(); + State.PossibleWeldTargets.RebuildHash(); + } + lock (State.PossibleGrindTargets) + { + State.PossibleGrindTargets.Clear(); + State.PossibleGrindTargets.RebuildHash(); + } + lock (State.PossibleFloatingTargets) + { + State.PossibleFloatingTargets.Clear(); + State.PossibleFloatingTargets.RebuildHash(); + } + _AsyncUpdateSourcesAndTargetsRunning = false; + return; + }; + + lock (_Welder) + { + if (_AsyncUpdateSourcesAndTargetsRunning) return; + _AsyncUpdateSourcesAndTargetsRunning = true; + NanobotBuildAndRepairSystemMod.AddAsyncAction(() => AsyncUpdateSourcesAndTargets(updateSource)); + } + } + + /// + /// + /// + public void AsyncUpdateSourcesAndTargets(bool updateSource) + { + try + { + if (!State.Ready) return; + var weldingEnabled = BlockWeldPriority.AnyEnabled && Settings.WorkMode != WorkModes.GrindOnly; + var grindingEnabled = BlockGrindPriority.AnyEnabled && Settings.WorkMode != WorkModes.WeldOnly; + + updateSource &= _Welder.UseConveyorSystem; + var pos = 0; + try + { + pos = 1; + + var grids = new List(); + _TempPossibleWeldTargets.Clear(); + _TempPossibleGrindTargets.Clear(); + _TempPossibleFloatingTargets.Clear(); + _TempPossibleSources.Clear(); + _TempIgnore4Ingot.Clear(); + _TempIgnore4Components.Clear(); + _TempIgnore4Items.Clear(); + + var ignoreColor = Settings.IgnoreColorPacked; + var grindColor = Settings.GrindColorPacked; + var emitterMatrix = _Welder.WorldMatrix; + emitterMatrix.Translation = Vector3D.Transform(Settings.CorrectedAreaOffset, emitterMatrix); + var areaOrientedBox = new MyOrientedBoundingBoxD(Settings.CorrectedAreaBoundingBox, emitterMatrix); + + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Search: IgnoreColor={1}, GrindColor={2}, UseGrindJanitorOn={3}, Settings.WorkMode={4}, GrindJanitorOptions={5}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), ignoreColor, grindColor, Settings.UseGrindJanitorOn, Settings.WorkMode, Settings.GrindJanitorOptions); + + AsyncAddBlocksOfGrid(ref areaOrientedBox, (Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) != 0, ref ignoreColor, (Settings.Flags & SyncBlockSettings.Settings.UseGrindColor) != 0, ref grindColor, Settings.UseGrindJanitorOn, Settings.GrindJanitorOptions, _Welder.CubeGrid, grids, updateSource ? _TempPossibleSources : null, weldingEnabled ? _TempPossibleWeldTargets : null, grindingEnabled ? _TempPossibleGrindTargets : null); + switch (Settings.SearchMode) + { + case SearchModes.Grids: + break; + + case SearchModes.BoundingBox: + AsyncAddBlocksOfBox(ref areaOrientedBox, (Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) != 0, ref ignoreColor, (Settings.Flags & SyncBlockSettings.Settings.UseGrindColor) != 0, ref grindColor, Settings.UseGrindJanitorOn, Settings.GrindJanitorOptions, grids, weldingEnabled ? _TempPossibleWeldTargets : null, grindingEnabled ? _TempPossibleGrindTargets : null, _ComponentCollectPriority.AnyEnabled ? _TempPossibleFloatingTargets : null); + break; + } + + pos = 2; + if (updateSource) + { + Vector3D posWelder; + _Welder.SlimBlock.ComputeWorldCenter(out posWelder); + _TempPossibleSources.Sort((a, b) => + { + var blockA = a.Owner as IMyCubeBlock; + var blockB = b.Owner as IMyCubeBlock; + if (blockA != null && blockB != null) + { + var welderA = blockA as IMyShipWelder; + var welderB = blockB as IMyShipWelder; + if (welderA == null == (welderB == null)) + { + Vector3D posA; + Vector3D posB; + blockA.SlimBlock.ComputeWorldCenter(out posA); + blockB.SlimBlock.ComputeWorldCenter(out posB); + var distanceA = (int)Math.Abs((posWelder - posA).Length()); + var distanceB = (int)Math.Abs((posWelder - posA).Length()); + return distanceA - distanceB; + } + else if (welderA == null) + { + return -1; + } + else + { + return 1; + } + } + else if (blockA != null) return -1; + else if (blockB != null) return 1; + else return 0; + }); + foreach (var inventory in _TempPossibleSources) + { + var block = inventory.Owner as IMyShipWelder; + if (block != null && block.BlockDefinition.SubtypeName.Contains("NanobotBuildAndRepairSystem") && block.GameLogic != null) + { + var bar = block.GameLogic.GetAs(); + //Don't use Bar's as destination that would push immediately + if (bar != null) + { + if ((bar.Settings.Flags & SyncBlockSettings.Settings.PushIngotOreImmediately) != 0) + { + _TempIgnore4Ingot.Add(inventory); + } + if ((bar.Settings.Flags & SyncBlockSettings.Settings.PushComponentImmediately) != 0) + { + _TempIgnore4Components.Add(inventory); + } + if ((bar.Settings.Flags & SyncBlockSettings.Settings.PushItemsImmediately) != 0) + { + _TempIgnore4Items.Add(inventory); + } + } + } + } + } + + pos = 3; + _TempPossibleWeldTargets.Sort((a, b) => + { + var priorityA = BlockWeldPriority.GetPriority(a.Block); + var priorityB = BlockWeldPriority.GetPriority(b.Block); + if (priorityA == priorityB) + { + return Utils.CompareDistance(a.Distance, b.Distance); + } + else return priorityA - priorityB; + }); + + pos = 4; + _TempPossibleGrindTargets.Sort((a, b) => + { + if ((a.Attributes & TargetBlockData.AttributeFlags.Autogrind) == (b.Attributes & TargetBlockData.AttributeFlags.Autogrind)) + { + if ((a.Attributes & TargetBlockData.AttributeFlags.Autogrind) != 0) + { + var priorityA = BlockGrindPriority.GetPriority(a.Block); + var priorityB = BlockGrindPriority.GetPriority(b.Block); + if (priorityA == priorityB) + { + if ((Settings.Flags & SyncBlockSettings.Settings.GrindSmallestGridFirst) != 0) + { + var res = ((MyCubeGrid)a.Block.CubeGrid).BlocksCount - ((MyCubeGrid)b.Block.CubeGrid).BlocksCount; + return res != 0 ? res : Utils.CompareDistance(a.Distance, b.Distance); + } + if ((Settings.Flags & SyncBlockSettings.Settings.GrindNearFirst) != 0) return Utils.CompareDistance(a.Distance, b.Distance); + return Utils.CompareDistance(b.Distance, a.Distance); + } + else return priorityA - priorityB; + } + + if ((Settings.Flags & SyncBlockSettings.Settings.GrindSmallestGridFirst) != 0) + { + var res = ((MyCubeGrid)a.Block.CubeGrid).BlocksCount - ((MyCubeGrid)b.Block.CubeGrid).BlocksCount; + return res != 0 ? res : Utils.CompareDistance(a.Distance, b.Distance); + } + if ((Settings.Flags & SyncBlockSettings.Settings.GrindNearFirst) != 0) return Utils.CompareDistance(a.Distance, b.Distance); + return Utils.CompareDistance(b.Distance, a.Distance); + } + else if ((a.Attributes & TargetBlockData.AttributeFlags.Autogrind) != 0) return -1; + else if ((b.Attributes & TargetBlockData.AttributeFlags.Autogrind) != 0) return 1; + return 0; + }); + + _TempPossibleFloatingTargets.Sort((a, b) => + { + var itemA = a.Entity; + var itemB = b.Entity; + var itemAFloating = itemA as MyFloatingObject; + var itemBFloating = itemB as MyFloatingObject; + if (itemAFloating != null && itemBFloating != null) + { + var priorityA = ComponentCollectPriority.GetPriority(itemAFloating.Item.Content.GetObjectId()); + var priorityB = ComponentCollectPriority.GetPriority(itemAFloating.Item.Content.GetObjectId()); + if (priorityA == priorityB) + { + return Utils.CompareDistance(a.Distance, b.Distance); + } + else return priorityA - priorityB; + } + else if (itemAFloating == null) return -1; + else if (itemBFloating == null) return 1; + return Utils.CompareDistance(a.Distance, b.Distance); + }); + + pos = 5; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) + { + lock (Mod.Log) + { + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Possible Build Target Blocks ---> {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _TempPossibleWeldTargets.Count); + Mod.Log.IncreaseIndent(Logging.Level.Verbose); + foreach (var blockData in _TempPossibleWeldTargets) + { + Mod.Log.Write(Logging.Level.Verbose, "Block: {0} ({1})", Logging.BlockName(blockData.Block), blockData.Distance); + } + Mod.Log.DecreaseIndent(Logging.Level.Verbose); + Mod.Log.Write(Logging.Level.Verbose, "<---"); + + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Possible Grind Target Blocks ---> {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _TempPossibleGrindTargets.Count); + Mod.Log.IncreaseIndent(Logging.Level.Verbose); + foreach (var blockData in _TempPossibleGrindTargets) + { + Mod.Log.Write(Logging.Level.Verbose, "Block: {0} ({1})", Logging.BlockName(blockData.Block), blockData.Distance); + } + Mod.Log.DecreaseIndent(Logging.Level.Verbose); + Mod.Log.Write(Logging.Level.Verbose, "<---"); + + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Possible Floating Targets ---> {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _TempPossibleFloatingTargets.Count); + Mod.Log.IncreaseIndent(Logging.Level.Verbose); + foreach (var floatingData in _TempPossibleFloatingTargets) + { + Mod.Log.Write(Logging.Level.Verbose, "Floating: {0} ({1})", Logging.BlockName(floatingData.Entity), floatingData.Distance); + } + Mod.Log.DecreaseIndent(Logging.Level.Verbose); + Mod.Log.Write(Logging.Level.Verbose, "<---"); + + if (updateSource) + { + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets Possible Source Blocks ---> {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), _TempPossibleSources.Count); + Mod.Log.IncreaseIndent(Logging.Level.Verbose); + foreach (var inventory in _TempPossibleSources) + { + Mod.Log.Write(Logging.Level.Verbose, "Inventory: {0} {1}{2}{3}", Logging.BlockName(inventory), _TempIgnore4Ingot.Contains(inventory) ? string.Empty : "(Not 4 Ingot)", _TempIgnore4Components.Contains(inventory) ? string.Empty : "(Not 4 Components)", _TempIgnore4Items.Contains(inventory) ? string.Empty : "(Not 4 Items)"); + } + Mod.Log.DecreaseIndent(Logging.Level.Verbose); + Mod.Log.Write(Logging.Level.Verbose, "<---"); + } + } + } + + pos = 6; + lock (State.PossibleWeldTargets) + { + State.PossibleWeldTargets.Clear(); + State.PossibleWeldTargets.AddRange(_TempPossibleWeldTargets); + State.PossibleWeldTargets.RebuildHash(); + } + _TempPossibleWeldTargets.Clear(); + pos = 7; + lock (State.PossibleGrindTargets) + { + State.PossibleGrindTargets.Clear(); + State.PossibleGrindTargets.AddRange(_TempPossibleGrindTargets); + State.PossibleGrindTargets.RebuildHash(); + } + _TempPossibleGrindTargets.Clear(); + pos = 8; + lock (State.PossibleFloatingTargets) + { + State.PossibleFloatingTargets.Clear(); + State.PossibleFloatingTargets.AddRange(_TempPossibleFloatingTargets); + State.PossibleFloatingTargets.RebuildHash(); + } + _TempPossibleFloatingTargets.Clear(); + + pos = 9; + if (updateSource) + { + lock (_PossibleSources) + { + _PossibleSources.Clear(); + _PossibleSources.AddRange(_TempPossibleSources); + _Ignore4Ingot.Clear(); + _Ignore4Ingot.UnionWith(_TempIgnore4Ingot); + _Ignore4Components.Clear(); + _Ignore4Components.UnionWith(_TempIgnore4Components); + _Ignore4Items.Clear(); + _Ignore4Items.UnionWith(_TempIgnore4Items); + } + _TempPossibleSources.Clear(); + _TempIgnore4Ingot.Clear(); + _TempIgnore4Components.Clear(); + _TempIgnore4Items.Clear(); + } + + _ContinuouslyError = 0; + } + catch (Exception ex) + { + _ContinuouslyError++; + if (_ContinuouslyError > 10 || Mod.Log.ShouldLog(Logging.Level.Info) || Mod.Log.ShouldLog(Logging.Level.Verbose)) + { + Mod.Log.Error("BuildAndRepairSystemBlock {0}: AsyncUpdateSourcesAndTargets exception at {1}: {2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), pos, ex); + _ContinuouslyError = 0; + } + } + } + finally + { + _LastTargetsUpdate = MyAPIGateway.Session.ElapsedPlayTime; + if (updateSource) _LastSourceUpdate = _LastTargetsUpdate; + _AsyncUpdateSourcesAndTargetsRunning = false; + } + } + + /// + /// Search for grids inside bounding box and add their damaged block also + /// + private void AsyncAddBlocksOfBox(ref MyOrientedBoundingBoxD areaBox, bool useIgnoreColor, ref uint ignoreColor, bool useGrindColor, ref uint grindColor, AutoGrindRelation autoGrindRelation, AutoGrindOptions autoGrindOptions, List grids, List possibleWeldTargets, List possibleGrindTargets, List possibleFloatingTargets) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncAddBlockOfBox", Logging.BlockName(_Welder, Logging.BlockNameOptions.None)); + + var emitterMatrix = _Welder.WorldMatrix; + emitterMatrix.Translation = Vector3D.Transform(Settings.CorrectedAreaOffset, emitterMatrix); + var areaBoundingBox = Settings.CorrectedAreaBoundingBox.TransformFast(emitterMatrix); + List entityInRange = null; + lock (MyAPIGateway.Entities) + { + //API not thread save !!! + entityInRange = MyAPIGateway.Entities.GetElementsInBox(ref areaBoundingBox); + //The list contains grid, Fatblocks and Damaged blocks in range. But as I would like to use the searchfunction also for grinding, + //I only could use the grids and have to traverse through the grids to get all slimblocks. + } + if (entityInRange != null) + { + foreach (var entity in entityInRange) + { + var grid = entity as IMyCubeGrid; + if (grid != null) + { + AsyncAddBlocksOfGrid(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, grid, grids, null, possibleWeldTargets, possibleGrindTargets); + continue; + } + + if (possibleFloatingTargets != null) + { + var floating = entity as MyFloatingObject; + if (floating != null) + { + if (!floating.MarkedForClose && ComponentCollectPriority.GetEnabled(floating.Item.Content.GetObjectId())) + { + var distance = (areaBox.Center - floating.WorldMatrix.Translation).Length(); + possibleFloatingTargets.Add(new TargetEntityData(floating, distance)); + } + continue; + } + + var character = entity as IMyCharacter; + if (character != null) + { + if (character.IsDead && !character.InventoriesEmpty() && !((MyCharacterDefinition)character.Definition).EnableSpawnInventoryAsContainer) + { + var distance = (areaBox.Center - character.WorldMatrix.Translation).Length(); + possibleFloatingTargets.Add(new TargetEntityData(character, distance)); + } + continue; + } + + var inventoryBag = entity as IMyInventoryBag; + if (inventoryBag != null) + { + if (!inventoryBag.InventoriesEmpty()) + { + var distance = (areaBox.Center - inventoryBag.WorldMatrix.Translation).Length(); + possibleFloatingTargets.Add(new TargetEntityData(inventoryBag, distance)); + } + continue; + } + } + } + } + } + + /// + /// + /// + private void AsyncAddBlocksOfGrid(ref MyOrientedBoundingBoxD areaBox, bool useIgnoreColor, ref uint ignoreColor, bool useGrindColor, ref uint grindColor, AutoGrindRelation autoGrindRelation, AutoGrindOptions autoGrindOptions, IMyCubeGrid cubeGrid, List grids, List possibleSources, List possibleWeldTargets, List possibleGrindTargets) + { + if (!State.Ready) return; //Block not ready + if (grids.Contains(cubeGrid)) return; //Allready parsed + + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: AsyncAddBlocksOfGrid AddGrid {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), cubeGrid.DisplayName); + grids.Add(cubeGrid); + + var newBlocks = new List(); + cubeGrid.GetBlocks(newBlocks); + + foreach (var slimBlock in newBlocks) + { + AsyncAddBlockIfTargetOrSource(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, slimBlock, possibleSources, possibleWeldTargets, possibleGrindTargets); + + var fatBlock = slimBlock.FatBlock; + if (fatBlock == null) continue; + + var mechanicalConnectionBlock = fatBlock as Sandbox.ModAPI.IMyMechanicalConnectionBlock; + if (mechanicalConnectionBlock != null) + { + if (mechanicalConnectionBlock.TopGrid != null) + AsyncAddBlocksOfGrid(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, mechanicalConnectionBlock.TopGrid, grids, possibleSources, possibleWeldTargets, possibleGrindTargets); + continue; + } + + var attachableTopBlock = fatBlock as Sandbox.ModAPI.IMyAttachableTopBlock; + if (attachableTopBlock != null) + { + if (attachableTopBlock.Base?.CubeGrid != null) + AsyncAddBlocksOfGrid(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, attachableTopBlock.Base.CubeGrid, grids, possibleSources, possibleWeldTargets, possibleGrindTargets); + continue; + } + + var connector = fatBlock as Sandbox.ModAPI.IMyShipConnector; + if (connector != null) + { + if (connector.Status == MyShipConnectorStatus.Connected && connector.OtherConnector != null) + { + AsyncAddBlocksOfGrid(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, connector.OtherConnector.CubeGrid, grids, possibleSources, possibleWeldTargets, possibleGrindTargets); + } + continue; + } + + if (possibleWeldTargets != null && (Settings.Flags & SyncBlockSettings.Settings.AllowBuild) != 0) //If projected blocks should be build + { + var projector = fatBlock as Sandbox.ModAPI.IMyProjector; + if (projector != null) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: Projector={1} IsProjecting={2} BuildableBlockCount={3} IsRelationAllowed={4} Relation={5}/{6}/{7}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(projector), projector.IsProjecting, projector.BuildableBlocksCount, IsRelationAllowed4Welding(slimBlock), slimBlock.GetUserRelationToOwner(_Welder.OwnerId), projector.GetUserRelationToOwner(_Welder.OwnerId), slimBlock.CubeGrid.GetUserRelationToOwner(_Welder.OwnerId)); + if (projector.IsProjecting && projector.BuildableBlocksCount > 0 && IsRelationAllowed4Welding(slimBlock)) + { + //Add buildable blocks + var projectedCubeGrid = projector.ProjectedGrid; + if (projectedCubeGrid != null && !grids.Contains(projectedCubeGrid)) + { + grids.Add(projectedCubeGrid); + var projectedBlocks = new List(); + projectedCubeGrid.GetBlocks(projectedBlocks); + + foreach (var block in projectedBlocks) + { + double distance; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: Projector={1} Block={2} BlockKindEnabled={3}, InRange={4}, CanBuild={5}/{6} BlockClass={7}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(projector), Logging.BlockName(block), BlockWeldPriority.GetEnabled(block), block.IsInRange(ref areaBox, out distance), block.CanBuild(false), block.Dithering, BlockWeldPriority.GetItemAlias(block, true)); + if (BlockWeldPriority.GetEnabled(block) && block.IsInRange(ref areaBox, out distance) && block.CanBuild(false)) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: Add projected Block {1}:{2}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(projector), Logging.BlockName(block)); + possibleWeldTargets.Add(new TargetBlockData(block, distance, TargetBlockData.AttributeFlags.Projected)); + } + } + } + } + continue; + } + } + } + } + + /// + /// + /// + private void AsyncAddBlockIfTargetOrSource(ref MyOrientedBoundingBoxD areaBox, bool useIgnoreColor, ref uint ignoreColor, bool useGrindColor, ref uint grindColor, AutoGrindRelation autoGrindRelation, AutoGrindOptions autoGrindOptions, IMySlimBlock block, List possibleSources, List possibleWeldTargets, List possibleGrindTargets) + { + try + { + if (possibleSources != null) + { + //Search for sources of components (Container, Assembler, Welder, Grinder, ?) + var terminalBlock = block.FatBlock as IMyTerminalBlock; + if (terminalBlock != null && terminalBlock.EntityId != _Welder.EntityId && terminalBlock.IsFunctional) //Own inventory is no external source (handled internally) + { + var relation = terminalBlock.GetUserRelationToOwner(_Welder.OwnerId); + if (MyRelationsBetweenPlayerAndBlockExtensions.IsFriendly(relation)) + { + try + { + var welderInventory = _Welder.GetInventory(0); + var maxInv = terminalBlock.InventoryCount; + for (var idx = 0; idx < maxInv; idx++) + { + var inventory = terminalBlock.GetInventory(idx); + if (!possibleSources.Contains(inventory) && inventory.IsConnectedTo(welderInventory)) + { + possibleSources.Add(inventory); + } + } + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Event, "BuildAndRepairSystemBlock {0}: AsyncAddBlockIfTargetOrSource1 exception: {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), ex); + } + } + } + } + + var added = false; + if (possibleGrindTargets != null && (useGrindColor || autoGrindRelation != 0)) + { + added = AsyncAddBlockIfGrindTarget(ref areaBox, useGrindColor, ref grindColor, autoGrindRelation, autoGrindOptions, block, possibleGrindTargets); + } + + if (possibleWeldTargets != null && !added) //Do not weld if in grind list (could happen if auto grind neutrals is enabled and "HelpOthers" is active) + { + AsyncAddBlockIfWeldTarget(ref areaBox, useIgnoreColor, ref ignoreColor, useGrindColor, ref grindColor, block, possibleWeldTargets); + } + } + catch (Exception ex) + { + Mod.Log.Error("BuildAndRepairSystemBlock {0}: AsyncAddBlockIfTargetOrSource2 exception: {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), ex); + throw; + } + } + + /// + /// Check if the given slim block is a weld target (in range, owned, damaged, new, ..) + /// + private bool AsyncAddBlockIfWeldTarget(ref MyOrientedBoundingBoxD areaBox, bool useIgnoreColor, ref uint ignoreColor, bool useGrindColor, ref uint grindColor, IMySlimBlock block, List possibleWeldTargets) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) + { + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: Weld Check Block {1} IsProjected={2} IsDestroyed={3}, IsFullyDismounted={4}, HasFatBlock={5}, FatBlockClosed={6}, MaxDeformation={7}, (HasDeformation={8}), IsFullIntegrity={9}, Integrity={10}, NeedRepair={11}, Relation={12}, useIgnorColor={13}, HasIgnoreColor={14} ({15},{16})", //, ActionAllowed={17}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), + Logging.BlockName(block), + block.IsProjected(), + block.IsDestroyed, block.IsFullyDismounted, block.FatBlock != null, block.FatBlock != null ? block.FatBlock.Closed.ToString() : "-", + block.MaxDeformation, block.HasDeformation, block.IsFullIntegrity, block.Integrity, block.NeedRepair((Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) != 0), block.GetUserRelationToOwner(_Welder.OwnerId), + useIgnoreColor, IsColorNearlyEquals(ignoreColor, block.GetColorMask()), ignoreColor, block.GetColorMask().PackHSVToUint() + ); + } + + double distance; + var colorMask = block.GetColorMask(); + Sandbox.ModAPI.IMyProjector projector; + if (block.IsProjected(out projector)) + { + if ((Settings.Flags & SyncBlockSettings.Settings.AllowBuild) != 0 && + (!useGrindColor || !IsColorNearlyEquals(grindColor, colorMask)) && + BlockWeldPriority.GetEnabled(block) && + block.IsInRange(ref areaBox, out distance) && + IsRelationAllowed4Welding(projector.SlimBlock) && + block.CanBuild(false)) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: Add projected Block {1}, HasFatBlock={2}, Class={3}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(block), block.FatBlock != null, BlockWeldPriority.GetItemAlias(block, true)); + possibleWeldTargets.Add(new TargetBlockData(block, distance, TargetBlockData.AttributeFlags.Projected)); + return true; + } + } + else + { + if ((!useIgnoreColor || !IsColorNearlyEquals(ignoreColor, colorMask)) && (!useGrindColor || !IsColorNearlyEquals(grindColor, colorMask)) && + BlockWeldPriority.GetEnabled(block) && + block.IsInRange(ref areaBox, out distance) && + IsRelationAllowed4Welding(block) && + block.NeedRepair((Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) != 0)) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: Add damaged Block {1} MaxDeformation={2}, (HasDeformation={3}), IsFullIntegrity={4}, HasFatBlock={5}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(block), block.MaxDeformation, block.HasDeformation, block.IsFullIntegrity, block.FatBlock != null); + possibleWeldTargets.Add(new TargetBlockData(block, distance, 0)); + return true; + } + } + + return false; + } + + /// + /// Check if the given slim block is a grind target (in range, color ) + /// + private bool AsyncAddBlockIfGrindTarget(ref MyOrientedBoundingBoxD areaBox, bool useGrindColor, ref uint grindColor, AutoGrindRelation autoGrindRelation, AutoGrindOptions autoGrindOptions, IMySlimBlock block, List possibleGrindTargets) + { + // Do not allow grinding if our shields are up. + if (IsWelderShielded() && block.CubeGrid.EntityId != Welder.CubeGrid.EntityId) + return false; + + //block.CubeGrid.BlocksDestructionEnabled is not available for modding, so at least check if general destruction is enabled + if ((MyAPIGateway.Session.SessionSettings.Scenario || MyAPIGateway.Session.SessionSettings.ScenarioEditMode) && !MyAPIGateway.Session.SessionSettings.DestructibleBlocks) return false; + + //block.CubeGrid.Editable is not available for modding -> wait until it might be availabel + //if (!block.CubeGrid.Editable) return; + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) + { + Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemBlock {0}: Grind Check Block {1} Projected={2} AutoGrindRelation={3} Relation={4} UseGrindColor={5} HasGrindColor={6} ({7},{8})/", // ActionAllowed={10}", + Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(block), block.IsProjected(), autoGrindRelation, block.GetUserRelationToOwner(_Welder.OwnerId), useGrindColor, + IsColorNearlyEquals(grindColor, block.GetColorMask()), grindColor, block.GetColorMask().PackHSVToUint()); + } + + // Is being projected? + if (block.IsProjected()) + return false; + + var autoGrind = autoGrindRelation != 0 && BlockGrindPriority.GetEnabled(block); + if (autoGrind) + { + var relation = block.GetUserRelationToOwner(_Welder.OwnerId); + autoGrind = + (relation == MyRelationsBetweenPlayerAndBlock.NoOwnership && (autoGrindRelation & AutoGrindRelation.NoOwnership) != 0) || + (relation == MyRelationsBetweenPlayerAndBlock.Enemies && (autoGrindRelation & AutoGrindRelation.Enemies) != 0) || + (relation == MyRelationsBetweenPlayerAndBlock.Neutral && (autoGrindRelation & AutoGrindRelation.Neutral) != 0); + } + + if (autoGrind && (autoGrindOptions & (AutoGrindOptions.DisableOnly | AutoGrindOptions.HackOnly)) != 0) + { + var criticalIntegrityRatio = ((MyCubeBlockDefinition)block.BlockDefinition).CriticalIntegrityRatio; + var ownershipIntegrityRatio = ((MyCubeBlockDefinition)block.BlockDefinition).OwnershipIntegrityRatio > 0 ? ((MyCubeBlockDefinition)block.BlockDefinition).OwnershipIntegrityRatio : criticalIntegrityRatio; + var integrityRation = block.Integrity / block.MaxIntegrity; + if (autoGrind && (autoGrindOptions & AutoGrindOptions.DisableOnly) != 0) + { + autoGrind = block.FatBlock != null && integrityRation > criticalIntegrityRatio; + } + if (autoGrind && (autoGrindOptions & AutoGrindOptions.HackOnly) != 0) + { + autoGrind = block.FatBlock != null && integrityRation > ownershipIntegrityRatio; + } + } + + if (autoGrind || (useGrindColor && IsColorNearlyEquals(grindColor, block.GetColorMask()))) + { + double distance; + if (block.IsInRange(ref areaBox, out distance)) + { + // Is protected by SafeZone? + if (SafeZoneProtection.IsProtected(block, Welder)) + { + Deb.Write("Block {0} is protected by SafeZone"); + return false; + } + + // Is protected by shields. + if (IsShieldProtected(block)) + { + Deb.Write("Block {0} is protected by SafeZone"); + return false; + } + + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemBlock {0}: Add grind Block {1}", Logging.BlockName(_Welder, Logging.BlockNameOptions.None), Logging.BlockName(block)); + possibleGrindTargets.Add(new TargetBlockData(block, distance, autoGrind ? TargetBlockData.AttributeFlags.Autogrind : 0)); + return true; + } + } + return false; + } + + /// + /// + /// + /// + /// + private bool IsRelationAllowed4Welding(IMySlimBlock block) + { + var relation = _Welder.OwnerId == 0 ? MyRelationsBetweenPlayerAndBlock.NoOwnership : block.GetUserRelationToOwner(_Welder.OwnerId); + if (relation == MyRelationsBetweenPlayerAndBlock.Enemies) return false; + if (!_Welder.HelpOthers && (relation == MyRelationsBetweenPlayerAndBlock.Neutral || relation == MyRelationsBetweenPlayerAndBlock.NoOwnership)) return false; + return true; + } + + /// + /// + /// + /// + /// + private static bool IsColorNearlyEquals(uint colorA, Vector3 colorB) + { + return colorA == colorB.PackHSVToUint(); + } + + /// + /// Update custom info of the block + /// + /// + /// + private void AppendingCustomInfo(IMyTerminalBlock terminalBlock, StringBuilder customInfo) + { + customInfo.Clear(); + + customInfo.Append(MyTexts.Get(MyStringId.GetOrCompute("BlockPropertiesText_Type"))); + customInfo.Append(_Welder.SlimBlock.BlockDefinition.DisplayNameText); + customInfo.Append(Environment.NewLine); + + var resourceSink = _Welder.Components.Get(); + if (resourceSink != null) + { + customInfo.Append(MyTexts.Get(MyStringId.GetOrCompute("BlockPropertiesText_MaxRequiredInput"))); + MyValueFormatter.AppendWorkInBestUnit(resourceSink.MaxRequiredInputByType(ElectricityId), customInfo); + customInfo.Append(Environment.NewLine); + customInfo.Append(MyTexts.Get(MyStringId.GetOrCompute("BlockPropertiesText_RequiredInput"))); + MyValueFormatter.AppendWorkInBestUnit(resourceSink.RequiredInputByType(ElectricityId), customInfo); + customInfo.Append(Environment.NewLine); + } + customInfo.Append(Environment.NewLine); + + if (_Welder.Enabled && _Welder.IsWorking && _Welder.IsFunctional) + { + if ((Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0) + { + customInfo.Append(Texts.Info_CurentWeldEntity + Environment.NewLine); + customInfo.Append(string.Format(" -{0}" + Environment.NewLine, Settings.CurrentPickedWeldingBlock.BlockName())); + customInfo.Append(Texts.Info_CurentGrindEntity + Environment.NewLine); + customInfo.Append(string.Format(" -{0}" + Environment.NewLine, Settings.CurrentPickedGrindingBlock.BlockName())); + } + + if (State.InventoryFull) customInfo.Append(Texts.Info_InventoryFull + Environment.NewLine); + if (State.LimitsExceeded) customInfo.Append(Texts.Info_LimitReached + Environment.NewLine); + + var cnt = 0; + customInfo.Append(Texts.Info_MissingItems + Environment.NewLine); + lock (State.MissingComponents) + { + foreach (var component in State.MissingComponents) + { + var componentId = new MyDefinitionId(typeof(MyObjectBuilder_Component), component.Key.SubtypeId); + MyComponentDefinition componentDefnition; + var name = MyDefinitionManager.Static.TryGetComponentDefinition(componentId, out componentDefnition) ? componentDefnition.DisplayNameText : component.Key.SubtypeName; + customInfo.Append(string.Format(" -{0}: {1}" + Environment.NewLine, name, component.Value)); + cnt++; + if (cnt >= SyncBlockState.MaxSyncItems) + { + customInfo.Append(Texts.Info_More + Environment.NewLine); + break; + } + } + } + customInfo.Append(Environment.NewLine); + + cnt = 0; + customInfo.Append(Texts.Info_BlocksToBuild + Environment.NewLine); + lock (State.PossibleWeldTargets) + { + foreach (var blockData in State.PossibleWeldTargets) + { + customInfo.Append(string.Format(" -{0}" + Environment.NewLine, blockData.Block.BlockName())); + cnt++; + if (cnt >= SyncBlockState.MaxSyncItems) + { + customInfo.Append(Texts.Info_More + Environment.NewLine); + break; + } + } + } + customInfo.Append(Environment.NewLine); + + cnt = 0; + customInfo.Append(Texts.Info_BlocksToGrind + Environment.NewLine); + lock (State.PossibleGrindTargets) + { + foreach (var blockData in State.PossibleGrindTargets) + { + customInfo.Append(string.Format(" -{0}" + Environment.NewLine, blockData.Block.BlockName())); + cnt++; + if (cnt >= SyncBlockState.MaxSyncItems) + { + customInfo.Append(Texts.Info_More + Environment.NewLine); + break; + } + } + } + customInfo.Append(Environment.NewLine); + + cnt = 0; + customInfo.Append(Texts.Info_ItemsToCollect + Environment.NewLine); + lock (State.PossibleFloatingTargets) + { + foreach (var entityData in State.PossibleFloatingTargets) + { + customInfo.Append(string.Format(" -{0}" + Environment.NewLine, Logging.BlockName(entityData.Entity))); + cnt++; + if (cnt >= SyncBlockState.MaxSyncItems) + { + customInfo.Append(Texts.Info_More + Environment.NewLine); + break; + } + } + } + } + else + { + if (!_Welder.Enabled) customInfo.Append(Texts.Info_BlockSwitchedOff + Environment.NewLine); + else if (!_Welder.IsFunctional) customInfo.Append(Texts.Info_BlockDamaged + Environment.NewLine); + else if (!_Welder.IsWorking) customInfo.Append(Texts.Info_BlockUnpowered + Environment.NewLine); + } + } + + /// + /// Check if block currently has been damaged by friendly(grinder) + /// + public bool IsFriendlyDamage(IMySlimBlock slimBlock) + { + return FriendlyDamage.ContainsKey(slimBlock); + } + + /// + /// Clear timedout friendly damaged blocks + /// + private void CleanupFriendlyDamage() + { + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + if (playTime.Subtract(_LastFriendlyDamageCleanup) > NanobotBuildAndRepairSystemMod.Settings.FriendlyDamageCleanup) + { + //Cleanup + var timedout = new List(); + foreach (var entry in FriendlyDamage) + { + if (entry.Value < playTime) timedout.Add(entry.Key); + } + for (var idx = timedout.Count - 1; idx >= 0; idx--) + { + FriendlyDamage.Remove(timedout[idx]); + } + _LastFriendlyDamageCleanup = playTime; + } + } + + /// + /// + /// + /// + private WorkingState GetWorkingState() + { + if (!State.Ready) return WorkingState.NotReady; + else if (State.Welding) return WorkingState.Welding; + else if (State.NeedWelding) + { + if (State.MissingComponents.Count > 0) return WorkingState.MissingComponents; + if (State.LimitsExceeded) return WorkingState.LimitsExceeded; + return WorkingState.NeedWelding; + } + else if (State.Grinding) return WorkingState.Grinding; + else if (State.NeedGrinding) + { + if (State.InventoryFull) return WorkingState.InventoryFull; + return WorkingState.NeedGrinding; + } + return WorkingState.Idle; + } + + /// + /// Set actual state and position of visual effects + /// + private void UpdateEffects() + { + var transportState = State.Transporting && State.CurrentTransportTarget != null; + if (transportState != _TransportStateSet) + { + SetTransportEffects(transportState); + } + else + { + UpdateTransportEffectPosition(); + } + + //Welding/Grinding state + var workingState = GetWorkingState(); + if (workingState != _WorkingStateSet || Settings.SoundVolume != _SoundVolumeSet) + { + SetWorkingEffects(workingState); + _WorkingStateSet = workingState; + _SoundVolumeSet = Settings.SoundVolume; + } + else + { + UpdateWorkingEffectPosition(workingState); + } + } + + /// + /// + /// + private void StopSoundEffects() + { + _SoundEmitter?.StopSound(false); + + if (_SoundEmitterWorking != null) + { + _SoundEmitterWorking.StopSound(false); + _SoundEmitterWorking.SetPosition(null); //Reset + _SoundEmitterWorkingPosition = null; + } + } + + /// + /// Start visual effects for welding/grinding + /// + private void SetWorkingEffects(WorkingState workingState) + { + if (_ParticleEffectWorking1 != null) + { + Interlocked.Decrement(ref _ActiveWorkingEffects); + _ParticleEffectWorking1.Stop(); + _ParticleEffectWorking1 = null; + } + + if (_LightEffect != null) + { + MyLights.RemoveLight(_LightEffect); + _LightEffect = null; + } + + switch (workingState) + { + case WorkingState.Welding: + case WorkingState.Grinding: + if (_ActiveWorkingEffects < MaxWorkingEffects && + ((workingState == WorkingState.Welding && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedEffects & VisualAndSoundEffects.WeldingVisualEffect) != 0) || + (workingState == WorkingState.Grinding && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedEffects & VisualAndSoundEffects.GrindingVisualEffect) != 0))) + { + Interlocked.Increment(ref _ActiveWorkingEffects); + + MyParticlesManager.TryCreateParticleEffect(workingState == WorkingState.Welding ? PARTICLE_EFFECT_WELDING1 : PARTICLE_EFFECT_GRINDING1, ref MatrixD.Identity, ref Vector3D.Zero, uint.MaxValue, out _ParticleEffectWorking1); + if (_ParticleEffectWorking1 != null) _ParticleEffectWorking1.UserRadiusMultiplier = workingState == WorkingState.Welding ? 4f : 2f;// 0.5f; + + if (workingState == WorkingState.Welding && _LightEffectFlareWelding == null) + { + var myDefinitionId = new MyDefinitionId(typeof(MyObjectBuilder_FlareDefinition), "ShipWelder"); + _LightEffectFlareWelding = MyDefinitionManager.Static.GetDefinition(myDefinitionId) as MyFlareDefinition; + } + else if (workingState == WorkingState.Grinding && _LightEffectFlareGrinding == null) + { + var myDefinitionId = new MyDefinitionId(typeof(MyObjectBuilder_FlareDefinition), "ShipGrinder"); + _LightEffectFlareGrinding = MyDefinitionManager.Static.GetDefinition(myDefinitionId) as MyFlareDefinition; + } + + var flare = workingState == WorkingState.Welding ? _LightEffectFlareWelding : _LightEffectFlareGrinding; + + if (flare != null) + { + _LightEffect = MyLights.AddLight(); + _LightEffect.Start(Vector3.Zero, new Vector4(0.7f, 0.85f, 1f, 1f), 5f, string.Concat(_Welder.DisplayNameText, " EffectLight")); + _LightEffect.Falloff = 2f; + _LightEffect.LightOn = true; + _LightEffect.GlareOn = true; + _LightEffect.GlareQuerySize = 0.8f; + _LightEffect.PointLightOffset = 0.1f; + _LightEffect.GlareType = VRageRender.Lights.MyGlareTypeEnum.Normal; + _LightEffect.SubGlares = flare.SubGlares; + _LightEffect.Intensity = flare.Intensity; + _LightEffect.GlareSize = flare.Size; + } + } + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Green, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", workingState == WorkingState.Welding ? Color.Yellow : Color.Blue, 1.0f); + break; + + case WorkingState.MissingComponents: + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Yellow, 1.0f); + break; + + case WorkingState.InventoryFull: + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Blue, 1.0f); + break; + + case WorkingState.NeedWelding: + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Green, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Yellow, 1.0f); + break; + + case WorkingState.NeedGrinding: + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Green, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Blue, 1.0f); + break; + + case WorkingState.Idle: + _Welder.SetEmissiveParts("Emissive", Color.Red, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Green, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Black, 1.0f); + break; + + case WorkingState.Invalid: + case WorkingState.NotReady: + _Welder.SetEmissiveParts("Emissive", Color.White, 1.0f); + _Welder.SetEmissiveParts("EmissiveReady", Color.Black, 1.0f); + _Welder.SetEmissiveParts("EmissiveWorking", Color.Black, 1.0f); + break; + } + + var sound = _Sounds[(int)workingState]; + if (sound != null) + { + if (_SoundEmitter == null) + { + _SoundEmitter = new MyEntity3DSoundEmitter((VRage.Game.Entity.MyEntity)_Welder); + _SoundEmitter.CustomMaxDistance = 30f; + _SoundEmitter.CustomVolume = _SoundLevels[(int)workingState] * Settings.SoundVolume; + } + if (_SoundEmitterWorking == null) + { + _SoundEmitterWorking = new MyEntity3DSoundEmitter((VRage.Game.Entity.MyEntity)_Welder, true, 1f); + _SoundEmitterWorking.CustomMaxDistance = 30f; + _SoundEmitterWorking.CustomVolume = _SoundLevels[(int)workingState] * Settings.SoundVolume; + _SoundEmitterWorkingPosition = null; + } + + if (_SoundEmitter != null) + { + _SoundEmitter.StopSound(true); + _SoundEmitter.CustomVolume = _SoundLevels[(int)workingState] * Settings.SoundVolume; + _SoundEmitter.PlaySound(sound, true); + } + + if (_SoundEmitterWorking != null) + { + _SoundEmitterWorking.StopSound(true); + _SoundEmitterWorking.CustomVolume = _SoundLevels[(int)workingState] * Settings.SoundVolume; + _SoundEmitterWorking.SetPosition(null); //Reset + _SoundEmitterWorkingPosition = null; + //_SoundEmitterWorking.PlaySound(sound, true); done after position is set + } + } + else + { + _SoundEmitter?.StopSound(true); + + if (_SoundEmitterWorking != null) + { + _SoundEmitterWorking.StopSound(true); + _SoundEmitterWorking.SetPosition(null); //Reset + _SoundEmitterWorkingPosition = null; + } + } + UpdateWorkingEffectPosition(workingState); + } + + /// + /// Set the position of the visual and sound effects + /// + private void UpdateWorkingEffectPosition(WorkingState workingState) + { + if (_ParticleEffectWorking1 == null && _SoundEmitterWorking == null) return; + + Vector3D position; + MatrixD matrix; + if (State.CurrentWeldingBlock != null) + { + BoundingBoxD box; + State.CurrentWeldingBlock.GetWorldBoundingBox(out box, false); + matrix = box.Matrix; + position = matrix.Translation; + } + else if (State.CurrentGrindingBlock != null) + { + BoundingBoxD box; + State.CurrentGrindingBlock.GetWorldBoundingBox(out box, false); + matrix = box.Matrix; + position = matrix.Translation; + } + else + { + matrix = _Welder.WorldMatrix; + position = matrix.Translation; + } + + if (_LightEffect != null) + { + _LightEffect.Position = position; + _LightEffect.Intensity = MyUtils.GetRandomFloat(0.1f, 0.6f); + _LightEffect.UpdateLight(); + } + + if (_ParticleEffectWorking1 != null) + { + _ParticleEffectWorking1.WorldMatrix = matrix; + } + + var sound = _Sounds[(int)workingState]; + if (_SoundEmitterWorking != null && sound != null) + { + if (!_SoundEmitterWorking.IsPlaying || _SoundEmitterWorkingPosition == null || Math.Abs((_SoundEmitterWorkingPosition.Value - position).Length()) > 2) + { + _SoundEmitterWorking.SetPosition(position); + _SoundEmitterWorkingPosition = position; + _SoundEmitterWorking.PlaySound(sound, true); + } + } + } + + /// + /// Start visual effects for transport + /// + private void SetTransportEffects(bool active) + { + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedEffects & VisualAndSoundEffects.TransportVisualEffect) != 0) + { + if (active) + { + if (_ParticleEffectTransport1 != null) + { + Interlocked.Decrement(ref _ActiveTransportEffects); + _ParticleEffectTransport1.Stop(); + _ParticleEffectTransport1 = null; + } + + if (_ActiveTransportEffects < MaxTransportEffects) + { + MyParticlesManager.TryCreateParticleEffect(State.CurrentTransportIsPick ? PARTICLE_EFFECT_TRANSPORT1_PICK : PARTICLE_EFFECT_TRANSPORT1_DELIVER, ref MatrixD.Identity, ref Vector3D.Zero, uint.MaxValue, out _ParticleEffectTransport1); + if (_ParticleEffectTransport1 != null) + { + Interlocked.Increment(ref _ActiveTransportEffects); + _ParticleEffectTransport1.UserScale = 0.1f; + UpdateTransportEffectPosition(); + } + } + } + else + { + if (_ParticleEffectTransport1 != null) + { + Interlocked.Decrement(ref _ActiveTransportEffects); + _ParticleEffectTransport1.Stop(); + _ParticleEffectTransport1 = null; + } + } + } + _TransportStateSet = active; + } + + /// + /// Set the position of the visual effects for transport + /// + private void UpdateTransportEffectPosition() + { + if (_ParticleEffectTransport1 == null) return; + + var playTime = MyAPIGateway.Session.ElapsedPlayTime; + var elapsed = State.CurrentTransportTime.Ticks != 0 ? (double)playTime.Subtract(State.CurrentTransportStartTime).Ticks / State.CurrentTransportTime.Ticks : 0d; + elapsed = elapsed < 1 ? elapsed : 1; + elapsed = (elapsed > 0.5 ? 1 - elapsed : elapsed) * 2; + + MatrixD startMatrix; + var target = State.CurrentTransportTarget; + startMatrix = _Welder.WorldMatrix; + startMatrix.Translation = Vector3D.Transform(_EmitterPosition, _Welder.WorldMatrix); + + var direction = target.Value - startMatrix.Translation; + startMatrix.Translation += direction * elapsed; + _ParticleEffectTransport1.WorldMatrix = startMatrix; + } + + /// + /// + /// + /// + /// + internal Vector3D? ComputePosition(object target) + { + if (target is IMySlimBlock) + { + Vector3D endPosition; + ((IMySlimBlock)target).ComputeWorldCenter(out endPosition); + return endPosition; + } + else if (target is IMyEntity) return ((IMyEntity)target).WorldMatrix.Translation; + else if (target is Vector3D) return (Vector3D)target; + return null; + } + + /// + /// Get a list of currently missing components (Scripting) + /// + /// + internal Dictionary GetMissingComponentsDict() + { + var dict = new Dictionary(); + lock (State.MissingComponents) + { + foreach (var item in State.MissingComponents) + { + dict.Add(item.Key, item.Value); + } + } + return dict; + } + + /// + /// Get a list of currently build/repairable blocks (Scripting) + /// + /// + internal List GetPossibleWeldTargetsList() + { + var list = new List(); + lock (State.PossibleWeldTargets) + { + foreach (var blockData in State.PossibleWeldTargets) + { + if (!blockData.Ignore) list.Add(blockData.Block); + } + } + return list; + } + + /// + /// Get a list of currently grind blocks (Scripting) + /// + /// + internal List GetPossibleGrindTargetsList() + { + var list = new List(); + lock (State.PossibleGrindTargets) + { + foreach (var blockData in State.PossibleGrindTargets) + { + if (!blockData.Ignore) list.Add(blockData.Block); + } + } + return list; + } + + /// + /// Get a list of currently collectable floating objects (Scripting) + /// + /// + internal List GetPossibleCollectingTargetsList() + { + var list = new List(); + lock (State.PossibleFloatingTargets) + { + foreach (var floatingData in State.PossibleFloatingTargets) + { + if (!floatingData.Ignore) list.Add(floatingData.Entity); + } + } + return list; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemLocalization.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemLocalization.cs new file mode 100644 index 00000000..986c2bb3 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemLocalization.cs @@ -0,0 +1,550 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System.Collections.Generic; + using Sandbox.ModAPI; + using VRage; + using VRage.Utils; + + public static class Texts + { + public static readonly MyStringId ModeSettings_Headline; + public static readonly MyStringId SearchMode; + public static readonly MyStringId SearchMode_Tooltip; + public static readonly MyStringId SearchMode_Walk; + public static readonly MyStringId SearchMode_Fly; + + public static readonly MyStringId WorkMode; + public static readonly MyStringId WorkMode_Tooltip; + public static readonly MyStringId WorkMode_WeldB4Grind; + public static readonly MyStringId WorkMode_GrindB4Weld; + public static readonly MyStringId WorkMode_GrindIfWeldStuck; + public static readonly MyStringId WorkMode_WeldOnly; + public static readonly MyStringId WorkMode_GrindOnly; + + public static readonly MyStringId WeldSettings_Headline; + public static readonly MyStringId WeldUseIgnoreColor; + public static readonly MyStringId WeldUseIgnoreColor_Tooltip; + public static readonly MyStringId WeldBuildNew; + public static readonly MyStringId WeldBuildNew_Tooltip; + public static readonly MyStringId WeldToFuncOnly; + public static readonly MyStringId WeldToFuncOnly_Tooltip; + public static readonly MyStringId WeldPriority; + public static readonly MyStringId WeldPriority_Tooltip; + + public static readonly MyStringId GrindSettings_Headline; + public static readonly MyStringId GrindHeadline_Tooltip; + public static readonly MyStringId GrindUseGrindColor; + public static readonly MyStringId GrindUseGrindColor_Tooltip; + public static readonly MyStringId GrindJanitorEnemy; + public static readonly MyStringId GrindJanitorEnemy_Tooltip; + public static readonly MyStringId GrindJanitorNotOwned; + public static readonly MyStringId GrindJanitorNotOwned_Tooltip; + public static readonly MyStringId GrindJanitorNeutrals; + public static readonly MyStringId GrindJanitorNeutrals_Tooltip; + public static readonly MyStringId GrindJanitorDisableOnly; + public static readonly MyStringId GrindJanitorDisableOnly_Tooltip; + public static readonly MyStringId GrindJanitorHackOnly; + public static readonly MyStringId GrindJanitorHackOnly_Tooltip; + public static readonly MyStringId GrindPriority; + public static readonly MyStringId GrindPriority_Tooltip; + public static readonly MyStringId GrindOrderNearest; + public static readonly MyStringId GrindOrderNearest_Tooltip; + public static readonly MyStringId GrindOrderFurthest; + public static readonly MyStringId GrindOrderFurthest_Tooltip; + public static readonly MyStringId GrindOrderSmallest; + public static readonly MyStringId GrindOrderSmallest_Tooltip; + + public static readonly MyStringId CollectSettings_Headline; + public static readonly MyStringId CollectPriority; + public static readonly MyStringId CollectPriority_Tooltip; + public static readonly MyStringId CollectOnlyIfIdle; + public static readonly MyStringId CollectOnlyIfIdle_Tooltip; + public static readonly MyStringId CollectPushOre; + public static readonly MyStringId CollectPushOre_Tooltip; + public static readonly MyStringId CollectPushItems; + public static readonly MyStringId CollectPushItems_Tooltip; + public static readonly MyStringId CollectPushComp; + public static readonly MyStringId CollectPushComp_Tooltip; + + public static readonly MyStringId Color_PickCurrentColor; + public static readonly MyStringId Color_SetCurrentColor; + + public static readonly MyStringId Priority_Enable; + public static readonly MyStringId Priority_Disable; + public static readonly MyStringId Priority_Up; + public static readonly MyStringId Priority_Down; + + public static readonly MyStringId AreaShow; + public static readonly MyStringId AreaShow_Tooltip; + + public static readonly MyStringId AreaWidth; + public static readonly MyStringId AreaHeight; + public static readonly MyStringId AreaDepth; + + //public readonly static MyStringId RemoteCtrlBy; + //public readonly static MyStringId RemoteCtrlBy_Tooltip; + //public readonly static MyStringId RemoteCtrlBy_None; + //public readonly static MyStringId RemoteCtrlShowArea; + //public readonly static MyStringId RemoteCtrlShowArea_Tooltip; + //public readonly static MyStringId RemoteCtrlWorking; + //public readonly static MyStringId RemoteCtrlWorking_Tooltip; + + public static readonly MyStringId SoundVolume; + public static readonly MyStringId ScriptControlled; + public static readonly MyStringId ScriptControlled_Tooltip; + + public static readonly MyStringId Info_CurentWeldEntity; + public static readonly MyStringId Info_CurentGrindEntity; + public static readonly MyStringId Info_InventoryFull; + public static readonly MyStringId Info_LimitReached; + + //public readonly static MyStringId Info_DisabledByRemote; + public static readonly MyStringId Info_BlocksToBuild; + + public static readonly MyStringId Info_BlocksToGrind; + public static readonly MyStringId Info_ItemsToCollect; + public static readonly MyStringId Info_More; + public static readonly MyStringId Info_MissingItems; + public static readonly MyStringId Info_BlockSwitchedOff; + public static readonly MyStringId Info_BlockDamaged; + public static readonly MyStringId Info_BlockUnpowered; + + public static readonly MyStringId Cmd_HelpClient; + public static readonly MyStringId Cmd_HelpServer; + + static Texts() + { + var language = Mod.DisableLocalization ? MyLanguagesEnum.English : MyAPIGateway.Session.Config.Language; + Mod.Log.Write(Logging.Level.Error, "Localization: Disabled={0} Language={1}", Mod.DisableLocalization, language); + + var texts = LocalizationHelper.GetTexts(language, GetDictionaries(), Mod.Log); + ModeSettings_Headline = LocalizationHelper.GetStringId(texts, "ModeSettings_Headline"); + SearchMode = LocalizationHelper.GetStringId(texts, "SearchMode"); + SearchMode_Tooltip = LocalizationHelper.GetStringId(texts, "SearchMode_Tooltip"); + SearchMode_Walk = LocalizationHelper.GetStringId(texts, "SearchMode_Walk"); + SearchMode_Fly = LocalizationHelper.GetStringId(texts, "SearchMode_Fly"); + + WorkMode = LocalizationHelper.GetStringId(texts, "WorkMode"); + WorkMode_Tooltip = LocalizationHelper.GetStringId(texts, "WorkMode_Tooltip"); + WorkMode_WeldB4Grind = LocalizationHelper.GetStringId(texts, "WorkMode_WeldB4Grind"); + WorkMode_GrindB4Weld = LocalizationHelper.GetStringId(texts, "WorkMode_GrindB4Weld"); + WorkMode_GrindIfWeldStuck = LocalizationHelper.GetStringId(texts, "WorkMode_GrindIfWeldStuck"); + WorkMode_WeldOnly = LocalizationHelper.GetStringId(texts, "WorkMode_WeldOnly"); + WorkMode_GrindOnly = LocalizationHelper.GetStringId(texts, "WorkMode_GrindOnly"); + + WeldSettings_Headline = LocalizationHelper.GetStringId(texts, "WeldSettings_Headline"); + WeldUseIgnoreColor = LocalizationHelper.GetStringId(texts, "WeldUseIgnoreColor"); + WeldUseIgnoreColor_Tooltip = LocalizationHelper.GetStringId(texts, "WeldUseIgnoreColor_Tooltip"); + WeldBuildNew = LocalizationHelper.GetStringId(texts, "WeldBuildNew"); + WeldBuildNew_Tooltip = LocalizationHelper.GetStringId(texts, "WeldBuildNew_Tooltip"); + WeldToFuncOnly = LocalizationHelper.GetStringId(texts, "WeldToFuncOnly"); + WeldToFuncOnly_Tooltip = LocalizationHelper.GetStringId(texts, "WeldToFuncOnly_Tooltip"); + WeldPriority = LocalizationHelper.GetStringId(texts, "WeldPriority"); + WeldPriority_Tooltip = LocalizationHelper.GetStringId(texts, "WeldPriority_Tooltip"); + + GrindSettings_Headline = LocalizationHelper.GetStringId(texts, "GrindSettings_Headline"); + GrindUseGrindColor = LocalizationHelper.GetStringId(texts, "GrindUseGrindColor"); + GrindUseGrindColor_Tooltip = LocalizationHelper.GetStringId(texts, "GrindUseGrindColor_Tooltip"); + + GrindJanitorEnemy = LocalizationHelper.GetStringId(texts, "GrindJanitorEnemy"); + GrindJanitorEnemy_Tooltip = LocalizationHelper.GetStringId(texts, "GrindJanitorEnemy_Tooltip"); + GrindJanitorNotOwned = LocalizationHelper.GetStringId(texts, "GrindJanitorNotOwned"); + GrindJanitorNotOwned_Tooltip = LocalizationHelper.GetStringId(texts, "GrindJanitorNotOwned_Tooltip"); + GrindJanitorNeutrals = LocalizationHelper.GetStringId(texts, "GrindJanitorNeutrals"); + GrindJanitorNeutrals_Tooltip = LocalizationHelper.GetStringId(texts, "GrindJanitorNeutrals_Tooltip"); + GrindJanitorDisableOnly = LocalizationHelper.GetStringId(texts, "GrindJanitorDisableOnly"); + GrindJanitorDisableOnly_Tooltip = LocalizationHelper.GetStringId(texts, "GrindJanitorDisableOnly_Tooltip"); + GrindJanitorHackOnly = LocalizationHelper.GetStringId(texts, "GrindJanitorHackOnly"); + GrindJanitorHackOnly_Tooltip = LocalizationHelper.GetStringId(texts, "GrindJanitorHackOnly_Tooltip"); + + GrindPriority = LocalizationHelper.GetStringId(texts, "GrindPriority"); + GrindPriority_Tooltip = LocalizationHelper.GetStringId(texts, "GrindPriority_Tooltip"); + + GrindOrderNearest = LocalizationHelper.GetStringId(texts, "GrindOrderNearest"); + GrindOrderNearest_Tooltip = LocalizationHelper.GetStringId(texts, "GrindOrderNearest_Tooltip"); + GrindOrderFurthest = LocalizationHelper.GetStringId(texts, "GrindOrderFurthest"); + GrindOrderFurthest_Tooltip = LocalizationHelper.GetStringId(texts, "GrindOrderFurthest_Tooltip"); + GrindOrderSmallest = LocalizationHelper.GetStringId(texts, "GrindOrderSmallest"); + GrindOrderSmallest_Tooltip = LocalizationHelper.GetStringId(texts, "GrindOrderSmallest_Tooltip"); + + CollectSettings_Headline = LocalizationHelper.GetStringId(texts, "CollectSettings_Headline"); + CollectPriority = LocalizationHelper.GetStringId(texts, "CollectPriority"); + CollectPriority_Tooltip = LocalizationHelper.GetStringId(texts, "CollectPriority_Tooltip"); + CollectOnlyIfIdle = LocalizationHelper.GetStringId(texts, "CollectOnlyIfIdle"); + CollectOnlyIfIdle_Tooltip = LocalizationHelper.GetStringId(texts, "CollectOnlyIfIdle_Tooltip"); + CollectPushOre = LocalizationHelper.GetStringId(texts, "CollectPushOre"); + CollectPushOre_Tooltip = LocalizationHelper.GetStringId(texts, "CollectPushOre_Tooltip"); + CollectPushItems = LocalizationHelper.GetStringId(texts, "CollectPushItems"); + CollectPushItems_Tooltip = LocalizationHelper.GetStringId(texts, "CollectPushItems_Tooltip"); + CollectPushComp = LocalizationHelper.GetStringId(texts, "CollectPushComp"); + CollectPushComp_Tooltip = LocalizationHelper.GetStringId(texts, "CollectPushComp_Tooltip"); + + Color_PickCurrentColor = LocalizationHelper.GetStringId(texts, "Color_PickCurrentColor"); + Color_SetCurrentColor = LocalizationHelper.GetStringId(texts, "Color_SetCurrentColor"); + + Priority_Enable = LocalizationHelper.GetStringId(texts, "Priority_Enable"); + Priority_Disable = LocalizationHelper.GetStringId(texts, "Priority_Disable"); + Priority_Up = LocalizationHelper.GetStringId(texts, "Priority_Up"); + Priority_Down = LocalizationHelper.GetStringId(texts, "Priority_Down"); + + AreaShow = LocalizationHelper.GetStringId(texts, "AreaShow"); + AreaShow_Tooltip = LocalizationHelper.GetStringId(texts, "AreaShow_Tooltip"); + AreaWidth = LocalizationHelper.GetStringId(texts, "AreaWidth"); + AreaHeight = LocalizationHelper.GetStringId(texts, "AreaHeight"); + AreaDepth = LocalizationHelper.GetStringId(texts, "AreaDepth"); + + //RemoteCtrlBy = LocalizationHelper.GetStringId(texts, "RemoteCtrlBy"); + //RemoteCtrlBy_Tooltip = LocalizationHelper.GetStringId(texts, "RemoteCtrlBy_Tooltip"); + //RemoteCtrlBy_None = LocalizationHelper.GetStringId(texts, "RemoteCtrlBy_None"); + + //RemoteCtrlShowArea = LocalizationHelper.GetStringId(texts, "RemoteCtrlShowArea"); + //RemoteCtrlShowArea_Tooltip = LocalizationHelper.GetStringId(texts, "RemoteCtrlShowArea_Tooltip"); + //RemoteCtrlWorking = LocalizationHelper.GetStringId(texts, "RemoteCtrlWorking"); + //RemoteCtrlWorking_Tooltip = LocalizationHelper.GetStringId(texts, "RemoteCtrlWorking_Tooltip"); + + SoundVolume = LocalizationHelper.GetStringId(texts, "SoundVolume"); + ScriptControlled = LocalizationHelper.GetStringId(texts, "ScriptControlled"); + ScriptControlled_Tooltip = LocalizationHelper.GetStringId(texts, "ScriptControlled_Tooltip"); + + Info_CurentWeldEntity = LocalizationHelper.GetStringId(texts, "Info_CurentWeldEntity"); + Info_CurentGrindEntity = LocalizationHelper.GetStringId(texts, "Info_CurentGrindEntity"); + Info_InventoryFull = LocalizationHelper.GetStringId(texts, "Info_InventoryFull"); + Info_LimitReached = LocalizationHelper.GetStringId(texts, "Info_LimitReached"); + //Info_DisabledByRemote = LocalizationHelper.GetStringId(texts, "Info_DisabledByRemote"); + Info_BlocksToBuild = LocalizationHelper.GetStringId(texts, "Info_BlocksToBuild"); + Info_BlocksToGrind = LocalizationHelper.GetStringId(texts, "Info_BlocksToGrind"); + Info_ItemsToCollect = LocalizationHelper.GetStringId(texts, "Info_ItemsToCollect"); + Info_More = LocalizationHelper.GetStringId(texts, "Info_More"); + Info_MissingItems = LocalizationHelper.GetStringId(texts, "Info_MissingItems"); + Info_BlockSwitchedOff = LocalizationHelper.GetStringId(texts, "Info_BlockSwitchedOff"); + Info_BlockDamaged = LocalizationHelper.GetStringId(texts, "Info_BlockDamaged"); + Info_BlockUnpowered = LocalizationHelper.GetStringId(texts, "Info_BlockUnpowered"); + + Cmd_HelpClient = LocalizationHelper.GetStringId(texts, "Cmd_HelpClient"); + Cmd_HelpServer = LocalizationHelper.GetStringId(texts, "Cmd_HelpServer"); + } + + public static Dictionary GetDictionary(MyLanguagesEnum language) + { + return LocalizationHelper.GetTexts(language, GetDictionaries(), null); + } + + private static Dictionary> GetDictionaries() + { + var dicts = new Dictionary> + { + { MyLanguagesEnum.English, new Dictionary + { + {"ModeSettings_Headline", "———————Mode Settings———————"}, + {"SearchMode", "Mode"}, + {"SearchMode_Tooltip", "Select how the nanobots search and reach their targets."}, + {"SearchMode_Walk", "Walk mode"}, + {"SearchMode_Fly", "Fly mode"}, + {"WorkMode", "WorkMode"}, + {"WorkMode_Tooltip", "Select how the nanobots decide what to do (weld or grind)."}, + {"WorkMode_WeldB4Grind", "Weld before grind"}, + {"WorkMode_GrindB4Weld", "Grind before weld"}, + {"WorkMode_GrindIfWeldStuck", "Grind if weld get stuck"}, + {"WorkMode_WeldOnly", "Welding only"}, + {"WorkMode_GrindOnly", "Grinding only"}, + {"WeldSettings_Headline", "———————Settings for Welding———————"}, + {"WeldUseIgnoreColor", "Use Ignore Color"}, + {"WeldUseIgnoreColor_Tooltip", "When checked, the system will ignore blocks with the color defined further down."}, + {"WeldBuildNew", "Build new"}, + {"WeldBuildNew_Tooltip", "When checked, the System will also construct projected blocks."}, + {"WeldToFuncOnly", "Weld to functional only"}, + {"WeldToFuncOnly_Tooltip", "When checked, bock only welded to functional state."}, + {"WeldPriority", "Welding Priority"}, + {"WeldPriority_Tooltip", "Enable/Disable build-repair of selected items kinds"}, + + {"GrindSettings_Headline", "———————Settings for Grinding———————"}, + {"GrindUseGrindColor", "Use Grind Color"}, + {"GrindUseGrindColor_Tooltip", "When checked, the system will grind blocks with the color defined further down."}, + {"GrindJanitorEnemy", "Janitor grinds enemy blocks"}, + {"GrindJanitorEnemy_Tooltip", "When checked, enemy blocks in range will be grinded."}, + {"GrindJanitorNotOwned", "Janitor grinds not owned blocks"}, + {"GrindJanitorNotOwned_Tooltip", "When checked, blocks without owner in range will be grinded."}, + {"GrindJanitorNeutrals", "Janitor grinds neutral blocks"}, + {"GrindJanitorNeutrals_Tooltip", "When checked, the system will grind also blocks owned by neutrals (factions not at war)."}, + {"GrindJanitorDisableOnly", "Janitor grind to disable only"}, + {"GrindJanitorDisableOnly_Tooltip", "When checked, only functional blocks are grinded and these only until they stop working."}, + {"GrindJanitorHackOnly", "Janitor grind to hack only"}, + {"GrindJanitorHackOnly_Tooltip", "When checked, only functional blocks are grinded and these only until they could be hacked."}, + {"GrindPriority", "Grind Priority"}, + {"GrindPriority_Tooltip", "Enable/Disable grinding of selected items kinds and set the priority while grinding\n(If grinded by grind color the priority and release status is ignored)"}, + {"GrindOrderNearest", "Nearest First"}, + {"GrindOrderNearest_Tooltip", "When checked, if blocks have the same priority, the nearest is grinded first."}, + {"GrindOrderFurthest", "Furthest first"}, + {"GrindOrderFurthest_Tooltip", "When checked, if blocks have the same priority, the furthest is grinded first."}, + {"GrindOrderSmallest", "Smallest grid first"}, + {"GrindOrderSmallest_Tooltip", "When checked, if blocks have the same priority, the smallest grid is grinded first."}, + + {"CollectSettings_Headline", "———————Settings for Collecting———————"}, + {"CollectPriority", "Collect Priority"}, + {"CollectPriority_Tooltip", "Enable/Disable collecting of selected items kind"}, + {"CollectOnlyIfIdle", "Collect only if idle"}, + {"CollectOnlyIfIdle_Tooltip", "if set collecting floating objects is done only if no welding/grinding is needed."}, + {"CollectPushOre", "Push ingot/ore immediately"}, + {"CollectPushOre_Tooltip", "When checked, the system will push ingot/ore immediately into connected container."}, + {"CollectPushItems", "Push items immediately"}, + {"CollectPushItems_Tooltip", "When checked, the system will push items (tools,weapons,ammo,bottles, ..) immediately into connected container."}, + {"CollectPushComp", "Push components immediately"}, + {"CollectPushComp_Tooltip", "When checked, the system will push components immediately into connected container."}, + + {"Priority_Enable", "Enable"}, + {"Priority_Disable", "Disable"}, + {"Priority_Up", "Priority Up"}, + {"Priority_Down", "Priority Down"}, + + {"Color_PickCurrentColor", "Pick current build color"}, + {"Color_SetCurrentColor", "Set current build color"}, + + {"AreaShow", "Show Area"}, + {"AreaShow_Tooltip", "When checked, it will show you the area this system covers"}, + {"AreaWidth", "Area Width"}, + {"AreaHeight", "Area Height"}, + {"AreaDepth", "Area Depth"}, + {"RemoteCtrlBy", "Remote controlled by"}, + {"RemoteCtrlBy_Tooltip", "Select if center of working area should follow a character. (As long as he is inside the maximum range)"}, + {"RemoteCtrlBy_None", "-None-"}, + {"RemoteCtrlShowArea", "Control Show Area"}, + {"RemoteCtrlShowArea_Tooltip", "Select if 'Show area' is active as long as character is equipped with hand welder/grinder"}, + {"RemoteCtrlWorking", "Control Working"}, + {"RemoteCtrlWorking_Tooltip", "Select if drill is only switched on as long as character is equipped with hand welder/grinder"}, + {"SoundVolume", "Sound Volume"}, + {"ScriptControlled", "Controlled by Script"}, + {"ScriptControlled_Tooltip", "When checked, the system will not build/repair blocks automatically. Each block has to be picked by calling scripting functions."}, + {"Info_CurentWeldEntity", "Picked Welding Block:"}, + {"Info_CurentGrindEntity", "Picked Grinding Block:"}, + {"Info_InventoryFull", "Block inventory is full!"}, + {"Info_LimitReached", "PCU limit reached!"}, + {"Info_DisabledByRemote", "Disabled by remote control!"}, + {"Info_BlocksToBuild", "Blocks to build:"}, + {"Info_BlocksToGrind", "Blocks to dismantle:"}, + {"Info_ItemsToCollect", "Floatings to collect:"}, + {"Info_More", " -.."}, + {"Info_MissingItems", "Missing items:"}, + {"Info_BlockSwitchedOff", "Block is switched off"}, + {"Info_BlockDamaged", "Block is damaged / incomplete"}, + {"Info_BlockUnpowered", "Block has not enough power"}, + {"Cmd_HelpClient", "Version: {0}" + + "\nAvailable commands:" + + "\n[{1};{2}]: Shows this info" + + "\n[{3} {4};{5}]: Set the current logging level. Warning: Setting level to '{4}' could produce very large log-files" + + "\n[{6} {7}]: Export the current translations for the selected language into a file located in {8}"}, + {"Cmd_HelpServer", "\n[{0}]: Creates a settings file inside your current world folder. After restart the settings in this file will be used, instead of the global mod-settings file." + + "\n[{1}]: Creates a global settings file inside mod folder (including all options)."} + } + }, + { MyLanguagesEnum.German, new Dictionary + { + {"ModeSettings_Headline", "—— Moduseinstellungen ——"}, + {"SearchMode", "Mode"}, + {"SearchMode_Tooltip", "Wählen Sie aus, wie die Nanobots ihre Ziele suchen und erreichen."}, + {"SearchMode_Walk", "Laufmodus"}, + {"SearchMode_Fly", "Flugmodus"}, + {"WorkMode", "Arbeitsmodus"}, + {"WorkMode_Tooltip", "Wählen Sie aus, wie die Nanobots entscheiden was zu tun ist (Schweißen oder Demontieren)."}, + {"WorkMode_WeldB4Grind", "Schweißen vor Demontieren"}, + {"WorkMode_GrindB4Weld", "Demontieren vor Schweißen"}, + {"WorkMode_GrindIfWeldStuck", "Demontieren wenn Schweißen blockiert ist"}, + {"WorkMode_WeldOnly", "Nur Schweißen"}, + {"WorkMode_GrindOnly", "Nur Demontieren"}, + {"WeldSettings_Headline", "——Einstellungen fürs Schweißen——"}, + {"WeldUseIgnoreColor", "Ignorierfarbe verwenden"}, + {"WeldUseIgnoreColor_Tooltip", "Wenn diese Option markiert ist, wird das System alle Blöcke, die die weiter unten definierte Farbe besitzen, ignorieren (nicht fertig schweißen)."}, + {"WeldBuildNew", "Neue Blöcke erzeugen"}, + {"WeldBuildNew_Tooltip", "Wenn diese Option markiert ist, wird das System auch projizierte Blöcke erzeugen und schweißen."}, + {"WeldToFuncOnly", "Nur bis Funktionsstufe schweißen"}, + {"WeldToFuncOnly_Tooltip", "Wenn diese Option markiert ist, werden Blöcke nur bis zur der Stufe geschweißt in der sie bereits arbeiten können."}, + {"WeldPriority", "Schweiß Priorität"}, + {"WeldPriority_Tooltip", "Schaltet das Erzeugen/Reparieren der selektierten Typen von Blöcken ein/aus"}, + + {"GrindSettings_Headline", "——Einstellungen fürs Demontieren——"}, + {"GrindUseGrindColor", "Demontierfarbe verwenden"}, + {"GrindUseGrindColor_Tooltip", "Wenn diese Option markiert ist, wird das System alle Blöcke, die die weiter unten definierte Farbe besitzen, demontieren."}, + {"GrindJanitorEnemy", "Aufräumen: Feindliche Blöcke demontieren"}, + {"GrindJanitorEnemy_Tooltip", "Wenn diese Option markiert ist, wird das System alle feindlichen Blöcke in Reichweite demontieren."}, + {"GrindJanitorNotOwned", "Aufräumen: Blöcke ohne Besitzer demontieren"}, + {"GrindJanitorNotOwned_Tooltip", "Wenn diese Option markiert ist, wird das System alle Blöcke ohne Besitzer in Reichweite demontieren."}, + {"GrindJanitorNeutrals", "Aufräumen: Blöcke von neutralen Besitzern demontieren"}, + {"GrindJanitorNeutrals_Tooltip", "Wenn diese Option markiert ist, wird das System alle Blöcke die neutralen Besitzern (Fraktionen die sich nicht im Krieg befinden) gehören in Reichweite demontieren."}, + {"GrindJanitorDisableOnly", "Aufräumen: Demontieren nur bis funktionslos"}, + {"GrindJanitorDisableOnly_Tooltip", "Wenn diese Option markiert ist, wird das System Blöcke nur solange demontieren bis sie ausser Funktion sind."}, + {"GrindJanitorHackOnly", "Aufräumen: Demontieren nur bis übernehmbar"}, + {"GrindJanitorHackOnly_Tooltip", "Wenn diese Option markiert ist, wird das System Blöcke nur solange demontieren bis sie übernehmbar (Hackbar) sind."}, + {"GrindPriority", "Zerlege Priorität"}, + {"GrindPriority_Tooltip", "Schlaltet das Demontieren des selektierten Blocktypes ein/aus und legt die Priorität fest.\n(Wenn das Demontieren per festgelegter Farbe erfolgt, wird die Priorät und die Freigabe ignorierd)"}, + {"GrindOrderNearest", "Nächstgelegen zurerst"}, + {"GrindOrderNearest_Tooltip", "Wenn diese Option markiert ist und Blöcke die gleiche Priorität besitzen, wird der nächgelegen Block zuerst demontiert."}, + {"GrindOrderFurthest", "Enferntester zuerst"}, + {"GrindOrderFurthest_Tooltip", "Wenn diese Option markiert ist und Blöcke die gleiche Priorität besitzen, wird der enfernteste Block zuerst demontiert."}, + {"GrindOrderSmallest", "Kleinster Verbund zuerst"}, + {"GrindOrderSmallest_Tooltip", "Wenn diese Option markiert ist und Blöcke die gleiche Priorität besitzen, werden die Blöcke im kleinsten Verbund zuerst demontiert."}, + + {"CollectSettings_Headline", "—— Einstellungen zum Sammeln ——————"}, + {"CollectPriority", "Sammelpriorität"}, + {"CollectPriority_Tooltip", "Sammeln von Objekten ein/ausschalten"}, + {"CollectOnlyIfIdle", "Nur im Leerlauf sammeln"}, + {"CollectOnlyIfIdle_Tooltip", "Wenn das Sammeln von freien Objekten eingestellt ist, erfolgt dies nur, wenn kein Schweißen / Demontieren erforderlich ist."}, + {"CollectPushOre", "Erze sofort auslagern"}, + {"CollectPushOre_Tooltip", "Wenn diese Option markiert ist, wird das Sytem sofort versuchen Erz in angschlosse Container auszulagern."}, + {"CollectPushItems", "Objekte sofort auslagern"}, + {"CollectPushItems_Tooltip", "Wenn diese Option markiert ist, wird das System sofort versuchen Objekte (Werkzeuge, Waffen, Munition, Flaschen, ..) in angschlosse Container auszulagern."}, + {"CollectPushComp", "Komponenten sofort auslagern"}, + {"CollectPushComp_Tooltip", "Wenn diese Option markiert ist, wird das System sofort versuchen Komponenten in angschlosse Container auszulagern."}, + + {"Priority_Enable", "Aktivieren"}, + {"Priority_Disable", "Deaktivieren"}, + {"Priority_Up", "Priorität hoch"}, + {"Priority_Down", "Priorität runter"}, + + {"Color_PickCurrentColor", "Aktuelle Farbe übernehmen"}, + {"Color_SetCurrentColor", "Aktuelle Farbe setzen"}, + + {"AreaShow", "Bereich anzeigen"}, + {"AreaShow_Tooltip", "Wenn diese Option aktiviert ist, wird der Bereich angezeigt, den dieses System abdeckt."}, + {"AreaWidth", "Bereichsbreite"}, + {"AreaHeight", "Bereichshöhe"}, + {"AreaDepth", "Bereichstiefe"}, + {"RemoteCtrlBy", "Ferngesteuert von"}, + {"RemoteCtrlBy_Tooltip", "Wählen Sie aus, ob die Mitte des Arbeitsbereichs einem Charakter folgen soll. (Solange er sich innerhalb der maximalen Reichweite befindet) "}, + {"RemoteCtrlBy_None", "-Keinem-"}, + {"RemoteCtrlShowArea", "Bereichsanzeige steuern"}, + {"RemoteCtrlShowArea_Tooltip", "Wählen Sie, ob 'Bereich anzeigen' aktiv ist, solange der Charakter mit einem Schweißgerät oder Winkelschleifer ausgestattet ist."}, + {"RemoteCtrlWorking", "Block ein/aus steuern"}, + {"RemoteCtrlWorking_Tooltip", "Wählen Sie, ob der Block nur eingeschaltet ist, solange der Charakter mit einem Schweißgerät oder Winkelschleifer ausgestattet ist."}, + {"SoundVolume", "Lautstärke"}, + {"ScriptControlled", "Vom Skript gesteuert"}, + {"ScriptControlled_Tooltip", "Wenn diese Option aktiviert ist, bohrt / füllt das System nicht automatisch. Jede Aktion muss durch Aufrufen von Skriptfunktionen ausgewählt werden."}, + + {"Info_CurentWeldEntity", "Aktuell geschweißter Block:"}, + {"Info_CurentGrindEntity", "Aktuell demontierter Block:"}, + {"Info_InventoryFull", "Blockinventar ist voll!"}, + {"Info_LimitReached", "PCU Limit erreicht!"}, + {"Info_DisabledByRemote", "Durch Fernbedienung deaktiviert!"}, + {"Info_BlocksToBuild", "Zu schweißende Blöcke:"}, + {"Info_BlocksToGrind", "Zu demontierende Blöcke:"}, + {"Info_ItemsToCollect", "Zu sammelnde Objekte:"}, + {"Info_More", " -.."}, + {"Info_MissingItems", "Fehlenden Komponenten:"}, + {"Info_BlockSwitchedOff", "Block ist ausgeschaltet"}, + {"Info_BlockDamaged", "Block ist beschädigt / unvollständig"}, + {"Info_BlockUnpowered", "Block hat nicht genug Energie"}, + {"Cmd_HelpClient", "Version: {0}" + + "\nVerfügbare Befehle:" + + "\n[{1}; {2}]: Zeigt diese Info an" + + "\n[{3} {4}; {5}]: Legen Sie die aktuelle Protokollierungsstufe fest. Warnung: Das Setzen der Stufe auf '{4}' kann zu sehr großen Protokolldateien führen." + + "\n[{6} {7}]: Exportiert die aktuelle Übersetzung für dei gewählte Sprache in eine Datei im Ordner: {8}"}, + {"Cmd_HelpServer", "\n[{0}]: Erstellt eine Einstellungsdatei in Ihrem aktuellen Weltordner. Nach dem Neustart werden die Einstellungen in dieser Datei anstelle der globalen Mod - Einstellungsdatei verwendet."+ + "\n[{1}]: Erstellt eine globale Einstellungsdatei im Mod-Ordner (einschließlich aller Optionen)."} + } + }, + { MyLanguagesEnum.Russian, new Dictionary + { + {"ModeSettings_Headline", "——————— Настройки режима ———————"}, + {"SearchMode", "Режим"}, + {"SearchMode_Tooltip", "Выбор способа поиска целей наноботами"}, + {"SearchMode_Walk", "Только эта сетка в поле"}, + {"SearchMode_Fly", "Все сетки в поле"}, + {"WorkMode", "Режим работы"}, + {"WorkMode_Tooltip", "Выберите, как наноботы решают, что делать (сварка или распиливание)."}, + {"WorkMode_WeldB4Grind", "Сварка затем распиливание"}, + {"WorkMode_GrindB4Weld", "Распиливание затем сварка"}, + {"WorkMode_GrindIfWeldStuck", "Распиливать, если не сваривает"}, + {"WorkMode_WeldOnly", "Только сварка"}, + {"WorkMode_GrindOnly", "Только распиливание"}, + {"WeldSettings_Headline", "——————— Настройки для сварки ———————"}, + {"WeldUseIgnoreColor", "Игнорировать цвет"}, + {"WeldUseIgnoreColor_Tooltip", "Когда установлен этот флажок, система будет\nигнорировать блоки с цветом, определенным ниже."}, + {"WeldBuildNew", "Построить новый"}, + {"WeldBuildNew_Tooltip", "Если этот флажок установлен, система также будет\nсоздавать проецируемые блоки."}, + {"WeldToFuncOnly", "Перестал быть активным"}, + {"WeldToFuncOnly_Tooltip", "Сваривать только когда блок перестал функционировать\nиз за повреждений/распиливания."}, + {"WeldPriority", "Приоритет сварки"}, + {"WeldPriority_Tooltip", "Включить/выключить сборку-ремонт выбранных видов предметов."}, + + {"GrindSettings_Headline", "——————— Настройки для распиливания ———————"}, + {"GrindUseGrindColor", "Цвет для распиливания"}, + {"GrindUseGrindColor_Tooltip", "Когда установлен этот флажок, система будет\nраспиливать блоки с цветом, определенным ниже."}, + {"GrindJanitorEnemy", "Распиливать вражеские блоки"}, + {"GrindJanitorEnemy_Tooltip", "Если вражеские блоки в радиусе действия то они будут распилены."}, + {"GrindJanitorNotOwned", "Распиливать блоки без владельца"}, + {"GrindJanitorNotOwned_Tooltip", "Распиливать блоки без владельца в радиусе действия."}, + {"GrindJanitorNeutrals", "Распиливать нейтральные блоки"}, + {"GrindJanitorNeutrals_Tooltip", "Распиливать блоки принадлежащие нейтралам (не воюющим фракциям)."}, + {"GrindJanitorDisableOnly", "Распиливать до отключения"}, + {"GrindJanitorDisableOnly_Tooltip", "Если этот флажок установлен, распиливаются только\nфункционирующие блоки, и только до тех пор, пока они не перестанут работать."}, + {"GrindJanitorHackOnly", "Распиливать до взлома"}, + {"GrindJanitorHackOnly_Tooltip", "Если этот флажок установлен, распиливаются только\nфункционирующие блоки, и только до тех пор, пока они не будут взломаны."}, + {"GrindPriority", "Приоритет распиливания"}, + {"GrindPriority_Tooltip", "Включить/выключить распиливание выбранных видов\nэлементов и установить приоритет во время распиливания (Если распиливать по цвету распиливания, приоритет и статус игнорируются)."}, + {"GrindOrderNearest", "Сначала ближайший блок"}, + {"GrindOrderNearest_Tooltip", "Если блоки имеют одинаковый приоритет, ближайший распиливается первым."}, + {"GrindOrderFurthest", "Сначала самый дальний блок"}, + {"GrindOrderFurthest_Tooltip", "Если блоки имеют одинаковый приоритет, самый\nдальний распиливается первым."}, + {"GrindOrderSmallest", "Сначала самый маленький блок"}, + {"GrindOrderSmallest_Tooltip", "Если блоки имеют одинаковый приоритет, вначале\nраспиливается самый маленький блок."}, + + {"CollectSettings_Headline", "——————— Настройки для сбора ———————"}, + {"CollectPriority", "Приоритет сбора"}, + {"CollectPriority_Tooltip", "Включить/отключить сбор выбранного вида предметов."}, + {"CollectOnlyIfIdle", "Только в режиме ожидания"}, + {"CollectOnlyIfIdle_Tooltip", "Сбор плавающих объектов выполняется только\nесли не выполняется сварка/распил"}, + {"CollectPushOre", "Отправлять слитки/руду"}, + {"CollectPushOre_Tooltip", "Если этот флажок установлен, система будет\nсразу отправлять слитки/руду в подключенный контейнер."}, + {"CollectPushItems", "Отправить предметы"}, + {"CollectPushItems_Tooltip", "Если этот флажок установлен, система будетотправлять предметы\n(инструменты, оружие, боеприпасы, бутылки и т.д.) в подключенный контейнер."}, + {"CollectPushComp", "Отправить компоненты"}, + {"CollectPushComp_Tooltip", "Если этот флажок установлен, система немедленно\nотправит компоненты в подключенный контейнер."}, + + {"Priority_Enable", "Вкл"}, + {"Priority_Disable", "Откл"}, + {"Priority_Up", "Приоритет вверх"}, + {"Priority_Down", "Приоритет вниз"}, + + {"Color_PickCurrentColor", "Выбрать текущий цвет"}, + {"Color_SetCurrentColor", "Установить цвет"}, + + {"AreaShow", "Показать область"}, + {"AreaShow_Tooltip", "Когда флажок установлен, он покажет область,\nкоторую охватывает эта система"}, + {"AreaWidth", "Ширина области"}, + {"AreaHeight", "Высота области"}, + {"AreaDepth", "Глубина области"}, + {"RemoteCtrlBy", "Кем управляется"}, + {"RemoteCtrlBy_Tooltip", "Если выбрано, центр рабочей области будет следовать\nза персонажем (Пока он находится в пределах максимальной дистанции)."}, + {"RemoteCtrlBy_None", "-Никто-"}, + {"RemoteCtrlShowArea", "Контроль показа области"}, + {"RemoteCtrlShowArea_Tooltip", "Если отмечено, область будет отображатся пока\nперсонаж оснащен ручным сварщиком/резаком."}, + {"RemoteCtrlWorking", "Контроль работы"}, + {"RemoteCtrlWorking_Tooltip", "Если отмечено, система будет включена пока\nперсонаж оснащен ручным сварщиком/резаком."}, + {"SoundVolume", "Громкость"}, + {"ScriptControlled", "Контролируется скриптом"}, + {"ScriptControlled_Tooltip", "Если этот флажок установлен, система не будет автоматически\nсваривать/разрезать. Каждое действие нужно выбирать, вызывая скриптовые функции."}, + {"Info_CurentWeldEntity", "Выбран блок сварщика:"}, + {"Info_CurentGrindEntity", "Выбран блок резака:"}, + {"Info_InventoryFull", "Блок инвентаря полон!"}, + {"Info_LimitReached", "Достигнут предел PCU!"}, + {"Info_DisabledByRemote", "Отключено дистанционным управлением!"}, + {"Info_BlocksToBuild", "Блоки для сборки:"}, + {"Info_BlocksToGrind", "Блоки для демонтажа:"}, + {"Info_ItemsToCollect", "Предметы для сбора:"}, + {"Info_More", "- .."}, + {"Info_MissingItems", "Недостающие предметы:"}, + {"Info_BlockSwitchedOff", "Блок выключен"}, + {"Info_BlockDamaged", "Блок поврежден/недостроен"}, + {"Info_BlockUnpowered", "Блоку не хватает мощности"}, + {"Cmd_HelpClient", "Версия: {0}" + + "\nДоступные команды:" + + "\n[{1}; {2}]: показать эту информацию" + + "\n[{3} {4}; {5}]: установить текущий уровень ведения журнала. Предупреждение: установка уровня '{4}' может привести к очень большим лог-файлам"}, + {"Cmd_HelpServer", "\n[{0}]: создает файл настроек в текущей папке мира. После перезагрузки будут использованы настройки в этом файле вместо файла глобальных мод-настроек. "+ + "\n[{1}]: создает файл глобальных настроек в папке мода (включая все опции)."} + } + } + }; + + return dicts; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemMod.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemMod.cs new file mode 100644 index 00000000..ffb5a958 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemMod.cs @@ -0,0 +1,699 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System; + using System.Collections.Generic; + using System.IO; + using Sandbox.ModAPI; + using Sandbox.ModAPI.Weapons; + using VRage; + using VRage.Game; + using VRage.Game.Components; + using VRage.Game.ModAPI; + using VRage.ModAPI; + + internal static class Mod + { + public static Logging Log = new Logging("NanobotBuildAndRepairSystem", 0, "NanobotBuildAndRepairSystem.log", typeof(NanobotBuildAndRepairSystemMod)) + { + LogLevel = Logging.Level.Error, //Default + EnableHudNotification = false + }; + + public static bool DisableLocalization = false; + } + + [MySessionComponentDescriptor(MyUpdateOrder.BeforeSimulation)] + public class NanobotBuildAndRepairSystemMod : MySessionComponentBase + { + private const string Version = "V2.1.5 2020-04-12" + ; + + private const string CmdKey = "/nanobars"; + private const string CmdHelp1 = "-?"; + private const string CmdHelp2 = "-help"; + private const string CmdCwsf = "-cwsf"; + private const string CmdCpsf = "-cpsf"; + private const string CmdLogLevel = "-loglevel"; + private const string CmdWritePerfCounter = "-writeperf"; + private const string CmdWriteTranslation = "-writetranslation"; + + private const string CmdLogLevel_All = "all"; + private const string CmdLogLevel_Default = "default"; + + private static readonly ushort MSGID_MOD_DATAREQUEST = 40000; + private static readonly ushort MSGID_MOD_SETTINGS = 40001; + private static readonly ushort MSGID_MOD_COMMAND = 40010; + private static readonly ushort MSGID_BLOCK_DATAREQUEST = 40100; + private static readonly ushort MSGID_BLOCK_SETTINGS_FROM_SERVER = 40102; + private static readonly ushort MSGID_BLOCK_SETTINGS_FROM_CLIENT = 40103; + private static readonly ushort MSGID_BLOCK_STATE_FROM_SERVER = 40104; + + private bool _Init = false; + public static bool SettingsValid = false; + public static SyncModSettings Settings = new SyncModSettings(); + private static TimeSpan _LastSourcesAndTargetsUpdateTimer; + private static readonly TimeSpan SourcesAndTargetsUpdateTimerInterval = new TimeSpan(0, 0, 2); + private static TimeSpan _LastSyncModDataRequestSend; + + public static Guid ModGuid = new Guid("8B57046C-DA20-4DE1-8E35-513FD21E3B9F"); + public const int MaxBackgroundTasks_Default = 4; + public const int MaxBackgroundTasks_Max = 10; + public const int MaxBackgroundTasks_Min = 1; + + public static List AsynActions = new List(); + private static int ActualBackgroundTaskCount = 0; + + /// + /// Current known Build and Repair Systems in world + /// + private static Dictionary _BuildAndRepairSystems; + + public static Dictionary BuildAndRepairSystems + { + get + { + return _BuildAndRepairSystems ?? (_BuildAndRepairSystems = new Dictionary()); + } + } + + /// + /// + /// + public void Init() + { + Mod.Log.Write("BuildAndRepairSystemMod: Initializing IsServer={0}, IsDedicated={1}", MyAPIGateway.Session.IsServer, MyAPIGateway.Utilities.IsDedicated); + _Init = true; + + Settings = SyncModSettings.Load(); + SettingsValid = MyAPIGateway.Session.IsServer; + Mod.Log.LogLevel = Settings.LogLevel; + SettingsChanged(); + + MyAPIGateway.Session.DamageSystem.RegisterBeforeDamageHandler(0, BeforeDamageHandlerNoDamageByBuildAndRepairSystem); + if (MyAPIGateway.Session.IsServer) + { + //Detect friendly damage (only needed on server) + MyAPIGateway.Session.DamageSystem.RegisterAfterDamageHandler(100, AfterDamageHandlerNoDamageByBuildAndRepairSystem); + + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_MOD_COMMAND, SyncModCommandReceived); + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_MOD_DATAREQUEST, SyncModDataRequestReceived); + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_BLOCK_DATAREQUEST, SyncBlockDataRequestReceived); + //Same as MSGID_BLOCK_SETTINGS but SendMessageToOthers sends also to self, which will result in stack overflow + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_BLOCK_SETTINGS_FROM_CLIENT, SyncBlockSettingsReceived); + } + else + { + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_MOD_SETTINGS, SyncModSettingsReceived); + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_BLOCK_SETTINGS_FROM_SERVER, SyncBlockSettingsReceived); + MyAPIGateway.Multiplayer.RegisterMessageHandler(MSGID_BLOCK_STATE_FROM_SERVER, SyncBlockStateReceived); + SyncModDataRequestSend(); + } + MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered; + + Mod.Log.Write("BuildAndRepairSystemMod: Initialized."); + } + + /// + /// + /// + private static void SettingsChanged() + { + if (SettingsValid) + { + foreach (var entry in BuildAndRepairSystems) + { + entry.Value.SettingsChanged(); + } + InitControls(); + } + } + + /// + /// + /// + public static void InitControls() + { + //Call also on dedicated else the properties for the scripting interface are not initialized + if (SettingsValid && !NanobotBuildAndRepairSystemTerminal.CustomControlsInit && BuildAndRepairSystems.Count > 0) NanobotBuildAndRepairSystemTerminal.InitializeControls(); + } + + /// + /// + /// + private void Utilities_MessageEntered(string messageText, ref bool sendToOthers) + { + if (string.IsNullOrEmpty(messageText)) return; + var cmd = messageText.ToLower(); + if (cmd.StartsWith(CmdKey)) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemMod: Cmd: {0}", messageText); + var args = cmd.Remove(0, CmdKey.Length).Trim().Split(' '); + if (args.Length > 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemMod: Cmd args[0]: {0}", args[0]); + switch (args[0].Trim()) + { + case CmdCpsf: + if (MyAPIGateway.Session.IsServer) + { + SyncModSettings.Save(Settings, false); + MyAPIGateway.Utilities.ShowMessage(CmdKey, "Settings file created inside mod folder"); + } + else + { + MyAPIGateway.Utilities.ShowMessage(CmdKey, "command not allowed on client"); + } + break; + + case CmdCwsf: + if (MyAPIGateway.Session.IsServer) + { + SyncModSettings.Save(Settings, true); + MyAPIGateway.Utilities.ShowMessage(CmdKey, "Settings file created inside world folder"); + } + else + { + MyAPIGateway.Utilities.ShowMessage(CmdKey, "command not allowed on client"); + } + break; + + case CmdLogLevel: + if (args.Length > 1) + { + switch (args[1].Trim()) + { + case CmdLogLevel_All: + Mod.Log.LogLevel = Logging.Level.All; + MyAPIGateway.Utilities.ShowMessage(CmdKey, + $"Logging level switched to All [{Mod.Log.LogLevel:X}]"); + Mod.Log.Write("BuildAndRepairSystemMod: Logging level switched to [{0:X}]", Mod.Log.LogLevel); + foreach (var modItem in MyAPIGateway.Session.Mods) + { + Mod.Log.Write("BuildAndRepairSystemMod: Mod {0} (Name={1}, FileId={2}, Path={3})", modItem.FriendlyName, modItem.Name, modItem.PublishedFileId, modItem.GetPath()); + } + break; + + case CmdLogLevel_Default: + default: + Mod.Log.LogLevel = Settings.LogLevel; + MyAPIGateway.Utilities.ShowMessage(CmdKey, + $"Logging level switched to Default [{Mod.Log.LogLevel:X}]"); + Mod.Log.Write("BuildAndRepairSystemMod: Logging level switched to [{0:X}]", Mod.Log.LogLevel); + break; + } + } + break; + + case CmdWriteTranslation: + if (Mod.Log.ShouldLog(Logging.Level.Verbose)) Mod.Log.Write(Logging.Level.Verbose, "BuildAndRepairSystemMod: CmdWriteTranslation"); + if (args.Length > 1) + { + MyLanguagesEnum lang; + if (Enum.TryParse(args[1], true, out lang)) + { + LocalizationHelper.ExportDictionary(lang.ToString() + ".txt", Texts.GetDictionary(lang)); + MyAPIGateway.Utilities.ShowMessage(CmdKey, string.Format(lang.ToString() + ".txt writtenwa.")); + } + else + { + MyAPIGateway.Utilities.ShowMessage(CmdKey, + $"'{args[1]}' is not a valid language name {string.Join(",", Enum.GetNames(typeof(MyLanguagesEnum)))}"); + } + } + break; + + case CmdHelp1: + case CmdHelp2: + default: + MyAPIGateway.Utilities.ShowMissionScreen("NanobotBuildAndRepairSystem", "Help", "", GetHelpText()); + break; + } + } + else + { + MyAPIGateway.Utilities.ShowMissionScreen("NanobotBuildAndRepairSystem", "Help", "", GetHelpText()); + } + sendToOthers = false; + } + } + + private string GetHelpText() + { + var text = string.Format(Texts.Cmd_HelpClient.String, Version, CmdHelp1, CmdHelp2, + CmdLogLevel, CmdLogLevel_All, CmdLogLevel_Default, + CmdWriteTranslation, string.Join(",", Enum.GetNames(typeof(MyLanguagesEnum))), MyAPIGateway.Utilities.GamePaths.UserDataPath + Path.DirectorySeparatorChar + "Storage" + Path.DirectorySeparatorChar + MyAPIGateway.Utilities.GamePaths.ModScopeName); + if (MyAPIGateway.Session.IsServer) text += string.Format(Texts.Cmd_HelpServer.String, CmdCwsf, CmdCpsf); + return text; + } + + /// + /// + /// + protected override void UnloadData() + { + while (true) + { + int actualBackgroundTaskCount; + lock (AsynActions) + { + actualBackgroundTaskCount = ActualBackgroundTaskCount; + } + if (actualBackgroundTaskCount <= 0) break; + } + + _Init = false; + try + { + if (MyAPIGateway.Utilities != null && MyAPIGateway.Multiplayer != null) + { + if (MyAPIGateway.Session.IsServer) + { + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_MOD_DATAREQUEST, SyncModDataRequestReceived); + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_BLOCK_DATAREQUEST, SyncBlockDataRequestReceived); + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_BLOCK_SETTINGS_FROM_CLIENT, SyncBlockSettingsReceived); + } + else + { + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_MOD_SETTINGS, SyncModSettingsReceived); + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_BLOCK_SETTINGS_FROM_SERVER, SyncBlockSettingsReceived); + MyAPIGateway.Multiplayer.UnregisterMessageHandler(MSGID_BLOCK_STATE_FROM_SERVER, SyncBlockStateReceived); + } + } + Mod.Log.Write("BuildAndRepairSystemMod: UnloadData."); + Mod.Log.Close(); + } + catch (Exception e) + { + Mod.Log.Error("NanobotBuildAndRepairSystemMod.UnloadData: {0}", e.ToString()); + } + base.UnloadData(); + } + + /// + /// + /// + public override void UpdateBeforeSimulation() + { + try + { + if (!_Init) + { + if (MyAPIGateway.Session == null) return; + Init(); + } + else + { + if (MyAPIGateway.Session.IsServer) + { + RebuildSourcesAndTargetsTimer(); + } + else if (!SettingsValid) + { + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(_LastSyncModDataRequestSend) >= TimeSpan.FromSeconds(10)) + { + SyncModDataRequestSend(); + _LastSyncModDataRequestSend = MyAPIGateway.Session.ElapsedPlayTime; + } + } + } + } + catch (Exception e) + { + Mod.Log.Error(e); + } + } + + /// + /// Damage Handler: Prevent Damage from BuildAndRepairSystem + /// + public void BeforeDamageHandlerNoDamageByBuildAndRepairSystem(object target, ref MyDamageInformation info) + { + try + { + if (info.Type == MyDamageType.Weld) + { + if (target is IMyCharacter) + { + var logicalComponent = BuildAndRepairSystems.GetValueOrDefault(info.AttackerId); + if (logicalComponent != null) + { + var terminalBlock = logicalComponent.Entity as IMyTerminalBlock; + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: Prevent Damage from BuildAndRepairSystem={0} Amount={1}", terminalBlock != null ? terminalBlock.CustomName : logicalComponent.Entity.DisplayName, info.Amount); + info.Amount = 0; + } + } + } + } + catch (Exception e) + { + Mod.Log.Error("BuildAndRepairSystemMod: Exception in BeforeDamageHandlerNoDamageByBuildAndRepairSystem: Source={0}, Message={1}", e.Source, e.Message); + } + } + + /// + /// Damage Handler: Register friendly damage + /// + public void AfterDamageHandlerNoDamageByBuildAndRepairSystem(object target, MyDamageInformation info) + { + try + { + if (info.Type == MyDamageType.Grind && info.Amount > 0) + { + var targetBlock = target as IMySlimBlock; + if (targetBlock != null) + { + IMyEntity attackerEntity; + MyAPIGateway.Entities.TryGetEntityById(info.AttackerId, out attackerEntity); + + var attackerId = 0L; + + var shipGrinder = attackerEntity as IMyShipGrinder; + if (shipGrinder != null) + { + attackerId = shipGrinder.OwnerId; + } + else + { + var characterGrinder = attackerEntity as IMyEngineerToolBase; + if (characterGrinder != null) + { + attackerId = characterGrinder.OwnerIdentityId; + } + } + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: AfterDamaged1 {0} from {1} attackerId={2} Amount={3}", Logging.BlockName(target), Logging.BlockName(attackerEntity), attackerId, info.Amount); + + if (attackerId != 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: Damaged {0} from attackerId={1} Amount={2}", Logging.BlockName(target), attackerId, info.Amount); + foreach (var entry in BuildAndRepairSystems) + { + var relation = entry.Value.Welder.GetUserRelationToOwner(attackerId); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: {0} Damaged Check Add FriendlyDamage {1} relation {2}", Logging.BlockName(entry.Value.Welder), Logging.BlockName(targetBlock), relation); + if (relation.IsFriendly()) + { + //A 'friendly' damage from grinder -> do not repair (for a while) + entry.Value.FriendlyDamage[targetBlock] = MyAPIGateway.Session.ElapsedPlayTime + Settings.FriendlyDamageTimeout; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: {0} Damaged Add FriendlyDamage {0} Timeout {1}", Logging.BlockName(entry.Value.Welder), Logging.BlockName(targetBlock), entry.Value.FriendlyDamage[targetBlock]); + } + } + } + } + } + } + catch (Exception e) + { + Mod.Log.Error("BuildAndRepairSystemMod: Exception in BeforeDamageHandlerNoDamageByBuildAndRepairSystem: Source={0}, Message={1}", e.Source, e.Message); + } + } + + /// + /// Rebuild the list of targets and inventory sources + /// + protected void RebuildSourcesAndTargetsTimer() + { + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(_LastSourcesAndTargetsUpdateTimer) > SourcesAndTargetsUpdateTimerInterval) + { + foreach (var buildAndRepairSystem in BuildAndRepairSystems.Values) + { + buildAndRepairSystem.UpdateSourcesAndTargetsTimer(); + } + _LastSourcesAndTargetsUpdateTimer = MyAPIGateway.Session.ElapsedPlayTime; + } + } + + /// + /// + /// + /// + public static void AddAsyncAction(Action newAction) + { + lock (AsynActions) + { + AsynActions.Add(newAction); + if (ActualBackgroundTaskCount < Settings.MaxBackgroundTasks) + { + ActualBackgroundTaskCount++; + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: AddAsyncAction Start Task {0} of max {1}", ActualBackgroundTaskCount, Settings.MaxBackgroundTasks); + MyAPIGateway.Parallel.StartBackground(() => + { + try + { + while (true) + { + Action pendingAction = null; + lock (AsynActions) + { + if (AsynActions.Count > 0) + { + pendingAction = AsynActions[0]; + AsynActions.RemoveAt(0); + } + if (pendingAction == null) + { + ActualBackgroundTaskCount--; + break; + } + } + + try + { + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: AddAsyncAction Task Working start."); + pendingAction(); + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: AddAsyncAction Task Working finished."); + } + catch + { + // ignored + } + } + if (Mod.Log.ShouldLog(Logging.Level.Info)) Mod.Log.Write(Logging.Level.Info, "BuildAndRepairSystemMod: AddAsyncAction Task ended. Still running {0} of max {1}", ActualBackgroundTaskCount, Settings.MaxBackgroundTasks); + } + catch + { + lock (AsynActions) + { + ActualBackgroundTaskCount--; + } + } + }); + } + } + } + + /// + /// + /// + private void SyncModCommandReceived(byte[] dataRcv) + { + } + + /// + /// + /// + private void SyncModDataRequestSend() + { + if (MyAPIGateway.Session.IsServer) return; + + var msgSnd = new MsgModDataRequest(); + msgSnd.SteamId = MyAPIGateway.Session.Player != null ? MyAPIGateway.Session.Player.SteamUserId : (ulong)0; + + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncModDataRequestSend SteamId={0}", msgSnd.SteamId); + MyAPIGateway.Multiplayer.SendMessageToServer(MSGID_MOD_DATAREQUEST, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), true); + } + + /// + /// + /// + private void SyncModDataRequestReceived(byte[] dataRcv) + { + var msgRcv = MyAPIGateway.Utilities.SerializeFromBinary(dataRcv); + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncModDataRequestReceived SteamId={0}", msgRcv.SteamId); + SyncModSettingsSend(msgRcv.SteamId); + } + + /// + /// + /// + private void SyncModSettingsSend(ulong steamId) + { + if (!MyAPIGateway.Session.IsServer) return; + var msgSnd = new MsgModSettings(); + msgSnd.Settings = Settings; + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncModSettingsSend SteamId={0}", steamId); + if (!MyAPIGateway.Multiplayer.SendMessageTo(MSGID_MOD_SETTINGS, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), steamId, true)) + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncModSettingsSend failed"); + } + } + + /// + /// + /// + private void SyncModSettingsReceived(byte[] dataRcv) + { + try + { + var msgRcv = MyAPIGateway.Utilities.SerializeFromBinary(dataRcv); + SyncModSettings.AdjustSettings(msgRcv.Settings); + Settings = msgRcv.Settings; + SettingsValid = true; + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncModSettingsReceived"); + SettingsChanged(); + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncModSettingsReceived Exception:{0}", ex); + } + } + + /// + /// + /// + public static void SyncBlockDataRequestSend(NanobotBuildAndRepairSystemBlock block) + { + if (MyAPIGateway.Session.IsServer) return; + + var msgSnd = new MsgBlockDataRequest(); + if (MyAPIGateway.Session.Player != null) + msgSnd.SteamId = MyAPIGateway.Session.Player.SteamUserId; + else + msgSnd.SteamId = 0; + msgSnd.EntityId = block.Entity.EntityId; + + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockDataRequestSend SteamId={0} EntityId={1}/{2}", msgSnd.SteamId, msgSnd.EntityId, Logging.BlockName(block.Entity, Logging.BlockNameOptions.None)); + MyAPIGateway.Multiplayer.SendMessageToServer(MSGID_BLOCK_DATAREQUEST, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), true); + } + + /// + /// + /// + private void SyncBlockDataRequestReceived(byte[] dataRcv) + { + var msgRcv = MyAPIGateway.Utilities.SerializeFromBinary(dataRcv); + + NanobotBuildAndRepairSystemBlock system; + if (BuildAndRepairSystems.TryGetValue(msgRcv.EntityId, out system)) + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockDataRequestReceived SteamId={0} EntityId={1}/{2}", msgRcv.SteamId, msgRcv.EntityId, Logging.BlockName(system.Entity, Logging.BlockNameOptions.None)); + SyncBlockSettingsSend(msgRcv.SteamId, system); + SyncBlockStateSend(msgRcv.SteamId, system); + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockDataRequestReceived for unknown system SteamId{0} EntityId={1}", msgRcv.SteamId, msgRcv.EntityId); + } + } + + /// + /// + /// + public static void SyncBlockSettingsSend(ulong steamId, NanobotBuildAndRepairSystemBlock block) + { + var msgSnd = new MsgBlockSettings(); + msgSnd.EntityId = block.Entity.EntityId; + msgSnd.Settings = block.Settings.GetTransmit(); + + var res = false; + if (MyAPIGateway.Session.IsServer) + { + if (steamId == 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockSettingsSend To Others EntityId={0}/{1}", block.Entity.EntityId, Logging.BlockName(block.Entity, Logging.BlockNameOptions.None)); + res = MyAPIGateway.Multiplayer.SendMessageToOthers(MSGID_BLOCK_SETTINGS_FROM_SERVER, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), true); + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockSettingsSend To SteamId={2} EntityId={0}/{1}", block.Entity.EntityId, Logging.BlockName(block.Entity, Logging.BlockNameOptions.None), steamId); + res = MyAPIGateway.Multiplayer.SendMessageTo(MSGID_BLOCK_SETTINGS_FROM_SERVER, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), steamId, true); + } + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockSettingsSend To Server EntityId={0}/{1} to Server", block.Entity.EntityId, Logging.BlockName(block.Entity, Logging.BlockNameOptions.None)); + res = MyAPIGateway.Multiplayer.SendMessageToServer(MSGID_BLOCK_SETTINGS_FROM_CLIENT, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), true); + } + if (!res && Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockSettingsSend failed", Logging.BlockName(block.Entity, Logging.BlockNameOptions.None)); + } + + /// + /// + /// + private void SyncBlockSettingsReceived(byte[] dataRcv) + { + try + { + var msgRcv = MyAPIGateway.Utilities.SerializeFromBinary(dataRcv); + + NanobotBuildAndRepairSystemBlock system; + if (BuildAndRepairSystems.TryGetValue(msgRcv.EntityId, out system)) + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockSettingsReceived EntityId={0}/{1}", msgRcv.EntityId, Logging.BlockName(system.Entity, Logging.BlockNameOptions.None)); + system.Settings.AssignReceived(msgRcv.Settings, system.BlockWeldPriority, system.BlockGrindPriority, system.ComponentCollectPriority); + system.SettingsChanged(); + if (MyAPIGateway.Session.IsServer) + { + SyncBlockSettingsSend(0, system); + } + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockSettingsReceived for unknown system EntityId={0}", msgRcv.EntityId); + } + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockSettingsReceived Exception:{0}", ex); + } + } + + /// + /// + /// + public static void SyncBlockStateSend(ulong steamId, NanobotBuildAndRepairSystemBlock system) + { + if (!MyAPIGateway.Session.IsServer) return; + if (!MyAPIGateway.Multiplayer.MultiplayerActive) return; + + var msgSnd = new MsgBlockState(); + msgSnd.EntityId = system.Entity.EntityId; + msgSnd.State = system.State.GetTransmit(); + + var res = false; + if (steamId == 0) + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockStateSend to others EntityId={0}/{1}, State={2}", system.Entity.EntityId, Logging.BlockName(system.Entity, Logging.BlockNameOptions.None), msgSnd.State.ToString()); + res = MyAPIGateway.Multiplayer.SendMessageToOthers(MSGID_BLOCK_STATE_FROM_SERVER, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), true); + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockStateSend to SteamId={0} EntityId={1}/{2}, State={3}", steamId, system.Entity.EntityId, Logging.BlockName(system.Entity, Logging.BlockNameOptions.None), msgSnd.State.ToString()); + res = MyAPIGateway.Multiplayer.SendMessageTo(MSGID_BLOCK_STATE_FROM_SERVER, MyAPIGateway.Utilities.SerializeToBinary(msgSnd), steamId, true); + } + + if (!res && Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockStateSend Failed"); + } + + private void SyncBlockStateReceived(byte[] dataRcv) + { + try + { + var msgRcv = MyAPIGateway.Utilities.SerializeFromBinary(dataRcv); + + NanobotBuildAndRepairSystemBlock system; + if (BuildAndRepairSystems.TryGetValue(msgRcv.EntityId, out system)) + { + if (Mod.Log.ShouldLog(Logging.Level.Communication)) Mod.Log.Write(Logging.Level.Communication, "BuildAndRepairSystemMod: SyncBlockStateReceived EntityId={0}/{1}, State={2}", system.Entity.EntityId, Logging.BlockName(system.Entity, Logging.BlockNameOptions.None), msgRcv.State.ToString()); + system.State.AssignReceived(msgRcv.State); + } + else + { + if (Mod.Log.ShouldLog(Logging.Level.Error)) Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockStateReceived for unknown system EntityId={0}", msgRcv.EntityId); + } + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Error, "BuildAndRepairSystemMod: SyncBlockStateReceived Exception:{0}", ex); + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemPriorityHandling.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemPriorityHandling.cs new file mode 100644 index 00000000..4209bfb1 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemPriorityHandling.cs @@ -0,0 +1,108 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System; + using VRage.Game; + using VRage.Game.ModAPI; + + public enum BlockClass + { + AutoRepairSystem = 1, + ShipController, + Thruster, + Gyroscope, + CargoContainer, + Conveyor, + ControllableGun, + PowerBlock, + ProgrammableBlock, + Projector, + FunctionalBlock, + ProductionBlock, + Door, + ArmorBlock + } + + public enum ComponentClass + { + Material = 1, + Ingot, + Ore, + Stone, + Gravel + } + + public class NanobotBuildAndRepairSystemBlockPriorityHandling : PriorityHandling + { + public NanobotBuildAndRepairSystemBlockPriorityHandling() + { + foreach (var item in Enum.GetValues(typeof(BlockClass))) + { + Add(new PrioItemState(new PrioItem((int)item, item.ToString()), true, true)); + } + } + + public override int GetItemKey(IMySlimBlock a, bool real) + { + var block = a.FatBlock; + if (block == null) return (int)BlockClass.ArmorBlock; + var functionalBlock = block as Sandbox.ModAPI.IMyFunctionalBlock; + if (!real && functionalBlock != null && !functionalBlock.Enabled) return (int)BlockClass.ArmorBlock; //Switched off -> handle as structural block (if logical class is asked) + + if (block is Sandbox.ModAPI.IMyShipWelder && block.BlockDefinition.SubtypeName.Contains("NanobotBuildAndRepairSystem")) return (int)BlockClass.AutoRepairSystem; + if (block is Sandbox.ModAPI.IMyShipController) return (int)BlockClass.ShipController; + if (block is Sandbox.ModAPI.IMyThrust || block is Sandbox.ModAPI.IMyWheel || block is Sandbox.ModAPI.IMyMotorRotor) return (int)BlockClass.Thruster; + if (block is Sandbox.ModAPI.IMyGyro) return (int)BlockClass.Gyroscope; + if (block is Sandbox.ModAPI.IMyCargoContainer) return (int)BlockClass.CargoContainer; + if (block is Sandbox.ModAPI.IMyConveyor || a.FatBlock is Sandbox.ModAPI.IMyConveyorSorter || a.FatBlock is Sandbox.ModAPI.IMyConveyorTube) return (int)BlockClass.Conveyor; + if (block is Sandbox.ModAPI.IMyUserControllableGun) return (int)BlockClass.ControllableGun; + if (block is Sandbox.ModAPI.IMyWarhead) return (int)BlockClass.ControllableGun; + if (block is Sandbox.ModAPI.IMyPowerProducer) return (int)BlockClass.PowerBlock; + if (block is Sandbox.ModAPI.IMyProgrammableBlock) return (int)BlockClass.ProgrammableBlock; + if (block is SpaceEngineers.Game.ModAPI.IMyTimerBlock) return (int)BlockClass.ProgrammableBlock; + if (block is Sandbox.ModAPI.IMyProjector) return (int)BlockClass.Projector; + if (block is Sandbox.ModAPI.IMyDoor) return (int)BlockClass.Door; + if (block is Sandbox.ModAPI.IMyProductionBlock) return (int)BlockClass.ProductionBlock; + if (functionalBlock != null) return (int)BlockClass.FunctionalBlock; + + return (int)BlockClass.ArmorBlock; + } + + public override string GetItemAlias(IMySlimBlock a, bool real) + { + var key = GetItemKey(a, real); + return ((BlockClass)key).ToString(); + } + } + + public class NanobotBuildAndRepairSystemComponentPriorityHandling : PriorityHandling + { + public NanobotBuildAndRepairSystemComponentPriorityHandling() + { + foreach (var item in Enum.GetValues(typeof(ComponentClass))) + { + Add(new PrioItemState(new PrioItem((int)item, item.ToString()), true, true)); + } + } + + public override int GetItemKey(MyDefinitionId a, bool real) + { + if (a.TypeId == typeof(MyObjectBuilder_Ingot)) + { + if (a.SubtypeName == "Stone") return (int)ComponentClass.Gravel; + return (int)ComponentClass.Ingot; + } + if (a.TypeId == typeof(MyObjectBuilder_Ore)) + { + if (a.SubtypeName == "Stone") return (int)ComponentClass.Stone; + return (int)ComponentClass.Ore; + } + return (int)ComponentClass.Material; + } + + public override string GetItemAlias(MyDefinitionId a, bool real) + { + var key = GetItemKey(a, real); + return ((ComponentClass)key).ToString(); + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemSynchronization.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemSynchronization.cs new file mode 100644 index 00000000..f4f86dd5 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemSynchronization.cs @@ -0,0 +1,1791 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System; + using System.Collections.Generic; + using System.Xml.Serialization; + using ProtoBuf; + using Sandbox.Game.EntityComponents; + using Sandbox.ModAPI; + using VRage.Game; + using VRage.Game.ModAPI; + using VRage.ModAPI; + using VRageMath; + + /// + /// The settings for Mod + /// + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class SyncModSettings + { + private const int CurrentSettingsVersion = 6; + + [ProtoMember(2000), XmlElement] + public int Version { get; set; } + + [XmlElement] + public bool DisableLocalization { get; set; } + + [ProtoMember(1), XmlElement] + public Logging.Level LogLevel { get; set; } + + [XmlIgnore] + public TimeSpan SourcesUpdateInterval { get; set; } + + [XmlIgnore] + public TimeSpan TargetsUpdateInterval { get; set; } + + [XmlIgnore] + public TimeSpan FriendlyDamageTimeout { get; set; } + + [XmlIgnore] + public TimeSpan FriendlyDamageCleanup { get; set; } + + [ProtoMember(2), XmlElement] + public int Range { get; set; } + + [ProtoMember(3), XmlElement] + public long SourcesAndTargetsUpdateIntervalTicks + { + get { return TargetsUpdateInterval.Ticks; } + set + { + TargetsUpdateInterval = new TimeSpan(value); + SourcesUpdateInterval = new TimeSpan(value * 6); + } + } + + [ProtoMember(4), XmlElement] + public long FriendlyDamageTimeoutTicks + { + get { return FriendlyDamageTimeout.Ticks; } + set { FriendlyDamageTimeout = new TimeSpan(value); } + } + + [ProtoMember(5), XmlElement] + public long FriendlyDamageCleanupTicks + { + get { return FriendlyDamageCleanup.Ticks; } + set { FriendlyDamageCleanup = new TimeSpan(value); } + } + + [ProtoMember(8), XmlElement] + public float MaximumRequiredElectricPowerTransport { get; set; } + + [ProtoMember(9), XmlElement] + public float MaximumRequiredElectricPowerStandby { get; set; } + + [ProtoMember(10), XmlElement] + public SyncModSettingsWelder Welder { get; set; } + + [ProtoMember(20), XmlElement] + public int MaxBackgroundTasks { get; set; } + + [ProtoMember(21), XmlElement] + public int MaximumOffset { get; set; } + + public SyncModSettings() + { + DisableLocalization = false; + LogLevel = Logging.Level.Error; //Default + MaxBackgroundTasks = NanobotBuildAndRepairSystemMod.MaxBackgroundTasks_Default; + TargetsUpdateInterval = TimeSpan.FromSeconds(10); + SourcesUpdateInterval = TimeSpan.FromSeconds(60); + FriendlyDamageTimeout = TimeSpan.FromSeconds(60); + FriendlyDamageCleanup = TimeSpan.FromSeconds(10); + Range = NanobotBuildAndRepairSystemBlock.WELDER_RANGE_DEFAULT_IN_M; + MaximumOffset = NanobotBuildAndRepairSystemBlock.WELDER_OFFSET_MAX_DEFAULT_IN_M; + MaximumRequiredElectricPowerStandby = NanobotBuildAndRepairSystemBlock.WELDER_REQUIRED_ELECTRIC_POWER_STANDBY_DEFAULT; + MaximumRequiredElectricPowerTransport = NanobotBuildAndRepairSystemBlock.WELDER_REQUIRED_ELECTRIC_POWER_TRANSPORT_DEFAULT; + Welder = new SyncModSettingsWelder(); + } + + public static SyncModSettings Load() + { + SyncModSettings settings = null; + try + { + if (MyAPIGateway.Utilities.FileExistsInWorldStorage("ModSettings.xml", typeof(SyncModSettings))) + { + using (var reader = MyAPIGateway.Utilities.ReadFileInWorldStorage("ModSettings.xml", typeof(SyncModSettings))) + { + settings = MyAPIGateway.Utilities.SerializeFromXML(reader.ReadToEnd()); + Mod.Log.Write("NanobotBuildAndRepairSystemSettings: Loaded from world file."); + } + } + else if (MyAPIGateway.Utilities.FileExistsInLocalStorage("ModSettings.xml", typeof(SyncModSettings))) + { + using (var reader = MyAPIGateway.Utilities.ReadFileInLocalStorage("ModSettings.xml", typeof(SyncModSettings))) + { + settings = MyAPIGateway.Utilities.SerializeFromXML(reader.ReadToEnd()); + Mod.Log.Write("NanobotBuildAndRepairSystemSettings: Loaded from local storage."); + } + } + + if (settings != null) + { + var adjusted = AdjustSettings(settings); + if (settings.MaxBackgroundTasks > NanobotBuildAndRepairSystemMod.MaxBackgroundTasks_Max) + { + settings.MaxBackgroundTasks = NanobotBuildAndRepairSystemMod.MaxBackgroundTasks_Max; + adjusted = true; + } + else if (settings.MaxBackgroundTasks < NanobotBuildAndRepairSystemMod.MaxBackgroundTasks_Min) + { + settings.MaxBackgroundTasks = NanobotBuildAndRepairSystemMod.MaxBackgroundTasks_Min; + adjusted = true; + } + + if (settings.Range > NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MAX_IN_M) + { + settings.Range = NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MAX_IN_M; + adjusted = true; + } + else if (settings.Range < NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MIN_IN_M) + { + settings.Range = NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MIN_IN_M; + adjusted = true; + } + + if (settings.MaximumOffset > NanobotBuildAndRepairSystemBlock.WELDER_OFFSET_MAX_IN_M) + { + settings.MaximumOffset = NanobotBuildAndRepairSystemBlock.WELDER_OFFSET_MAX_IN_M; + adjusted = true; + } + else if (settings.MaximumOffset < 0) + { + settings.MaximumOffset = 0; + adjusted = true; + } + + if (settings.Welder.WeldingMultiplier < NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MIN) + { + settings.Welder.WeldingMultiplier = NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MIN; + adjusted = true; + } + else if (settings.Welder.WeldingMultiplier >= NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MAX) + { + settings.Welder.WeldingMultiplier = NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MAX; + adjusted = true; + } + + if (settings.Welder.GrindingMultiplier < NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MIN) + { + settings.Welder.GrindingMultiplier = NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MIN; + adjusted = true; + } + else if (settings.Welder.GrindingMultiplier >= NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MAX) + { + settings.Welder.GrindingMultiplier = NanobotBuildAndRepairSystemBlock.WELDING_GRINDING_MULTIPLIER_MAX; + adjusted = true; + } + + Mod.Log.Write(Logging.Level.Info, "NanobotBuildAndRepairSystemSettings: Settings {0}", settings); + //if (adjusted) Save(settings, world); don't save file + } + else + { + settings = new SyncModSettings() { Version = CurrentSettingsVersion }; + //Save(settings, world); don't save file with default values + } + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Error, "NanobotBuildAndRepairSystemSettings: Exception while loading: {0}", ex); + } + + return settings; + } + + public static void Save(SyncModSettings settings, bool world) + { + if (world) + { + using (var writer = MyAPIGateway.Utilities.WriteFileInWorldStorage("ModSettings.xml", typeof(SyncModSettings))) + { + writer.Write(MyAPIGateway.Utilities.SerializeToXML(settings)); + } + } + else + { + using (var writer = MyAPIGateway.Utilities.WriteFileInLocalStorage("ModSettings.xml", typeof(SyncModSettings))) + { + writer.Write(MyAPIGateway.Utilities.SerializeToXML(settings)); + } + } + } + + public static bool AdjustSettings(SyncModSettings settings) + { + if (settings.Version >= CurrentSettingsVersion) return false; + + Mod.Log.Write("NanobotBuildAndRepairSystemSettings: Settings have old version: {0} update to {1}", settings.Version, CurrentSettingsVersion); + + if (settings.Version <= 0) settings.LogLevel = Logging.Level.Error; + if (settings.Version <= 4 && settings.Welder.AllowedSearchModes == 0) settings.Welder.AllowedSearchModes = SearchModes.Grids | SearchModes.BoundingBox; + if (settings.Version <= 4 && settings.Welder.AllowedWorkModes == 0) settings.Welder.AllowedWorkModes = WorkModes.WeldBeforeGrind | WorkModes.GrindBeforeWeld | WorkModes.GrindIfWeldGetStuck | WorkModes.WeldOnly | WorkModes.GrindOnly; + if (settings.Version <= 4 && settings.Welder.WeldingMultiplier == 0) settings.Welder.WeldingMultiplier = 1; + if (settings.Version <= 4 && settings.Welder.GrindingMultiplier == 0) settings.Welder.GrindingMultiplier = 1; + if (settings.Version <= 5 && settings.Welder.AllowedGrindJanitorRelations == 0) settings.Welder.AllowedGrindJanitorRelations = AutoGrindRelation.NoOwnership | AutoGrindRelation.Enemies | AutoGrindRelation.Neutral; + + settings.Version = CurrentSettingsVersion; + return true; + } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class SyncModSettingsWelder + { + [ProtoMember(1), XmlElement] + public float MaximumRequiredElectricPowerWelding { get; set; } + + [ProtoMember(2), XmlElement] + public float MaximumRequiredElectricPowerGrinding { get; set; } + + [ProtoMember(10), XmlElement] + public float WeldingMultiplier { get; set; } + + [ProtoMember(11), XmlElement] + public float GrindingMultiplier { get; set; } + + [ProtoMember(90), XmlElement] + public SearchModes AllowedSearchModes { get; set; } + + [ProtoMember(91), XmlElement] + public SearchModes SearchModeDefault { get; set; } + + [ProtoMember(101), XmlElement] + public bool AllowBuildFixed { get; set; } + + [ProtoMember(102), XmlElement] + public bool AllowBuildDefault { get; set; } + + [ProtoMember(105), XmlElement] + public WorkModes AllowedWorkModes { get; set; } + + [ProtoMember(106), XmlElement] + public WorkModes WorkModeDefault { get; set; } + + [ProtoMember(110), XmlElement] + public bool UseIgnoreColorFixed { get; set; } + + [ProtoMember(111), XmlElement] + public bool UseIgnoreColorDefault { get; set; } + + [ProtoMember(112), XmlArray] + public float[] IgnoreColorDefault { get; set; } + + [ProtoMember(115), XmlElement] + public bool UseGrindColorFixed { get; set; } + + [ProtoMember(116), XmlElement] + public bool UseGrindColorDefault { get; set; } + + [ProtoMember(117), XmlArray] + public float[] GrindColorDefault { get; set; } + + [ProtoMember(118), XmlElement] + public bool UseGrindJanitorFixed { get; set; } + + [ProtoMember(119), XmlElement] + public AutoGrindRelation UseGrindJanitorDefault { get; set; } + + [ProtoMember(120), XmlElement] + public AutoGrindOptions GrindJanitorOptionsDefault { get; set; } + + [ProtoMember(121), XmlElement] + public AutoGrindRelation AllowedGrindJanitorRelations { get; set; } + + [ProtoMember(125), XmlElement] + public bool ShowAreaFixed { get; set; } + + [ProtoMember(130), XmlElement] + public bool AreaSizeFixed { get; set; } + + [ProtoMember(131), XmlElement] + public bool AreaOffsetFixed { get; set; } + + [ProtoMember(140), XmlElement] + public bool PriorityFixed { get; set; } + + [ProtoMember(144), XmlElement] + public bool CollectPriorityFixed { get; set; } + + [ProtoMember(145), XmlElement] + public bool PushIngotOreImmediatelyFixed { get; set; } + + [ProtoMember(146), XmlElement] + public bool PushIngotOreImmediatelyDefault { get; set; } + + [ProtoMember(147), XmlElement] + public bool PushComponentImmediatelyFixed { get; set; } + + [ProtoMember(148), XmlElement] + public bool PushComponentImmediatelyDefault { get; set; } + + [ProtoMember(149), XmlElement] + public bool PushItemsImmediatelyFixed { get; set; } + + [ProtoMember(150), XmlElement] + public bool PushItemsImmediatelyDefault { get; set; } + + [ProtoMember(156), XmlElement] + public bool CollectIfIdleFixed { get; set; } + + [ProtoMember(157), XmlElement] + public bool CollectIfIdleDefault { get; set; } + + [ProtoMember(160), XmlElement] + public bool SoundVolumeFixed { get; set; } + + [ProtoMember(161), XmlElement] + public float SoundVolumeDefault { get; set; } + + [ProtoMember(170), XmlElement] + public bool ScriptControllFixed { get; set; } + + [ProtoMember(200), XmlElement] + public VisualAndSoundEffects AllowedEffects { get; set; } + + public SyncModSettingsWelder() + { + MaximumRequiredElectricPowerWelding = NanobotBuildAndRepairSystemBlock.WELDER_REQUIRED_ELECTRIC_POWER_WELDING_DEFAULT; + MaximumRequiredElectricPowerGrinding = NanobotBuildAndRepairSystemBlock.WELDER_REQUIRED_ELECTRIC_POWER_GRINDING_DEFAULT; + + WeldingMultiplier = 1f; + GrindingMultiplier = 1f; + + AllowedSearchModes = SearchModes.Grids | SearchModes.BoundingBox; + SearchModeDefault = SearchModes.Grids; + + AllowBuildFixed = false; + AllowBuildDefault = true; + + AllowedWorkModes = WorkModes.GrindBeforeWeld | WorkModes.GrindIfWeldGetStuck | WorkModes.WeldBeforeGrind | WorkModes.WeldOnly | WorkModes.GrindOnly; + WorkModeDefault = WorkModes.WeldBeforeGrind; + + UseIgnoreColorFixed = false; + UseIgnoreColorDefault = true; + IgnoreColorDefault = new float[] { 321f, 100f, 51f }; + + UseGrindColorFixed = false; + UseGrindColorDefault = true; + GrindColorDefault = new float[] { 321f, 100f, 50f }; + + UseGrindJanitorFixed = false; + UseGrindJanitorDefault = AutoGrindRelation.Enemies | AutoGrindRelation.NoOwnership; + GrindJanitorOptionsDefault = 0; + AllowedGrindJanitorRelations = AutoGrindRelation.NoOwnership | AutoGrindRelation.Enemies | AutoGrindRelation.Neutral; + + ShowAreaFixed = false; + AreaSizeFixed = false; + AreaOffsetFixed = false; + PriorityFixed = false; + CollectPriorityFixed = false; + + PushIngotOreImmediatelyFixed = false; + PushIngotOreImmediatelyDefault = true; + PushItemsImmediatelyFixed = false; + PushItemsImmediatelyDefault = true; + PushComponentImmediatelyFixed = false; + PushComponentImmediatelyDefault = false; + + CollectIfIdleDefault = false; + + SoundVolumeFixed = false; + SoundVolumeDefault = NanobotBuildAndRepairSystemBlock.WELDER_SOUND_VOLUME / 2; + + ScriptControllFixed = false; + AllowedEffects = VisualAndSoundEffects.WeldingVisualEffect | VisualAndSoundEffects.WeldingSoundEffect + | VisualAndSoundEffects.GrindingVisualEffect | VisualAndSoundEffects.GrindingSoundEffect + | VisualAndSoundEffects.TransportVisualEffect; + } + } + + /// + /// The settings for Block + /// + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class SyncBlockSettings + { + [Flags] + public enum Settings + { + AllowBuild = 0x00000001, + ShowArea = 0x00000002, + ScriptControlled = 0x00000004, + UseIgnoreColor = 0x00000010, + UseGrindColor = 0x00000020, + GrindNearFirst = 0x00000100, + GrindSmallestGridFirst = 0x00000200, + ComponentCollectIfIdle = 0x00010000, + PushIngotOreImmediately = 0x00020000, + PushComponentImmediately = 0x00040000, + PushItemsImmediately = 0x00080000 + } + + private BoundingBoxD _CorrectedAreaBoundingBox; + private Vector3 _CorrectedAreaOffset; + private Settings _Flags; + private Vector3 _IgnoreColor; + private uint _IgnoreColorPacked; + private Vector3 _GrindColor; + private uint _GrindColorPacked; + private AutoGrindRelation _UseGrindJanitorOn; + private AutoGrindOptions _GrindJanitorOptions; + private AutoWeldOptions _WeldOptions; + private Vector3 _AreaOffset; + private Vector3 _AreaSize; + private string _WeldPriority; + private string _GrindPriority; + private string _ComponentCollectPriority; + private float _SoundVolume; + private SearchModes _SearchMode; + private WorkModes _WorkMode; + private VRage.Game.ModAPI.Ingame.IMySlimBlock _CurrentPickedWeldingBlock; + private VRage.Game.ModAPI.Ingame.IMySlimBlock _CurrentPickedGrindingBlock; + private TimeSpan _LastStored; + private TimeSpan _LastTransmitted; + + private void SetFlags(bool set, Settings setting) + { + if (set != ((_Flags & setting) != 0)) + { + _Flags = (_Flags & ~setting) | (set ? setting : 0); + Changed = 3u; + } + } + + [XmlIgnore] + public uint Changed { get; private set; } + + [ProtoMember(5), XmlElement] + public Settings Flags + { + get + { + return _Flags; + } + set + { + if (_Flags != value) + { + _Flags = value; + Changed = 3u; + } + } + } + + [ProtoMember(20), XmlElement] + public SearchModes SearchMode + { + get + { + return _SearchMode; + } + set + { + if (_SearchMode != value) + { + _SearchMode = value; + Changed = 3u; + } + } + } + + [ProtoMember(25), XmlElement] + public WorkModes WorkMode + { + get + { + return _WorkMode; + } + set + { + if (_WorkMode != value) + { + _WorkMode = value; + Changed = 3u; + } + } + } + + [ProtoMember(31), XmlElement] + public Vector3 IgnoreColor + { + get + { + return _IgnoreColor; + } + set + { + if (_IgnoreColor != value) + { + _IgnoreColor = value; + _IgnoreColorPacked = value.PackHSVToUint(); + Changed = 3u; + } + } + } + + public uint IgnoreColorPacked + { + get + { + return _IgnoreColorPacked; + } + } + + [ProtoMember(36), XmlElement] + public Vector3 GrindColor + { + get + { + return _GrindColor; + } + set + { + if (_GrindColor != value) + { + _GrindColor = value; + _GrindColorPacked = value.PackHSVToUint(); + Changed = 3u; + } + } + } + + public uint GrindColorPacked + { + get + { + return _GrindColorPacked; + } + } + + [ProtoMember(39), XmlElement] + public AutoGrindRelation UseGrindJanitorOn + { + get + { + return _UseGrindJanitorOn; + } + set + { + if (_UseGrindJanitorOn != value) + { + _UseGrindJanitorOn = value; + Changed = 3u; + } + } + } + + [ProtoMember(40), XmlElement] + public AutoGrindOptions GrindJanitorOptions + { + get + { + return _GrindJanitorOptions; + } + set + { + if (_GrindJanitorOptions != value) + { + _GrindJanitorOptions = value; + Changed = 3u; + } + } + } + + [ProtoMember(49), XmlElement] + public AutoWeldOptions WeldOptions + { + get + { + return _WeldOptions; + } + set + { + if (_WeldOptions != value) + { + _WeldOptions = value; + Changed = 3u; + } + } + } + + //+X = Right -Y = Left + //+Y = Up -Y = Down + //+Z = Forward -Z = Backward + [ProtoMember(50), XmlElement] + public Vector3 AreaOffset + { + get + { + return _AreaOffset; + } + set + { + if (_AreaOffset != value) + { + _AreaOffset = value; + Changed = 3u; + RecalcAreaBoundigBox(); + } + } + } + + [ProtoMember(51), XmlElement] + public Vector3 AreaSize + { + get + { + return _AreaSize; + } + set + { + if (_AreaSize != value) + { + _AreaSize = value; + Changed = 3u; + RecalcAreaBoundigBox(); + } + } + } + + private int? _AreaWidthLeft; + + [XmlElement] + public int? AreaWidthLeft + { + get + { + return null; + } + set + { + _AreaWidthLeft = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + private int? _AreaWidthRight; + + [XmlElement] + public int? AreaWidthRight + { + get + { + return null; + } + set + { + _AreaWidthRight = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + private int? _AreaWidthTop; + + [XmlElement] + public int? AreaHeightTop + { + get + { + return null; + } + set + { + _AreaWidthTop = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + private int? _AreaWidthBottom; + + [XmlElement] + public int? AreaHeightBottom + { + get + { + return null; + } + set + { + _AreaWidthBottom = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + private int? _AreaWidthFront; + + [XmlElement] + public int? AreaDepthFront + { + get + { + return null; + } + set + { + _AreaWidthFront = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + private int? _AreaWidthRear; + + [XmlElement] + public int? AreaDepthRear + { + get + { + return null; + } + set + { + _AreaWidthRear = value; + if (value != null) RecalcOffsetAndSize(); + } + } + + [ProtoMember(61), XmlElement] + public string WeldPriority + { + get + { + return _WeldPriority; + } + set + { + if (_WeldPriority != value) + { + _WeldPriority = value; + Changed = 3u; + } + } + } + + [ProtoMember(62), XmlElement] + public string GrindPriority + { + get + { + return _GrindPriority; + } + set + { + if (_GrindPriority != value) + { + _GrindPriority = value; + Changed = 3u; + } + } + } + + [ProtoMember(65), XmlElement] + public string ComponentCollectPriority + { + get + { + return _ComponentCollectPriority; + } + set + { + if (_ComponentCollectPriority != value) + { + _ComponentCollectPriority = value; + Changed = 3u; + } + } + } + + [ProtoMember(80), XmlElement] + public float SoundVolume + { + get + { + return _SoundVolume; + } + set + { + if (_SoundVolume != value) + { + _SoundVolume = value; + Changed = 3u; + } + } + } + + [XmlIgnore] + public VRage.Game.ModAPI.Ingame.IMySlimBlock CurrentPickedWeldingBlock + { + get + { + return _CurrentPickedWeldingBlock; + } + set + { + if (_CurrentPickedWeldingBlock != value) + { + _CurrentPickedWeldingBlock = value; + Changed = 3u; + } + } + } + + [ProtoMember(100), XmlElement] + public SyncEntityId CurrentPickedWeldingBlockSync + { + get + { + return SyncEntityId.GetSyncId(_CurrentPickedWeldingBlock); + } + set + { + CurrentPickedWeldingBlock = SyncEntityId.GetItemAsSlimBlock(value); + } + } + + [XmlIgnore] + public VRage.Game.ModAPI.Ingame.IMySlimBlock CurrentPickedGrindingBlock + { + get + { + return _CurrentPickedGrindingBlock; + } + set + { + if (_CurrentPickedGrindingBlock != value) + { + _CurrentPickedGrindingBlock = value; + Changed = 3u; + } + } + } + + [ProtoMember(105), XmlElement] + public SyncEntityId CurrentPickedGrindingBlockSync + { + get + { + return SyncEntityId.GetSyncId(_CurrentPickedGrindingBlock); + } + set + { + CurrentPickedGrindingBlock = SyncEntityId.GetItemAsSlimBlock(value); + } + } + + [XmlIgnore] + public int MaximumRange { get; private set; } + + [XmlIgnore] + public int MaximumOffset { get; private set; } + + [XmlIgnore] + public float TransportSpeed { get; private set; } + + [XmlIgnore] + public float MaximumRequiredElectricPowerStandby { get; private set; } + + [XmlIgnore] + public float MaximumRequiredElectricPowerWelding { get; private set; } + + [XmlIgnore] + public float MaximumRequiredElectricPowerGrinding { get; private set; } + + [XmlIgnore] + public float MaximumRequiredElectricPowerTransport { get; private set; } + + //+X = Forward -X = Backward + //+Y = Left -Y = Right + //+Z = Up -Z = Down + internal Vector3 CorrectedAreaOffset + { + get + { + return _CorrectedAreaOffset; + } + } + + internal BoundingBoxD CorrectedAreaBoundingBox + { + get + { + return _CorrectedAreaBoundingBox; + } + } + + public SyncBlockSettings() : this(null) + { + } + + public SyncBlockSettings(NanobotBuildAndRepairSystemBlock system) + { + _WeldPriority = string.Empty; + _GrindPriority = string.Empty; + _ComponentCollectPriority = string.Empty; + CheckLimits(system, true); + + Changed = 0; + _LastStored = MyAPIGateway.Session.ElapsedPlayTime.Add(TimeSpan.FromSeconds(60)); + _LastTransmitted = MyAPIGateway.Session.ElapsedPlayTime; + + RecalcAreaBoundigBox(); + } + + public void TrySave(IMyEntity entity, Guid guid) + { + if ((Changed & 2u) == 0) return; + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(_LastStored) < TimeSpan.FromSeconds(20)) return; + Save(entity, guid); + } + + public void Save(IMyEntity entity, Guid guid) + { + if (entity.Storage == null) + { + entity.Storage = new MyModStorageComponent(); + } + + var storage = entity.Storage; + storage[guid] = GetAsXML(); + Changed = Changed & ~2u; + _LastStored = MyAPIGateway.Session.ElapsedPlayTime; + } + + public string GetAsXML() + { + return MyAPIGateway.Utilities.SerializeToXML(this); + } + + public void ResetChanged() + { + Changed = Changed & ~2u; + } + + public static SyncBlockSettings Load(NanobotBuildAndRepairSystemBlock system, Guid guid, NanobotBuildAndRepairSystemBlockPriorityHandling blockWeldPriority, NanobotBuildAndRepairSystemBlockPriorityHandling blockGrindPriority, NanobotBuildAndRepairSystemComponentPriorityHandling componentCollectPriority) + { + var storage = system.Entity.Storage; + string data; + SyncBlockSettings settings = null; + if (storage != null && storage.TryGetValue(guid, out data)) + { + try + { + //Fix changed names + data = data.Replace("GrindColorNearFirst", "GrindNearFirst"); + settings = MyAPIGateway.Utilities.SerializeFromXML(data); + if (settings != null) + { + settings.RecalcAreaBoundigBox(); + //Retrieve current settings or default if WeldPriority/GrindPriority/ComponentCollectPriority was empty + blockWeldPriority.SetEntries(settings.WeldPriority); + settings.WeldPriority = blockWeldPriority.GetEntries(); + + blockGrindPriority.SetEntries(settings.GrindPriority); + settings.GrindPriority = blockGrindPriority.GetEntries(); + + componentCollectPriority.SetEntries(settings.ComponentCollectPriority); + settings.ComponentCollectPriority = componentCollectPriority.GetEntries(); + + settings.Changed = 0; + settings._LastStored = MyAPIGateway.Session.ElapsedPlayTime.Add(TimeSpan.FromSeconds(60)); + settings._LastTransmitted = MyAPIGateway.Session.ElapsedPlayTime; + return settings; + } + } + catch (Exception ex) + { + Mod.Log.Write("SyncBlockSettings: Exception: " + ex); + } + } + + settings = new SyncBlockSettings(system); + blockWeldPriority.SetEntries(settings.WeldPriority); + blockGrindPriority.SetEntries(settings.GrindPriority); + componentCollectPriority.SetEntries(settings.ComponentCollectPriority); + settings.Changed = 0; + return settings; + } + + public void AssignReceived(SyncBlockSettings newSettings, NanobotBuildAndRepairSystemBlockPriorityHandling weldPriority, NanobotBuildAndRepairSystemBlockPriorityHandling grindPriority, NanobotBuildAndRepairSystemComponentPriorityHandling componentCollectPriority) + { + _Flags = newSettings._Flags; + _IgnoreColor = newSettings.IgnoreColor; + _GrindColor = newSettings.GrindColor; + _UseGrindJanitorOn = newSettings.UseGrindJanitorOn; + _GrindJanitorOptions = newSettings.GrindJanitorOptions; + _WeldOptions = newSettings.WeldOptions; + + _AreaOffset = newSettings.AreaOffset; + _AreaSize = newSettings.AreaSize; + + _WeldPriority = newSettings.WeldPriority; + _GrindPriority = newSettings.GrindPriority; + _ComponentCollectPriority = newSettings.ComponentCollectPriority; + + _SoundVolume = newSettings.SoundVolume; + _SearchMode = newSettings.SearchMode; + _WorkMode = newSettings.WorkMode; + + RecalcAreaBoundigBox(); + _IgnoreColorPacked = _IgnoreColor.PackHSVToUint(); + _GrindColorPacked = _GrindColor.PackHSVToUint(); + weldPriority.SetEntries(WeldPriority); + grindPriority.SetEntries(GrindPriority); + componentCollectPriority.SetEntries(ComponentCollectPriority); + + Changed = 2u; + } + + public SyncBlockSettings GetTransmit() + { + _LastTransmitted = MyAPIGateway.Session.ElapsedPlayTime; + Changed = Changed & ~1u; + return this; + } + + public bool IsTransmitNeeded() + { + return (Changed & 1u) != 0 && MyAPIGateway.Session.ElapsedPlayTime.Subtract(_LastTransmitted) >= TimeSpan.FromSeconds(2); + } + + private void RecalcAreaBoundigBox() + { + var border = 0.25d; + _CorrectedAreaBoundingBox = new BoundingBoxD(new Vector3D(-AreaSize.Z / 2 + border, -AreaSize.X / 2 + border, -AreaSize.Y / 2 + border), new Vector3D(AreaSize.Z / 2 - border, AreaSize.X / 2 - border, AreaSize.Y / 2 - border)); + _CorrectedAreaOffset = new Vector3(AreaOffset.Z, -AreaOffset.X, AreaOffset.Y); + } + + private void RecalcOffsetAndSize() + { + if (_AreaWidthLeft != null && _AreaWidthRight != null) + { + AreaSize = new Vector3(_AreaWidthRight.Value + _AreaWidthLeft.Value, AreaSize.Y, AreaSize.Z); + AreaOffset = new Vector3(AreaSize.X / 2 - _AreaWidthRight.Value, AreaOffset.Y, AreaOffset.Z); + } + if (_AreaWidthTop != null && _AreaWidthBottom != null) + { + AreaSize = new Vector3(AreaSize.X, _AreaWidthTop.Value + _AreaWidthBottom.Value, AreaSize.Z); + AreaOffset = new Vector3(AreaOffset.X, AreaSize.Y / 2 - _AreaWidthBottom.Value, AreaOffset.Z); + } + if (_AreaWidthFront != null && _AreaWidthRear != null) + { + AreaSize = new Vector3(AreaSize.X, AreaSize.Y, _AreaWidthFront.Value + _AreaWidthRear.Value); + AreaOffset = new Vector3(AreaOffset.X, AreaOffset.Y, AreaSize.Z / 2 - _AreaWidthRear.Value); + } + } + + public void CheckLimits(NanobotBuildAndRepairSystemBlock system, bool init) + { + var scale = system?.Welder != null ? system.Welder.BlockDefinition.SubtypeName.Contains("Large") ? 1f : 3f : 1f; + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed || init) + { + MaximumOffset = 0; + AreaOffset = new Vector3(0, 0, 0); + } + else + { + MaximumOffset = (int)Math.Ceiling(NanobotBuildAndRepairSystemMod.Settings.MaximumOffset / scale); + if (AreaOffset.X > MaximumOffset || init) AreaOffset = new Vector3(init ? 0 : (float)MaximumOffset, AreaOffset.Y, AreaOffset.Z); + else if (AreaOffset.X < -MaximumOffset || init) AreaOffset = new Vector3(init ? 0 : (float)-MaximumOffset, AreaOffset.Y, AreaOffset.Z); + + if (AreaOffset.Y > MaximumOffset || init) AreaOffset = new Vector3(AreaOffset.X, init ? 0 : (float)MaximumOffset, AreaOffset.Z); + else if (AreaOffset.Y < -MaximumOffset || init) AreaOffset = new Vector3(AreaOffset.X, init ? 0 : (float)-MaximumOffset, AreaOffset.Z); + + if (AreaOffset.Z > MaximumOffset || init) AreaOffset = new Vector3(AreaOffset.X, AreaOffset.Y, init ? 0 : (float)MaximumOffset); + else if (AreaOffset.Z < -MaximumOffset || init) AreaOffset = new Vector3(AreaOffset.X, AreaOffset.Y, init ? 0 : (float)-MaximumOffset); + } + + MaximumRange = (int)Math.Ceiling(NanobotBuildAndRepairSystemMod.Settings.Range * 2 / scale); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed || init) + { + AreaSize = new Vector3(MaximumRange, MaximumRange, MaximumRange); + } + else + { + if (AreaSize.X > MaximumRange || init) AreaSize = new Vector3(MaximumRange, AreaSize.Y, AreaSize.Z); + if (AreaSize.Y > MaximumRange || init) AreaSize = new Vector3(AreaSize.X, MaximumRange, AreaSize.Z); + if (AreaSize.Z > MaximumRange || init) AreaSize = new Vector3(AreaSize.X, AreaSize.Y, MaximumRange); + } + + MaximumRequiredElectricPowerStandby = NanobotBuildAndRepairSystemMod.Settings.MaximumRequiredElectricPowerStandby / scale; + MaximumRequiredElectricPowerTransport = NanobotBuildAndRepairSystemMod.Settings.MaximumRequiredElectricPowerTransport / scale; + MaximumRequiredElectricPowerWelding = NanobotBuildAndRepairSystemMod.Settings.Welder.MaximumRequiredElectricPowerWelding / scale; + MaximumRequiredElectricPowerGrinding = NanobotBuildAndRepairSystemMod.Settings.Welder.MaximumRequiredElectricPowerGrinding / scale; + + var maxMultiplier = Math.Max(NanobotBuildAndRepairSystemMod.Settings.Welder.WeldingMultiplier, NanobotBuildAndRepairSystemMod.Settings.Welder.GrindingMultiplier); + TransportSpeed = maxMultiplier * NanobotBuildAndRepairSystemBlock.WELDER_TRANSPORTSPEED_METER_PER_SECOND_DEFAULT * Math.Min(NanobotBuildAndRepairSystemMod.Settings.Range / NanobotBuildAndRepairSystemBlock.WELDER_RANGE_DEFAULT_IN_M, 4.0f); + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowBuildFixed || init) + { + Flags = (Flags & ~Settings.AllowBuild) | (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowBuildDefault ? Settings.AllowBuild : 0); + } + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed || init) + { + Flags = (Flags & ~Settings.UseIgnoreColor) | (NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorDefault ? Settings.UseIgnoreColor : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.IgnoreColorDefault != null && NanobotBuildAndRepairSystemMod.Settings.Welder.IgnoreColorDefault.Length >= 3) + { + IgnoreColor = new Vector3D(NanobotBuildAndRepairSystemMod.Settings.Welder.IgnoreColorDefault[0] / 360f, + (float)Math.Round(NanobotBuildAndRepairSystemMod.Settings.Welder.IgnoreColorDefault[1], 1, MidpointRounding.AwayFromZero) / 100f - NanobotBuildAndRepairSystemTerminal.SATURATION_DELTA, + (float)Math.Round(NanobotBuildAndRepairSystemMod.Settings.Welder.IgnoreColorDefault[2], 1, MidpointRounding.AwayFromZero) / 100f - NanobotBuildAndRepairSystemTerminal.VALUE_DELTA + NanobotBuildAndRepairSystemTerminal.VALUE_COLORIZE_DELTA); + } + } + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed || init) + { + Flags = (Flags & ~Settings.UseGrindColor) | (NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorDefault ? Settings.UseGrindColor : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.GrindColorDefault != null && NanobotBuildAndRepairSystemMod.Settings.Welder.GrindColorDefault.Length >= 3) + { + GrindColor = new Vector3D(NanobotBuildAndRepairSystemMod.Settings.Welder.GrindColorDefault[0] / 360f, + (float)Math.Round(NanobotBuildAndRepairSystemMod.Settings.Welder.GrindColorDefault[1], 1, MidpointRounding.AwayFromZero) / 100f - NanobotBuildAndRepairSystemTerminal.SATURATION_DELTA, + (float)Math.Round(NanobotBuildAndRepairSystemMod.Settings.Welder.GrindColorDefault[2], 1, MidpointRounding.AwayFromZero) / 100f - NanobotBuildAndRepairSystemTerminal.VALUE_DELTA + NanobotBuildAndRepairSystemTerminal.VALUE_COLORIZE_DELTA); + } + } + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || init) + { + UseGrindJanitorOn = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorDefault; + GrindJanitorOptions = NanobotBuildAndRepairSystemMod.Settings.Welder.GrindJanitorOptionsDefault; + } + + UseGrindJanitorOn &= NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations; + + if (NanobotBuildAndRepairSystemMod.Settings.Welder.ShowAreaFixed || init) Flags = Flags & ~Settings.ShowArea; + if (NanobotBuildAndRepairSystemMod.Settings.Welder.PushIngotOreImmediatelyFixed || init) Flags = (Flags & ~Settings.PushIngotOreImmediately) | (NanobotBuildAndRepairSystemMod.Settings.Welder.PushIngotOreImmediatelyDefault ? Settings.PushIngotOreImmediately : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.PushComponentImmediatelyFixed || init) Flags = (Flags & ~Settings.PushComponentImmediately) | (NanobotBuildAndRepairSystemMod.Settings.Welder.PushComponentImmediatelyDefault ? Settings.PushComponentImmediately : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.PushItemsImmediatelyFixed || init) Flags = (Flags & ~Settings.PushItemsImmediately) | (NanobotBuildAndRepairSystemMod.Settings.Welder.PushItemsImmediatelyDefault ? Settings.PushItemsImmediately : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.CollectIfIdleFixed || init) Flags = (Flags & ~Settings.ComponentCollectIfIdle) | (NanobotBuildAndRepairSystemMod.Settings.Welder.CollectIfIdleDefault ? Settings.ComponentCollectIfIdle : 0); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.SoundVolumeFixed || init) SoundVolume = NanobotBuildAndRepairSystemMod.Settings.Welder.SoundVolumeDefault; + if (NanobotBuildAndRepairSystemMod.Settings.Welder.ScriptControllFixed || init) Flags = Flags & ~Settings.ScriptControlled; + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & SearchMode) == 0 || init) + { + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & NanobotBuildAndRepairSystemMod.Settings.Welder.SearchModeDefault) != 0) + { + SearchMode = NanobotBuildAndRepairSystemMod.Settings.Welder.SearchModeDefault; + } + else + { + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & SearchModes.Grids) != 0) SearchMode = SearchModes.Grids; + else if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & SearchModes.BoundingBox) != 0) SearchMode = SearchModes.BoundingBox; + } + } + + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & WorkMode) == 0 || init) + { + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & NanobotBuildAndRepairSystemMod.Settings.Welder.WorkModeDefault) != 0) + { + WorkMode = NanobotBuildAndRepairSystemMod.Settings.Welder.WorkModeDefault; + } + else + { + if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & WorkModes.WeldBeforeGrind) != 0) WorkMode = WorkModes.WeldBeforeGrind; + else if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & WorkModes.GrindBeforeWeld) != 0) WorkMode = WorkModes.GrindBeforeWeld; + else if ((NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & WorkModes.GrindIfWeldGetStuck) != 0) WorkMode = WorkModes.GrindIfWeldGetStuck; + } + } + } + } + + /// + /// Current State of block + /// + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class SyncBlockState + { + public const int MaxSyncItems = 20; + private bool _Ready; + private bool _Welding; + private bool _NeedWelding; + private bool _Grinding; + private bool _NeedGrinding; + private bool _Transporting; + private bool _InventoryFull; + private bool _LimitsExceeded; + private List _MissingComponentsSync; + private List _PossibleWeldTargetsSync; + private List _PossibleGrindTargetsSync; + private List _PossibleFloatingTargetsSync; + private IMySlimBlock _CurrentWeldingBlock; + private IMySlimBlock _CurrentGrindingBlock; + + private Vector3D? _CurrentTransportTarget; + private Vector3D? _LastTransportTarget; + private bool _CurrentTransportIsPick; + private TimeSpan _CurrentTransportTime = TimeSpan.Zero; + private TimeSpan _CurrentTransportStartTime = TimeSpan.Zero; + + public bool Changed { get; private set; } + + public override string ToString() + { + return + $"Ready={Ready}, Welding={Welding}/{NeedWelding}, Grinding={Grinding}/{NeedGrinding}, MissingComponentsCount={MissingComponentsSync?.Count ?? -1}, PossibleWeldTargetsCount={PossibleWeldTargetsSync?.Count ?? -1}, PossibleGrindTargetsCount={PossibleGrindTargetsSync?.Count ?? -1}, PossibleFloatingTargetsCount={PossibleFloatingTargetsSync?.Count ?? -1}, CurrentWeldingBlock={Logging.BlockName(CurrentWeldingBlock, Logging.BlockNameOptions.None)}, CurrentGrindingBlock={Logging.BlockName(CurrentGrindingBlock, Logging.BlockNameOptions.None)}, CurrentTransportTarget={CurrentTransportTarget}"; + } + + [ProtoMember(1)] + public bool Ready + { + get { return _Ready; } + set + { + if (value != _Ready) + { + _Ready = value; + Changed = true; + } + } + } + + [ProtoMember(2)] + public bool Welding + { + get { return _Welding; } + set + { + if (value != _Welding) + { + _Welding = value; + Changed = true; + } + } + } + + [ProtoMember(3)] + public bool NeedWelding + { + get { return _NeedWelding; } + set + { + if (value != _NeedWelding) + { + _NeedWelding = value; + Changed = true; + } + } + } + + [ProtoMember(4)] + public bool Grinding + { + get { return _Grinding; } + set + { + if (value != _Grinding) + { + _Grinding = value; + Changed = true; + } + } + } + + [ProtoMember(5)] + public bool NeedGrinding + { + get { return _NeedGrinding; } + set + { + if (value != _NeedGrinding) + { + _NeedGrinding = value; + Changed = true; + } + } + } + + [ProtoMember(6)] + public bool Transporting + { + get { return _Transporting; } + set + { + if (value != _Transporting) + { + _Transporting = value; + Changed = true; + } + } + } + + [ProtoMember(7)] + public TimeSpan LastTransmitted { get; set; } + + public IMySlimBlock CurrentWeldingBlock + { + get { return _CurrentWeldingBlock; } + set + { + if (value != _CurrentWeldingBlock) + { + _CurrentWeldingBlock = value; + Changed = true; + } + } + } + + [ProtoMember(10)] + public SyncEntityId CurrentWeldingBlockSync + { + get + { + return SyncEntityId.GetSyncId(_CurrentWeldingBlock); + } + set + { + CurrentWeldingBlock = SyncEntityId.GetItemAsSlimBlock(value); + } + } + + public IMySlimBlock CurrentGrindingBlock + { + get { return _CurrentGrindingBlock; } + set + { + if (value != _CurrentGrindingBlock) + { + _CurrentGrindingBlock = value; + Changed = true; + } + } + } + + [ProtoMember(15)] + public SyncEntityId CurrentGrindingBlockSync + { + get + { + return SyncEntityId.GetSyncId(_CurrentGrindingBlock); + } + set + { + CurrentGrindingBlock = SyncEntityId.GetItemAsSlimBlock(value); + } + } + + [ProtoMember(16)] + public Vector3D? CurrentTransportTarget + { + get { return _CurrentTransportTarget; } + set + { + if (value != _CurrentTransportTarget) + { + _CurrentTransportTarget = value; + Changed = true; + } + } + } + + [ProtoMember(17)] + public Vector3D? LastTransportTarget + { + get { return _LastTransportTarget; } + set + { + if (value != _LastTransportTarget) + { + _LastTransportTarget = value; + Changed = true; + } + } + } + + [ProtoMember(18)] + public bool CurrentTransportIsPick + { + get { return _CurrentTransportIsPick; } + set + { + if (value != _CurrentTransportIsPick) + { + _CurrentTransportIsPick = value; + Changed = true; + } + } + } + + [ProtoMember(19)] + public TimeSpan CurrentTransportTime + { + get { return _CurrentTransportTime; } + set + { + if (value != _CurrentTransportTime) + { + _CurrentTransportTime = value; + Changed = true; + } + } + } + + [ProtoMember(20)] + public TimeSpan CurrentTransportStartTime + { + get { return _CurrentTransportStartTime; } + set + { + if (value != _CurrentTransportStartTime) + { + _CurrentTransportStartTime = value; + Changed = true; + } + } + } + + public DefinitionIdHashDictionary MissingComponents { get; private set; } + + [ProtoMember(21)] + public List MissingComponentsSync + { + get + { + if (_MissingComponentsSync == null) + { + if (MissingComponents != null) _MissingComponentsSync = MissingComponents.GetSyncList(); + else _MissingComponentsSync = new List(); + } + return _MissingComponentsSync; + } + } + + [ProtoMember(22)] + public bool InventoryFull + { + get { return _InventoryFull; } + set + { + if (value != _InventoryFull) + { + _InventoryFull = value; + Changed = true; + } + } + } + + [ProtoMember(23)] + public bool LimitsExceeded + { + get { return _LimitsExceeded; } + set + { + if (value != _LimitsExceeded) + { + _LimitsExceeded = value; + Changed = true; + } + } + } + + public TargetBlockDataHashList PossibleWeldTargets { get; private set; } + + [ProtoMember(30)] + public List PossibleWeldTargetsSync + { + get + { + if (_PossibleWeldTargetsSync == null) + { + if (PossibleWeldTargets != null) _PossibleWeldTargetsSync = PossibleWeldTargets.GetSyncList(); + else _PossibleWeldTargetsSync = new List(); + } + return _PossibleWeldTargetsSync; + } + } + + public TargetBlockDataHashList PossibleGrindTargets { get; private set; } + + [ProtoMember(35)] + public List PossibleGrindTargetsSync + { + get + { + if (_PossibleGrindTargetsSync == null) + { + if (PossibleGrindTargets != null) _PossibleGrindTargetsSync = PossibleGrindTargets.GetSyncList(); + else _PossibleGrindTargetsSync = new List(); + } + return _PossibleGrindTargetsSync; + } + } + + public TargetEntityDataHashList PossibleFloatingTargets { get; private set; } + + [ProtoMember(36)] + public List PossibleFloatingTargetsSync + { + get + { + if (_PossibleFloatingTargetsSync == null) + { + if (PossibleFloatingTargets != null) _PossibleFloatingTargetsSync = PossibleFloatingTargets.GetSyncList(); + else _PossibleFloatingTargetsSync = new List(); + } + return _PossibleFloatingTargetsSync; + } + } + + public SyncBlockState() + { + MissingComponents = new DefinitionIdHashDictionary(); + PossibleWeldTargets = new TargetBlockDataHashList(); + PossibleGrindTargets = new TargetBlockDataHashList(); + PossibleFloatingTargets = new TargetEntityDataHashList(); + } + + internal void HasChanged() + { + Changed = true; + } + + internal bool IsTransmitNeeded() + { + return Changed && MyAPIGateway.Session.ElapsedPlayTime.Subtract(LastTransmitted).TotalSeconds >= 2; + } + + internal SyncBlockState GetTransmit() + { + _MissingComponentsSync = null; + _PossibleWeldTargetsSync = null; + _PossibleGrindTargetsSync = null; + _PossibleFloatingTargetsSync = null; + LastTransmitted = MyAPIGateway.Session.ElapsedPlayTime; + Changed = false; + return this; + } + + internal void AssignReceived(SyncBlockState newState) + { + _Ready = newState.Ready; + _Welding = newState.Welding; + _NeedWelding = newState.NeedWelding; + _Grinding = newState.Grinding; + _NeedGrinding = newState.NeedGrinding; + _InventoryFull = newState.InventoryFull; + _LimitsExceeded = newState.LimitsExceeded; + _CurrentTransportStartTime = MyAPIGateway.Session.ElapsedPlayTime - (newState.LastTransmitted - newState.CurrentTransportStartTime); + _CurrentTransportTime = newState.CurrentTransportTime; + + _CurrentWeldingBlock = SyncEntityId.GetItemAsSlimBlock(newState.CurrentWeldingBlockSync); + _CurrentGrindingBlock = SyncEntityId.GetItemAsSlimBlock(newState.CurrentGrindingBlockSync); + _CurrentTransportTarget = newState.CurrentTransportTarget; + _CurrentTransportIsPick = newState.CurrentTransportIsPick; + + MissingComponents.Clear(); + var missingComponentsSync = newState.MissingComponentsSync; + if (missingComponentsSync != null) foreach (var item in missingComponentsSync) MissingComponents.Add(item.Component, item.Amount); + + PossibleWeldTargets.Clear(); + var possibleWeldTargetsSync = newState.PossibleWeldTargetsSync; + if (possibleWeldTargetsSync != null) foreach (var item in possibleWeldTargetsSync) PossibleWeldTargets.Add(new TargetBlockData(SyncEntityId.GetItemAsSlimBlock(item.Entity), item.Distance, 0)); + + PossibleGrindTargets.Clear(); + var possibleGrindTargetsSync = newState.PossibleGrindTargetsSync; + if (possibleGrindTargetsSync != null) foreach (var item in possibleGrindTargetsSync) PossibleGrindTargets.Add(new TargetBlockData(SyncEntityId.GetItemAsSlimBlock(item.Entity), item.Distance, 0)); + + PossibleFloatingTargets.Clear(); + var possibleFloatingTargetsSync = newState.PossibleFloatingTargetsSync; + if (possibleFloatingTargetsSync != null) foreach (var item in possibleFloatingTargetsSync) PossibleFloatingTargets.Add(new TargetEntityData(SyncEntityId.GetItemAs(item.Entity), item.Distance)); + + Changed = true; + } + + internal void ResetChanged() + { + Changed = false; + } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgModCommand + { + [ProtoMember(1)] + public ulong SteamId { get; set; } + + [ProtoMember(2)] + public string Command { get; set; } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgModDataRequest + { + [ProtoMember(1)] + public ulong SteamId { get; set; } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgModSettings + { + [ProtoMember(2)] + public SyncModSettings Settings { get; set; } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgBlockDataRequest + { + [ProtoMember(1)] + public ulong SteamId { get; set; } + + [ProtoMember(2)] + public long EntityId { get; set; } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgBlockSettings + { + [ProtoMember(1)] + public long EntityId { get; set; } + + [ProtoMember(2)] + public SyncBlockSettings Settings { get; set; } + } + + [ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)] + public class MsgBlockState + { + [ProtoMember(1)] + public long EntityId { get; set; } + + [ProtoMember(2)] + public SyncBlockState State { get; set; } + } + + /// + /// Hash list for TargetBlockData + /// + public class TargetBlockDataHashList : HashList + { + public override List GetSyncList() + { + var result = new List(); + var idx = 0; + foreach (var item in this) + { + result.Add(new SyncTargetEntityData() { Entity = SyncEntityId.GetSyncId(item.Block), Distance = item.Distance }); + idx++; + if (idx > SyncBlockState.MaxSyncItems) break; + } + return result; + } + + public override void RebuildHash() + { + uint hash = 0; + var idx = 0; + lock (this) + { + foreach (var entry in this) + { + hash ^= UtilsSynchronization.RotateLeft((uint)entry.Block.GetHashCode(), idx + 1); + idx++; + if (idx >= SyncBlockState.MaxSyncItems) break; + } + CurrentCount = this.Count; + CurrentHash = hash; + } + } + } + + /// + /// Hash list for TargetEntityData + /// + public class TargetEntityDataHashList : HashList + { + public override List GetSyncList() + { + var result = new List(); + var idx = 0; + foreach (var item in this) + { + result.Add(new SyncTargetEntityData() { Entity = SyncEntityId.GetSyncId(item.Entity), Distance = item.Distance }); + idx++; + if (idx > SyncBlockState.MaxSyncItems) break; + } + return result; + } + + public override void RebuildHash() + { + uint hash = 0; + var idx = 0; + lock (this) + { + foreach (var entry in this) + { + hash ^= UtilsSynchronization.RotateLeft((uint)entry.Entity.GetHashCode(), idx + 1); + idx++; + if (idx >= SyncBlockState.MaxSyncItems) break; + } + CurrentCount = this.Count; + CurrentHash = hash; + } + } + } + + public class DefinitionIdHashDictionary : HashDictionary + { + public override List GetSyncList() + { + var result = new List(); + var idx = 0; + foreach (var item in this) + { + result.Add(new SyncComponents() { Component = item.Key, Amount = item.Value }); + idx++; + if (idx > SyncBlockState.MaxSyncItems) break; + } + return result; + } + + public override void RebuildHash() + { + uint hash = 0; + var idx = 0; + lock (this) + { + foreach (var entry in this) + { + hash ^= UtilsSynchronization.RotateLeft((uint)entry.GetHashCode(), idx + 1); + idx++; + if (idx >= SyncBlockState.MaxSyncItems) break; + } + CurrentCount = Count; + CurrentHash = hash; + } + } + } + + public class TargetEntityData + { + public IMyEntity Entity { get; internal set; } + public double Distance { get; internal set; } + public bool Ignore { get; set; } + + public TargetEntityData(IMyEntity entity, double distance) + { + Entity = entity; + Distance = distance; + Ignore = false; + } + } + + public class TargetBlockData : TargetEntityData + { + [Flags] + public enum AttributeFlags + { + Projected = 0x0001, + Autogrind = 0x0100 + } + + public IMySlimBlock Block { get; internal set; } + public AttributeFlags Attributes { get; internal set; } + + public TargetBlockData(IMySlimBlock block, double distance, AttributeFlags attributes) : base(block?.FatBlock, distance) + { + Block = block; + Attributes = attributes; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemTerminal.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemTerminal.cs new file mode 100644 index 00000000..973b47f9 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/NanobotBuildAndRepairSystemTerminal.cs @@ -0,0 +1,2270 @@ +namespace SKONanobotBuildAndRepairSystem +{ + using System; + using System.Collections.Generic; + using System.Text; + using Sandbox.Game.Localization; + using Sandbox.ModAPI; + using Sandbox.ModAPI.Interfaces.Terminal; + using VRage; + using VRage.ModAPI; + using VRageMath; + + [Flags] + public enum SearchModes + { + /// + /// Search Target blocks only inside connected blocks + /// + Grids = 0x0001, + + /// + /// Search Target blocks in bounding boy independend of connection + /// + BoundingBox = 0x0002 + } + + [Flags] + 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 + } + + [Flags] + public enum AutoGrindRelation + { + NoOwnership = 0x0001, + Owner = 0x0002, + FactionShare = 0x0004, + Neutral = 0x0008, + Enemies = 0x0010 + } + + [Flags] + public enum AutoGrindOptions + { + DisableOnly = 0x0001, + HackOnly = 0x0002 + } + + [Flags] + public enum AutoWeldOptions + { + FunctionalOnly = 0x0001 + } + + [Flags] + public enum VisualAndSoundEffects + { + WeldingVisualEffect = 0x00000001, + WeldingSoundEffect = 0x00000010, + GrindingVisualEffect = 0x00000100, + GrindingSoundEffect = 0x00001000, + TransportVisualEffect = 0x00010000, + } + + public static class NanobotBuildAndRepairSystemTerminal + { + public const float SATURATION_DELTA = 0.8f; + public const float VALUE_DELTA = 0.55f; + public const float VALUE_COLORIZE_DELTA = 0.1f; + + public static bool CustomControlsInit = false; + private static readonly List CustomControls = new List(); + + private static IMyTerminalControl _HelpOthers; + private static IMyTerminalControlSeparator _SeparateWeldOptions; + + private static IMyTerminalControlSlider _IgnoreColorHueSlider; + private static IMyTerminalControlSlider _IgnoreColorSaturationSlider; + private static IMyTerminalControlSlider _IgnoreColorValueSlider; + + private static IMyTerminalControlSlider _GrindColorHueSlider; + private static IMyTerminalControlSlider _GrindColorSaturationSlider; + private static IMyTerminalControlSlider _GrindColorValueSlider; + + private static IMyTerminalControlOnOffSwitch _WeldEnableDisableSwitch; + private static IMyTerminalControlButton _WeldPriorityButtonUp; + private static IMyTerminalControlButton _WeldPriorityButtonDown; + private static IMyTerminalControlListbox _WeldPriorityListBox; + private static IMyTerminalControlOnOffSwitch _GrindEnableDisableSwitch; + private static IMyTerminalControlButton _GrindPriorityButtonUp; + private static IMyTerminalControlButton _GrindPriorityButtonDown; + private static IMyTerminalControlListbox _GrindPriorityListBox; + + private static IMyTerminalControlOnOffSwitch _ComponentCollectEnableDisableSwitch; + private static IMyTerminalControlButton _ComponentCollectPriorityButtonUp; + private static IMyTerminalControlButton _ComponentCollectPriorityButtonDown; + private static IMyTerminalControlListbox _ComponentCollectPriorityListBox; + private static IMyTerminalControlCheckbox _ComponentCollectIfIdleSwitch; + + /// + /// Check an return the GameLogic object + /// + /// + /// + private static NanobotBuildAndRepairSystemBlock GetSystem(IMyTerminalBlock block) + { + return block?.GameLogic?.GetAs(); + } + + /// + /// Initialize custom control definition + /// + public static void InitializeControls() + { + lock (CustomControls) + { + if (CustomControlsInit) return; + CustomControlsInit = true; + try + { + // As CustomControlGetter is only called if the Terminal is opened, + // I add also some properties immediately and permanent to support scripting. + // !! As we can't subtype here they will be also available in every Shipwelder but without function !! + + if (Mod.Log.ShouldLog(Logging.Level.Event)) Mod.Log.Write(Logging.Level.Event, "InitializeControls"); + + MyAPIGateway.TerminalControls.CustomControlGetter += CustomControlGetter; + + IMyTerminalControlLabel label; + IMyTerminalControlCheckbox checkbox; + IMyTerminalControlCombobox comboBox; + IMyTerminalControlSeparator separateArea; + IMyTerminalControlSlider slider; + IMyTerminalControlOnOffSwitch onoffSwitch; + IMyTerminalControlButton button; + + var weldingAllowed = (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & (WorkModes.WeldBeforeGrind | WorkModes.GrindBeforeWeld | WorkModes.GrindIfWeldGetStuck | WorkModes.WeldOnly)) != 0; + var grindingAllowed = (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & (WorkModes.WeldBeforeGrind | WorkModes.GrindBeforeWeld | WorkModes.GrindIfWeldGetStuck | WorkModes.GrindOnly)) != 0; + var janitorAllowed = grindingAllowed && NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations != 0; + var janitorAllowedNoOwnership = janitorAllowed && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations & AutoGrindRelation.NoOwnership) != 0; + var janitorAllowedOwner = janitorAllowed && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations & AutoGrindRelation.Owner) != 0; + var janitorAllowedFactionShare = janitorAllowed && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations & AutoGrindRelation.FactionShare) != 0; + var janitorAllowedNeutral = janitorAllowed && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations & AutoGrindRelation.Neutral) != 0; + var janitorAllowedEnemies = janitorAllowed && (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedGrindJanitorRelations & AutoGrindRelation.Enemies) != 0; + + Func isBaRSystem = (block) => + { + var system = GetSystem(block); + return system != null; + }; + + Func isReadonly = (block) => { return false; }; + Func isWeldingAllowed = (block) => { return weldingAllowed; }; + Func isGrindingAllowed = (block) => { return grindingAllowed; }; + Func isJanitorAllowed = (block) => { return janitorAllowed; }; + Func isJanitorAllowedNoOwnership = (block) => { return janitorAllowedNoOwnership; }; + Func isJanitorAllowedOwner = (block) => { return janitorAllowedOwner; }; + Func isJanitorAllowedFactionShare = (block) => { return janitorAllowedFactionShare; }; + Func isJanitorAllowedNeutral = (block) => { return janitorAllowedNeutral; }; + Func isJanitorAllowedEnemies = (block) => { return janitorAllowedEnemies; }; + Func isCollectPossible = (block) => + { + var system = GetSystem(block); + return system != null && system.Settings.SearchMode == SearchModes.BoundingBox; + }; + Func isChangeCollectPriorityPossible = (block) => + { + var system = GetSystem(block); + return system?.ComponentCollectPriority?.Selected != null && system.Settings.SearchMode == SearchModes.BoundingBox && !NanobotBuildAndRepairSystemMod.Settings.Welder.CollectPriorityFixed; + }; + + List controls; + MyAPIGateway.TerminalControls.GetControls(out controls); + _HelpOthers = controls.Find((ctrl) => + { + var cb = ctrl as IMyTerminalControlCheckbox; + return cb != null && ctrl.Id == "helpOthers"; + }); + + // --- General + label = MyAPIGateway.TerminalControls.CreateControl("ModeSettings"); + label.Label = Texts.ModeSettings_Headline; + CustomControls.Add(label); + { + // --- Select search mode + var onlyOneAllowed = (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes & (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes - 1)) == 0; + comboBox = MyAPIGateway.TerminalControls.CreateControl("Mode"); + comboBox.Title = Texts.SearchMode; + comboBox.Tooltip = Texts.SearchMode_Tooltip; + comboBox.Enabled = onlyOneAllowed ? isReadonly : isBaRSystem; + + comboBox.ComboBoxContent = (list) => + { + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes.HasFlag(SearchModes.Grids)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)SearchModes.Grids, Value = Texts.SearchMode_Walk }); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes.HasFlag(SearchModes.BoundingBox)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)SearchModes.BoundingBox, Value = Texts.SearchMode_Fly }); + }; + comboBox.Getter = (block) => + { + var system = GetSystem(block); + if (system == null) return 0; + else return (long)system.Settings.SearchMode; + }; + comboBox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null) + { + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedSearchModes.HasFlag((SearchModes)value)) + { + system.Settings.SearchMode = (SearchModes)value; + UpdateVisual(_ComponentCollectPriorityListBox); + UpdateVisual(_ComponentCollectIfIdleSwitch); + } + } + }; + comboBox.SupportsMultipleBlocks = true; + CustomControls.Add(comboBox); + CreateProperty(comboBox, onlyOneAllowed); + + //Allow switch mode by Buttonpanel + var list1 = new List(); + comboBox.ComboBoxContent(list1); + foreach (var entry in list1) + { + var mode = entry.Key; + var comboBox1 = comboBox; + var action = MyAPIGateway.TerminalControls.CreateAction( + $"{((SearchModes)mode).ToString()}_On"); + action.Name = new StringBuilder($"{entry.Value} On"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOn.dds"; + action.Enabled = isBaRSystem; + action.Action = (block) => + { + comboBox1.Setter(block, mode); + }; + action.ValidForGroups = true; + MyAPIGateway.TerminalControls.AddAction(action); + } + + // --- Select work mode + onlyOneAllowed = (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes & (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes - 1)) == 0; + comboBox = MyAPIGateway.TerminalControls.CreateControl("WorkMode"); + comboBox.Title = Texts.WorkMode; + comboBox.Tooltip = Texts.WorkMode_Tooltip; + comboBox.Enabled = onlyOneAllowed ? isReadonly : isBaRSystem; + comboBox.ComboBoxContent = (list) => + { + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag(WorkModes.WeldBeforeGrind)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)WorkModes.WeldBeforeGrind, Value = Texts.WorkMode_WeldB4Grind }); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag(WorkModes.GrindBeforeWeld)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)WorkModes.GrindBeforeWeld, Value = Texts.WorkMode_GrindB4Weld }); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag(WorkModes.GrindIfWeldGetStuck)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)WorkModes.GrindIfWeldGetStuck, Value = Texts.WorkMode_GrindIfWeldStuck }); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag(WorkModes.WeldOnly)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)WorkModes.WeldOnly, Value = Texts.WorkMode_WeldOnly }); + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag(WorkModes.GrindOnly)) + list.Add(new MyTerminalControlComboBoxItem() { Key = (long)WorkModes.GrindOnly, Value = Texts.WorkMode_GrindOnly }); + }; + comboBox.Getter = (block) => + { + var system = GetSystem(block); + if (system == null) return 0; + else return (long)system.Settings.WorkMode; + }; + comboBox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null) + { + if (NanobotBuildAndRepairSystemMod.Settings.Welder.AllowedWorkModes.HasFlag((WorkModes)value)) + { + system.Settings.WorkMode = (WorkModes)value; + } + } + }; + comboBox.SupportsMultipleBlocks = true; + CustomControls.Add(comboBox); + CreateProperty(comboBox, onlyOneAllowed); + + //Allow switch work mode by Buttonpanel + list1 = new List(); + comboBox.ComboBoxContent(list1); + foreach (var entry in list1) + { + var mode = entry.Key; + var comboBox1 = comboBox; + var action = MyAPIGateway.TerminalControls.CreateAction( + $"{((WorkModes)mode).ToString()}_On"); + action.Name = new StringBuilder($"{entry.Value} On"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOn.dds"; + action.Enabled = isBaRSystem; + action.Action = (block) => + { + comboBox1.Setter(block, mode); + }; + action.ValidForGroups = true; + MyAPIGateway.TerminalControls.AddAction(action); + } + } + + // --- Welding + label = MyAPIGateway.TerminalControls.CreateControl("WeldingSettings"); + label.Label = Texts.WeldSettings_Headline; + CustomControls.Add(label); + { + // --- Set Color that marks blocks as 'ignore' + { + checkbox = MyAPIGateway.TerminalControls.CreateControl("UseIgnoreColor"); + checkbox.Title = Texts.WeldUseIgnoreColor; + checkbox.Tooltip = Texts.WeldUseIgnoreColor_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed || !weldingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isWeldingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed && isWeldingAllowed(block)) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.UseIgnoreColor) | (value ? SyncBlockSettings.Settings.UseIgnoreColor : 0); + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("IgnoreColor")) ctrl.UpdateVisual(); + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("UseIgnoreColor", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed); + + Func colorPickerEnabled = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.UseIgnoreColor) != 0 && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed && isWeldingAllowed(block); + }; + + button = MyAPIGateway.TerminalControls.CreateControl("IgnoreColorPickCurrent"); + button.Title = Texts.Color_PickCurrentColor; + button.Enabled = colorPickerEnabled; + button.Visible = isWeldingAllowed; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && MyAPIGateway.Session.LocalHumanPlayer != null) + { + system.Settings.IgnoreColor = MyAPIGateway.Session.LocalHumanPlayer.SelectedBuildColor; + UpdateVisual(_IgnoreColorHueSlider); + UpdateVisual(_IgnoreColorSaturationSlider); + UpdateVisual(_IgnoreColorValueSlider); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + button = MyAPIGateway.TerminalControls.CreateControl("IgnoreColorSetAsCurrent"); + button.Title = Texts.Color_SetCurrentColor; + button.Enabled = colorPickerEnabled; + button.Visible = isWeldingAllowed; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && MyAPIGateway.Session.LocalHumanPlayer != null) + { + MyAPIGateway.Session.LocalHumanPlayer.SelectedBuildColor = system.Settings.IgnoreColor; + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + slider = MyAPIGateway.TerminalControls.CreateControl("IgnoreColorHue"); + _IgnoreColorHueSlider = slider; + slider.Title = MySpaceTexts.EditFaction_HueSliderText; + slider.SetLimits(0, 360); + slider.Enabled = colorPickerEnabled; + slider.Visible = isWeldingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.IgnoreColor.X * 360f : 0; + }; + slider.Setter = (block, x) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + x = x < 0 ? 0 : x > 360 ? 360 : x; + hsv.X = (float)Math.Round(x, 1, MidpointRounding.AwayFromZero) / 360; + system.Settings.IgnoreColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + val.Append(Math.Round(hsv.X * 360f, 1, MidpointRounding.AwayFromZero)); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("IgnoreColorHue", slider); + + slider = MyAPIGateway.TerminalControls.CreateControl("IgnoreColorSaturation"); + _IgnoreColorSaturationSlider = slider; + slider.Title = MySpaceTexts.EditFaction_SaturationSliderText; + slider.SetLimits(0, 100); + slider.Enabled = colorPickerEnabled; + slider.Visible = isWeldingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? (system.Settings.IgnoreColor.Y + SATURATION_DELTA) * 100f : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + val = val < 0 ? 0 : val > 100 ? 100 : val; + hsv.Y = (float)Math.Round(val, 1, MidpointRounding.AwayFromZero) / 100f - SATURATION_DELTA; + system.Settings.IgnoreColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + val.Append(Math.Round((hsv.Y + SATURATION_DELTA) * 100f, 1, MidpointRounding.AwayFromZero)); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("IgnoreColorSaturation", slider); + + slider = MyAPIGateway.TerminalControls.CreateControl("IgnoreColorValue"); + _IgnoreColorValueSlider = slider; + slider.Title = MySpaceTexts.EditFaction_ValueSliderText; ; + slider.SetLimits(0, 100); + slider.Enabled = colorPickerEnabled; + slider.Visible = isWeldingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? (system.Settings.IgnoreColor.Z + VALUE_DELTA - VALUE_COLORIZE_DELTA) * 100f : 0; + }; + slider.Setter = (block, z) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + z = z < 0 ? 0 : z > 100 ? 100 : z; + hsv.Z = (float)Math.Round(z, 1, MidpointRounding.AwayFromZero) / 100f - VALUE_DELTA + VALUE_COLORIZE_DELTA; + system.Settings.IgnoreColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.IgnoreColor; + val.Append(Math.Round((hsv.Z + VALUE_DELTA - VALUE_COLORIZE_DELTA) * 100f, 1, MidpointRounding.AwayFromZero)); + } + }; + CustomControls.Add(slider); + CreateSliderActions("IgnoreColorValue", slider); + + var propertyIC = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.IgnoreColor"); + propertyIC.SupportsMultipleBlocks = false; + propertyIC.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? ConvertFromHSVColor(system.Settings.IgnoreColor) : Vector3.Zero; + }; + propertyIC.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseIgnoreColorFixed) + { + system.Settings.IgnoreColor = CheckConvertToHSVColor(value); + } + }; + MyAPIGateway.TerminalControls.AddControl(propertyIC); + } + + //Weld Options + _SeparateWeldOptions = MyAPIGateway.TerminalControls.CreateControl("SeparateWeldOptions"); + _SeparateWeldOptions.Visible = isWeldingAllowed; + CustomControls.Add(_SeparateWeldOptions); + { + // ---helpOthers + //Moved here + + // --- AllowBuild CheckBox + checkbox = MyAPIGateway.TerminalControls.CreateControl("AllowBuild"); + checkbox.Title = Texts.WeldBuildNew; + checkbox.Tooltip = Texts.WeldBuildNew_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AllowBuildFixed || !weldingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isWeldingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.AllowBuild) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.AllowBuildFixed && isWeldingAllowed(block)) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.AllowBuild) | (value ? SyncBlockSettings.Settings.AllowBuild : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("AllowBuild", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.AllowBuildFixed || !weldingAllowed); + + //--Weld to functional only + checkbox = MyAPIGateway.TerminalControls.CreateControl("WeldOptionFunctionalOnly"); + checkbox.Title = Texts.WeldToFuncOnly; + checkbox.Tooltip = Texts.WeldToFuncOnly_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = !weldingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isWeldingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.WeldOptions & AutoWeldOptions.FunctionalOnly) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && isWeldingAllowed(block)) + { + if (value) + { + system.Settings.WeldOptions = system.Settings.WeldOptions | AutoWeldOptions.FunctionalOnly; + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("WeldOption")) ctrl.UpdateVisual(); + } + } + else + { + system.Settings.WeldOptions = system.Settings.WeldOptions & ~AutoWeldOptions.FunctionalOnly; + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("WeldOptionFunctionalOnly", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, !weldingAllowed); + } + + // -- Priority Welding + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateWeldPrio"); + separateArea.Visible = isWeldingAllowed; + CustomControls.Add(separateArea); + { + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("WeldPriority"); + _WeldEnableDisableSwitch = onoffSwitch; + onoffSwitch.Title = Texts.WeldPriority; + onoffSwitch.Tooltip = Texts.WeldPriority_Tooltip; + onoffSwitch.OnText = Texts.Priority_Enable; + onoffSwitch.OffText = Texts.Priority_Disable; + onoffSwitch.Visible = isWeldingAllowed; + onoffSwitch.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockWeldPriority?.Selected != null && isWeldingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system?.BlockWeldPriority?.Selected != null && system.BlockWeldPriority.GetEnabled(system.BlockWeldPriority.Selected.Key); + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system?.BlockWeldPriority?.Selected != null && isWeldingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockWeldPriority.SetEnabled(system.BlockWeldPriority.Selected.Key, value); + system.Settings.WeldPriority = system.BlockWeldPriority.GetEntries(); + UpdateVisual(_WeldPriorityListBox); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CustomControls.Add(onoffSwitch); + + button = MyAPIGateway.TerminalControls.CreateControl("WeldPriorityUp"); + _WeldPriorityButtonUp = button; + button.Title = Texts.Priority_Up; + button.Visible = isWeldingAllowed; + button.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockWeldPriority?.Selected != null && isWeldingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockWeldPriority.MoveSelectedUp(); + system.Settings.WeldPriority = system.BlockWeldPriority.GetEntries(); + UpdateVisual(_WeldPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + button = MyAPIGateway.TerminalControls.CreateControl("WeldPriorityDown"); + _WeldPriorityButtonDown = button; + button.Title = Texts.Priority_Down; + button.Visible = isWeldingAllowed; + button.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockWeldPriority?.Selected != null && isWeldingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockWeldPriority.MoveSelectedDown(); + system.Settings.WeldPriority = system.BlockWeldPriority.GetEntries(); + UpdateVisual(_WeldPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + var listbox = MyAPIGateway.TerminalControls.CreateControl("WeldPriority"); + _WeldPriorityListBox = listbox; + + listbox.Multiselect = false; + listbox.VisibleRowsCount = 15; + listbox.Enabled = weldingAllowed ? isBaRSystem : isReadonly; + listbox.Visible = isWeldingAllowed; + listbox.ItemSelected = (block, selected) => + { + var system = GetSystem(block); + if (system?.BlockWeldPriority != null) + { + if (selected.Count > 0) system.BlockWeldPriority.SetSelectedByKey(((PrioItem)selected[0].UserData).Key); + else system.BlockWeldPriority.ClearSelected(); + UpdateVisual(_WeldEnableDisableSwitch); + UpdateVisual(_WeldPriorityButtonUp); + UpdateVisual(_WeldPriorityButtonDown); + } + }; + listbox.ListContent = (block, items, selected) => + { + var system = GetSystem(block); + system?.BlockWeldPriority?.FillTerminalList(items, selected); + }; + listbox.SupportsMultipleBlocks = true; + CustomControls.Add(listbox); + } + } + + // --- Grinding + label = MyAPIGateway.TerminalControls.CreateControl("GrindingSettings"); + label.Label = Texts.GrindSettings_Headline; + CustomControls.Add(label); + { + // --- Set Color that marks blocks as 'grind' + //separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateGrindColor"); + //separateArea.Visible = isGrindingAllowed; + //CustomControls.Add(separateArea); + { + checkbox = MyAPIGateway.TerminalControls.CreateControl("UseGrindColor"); + checkbox.Title = Texts.GrindUseGrindColor; + checkbox.Tooltip = Texts.GrindUseGrindColor_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed || !grindingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isGrindingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.UseGrindColor) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed && isGrindingAllowed(block)) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.UseGrindColor) | (value ? SyncBlockSettings.Settings.UseGrindColor : 0); + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindColor")) ctrl.UpdateVisual(); + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("UseGrindColor", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed || !grindingAllowed); + + Func colorPickerEnabled = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.UseGrindColor) != 0 && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed && isGrindingAllowed(block); + }; + + button = MyAPIGateway.TerminalControls.CreateControl("GrindColorPickCurrent"); + button.Title = Texts.Color_PickCurrentColor; + button.Enabled = colorPickerEnabled; + button.Visible = isGrindingAllowed; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && MyAPIGateway.Session.LocalHumanPlayer != null) + { + system.Settings.GrindColor = MyAPIGateway.Session.LocalHumanPlayer.SelectedBuildColor; + UpdateVisual(_GrindColorHueSlider); + UpdateVisual(_GrindColorSaturationSlider); + UpdateVisual(_GrindColorValueSlider); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + button = MyAPIGateway.TerminalControls.CreateControl("GrindColorSetAsCurrent"); + button.Title = Texts.Color_SetCurrentColor; + button.Enabled = colorPickerEnabled; + button.Visible = isGrindingAllowed; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && MyAPIGateway.Session.LocalHumanPlayer != null) + { + MyAPIGateway.Session.LocalHumanPlayer.SelectedBuildColor = system.Settings.GrindColor; + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + slider = MyAPIGateway.TerminalControls.CreateControl("GrindColorHue"); + _GrindColorHueSlider = slider; + slider.Title = MySpaceTexts.EditFaction_HueSliderText; + slider.SetLimits(0, 360); + slider.Enabled = colorPickerEnabled; + slider.Visible = isGrindingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.GrindColor.X * 360f : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + val = val < 0 ? 0 : val > 360 ? 360 : val; + hsv.X = (float)Math.Round(val, 1, MidpointRounding.AwayFromZero) / 360; + system.Settings.GrindColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + val.Append(Math.Round(hsv.X * 360f, 1, MidpointRounding.AwayFromZero)); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("GrindColorHue", slider); + + slider = MyAPIGateway.TerminalControls.CreateControl("GrindColorSaturation"); + _GrindColorSaturationSlider = slider; + slider.Title = MySpaceTexts.EditFaction_SaturationSliderText; + slider.SetLimits(0, 100); + slider.Enabled = colorPickerEnabled; + slider.Visible = isGrindingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? (system.Settings.GrindColor.Y + SATURATION_DELTA) * 100f : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + val = val < 0 ? 0 : val > 100 ? 100 : val; + hsv.Y = (float)Math.Round(val, 1, MidpointRounding.AwayFromZero) / 100f - SATURATION_DELTA; + system.Settings.GrindColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + val.Append(Math.Round((hsv.Y + SATURATION_DELTA) * 100f, 1, MidpointRounding.AwayFromZero)); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("GrindColorSaturation", slider); + + slider = MyAPIGateway.TerminalControls.CreateControl("GrindColorValue"); + _GrindColorValueSlider = slider; + slider.Title = MySpaceTexts.EditFaction_ValueSliderText; + slider.SetLimits(0, 100); + slider.Enabled = colorPickerEnabled; + slider.Visible = isGrindingAllowed; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? (system.Settings.GrindColor.Z + VALUE_DELTA - VALUE_COLORIZE_DELTA) * 100f : 0; + }; + slider.Setter = (block, z) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + z = z < 0 ? 0 : z > 100 ? 100 : z; + hsv.Z = (float)Math.Round(z, 1, MidpointRounding.AwayFromZero) / 100f - VALUE_DELTA + VALUE_COLORIZE_DELTA; + system.Settings.GrindColor = hsv; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var hsv = system.Settings.GrindColor; + val.Append(Math.Round((hsv.Z + VALUE_DELTA - VALUE_COLORIZE_DELTA) * 100f, 1, MidpointRounding.AwayFromZero)); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("GrindColorValue", slider); + + var propertyGC = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.GrindColor"); + propertyGC.SupportsMultipleBlocks = false; + propertyGC.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? ConvertFromHSVColor(system.Settings.GrindColor) : Vector3.Zero; + }; + propertyGC.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindColorFixed) + { + system.Settings.GrindColor = CheckConvertToHSVColor(value); + } + }; + MyAPIGateway.TerminalControls.AddControl(propertyGC); + } + + // --- Enable Janitor grinding + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateGrindJanitor"); + separateArea.Visible = isJanitorAllowed; + CustomControls.Add(separateArea); + { + //--Grind enemy + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("GrindJanitorEnemies"); + onoffSwitch.Title = Texts.GrindJanitorEnemy; + onoffSwitch.Tooltip = Texts.GrindJanitorEnemy_Tooltip; + onoffSwitch.OnText = MySpaceTexts.SwitchText_On; + onoffSwitch.OffText = MySpaceTexts.SwitchText_Off; + onoffSwitch.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedEnemies ? isReadonly : isBaRSystem; + onoffSwitch.Visible = isJanitorAllowedEnemies; + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.UseGrindJanitorOn & AutoGrindRelation.Enemies) != 0; + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed && isJanitorAllowedEnemies(block)) + { + system.Settings.UseGrindJanitorOn = (system.Settings.UseGrindJanitorOn & ~AutoGrindRelation.Enemies) | (value ? AutoGrindRelation.Enemies : 0); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CreateOnOffSwitchAction("GrindJanitorEnemies", onoffSwitch); + CustomControls.Add(onoffSwitch); + CreateProperty(onoffSwitch, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedEnemies); + + //--Grind not owned + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("GrindJanitorNotOwned"); + onoffSwitch.Title = Texts.GrindJanitorNotOwned; + onoffSwitch.Tooltip = Texts.GrindJanitorNotOwned_Tooltip; + onoffSwitch.OnText = MySpaceTexts.SwitchText_On; + onoffSwitch.OffText = MySpaceTexts.SwitchText_Off; + onoffSwitch.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedNoOwnership ? isReadonly : isBaRSystem; + onoffSwitch.Visible = isJanitorAllowedNoOwnership; + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.UseGrindJanitorOn & AutoGrindRelation.NoOwnership) != 0; + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed && isJanitorAllowedNoOwnership(block)) + { + system.Settings.UseGrindJanitorOn = (system.Settings.UseGrindJanitorOn & ~AutoGrindRelation.NoOwnership) | (value ? AutoGrindRelation.NoOwnership : 0); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CreateOnOffSwitchAction("GrindJanitorNotOwned", onoffSwitch); + CustomControls.Add(onoffSwitch); + CreateProperty(onoffSwitch, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedNoOwnership); + + //--Grind Neutrals + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("GrindJanitorNeutrals"); + onoffSwitch.Title = Texts.GrindJanitorNeutrals; + onoffSwitch.Tooltip = Texts.GrindJanitorNeutrals_Tooltip; + onoffSwitch.OnText = MySpaceTexts.SwitchText_On; + onoffSwitch.OffText = MySpaceTexts.SwitchText_Off; + onoffSwitch.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedNeutral ? isReadonly : isBaRSystem; + onoffSwitch.Visible = isJanitorAllowedNeutral; + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.UseGrindJanitorOn & AutoGrindRelation.Neutral) != 0; + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed && isJanitorAllowedNeutral(block)) + { + system.Settings.UseGrindJanitorOn = (system.Settings.UseGrindJanitorOn & ~AutoGrindRelation.Neutral) | (value ? AutoGrindRelation.Neutral : 0); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CreateOnOffSwitchAction("GrindJanitorNeutrals", onoffSwitch); + CustomControls.Add(onoffSwitch); + CreateProperty(onoffSwitch, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !janitorAllowedNeutral); + } + + //Grind Options + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateGrindOptions"); + separateArea.Visible = isJanitorAllowed; + CustomControls.Add(separateArea); + { + //--Grind Disable only + checkbox = MyAPIGateway.TerminalControls.CreateControl("GrindJanitorOptionDisableOnly"); + checkbox.Title = Texts.GrindJanitorDisableOnly; + checkbox.Tooltip = Texts.GrindJanitorDisableOnly_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !grindingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isJanitorAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.GrindJanitorOptions & AutoGrindOptions.DisableOnly) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed && isJanitorAllowed(block)) + { + //Only one option (HackOnly or DisableOnly) at a time is allowed + if (value) + { + system.Settings.GrindJanitorOptions = (system.Settings.GrindJanitorOptions & ~AutoGrindOptions.HackOnly) | AutoGrindOptions.DisableOnly; + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindJanitorOption")) ctrl.UpdateVisual(); + } + } + else + { + system.Settings.GrindJanitorOptions = system.Settings.GrindJanitorOptions & ~AutoGrindOptions.DisableOnly; + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("GrindJanitorOptionDisableOnly", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !grindingAllowed); + + //--Grind Hack only + checkbox = MyAPIGateway.TerminalControls.CreateControl("GrindJanitorOptionHackOnly"); + checkbox.Title = Texts.GrindJanitorHackOnly; + checkbox.Tooltip = Texts.GrindJanitorHackOnly_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !grindingAllowed ? isReadonly : isBaRSystem; + checkbox.Visible = isJanitorAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.GrindJanitorOptions & AutoGrindOptions.HackOnly) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed && isJanitorAllowed(block)) + { + //Only one option (HackOnly or DisableOnly) at a time is allowed + if (value) + { + system.Settings.GrindJanitorOptions = (system.Settings.GrindJanitorOptions & ~AutoGrindOptions.DisableOnly) | AutoGrindOptions.HackOnly; + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindJanitorOption")) ctrl.UpdateVisual(); + } + } + else + { + system.Settings.GrindJanitorOptions = system.Settings.GrindJanitorOptions & ~AutoGrindOptions.HackOnly; + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("GrindJanitorOptionHackOnly", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.UseGrindJanitorFixed || !grindingAllowed); + } + + //Grind Priority + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateGrindPrio"); + separateArea.Visible = isGrindingAllowed; + CustomControls.Add(separateArea); + { + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("GrindPriority"); + _GrindEnableDisableSwitch = onoffSwitch; + onoffSwitch.Title = Texts.GrindPriority; + onoffSwitch.Tooltip = Texts.GrindPriority_Tooltip; + onoffSwitch.OnText = Texts.Priority_Enable; + onoffSwitch.OffText = Texts.Priority_Disable; + onoffSwitch.Visible = isGrindingAllowed; + onoffSwitch.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockGrindPriority?.Selected != null && isGrindingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system?.BlockGrindPriority?.Selected != null && system.BlockGrindPriority.GetEnabled(system.BlockGrindPriority.Selected.Key); + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system?.BlockGrindPriority?.Selected != null && isGrindingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockGrindPriority.SetEnabled(system.BlockGrindPriority.Selected.Key, value); + system.Settings.GrindPriority = system.BlockGrindPriority.GetEntries(); + UpdateVisual(_GrindPriorityListBox); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CustomControls.Add(onoffSwitch); + + button = MyAPIGateway.TerminalControls.CreateControl("GrindPriorityUp"); + _GrindPriorityButtonUp = button; + button.Title = Texts.Priority_Up; + button.Visible = isGrindingAllowed; + button.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockGrindPriority?.Selected != null && isGrindingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockGrindPriority.MoveSelectedUp(); + system.Settings.GrindPriority = system.BlockGrindPriority.GetEntries(); + UpdateVisual(_GrindPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + button = MyAPIGateway.TerminalControls.CreateControl("GrindPriorityDown"); + _GrindPriorityButtonDown = button; + button.Title = Texts.Priority_Down; + button.Visible = isGrindingAllowed; + button.Enabled = (block) => + { + var system = GetSystem(block); + return system?.BlockGrindPriority?.Selected != null && isGrindingAllowed(block) && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed; + }; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PriorityFixed) + { + system.BlockGrindPriority.MoveSelectedDown(); + system.Settings.GrindPriority = system.BlockGrindPriority.GetEntries(); + UpdateVisual(_GrindPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + var listbox = MyAPIGateway.TerminalControls.CreateControl("GrindPriority"); + _GrindPriorityListBox = listbox; + + listbox.Multiselect = false; + listbox.VisibleRowsCount = 15; + listbox.Enabled = grindingAllowed ? isBaRSystem : isReadonly; + listbox.Visible = isGrindingAllowed; + listbox.ItemSelected = (block, selected) => + { + var system = GetSystem(block); + if (system?.BlockGrindPriority != null) + { + if (selected.Count > 0) system.BlockGrindPriority.SetSelectedByKey(((PrioItem)selected[0].UserData).Key); + else system.BlockGrindPriority.ClearSelected(); + UpdateVisual(_GrindEnableDisableSwitch); + UpdateVisual(_GrindPriorityButtonUp); + UpdateVisual(_GrindPriorityButtonDown); + } + }; + listbox.ListContent = (block, items, selected) => + { + var system = GetSystem(block); + system?.BlockGrindPriority?.FillTerminalList(items, selected); + }; + listbox.SupportsMultipleBlocks = true; + CustomControls.Add(listbox); + + //--Grind order near/far/smallest grid + checkbox = MyAPIGateway.TerminalControls.CreateControl("GrindNearFirst"); + checkbox.Title = Texts.GrindOrderNearest; + checkbox.Tooltip = Texts.GrindOrderNearest_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = grindingAllowed ? isBaRSystem : isReadonly; + checkbox.Visible = isGrindingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.GrindNearFirst) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && isGrindingAllowed(block)) + { + //Only one option (GrindNearFirst or GrindSmallestGridFirst) at a time is allowed + if (value) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.GrindSmallestGridFirst) | SyncBlockSettings.Settings.GrindNearFirst; + } + else + { + system.Settings.Flags = system.Settings.Flags & ~SyncBlockSettings.Settings.GrindNearFirst; + } + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindFarFirst")) ctrl.UpdateVisual(); + if (ctrl.Id.Contains("GrindSmallestGridFirst")) ctrl.UpdateVisual(); + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("GrindNearFirst", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox); + + checkbox = MyAPIGateway.TerminalControls.CreateControl("GrindFarFirst"); + checkbox.Title = Texts.GrindOrderFurthest; + checkbox.Tooltip = Texts.GrindOrderFurthest_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = grindingAllowed ? isBaRSystem : isReadonly; + checkbox.Visible = isGrindingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & (SyncBlockSettings.Settings.GrindNearFirst | SyncBlockSettings.Settings.GrindSmallestGridFirst)) == 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && isGrindingAllowed(block)) + { + //Only one option (GrindNearFirst or GrindSmallestGridFirst) at a time is allowed + if (value) + { + system.Settings.Flags = system.Settings.Flags & ~(SyncBlockSettings.Settings.GrindSmallestGridFirst | SyncBlockSettings.Settings.GrindNearFirst); + } + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindNearFirst")) ctrl.UpdateVisual(); + if (ctrl.Id.Contains("GrindSmallestGridFirst")) ctrl.UpdateVisual(); + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("GrindFarFirst", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox); + + checkbox = MyAPIGateway.TerminalControls.CreateControl("GrindSmallestGridFirst"); + checkbox.Title = Texts.GrindOrderSmallest; + checkbox.Tooltip = Texts.GrindOrderSmallest_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = grindingAllowed ? isBaRSystem : isReadonly; + checkbox.Visible = isGrindingAllowed; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.GrindSmallestGridFirst) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && isGrindingAllowed(block)) + { + //Only one option (GrindNearFirst or GrindSmallestGridFirst) at a time is allowed + if (value) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.GrindNearFirst) | SyncBlockSettings.Settings.GrindSmallestGridFirst; + } + foreach (var ctrl in CustomControls) + { + if (ctrl.Id.Contains("GrindNearFirst")) ctrl.UpdateVisual(); + if (ctrl.Id.Contains("GrindFarFirst")) ctrl.UpdateVisual(); + } + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("GrindSmallestGridFirst", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox); + } + } + + // --- Collecting + label = MyAPIGateway.TerminalControls.CreateControl("CollectingSettings"); + label.Label = Texts.CollectSettings_Headline; + CustomControls.Add(label); + { + // --- Collect floating objects + //separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateCollectPrio"); + //CustomControls.Add(separateArea); + { + onoffSwitch = MyAPIGateway.TerminalControls.CreateControl("CollectPriority"); + _ComponentCollectEnableDisableSwitch = onoffSwitch; + onoffSwitch.Title = Texts.CollectPriority; + onoffSwitch.Tooltip = Texts.CollectPriority_Tooltip; + onoffSwitch.OnText = Texts.Priority_Enable; + onoffSwitch.OffText = Texts.Priority_Disable; + onoffSwitch.Enabled = isChangeCollectPriorityPossible; + onoffSwitch.Getter = (block) => + { + var system = GetSystem(block); + return system?.ComponentCollectPriority?.Selected != null && system.ComponentCollectPriority.GetEnabled(system.ComponentCollectPriority.Selected.Key); + }; + onoffSwitch.Setter = (block, value) => + { + var system = GetSystem(block); + if (system?.ComponentCollectPriority?.Selected != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.CollectPriorityFixed) + { + system.ComponentCollectPriority.SetEnabled(system.ComponentCollectPriority.Selected.Key, value); + system.Settings.ComponentCollectPriority = system.ComponentCollectPriority.GetEntries(); + UpdateVisual(_ComponentCollectPriorityListBox); + } + }; + onoffSwitch.SupportsMultipleBlocks = true; + CustomControls.Add(onoffSwitch); + + button = MyAPIGateway.TerminalControls.CreateControl("CollectPriorityUp"); + _ComponentCollectPriorityButtonUp = button; + button.Title = Texts.Priority_Up; + button.Enabled = isChangeCollectPriorityPossible; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.CollectPriorityFixed) + { + system.ComponentCollectPriority.MoveSelectedUp(); + system.Settings.ComponentCollectPriority = system.ComponentCollectPriority.GetEntries(); + UpdateVisual(_ComponentCollectPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + button = MyAPIGateway.TerminalControls.CreateControl("CollectPriorityDown"); + _ComponentCollectPriorityButtonDown = button; + button.Title = Texts.Priority_Down; + button.Enabled = isChangeCollectPriorityPossible; + button.Action = (block) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.CollectPriorityFixed) + { + system.ComponentCollectPriority.MoveSelectedDown(); + system.Settings.ComponentCollectPriority = system.ComponentCollectPriority.GetEntries(); + UpdateVisual(_ComponentCollectPriorityListBox); + } + }; + button.SupportsMultipleBlocks = true; + CustomControls.Add(button); + + var listbox = MyAPIGateway.TerminalControls.CreateControl("CollectPriority"); + _ComponentCollectPriorityListBox = listbox; + + listbox.Multiselect = false; + listbox.VisibleRowsCount = 5; + listbox.Enabled = isCollectPossible; + listbox.ItemSelected = (block, selected) => + { + var system = GetSystem(block); + if (system?.ComponentCollectPriority != null) + { + if (selected.Count > 0) system.ComponentCollectPriority.SetSelectedByKey(((PrioItem)selected[0].UserData).Key); + else system.ComponentCollectPriority.ClearSelected(); + UpdateVisual(_ComponentCollectEnableDisableSwitch); + UpdateVisual(_ComponentCollectPriorityButtonUp); + UpdateVisual(_ComponentCollectPriorityButtonDown); + } + }; + listbox.ListContent = (block, items, selected) => + { + var system = GetSystem(block); + system?.ComponentCollectPriority?.FillTerminalList(items, selected); + }; + listbox.SupportsMultipleBlocks = true; + CustomControls.Add(listbox); + + // Collect if idle + checkbox = MyAPIGateway.TerminalControls.CreateControl("CollectIfIdle"); + _ComponentCollectIfIdleSwitch = checkbox; + checkbox.Title = Texts.CollectOnlyIfIdle; + checkbox.Tooltip = Texts.CollectOnlyIfIdle_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.CollectIfIdleFixed ? isReadonly : isCollectPossible; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.ComponentCollectIfIdle) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.CollectIfIdleFixed) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.ComponentCollectIfIdle) | (value ? SyncBlockSettings.Settings.ComponentCollectIfIdle : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("CollectIfIdle", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.CollectIfIdleFixed); + + //Push Ingot/ore immediately + checkbox = MyAPIGateway.TerminalControls.CreateControl("PushIngotOreImmediately"); + checkbox.Title = Texts.CollectPushOre; + checkbox.Tooltip = Texts.CollectPushOre_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.PushIngotOreImmediatelyFixed ? isReadonly : isBaRSystem; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.PushIngotOreImmediately) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PushIngotOreImmediatelyFixed) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.PushIngotOreImmediately) | (value ? SyncBlockSettings.Settings.PushIngotOreImmediately : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("PushIngotOreImmediately", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.PushIngotOreImmediatelyFixed); + + //Push Items immediately + checkbox = MyAPIGateway.TerminalControls.CreateControl("PushItemsImmediately"); + checkbox.Title = Texts.CollectPushItems; + checkbox.Tooltip = Texts.CollectPushItems_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.PushItemsImmediatelyFixed ? isReadonly : isBaRSystem; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.PushItemsImmediately) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PushItemsImmediatelyFixed) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.PushItemsImmediately) | (value ? SyncBlockSettings.Settings.PushItemsImmediately : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("PushItemsImmediately", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.PushItemsImmediatelyFixed); + + //Push Component immediately + checkbox = MyAPIGateway.TerminalControls.CreateControl("PushComponentImmediately"); + checkbox.Title = Texts.CollectPushComp; + checkbox.Tooltip = Texts.CollectPushComp_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.PushComponentImmediatelyFixed ? isReadonly : isBaRSystem; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.PushComponentImmediately) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.PushComponentImmediatelyFixed) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.PushComponentImmediately) | (value ? SyncBlockSettings.Settings.PushComponentImmediately : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("PushComponentImmediately", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.PushComponentImmediatelyFixed); + } + } + + // -- Highlight Area + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateArea"); + CustomControls.Add(separateArea); + { + Func getLimitOffsetMin = (block) => + { + var system = GetSystem(block); + return -system?.Settings?.MaximumOffset ?? -NanobotBuildAndRepairSystemBlock.WELDER_OFFSET_MAX_IN_M; + }; + Func getLimitOffsetMax = (block) => + { + var system = GetSystem(block); + return system?.Settings?.MaximumOffset ?? NanobotBuildAndRepairSystemBlock.WELDER_OFFSET_MAX_IN_M; + }; + + Func getLimitMin = (block) => NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MIN_IN_M; + Func getLimitMax = (block) => + { + var system = GetSystem(block); + return system?.Settings?.MaximumRange ?? NanobotBuildAndRepairSystemBlock.WELDER_RANGE_MAX_IN_M; + }; + + checkbox = MyAPIGateway.TerminalControls.CreateControl("ShowArea"); + checkbox.Title = Texts.AreaShow; + checkbox.Tooltip = Texts.AreaShow_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.ShowAreaFixed ? isReadonly : isBaRSystem; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return (system.Settings.Flags & SyncBlockSettings.Settings.ShowArea) != 0; + } + + return false; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null && !NanobotBuildAndRepairSystemMod.Settings.Welder.ShowAreaFixed) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.ShowArea) | (value ? SyncBlockSettings.Settings.ShowArea : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("ShowArea", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox, NanobotBuildAndRepairSystemMod.Settings.Welder.ShowAreaFixed); + + //Slider Offset + slider = MyAPIGateway.TerminalControls.CreateControl("AreaOffsetLeftRight"); + slider.Title = MySpaceTexts.BlockPropertyTitle_ProjectionOffsetX; + slider.SetLimits(getLimitOffsetMin, getLimitOffsetMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaOffset.X : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitOffsetMin(block); + var max = getLimitOffsetMax(block); + val = (float)Math.Round(val * 2, MidpointRounding.AwayFromZero) / 2f; + val = val < min ? min : val > max ? max : val; + system.Settings.AreaOffset = new Vector3(val, system.Settings.AreaOffset.Y, system.Settings.AreaOffset.Z); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaOffset.X + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaOffsetLeftRight", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed); + + slider = MyAPIGateway.TerminalControls.CreateControl("AreaOffsetUpDown"); + slider.Title = MySpaceTexts.BlockPropertyTitle_ProjectionOffsetY; + slider.SetLimits(getLimitOffsetMin, getLimitOffsetMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaOffset.Y : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitOffsetMin(block); + var max = getLimitOffsetMax(block); + val = (float)Math.Round(val * 2, MidpointRounding.AwayFromZero) / 2f; + val = val < min ? min : val > max ? max : val; + system.Settings.AreaOffset = new Vector3(system.Settings.AreaOffset.X, val, system.Settings.AreaOffset.Z); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaOffset.Y + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaOffsetUpDown", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed); + + slider = MyAPIGateway.TerminalControls.CreateControl("AreaOffsetFrontBack"); + slider.Title = MySpaceTexts.BlockPropertyTitle_ProjectionOffsetZ; + slider.SetLimits(getLimitOffsetMin, getLimitOffsetMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaOffset.Z : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitOffsetMin(block); + var max = getLimitOffsetMax(block); + val = (float)Math.Round(val * 2, MidpointRounding.AwayFromZero) / 2f; + val = val < min ? min : val > max ? max : val; + system.Settings.AreaOffset = new Vector3(system.Settings.AreaOffset.X, system.Settings.AreaOffset.Y, val); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaOffset.Z + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaOffsetFrontBack", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaOffsetFixed); + + //Slider Area + slider = MyAPIGateway.TerminalControls.CreateControl("AreaWidth"); + slider.Title = Texts.AreaWidth; + slider.SetLimits(getLimitMin, getLimitMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaSize.X : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitMin(block); + var max = getLimitMax(block); + val = val < min ? min : val > max ? max : val; + system.Settings.AreaSize = new Vector3((int)Math.Round(val), system.Settings.AreaSize.Y, system.Settings.AreaSize.Z); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaSize.X + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaWidth", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed); + + slider = MyAPIGateway.TerminalControls.CreateControl("AreaHeight"); + slider.Title = Texts.AreaHeight; + slider.SetLimits(getLimitMin, getLimitMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaSize.Y : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitMin(block); + var max = getLimitMax(block); + val = val < min ? min : val > max ? max : val; + system.Settings.AreaSize = new Vector3(system.Settings.AreaSize.X, (int)Math.Round(val), system.Settings.AreaSize.Z); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaSize.Y + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaHeight", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed); + + slider = MyAPIGateway.TerminalControls.CreateControl("AreaDepth"); + slider.Title = Texts.AreaDepth; + slider.SetLimits(getLimitMin, getLimitMax); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? system.Settings.AreaSize.Z : 0; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = getLimitMin(block); + var max = getLimitMax(block); + val = val < min ? min : val > max ? max : val; + system.Settings.AreaSize = new Vector3(system.Settings.AreaSize.X, system.Settings.AreaSize.Y, (int)Math.Round(val)); + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(system.Settings.AreaSize.Z + " m"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("AreaDepth", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.AreaSizeFixed); + + // -- Sound enabled + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateOther"); + CustomControls.Add(separateArea); + + slider = MyAPIGateway.TerminalControls.CreateControl("SoundVolume"); + slider.Title = Texts.SoundVolume; + slider.SetLimits(0f, 100f); + slider.Enabled = NanobotBuildAndRepairSystemMod.Settings.Welder.SoundVolumeFixed ? isReadonly : isBaRSystem; + slider.Getter = (block) => + { + var system = GetSystem(block); + return system != null ? 100f * system.Settings.SoundVolume / NanobotBuildAndRepairSystemBlock.WELDER_SOUND_VOLUME : 0f; + }; + slider.Setter = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + var min = 0; + var max = 100; + val = val < min ? min : val > max ? max : val; + system.Settings.SoundVolume = (float)Math.Round(val * NanobotBuildAndRepairSystemBlock.WELDER_SOUND_VOLUME) / 100f; + } + }; + slider.Writer = (block, val) => + { + var system = GetSystem(block); + if (system != null) + { + val.Append(Math.Round(100f * system.Settings.SoundVolume / NanobotBuildAndRepairSystemBlock.WELDER_SOUND_VOLUME) + " %"); + } + }; + slider.SupportsMultipleBlocks = true; + CustomControls.Add(slider); + CreateSliderActions("SoundVolume", slider); + CreateProperty(slider, NanobotBuildAndRepairSystemMod.Settings.Welder.SoundVolumeFixed); + } + + // -- Script Control + if (!NanobotBuildAndRepairSystemMod.Settings.Welder.ScriptControllFixed) + { + separateArea = MyAPIGateway.TerminalControls.CreateControl("SeparateScriptControl"); + CustomControls.Add(separateArea); + + checkbox = MyAPIGateway.TerminalControls.CreateControl("ScriptControlled"); + checkbox.Title = Texts.ScriptControlled; + checkbox.Tooltip = Texts.ScriptControlled_Tooltip; + checkbox.OnText = MySpaceTexts.SwitchText_On; + checkbox.OffText = MySpaceTexts.SwitchText_Off; + checkbox.Enabled = isBaRSystem; + checkbox.Getter = (block) => + { + var system = GetSystem(block); + return system != null && (system.Settings.Flags & SyncBlockSettings.Settings.ScriptControlled) != 0; + }; + checkbox.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null) + { + system.Settings.Flags = (system.Settings.Flags & ~SyncBlockSettings.Settings.ScriptControlled) | (value ? SyncBlockSettings.Settings.ScriptControlled : 0); + } + }; + checkbox.SupportsMultipleBlocks = true; + CreateCheckBoxAction("ScriptControlled", checkbox); + CustomControls.Add(checkbox); + CreateProperty(checkbox); + + //Scripting support for Priority and enabling Weld BlockClasses + var propertyWeldPriorityList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.WeldPriorityList"); + propertyWeldPriorityList.SupportsMultipleBlocks = false; + propertyWeldPriorityList.Getter = (block) => + { + var system = GetSystem(block); + return system?.BlockWeldPriority.GetList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyWeldPriorityList); + + var propertySWP = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetWeldPriority"); + propertySWP.SupportsMultipleBlocks = false; + propertySWP.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockWeldPriority.SetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySWP); + + var propertyGWP = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetWeldPriority"); + propertyGWP.SupportsMultipleBlocks = false; + propertyGWP.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockWeldPriority.GetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGWP); + + var propertySWE = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetWeldEnabled"); + propertySWE.SupportsMultipleBlocks = false; + propertySWE.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockWeldPriority.SetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySWE); + + var propertyGWE = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetWeldEnabled"); + propertyGWE.SupportsMultipleBlocks = false; + propertyGWE.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockWeldPriority.GetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGWE); + + //Scripting support for Priority and enabling GrindWeld BlockClasses + var propertyGrindPriorityList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GrindPriorityList"); + propertyGrindPriorityList.SupportsMultipleBlocks = false; + propertyGrindPriorityList.Getter = (block) => + { + var system = GetSystem(block); + return system?.BlockGrindPriority.GetList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyGrindPriorityList); + + var propertySGP = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetGrindPriority"); + propertySGP.SupportsMultipleBlocks = false; + propertySGP.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockGrindPriority.SetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySGP); + + var propertyGGP = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetGrindPriority"); + propertyGGP.SupportsMultipleBlocks = false; + propertyGGP.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockGrindPriority.GetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGGP); + + var propertySGE = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetGrindEnabled"); + propertySGE.SupportsMultipleBlocks = false; + propertySGE.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockGrindPriority.SetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySGE); + + var propertyGGE = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetGrindEnabled"); + propertyGGE.SupportsMultipleBlocks = false; + propertyGGE.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.BlockGrindPriority.GetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGGE); + + //Scripting support for Priority and enabling ComponentClasses + var propertyComponentClassList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.ComponentClassList"); + propertyComponentClassList.SupportsMultipleBlocks = false; + propertyComponentClassList.Getter = (block) => + { + var system = GetSystem(block); + return system?.ComponentCollectPriority.GetList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyComponentClassList); + + var propertySPC = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetCollectPriority"); + propertySPC.SupportsMultipleBlocks = false; + propertySPC.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.ComponentCollectPriority.SetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySPC); + + var propertyGPC = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetCollectPriority"); + propertyGPC.SupportsMultipleBlocks = false; + propertyGPC.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.ComponentCollectPriority.GetPriority; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGPC); + + var propertySEC = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.SetCollectEnabled"); + propertySEC.SupportsMultipleBlocks = false; + propertySEC.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.ComponentCollectPriority.SetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertySEC); + + var propertyGEC = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.GetCollectEnabled"); + propertyGEC.SupportsMultipleBlocks = false; + propertyGEC.Getter = (block) => + { + var system = GetSystem(block); + if (system != null) + { + return system.ComponentCollectPriority.GetEnabled; + } + return null; + }; + MyAPIGateway.TerminalControls.AddControl(propertyGEC); + + //Working Lists + var propertyMissingComponentsDict = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.MissingComponents"); + propertyMissingComponentsDict.SupportsMultipleBlocks = false; + propertyMissingComponentsDict.Getter = (block) => + { + var system = GetSystem(block); + return system?.GetMissingComponentsDict(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyMissingComponentsDict); + + var propertyPossibleWeldTargetsList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.PossibleTargets"); + propertyPossibleWeldTargetsList.SupportsMultipleBlocks = false; + propertyPossibleWeldTargetsList.Getter = (block) => + { + var system = GetSystem(block); + return system?.GetPossibleWeldTargetsList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyPossibleWeldTargetsList); + + var propertyPossibleGrindTargetsList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.PossibleGrindTargets"); + propertyPossibleGrindTargetsList.SupportsMultipleBlocks = false; + propertyPossibleGrindTargetsList.Getter = (block) => + { + var system = GetSystem(block); + return system?.GetPossibleGrindTargetsList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyPossibleGrindTargetsList); + + var propertyPossibleCollectTargetsList = MyAPIGateway.TerminalControls.CreateProperty, IMyShipWelder>("BuildAndRepair.PossibleCollectTargets"); + propertyPossibleCollectTargetsList.SupportsMultipleBlocks = false; + propertyPossibleCollectTargetsList.Getter = (block) => + { + var system = GetSystem(block); + return system?.GetPossibleCollectingTargetsList(); + }; + MyAPIGateway.TerminalControls.AddControl(propertyPossibleCollectTargetsList); + + //Control welding + var propertyCPT = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.CurrentPickedTarget"); + propertyCPT.SupportsMultipleBlocks = false; + propertyCPT.Getter = (block) => + { + var system = GetSystem(block); + return system?.Settings.CurrentPickedWeldingBlock; + }; + propertyCPT.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null) + { + system.Settings.CurrentPickedWeldingBlock = value; + } + }; + MyAPIGateway.TerminalControls.AddControl(propertyCPT); + + var propertyCT = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.CurrentTarget"); + propertyCT.SupportsMultipleBlocks = false; + propertyCT.Getter = (block) => + { + var system = GetSystem(block); + return system?.State.CurrentWeldingBlock; + }; + MyAPIGateway.TerminalControls.AddControl(propertyCT); + + //Control grinding + var propertyCPGT = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.CurrentPickedGrindTarget"); + propertyCPGT.SupportsMultipleBlocks = false; + propertyCPGT.Getter = (block) => + { + var system = GetSystem(block); + return system?.Settings.CurrentPickedGrindingBlock; + }; + propertyCPGT.Setter = (block, value) => + { + var system = GetSystem(block); + if (system != null) + { + system.Settings.CurrentPickedGrindingBlock = value; + } + }; + MyAPIGateway.TerminalControls.AddControl(propertyCPGT); + + var propertyCGT = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair.CurrentGrindTarget"); + propertyCGT.SupportsMultipleBlocks = false; + propertyCGT.Getter = (block) => + { + var system = GetSystem(block); + return system?.State.CurrentGrindingBlock; + }; + MyAPIGateway.TerminalControls.AddControl(propertyCGT); + + //Publish functions to scripting + var propertyPEQ = MyAPIGateway.TerminalControls.CreateProperty, VRage.Game.MyDefinitionId, int, int>, IMyShipWelder>("BuildAndRepair.ProductionBlock.EnsureQueued"); + propertyPEQ.SupportsMultipleBlocks = false; + propertyPEQ.Getter = (block) => UtilsProductionBlock.EnsureQueued; + MyAPIGateway.TerminalControls.AddControl(propertyPEQ); + + var propertyNC4B = MyAPIGateway.TerminalControls.CreateProperty, int>, IMyShipWelder>("BuildAndRepair.Inventory.NeededComponents4Blueprint"); + propertyNC4B.SupportsMultipleBlocks = false; + propertyNC4B.Getter = (block) => UtilsInventory.NeededComponents4Blueprint; + MyAPIGateway.TerminalControls.AddControl(propertyNC4B); + } + } + catch (Exception ex) + { + Mod.Log.Write(Logging.Level.Error, "NanobotBuildAndRepairSystemTerminal: InitializeControls exception: {0}", ex); + } + } + } + + private static void CreateCheckBoxAction(string name, IMyTerminalControlCheckbox checkbox) + { + var action = MyAPIGateway.TerminalControls.CreateAction($"{name}_OnOff"); + action.Name = new StringBuilder($"{name} On/Off"); + action.Icon = @"Textures\GUI\Icons\Actions\Toggle.dds"; + action.Enabled = checkbox.Enabled; + action.Action = (block) => + { + checkbox.Setter(block, !checkbox.Getter(block)); + }; + action.Writer = (block, result) => + { + result.Append(checkbox.Getter(block) ? MyTexts.Get(checkbox.OnText) : MyTexts.Get(checkbox.OffText)); + }; + action.ValidForGroups = checkbox.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + + action = MyAPIGateway.TerminalControls.CreateAction($"{name}_On"); + action.Name = new StringBuilder($"{name} On"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOn.dds"; + action.Enabled = checkbox.Enabled; + action.Action = (block) => + { + checkbox.Setter(block, true); + }; + action.Writer = (block, result) => + { + result.Append(checkbox.Getter(block) ? MyTexts.Get(checkbox.OnText) : MyTexts.Get(checkbox.OffText)); + }; + action.ValidForGroups = checkbox.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + + action = MyAPIGateway.TerminalControls.CreateAction($"{name}_Off"); + action.Name = new StringBuilder($"{name} Off"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOff.dds"; + action.Enabled = checkbox.Enabled; + action.Action = (block) => + { + checkbox.Setter(block, false); + }; + action.Writer = (block, result) => + { + result.Append(checkbox.Getter(block) ? MyTexts.Get(checkbox.OnText) : MyTexts.Get(checkbox.OffText)); + }; + action.ValidForGroups = checkbox.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + } + + private static void CreateOnOffSwitchAction(string name, IMyTerminalControlOnOffSwitch onoffSwitch) + { + var action = MyAPIGateway.TerminalControls.CreateAction($"{name}_OnOff"); + action.Name = new StringBuilder($"{name} {onoffSwitch.OnText}/{onoffSwitch.OffText}"); + action.Icon = @"Textures\GUI\Icons\Actions\Toggle.dds"; + action.Enabled = onoffSwitch.Enabled; + action.Action = (block) => + { + onoffSwitch.Setter(block, !onoffSwitch.Getter(block)); + }; + action.ValidForGroups = onoffSwitch.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + + action = MyAPIGateway.TerminalControls.CreateAction($"{name}_On"); + action.Name = new StringBuilder($"{name} {onoffSwitch.OnText}"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOn.dds"; + action.Enabled = onoffSwitch.Enabled; + action.Action = (block) => + { + onoffSwitch.Setter(block, true); + }; + action.ValidForGroups = onoffSwitch.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + + action = MyAPIGateway.TerminalControls.CreateAction($"{name}_Off"); + action.Name = new StringBuilder($"{name} {onoffSwitch.OffText}"); + action.Icon = @"Textures\GUI\Icons\Actions\SwitchOff.dds"; + action.Enabled = onoffSwitch.Enabled; + action.Action = (block) => + { + onoffSwitch.Setter(block, false); + }; + action.ValidForGroups = onoffSwitch.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + } + + private static void CreateProperty(IMyTerminalValueControl control, bool readOnly = false) + { + var property = MyAPIGateway.TerminalControls.CreateProperty("BuildAndRepair." + control.Id); + property.SupportsMultipleBlocks = false; + property.Getter = control.Getter; + if (!readOnly) property.Setter = control.Setter; + MyAPIGateway.TerminalControls.AddControl(property); + } + + private static void CreateSliderActions(string sliderName, IMyTerminalControlSlider slider) + { + var action = MyAPIGateway.TerminalControls.CreateAction($"{sliderName}_Increase"); + action.Name = new StringBuilder($"{sliderName} Increase"); + action.Icon = @"Textures\GUI\Icons\Actions\Increase.dds"; + action.Enabled = slider.Enabled; + action.Action = (block) => + { + var val = slider.Getter(block); + slider.Setter(block, val + 1); + }; + action.ValidForGroups = slider.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + + action = MyAPIGateway.TerminalControls.CreateAction($"{sliderName}_Decrease"); + action.Name = new StringBuilder($"{sliderName} Decrease"); + action.Icon = @"Textures\GUI\Icons\Actions\Decrease.dds"; + action.Enabled = slider.Enabled; + action.Action = (block) => + { + var val = slider.Getter(block); + slider.Setter(block, val - 1); + }; + action.ValidForGroups = slider.SupportsMultipleBlocks; + MyAPIGateway.TerminalControls.AddAction(action); + } + + private static Vector3 CheckConvertToHSVColor(Vector3 value) + { + if (value.X < 0f) value.X = 0f; + if (value.X > 360f) value.X = 360f; + if (value.Y < 0f) value.Y = 0f; + if (value.Y > 100f) value.Y = 100f; + if (value.Z < 0f) value.Z = 0f; + if (value.Z > 100f) value.Z = 100f; + + return new Vector3(value.X / 360f, + value.Y / 100f - NanobotBuildAndRepairSystemTerminal.SATURATION_DELTA, + value.Z / 100f - NanobotBuildAndRepairSystemTerminal.VALUE_DELTA + NanobotBuildAndRepairSystemTerminal.VALUE_COLORIZE_DELTA); + } + + private static Vector3 ConvertFromHSVColor(Vector3 value) + { + return new Vector3(value.X * 360f, + (value.Y + SATURATION_DELTA) * 100f, + (value.Z + VALUE_DELTA - VALUE_COLORIZE_DELTA) * 100f); + } + + private static void UpdateVisual(IMyTerminalControl control) + { + control?.UpdateVisual(); + } + + /// + /// Callback to add custom controls + /// + private static void CustomControlGetter(IMyTerminalBlock block, List controls) + { + if (block.BlockDefinition.SubtypeName.StartsWith("SELtd") && block.BlockDefinition.SubtypeName.Contains("NanobotBuildAndRepairSystem")) + { + foreach (var item in CustomControls) + { + controls.Add(item); + if (item == _SeparateWeldOptions) + { + var fromIdx = controls.IndexOf(_HelpOthers); + var toIdx = controls.IndexOf(_SeparateWeldOptions); + if (fromIdx >= 0 && toIdx >= 0) controls.Move(fromIdx, toIdx); + } + } + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/PriorityHandling.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/PriorityHandling.cs new file mode 100644 index 00000000..8c3a1265 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/PriorityHandling.cs @@ -0,0 +1,280 @@ +using System.Collections.Generic; +using System.Linq; +using VRage.ModAPI; +using VRage.Utils; + +namespace SKONanobotBuildAndRepairSystem +{ + public class PrioItem + { + public int Key; + public string Alias; + + public PrioItem(int key, string alias) + { + Key = key; + Alias = alias; + } + + public override string ToString() + { + return Alias; + } + } + + public class PrioItemState where T : PrioItem + { + public T PrioItem { get; } + public bool Enabled { get; set; } + public bool Visible { get; set; } + + public PrioItemState(T prioItem, bool enabled, bool visible) + { + PrioItem = prioItem; + Enabled = enabled; + Visible = visible; + } + } + + public abstract class PriorityHandling : List> where C : PrioItem //where C : struct + { + private bool _HashDirty = true; + private readonly List _ClassList = new List(); + private readonly Dictionary _PrioHash = new Dictionary(); + + public C Selected { get; private set; } //Visual + + /// + /// Set current active item + /// + public void SetSelectedByKey(int key) + { + var item = this.Find(i => i.PrioItem.Key == key); + if (item != null) Selected = item.PrioItem; + else Selected = null; + } + + /// + /// Clear current active item + /// + public void ClearSelected() + { + Selected = null; + } + + /// + /// Retrieve the build/repair priority of the item. + /// + internal int GetPriority(I a) + { + var itemKey = GetItemKey(a, false); + if (_HashDirty) UpdateHash(); + return _PrioHash[itemKey]; + } + + /// + /// Retrieve if the build/repair of this item kind is enabled. + /// + internal bool GetEnabled(I a) + { + var itemKey = GetItemKey(a, true); + if (_HashDirty) UpdateHash(); + return _PrioHash[itemKey] < int.MaxValue; + } + + /// + /// Retrieve if the build/repair of this item kind is enabled. + /// + //internal bool GetEnabled(C a) + //{ + // if (_HashDirty) UpdateHash(); + // return _PrioHash[a.Key] < int.MaxValue; + //} + + /// + /// Get the item key value + /// + /// + /// + public abstract int GetItemKey(I a, bool real); + + /// + /// Get the item alias + /// + /// + /// + public abstract string GetItemAlias(I a, bool real); + + /// + /// + /// + /// + internal void FillTerminalList(List items, List selected) + { + foreach (var entry in this) + { + if (entry.Visible) + { + var item = new MyTerminalControlListBoxItem(MyStringId.GetOrCompute( + $"({(entry.Enabled ? "X" : "-")}) {entry.PrioItem.ToString()}"), MyStringId.NullOrEmpty, entry.PrioItem); + items.Add(item); + + if (entry.PrioItem.Equals(Selected)) + { + selected.Add(item); + } + } + } + } + + internal void MoveSelectedUp() + { + if (Selected != null) + { + var currentPrio = FindIndex((kv) => kv.PrioItem.Equals(Selected)); + if (currentPrio > 0) + { + this.Move(currentPrio, currentPrio - 1); + _HashDirty = true; + } + } + } + + internal void MoveSelectedDown() + { + if (Selected != null) + { + var currentPrio = FindIndex((kv) => kv.PrioItem.Equals(Selected)); + if (currentPrio >= 0 && currentPrio < Count - 1) + { + this.Move(currentPrio, currentPrio + 1); + _HashDirty = true; + } + } + } + + internal void ToggleEnabled() + { + if (Selected != null) + { + var keyValue = this.FirstOrDefault((kv) => kv.PrioItem.Equals(Selected)); + if (keyValue != null) + { + keyValue.Enabled = !keyValue.Enabled; + _HashDirty = true; + } + } + } + + internal int GetPriority(int itemKey) + { + return FindIndex((kv) => kv.PrioItem.Key == itemKey); + } + + internal void SetPriority(int itemKey, int prio) + { + if (prio >= 0 && prio < Count) + { + var currentPrio = FindIndex((kv) => kv.PrioItem.Key == itemKey); + if (currentPrio >= 0) + { + this.Move(currentPrio, prio); + _HashDirty = true; + } + } + } + + internal bool GetEnabled(int itemKey) + { + var keyValue = this.FirstOrDefault((kv) => kv.PrioItem.Key == itemKey); + return keyValue?.Enabled ?? false; + } + + internal void SetEnabled(int itemKey, bool enabled) + { + var keyValue = this.FirstOrDefault((kv) => kv.PrioItem.Key == itemKey); + if (keyValue != null) + { + if (keyValue.Enabled != enabled) + { + keyValue.Enabled = enabled; + _HashDirty = true; + } + } + } + + public bool AnyEnabled + { + get + { + return this.Any(i => i.Enabled); + } + } + + internal string GetEntries() + { + var value = string.Empty; + foreach (var entry in this) + { + value += $"{entry.PrioItem.Key};{entry.Enabled}|"; + } + return value.Remove(value.Length - 1); + } + + internal void SetEntries(string value) + { + if (value == null) return; + var entries = value.Split('|'); + var prio = 0; + foreach (var val in entries) + { + var prioItemKey = 0; + var enabled = true; + var values = val.Split(';'); + if (values.Length >= 2 && + int.TryParse(values[0], out prioItemKey) && + bool.TryParse(values[1], out enabled)) + { + var keyValue = this.FirstOrDefault((kv) => kv.PrioItem.Key == prioItemKey); + if (keyValue != null) + { + keyValue.Enabled = enabled; + var currentPrio = IndexOf(keyValue); + this.Move(currentPrio, prio); + prio++; + } + } + } + _HashDirty = true; + } + + internal List GetList() + { + if (_HashDirty) UpdateHash(); + return _ClassList; + } + + public void UpdateHash() + { + lock (_ClassList) + { + if (_HashDirty) //Second check now thread safe + { + _ClassList.Clear(); + foreach (var item in this) + { + _ClassList.Add($"{item.PrioItem.Key};{item.Enabled}"); + } + _PrioHash.Clear(); + var prio = 1; + foreach (var item in this) + { + _PrioHash.Add(item.PrioItem.Key, item.Enabled ? prio : int.MaxValue); + prio++; + } + _HashDirty = false; + } + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/SafeZoneProtection.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/SafeZoneProtection.cs new file mode 100644 index 00000000..b23fb513 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/SafeZoneProtection.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using SpaceEngineers.Game.ModAPI; +using VRage.Game.ModAPI; +using VRageMath; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class SafeZoneProtection + { + private static readonly int IntervalToCacheSeconds = 10; + + public struct EntityProtectedState + { + public bool IsAllowed; + public TimeSpan Checked; + } + + public static ConcurrentDictionary GrindingNotAllowedCache = new ConcurrentDictionary(); + + public static T CastProhibit(T ptr, object val) => (T)val; + + private static bool? IsGrindingAllowed(long entityId) + { + try + { + if (GrindingNotAllowedCache.ContainsKey(entityId)) + { + if (MyAPIGateway.Session.ElapsedPlayTime.Subtract(GrindingNotAllowedCache[entityId].Checked).TotalSeconds > IntervalToCacheSeconds) + { + Deb.Write("Cache expired."); + EntityProtectedState state; + GrindingNotAllowedCache.TryRemove(entityId, out state); + } + else + { + Deb.Write("Cached found"); + return GrindingNotAllowedCache[entityId].IsAllowed; + } + } + } + catch + { + // ignored + } + + return null; + } + + public static void SetIsAllowed(long entityId, bool isAllowed) + { + try + { + GrindingNotAllowedCache[entityId] = new EntityProtectedState() + { + IsAllowed = isAllowed, + Checked = MyAPIGateway.Session.ElapsedPlayTime + }; + Deb.Write($"Set cache: {isAllowed}"); + } + catch + { + // ignored + } + } + + public static bool IsGridAllowedGrinding(MyCubeGrid grid) + { + try + { + var isAllowed = IsGrindingAllowed(grid.EntityId); + if (isAllowed.HasValue) + { + return isAllowed.Value; + } + else + { + isAllowed = MySessionComponentSafeZones.IsActionAllowed(grid, CastProhibit(MySessionComponentSafeZones.AllowedActions, 16)); + SetIsAllowed(grid.EntityId, isAllowed.Value); + return isAllowed.Value; + } + } + catch + { + // ignored + } + + return true; + } + + public static bool IsProtected(IMySlimBlock targetBlock, IMyCubeBlock attackerBlock) + { + try + { + if (targetBlock != null && attackerBlock != null) + { + long fatBlockId = 0; + if (targetBlock.FatBlock != null) + { + fatBlockId = targetBlock.FatBlock.EntityId; + var cached = IsGrindingAllowed(fatBlockId); + if (cached != null) + { + return cached.Value; + } + } + + var sphere = new BoundingSphereD(attackerBlock.GetPosition(), 500); + var list = MyAPIGateway.Entities.GetEntitiesInSphere(ref sphere); + var safeZones = list.OfType().ToList(); + + if (safeZones.Any()) + { + BoundingBoxD targetBox; + targetBlock.GetWorldBoundingBox(out targetBox); + + BoundingBoxD attackerBox; + attackerBlock.SlimBlock.GetWorldBoundingBox(out attackerBox); + + foreach (var safeZone in safeZones) + { + // Create a new sphere for the safe zone. + var checkSphere = new BoundingSphereD(safeZone.PositionComp.GetPosition(), safeZone.Radius); + + // Get intersections checks.. + var targetIntersects = checkSphere.Intersects(targetBox); + if (targetIntersects) + { + // If it is a safe-zone block. + if (safeZone.SafeZoneBlockId > 0) + { + var safeZoneBlock = MyEntities.GetEntityByName(safeZone.SafeZoneBlockId.ToString()) as IMySafeZoneBlock; + if (safeZoneBlock != null && safeZoneBlock.Enabled && safeZoneBlock.IsSafeZoneEnabled()) + { + var isAllowed = safeZone.IsActionAllowed(CastProhibit(MySessionComponentSafeZones.AllowedActions, 16), 0L, targetBox); + if (isAllowed) + { + var safeZoneBlockOwner = UtilsPlayer.GetOwner(safeZoneBlock.CubeGrid); + var targetGridOwner = UtilsPlayer.GetOwner(targetBlock.CubeGrid); + var attackerGridOwner = UtilsPlayer.GetOwner(attackerBlock.CubeGrid); + + if (safeZoneBlockOwner == attackerGridOwner || targetGridOwner == attackerGridOwner) + { + if (fatBlockId > 0) + { + SetIsAllowed(fatBlockId, true); + } + return false; + } + + // Check relation between owners. + var relation = attackerBlock.GetUserRelationToOwner(targetGridOwner); + if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Owner || + relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.FactionShare) + { + if (fatBlockId > 0) + { + SetIsAllowed(fatBlockId, true); + } + return false; + } + } + if (fatBlockId > 0) + { + SetIsAllowed(fatBlockId, false); + } + return true; + } + } + + if (fatBlockId > 0) + { + SetIsAllowed(fatBlockId, false); + } + return true; + } + } + } + } + } + catch + { + // ignored + } + + return false; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/ShieldApi.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/ShieldApi.cs new file mode 100644 index 00000000..78e86854 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/ShieldApi.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRageMath; + +namespace DefenseShields +{ + public class ShieldApi + { + private bool _apiInit; + private Func _rayAttackShield; // negative damage values heal + private Func _lineAttackShield; // negative damage values heal + private Func, RayD, bool, bool, long, float, MyTuple> _intersectEntToShieldFast; // fast check of entities for shield + private Func _pointAttackShield; // negative damage values heal + private Func _pointAttackShieldExt; // negative damage values heal + private Func _pointAttackShieldCon; // negative damage values heal, conditional secondary damage + private Action _setShieldHeat; + private Action _overLoad; + private Action _setCharge; + private Func _rayIntersectShield; + private Func _lineIntersectShield; + private Func _pointInShield; + private Func _getShieldPercent; + private Func _getShieldHeat; + private Func _getChargeRate; + private Func _hpToChargeRatio; + private Func _getMaxCharge; + private Func _getCharge; + private Func _getPowerUsed; + private Func _getPowerCap; + private Func _getMaxHpCap; + private Func _isShieldUp; + private Func _shieldStatus; + private Func _entityBypass; + private Func _gridHasShield; + private Func _gridShieldOnline; + private Func _protectedByShield; + private Func _getShieldBlock; + private Func _matchEntToShieldFast; + private Func, MyTuple>?> _matchEntToShieldFastExt; + private Func _isShieldBlock; + private Func _getClosestShield; + private Func _getDistanceToShield; + private Func _getClosestShieldPoint; + private Func> _getShieldInfo; + private Func> _getModulationInfo; + private Func> _getFaceInfo; + private Func _isBlockProtected; + private Func> _getFacesFast; + private Action>> _getLastAttackers; + + private Action _addAtacker; + + private const long Channel = 1365616918; + + public bool IsReady { get; private set; } + + private void HandleMessage(object o) + { + if (_apiInit) return; + var dict = o as IReadOnlyDictionary; + if (dict == null) + return; + ApiLoad(dict); + IsReady = true; + } + + private bool _isRegistered; + + public bool Load() + { + if (!_isRegistered) + { + _isRegistered = true; + MyAPIGateway.Utilities.RegisterMessageHandler(Channel, HandleMessage); + } + if (!IsReady) + MyAPIGateway.Utilities.SendModMessage(Channel, "ApiEndpointRequest"); + return IsReady; + } + + public void Unload() + { + if (_isRegistered) + { + _isRegistered = false; + MyAPIGateway.Utilities.UnregisterMessageHandler(Channel, HandleMessage); + } + IsReady = false; + } + + public void ApiLoad(IReadOnlyDictionary delegates) + { + _apiInit = true; + _rayAttackShield = (Func)delegates["RayAttackShield"]; + _lineAttackShield = (Func)delegates["LineAttackShield"]; + _intersectEntToShieldFast = (Func, RayD, bool, bool, long, float, MyTuple>)delegates["IntersectEntToShieldFast"]; + _pointAttackShield = (Func)delegates["PointAttackShield"]; + _pointAttackShieldExt = (Func)delegates["PointAttackShieldExt"]; + _pointAttackShieldCon = (Func)delegates["PointAttackShieldCon"]; + _setShieldHeat = (Action)delegates["SetShieldHeat"]; + _overLoad = (Action)delegates["OverLoadShield"]; + _setCharge = (Action)delegates["SetCharge"]; + _rayIntersectShield = (Func)delegates["RayIntersectShield"]; + _lineIntersectShield = (Func)delegates["LineIntersectShield"]; + _pointInShield = (Func)delegates["PointInShield"]; + _getShieldPercent = (Func)delegates["GetShieldPercent"]; + _getShieldHeat = (Func)delegates["GetShieldHeat"]; + _getChargeRate = (Func)delegates["GetChargeRate"]; + _hpToChargeRatio = (Func)delegates["HpToChargeRatio"]; + _getMaxCharge = (Func)delegates["GetMaxCharge"]; + _getCharge = (Func)delegates["GetCharge"]; + _getPowerUsed = (Func)delegates["GetPowerUsed"]; + _getPowerCap = (Func)delegates["GetPowerCap"]; + _getMaxHpCap = (Func)delegates["GetMaxHpCap"]; + _isShieldUp = (Func)delegates["IsShieldUp"]; + _shieldStatus = (Func)delegates["ShieldStatus"]; + _entityBypass = (Func)delegates["EntityBypass"]; + _gridHasShield = (Func)delegates["GridHasShield"]; + _gridShieldOnline = (Func)delegates["GridShieldOnline"]; + _protectedByShield = (Func)delegates["ProtectedByShield"]; + _getShieldBlock = (Func)delegates["GetShieldBlock"]; + _matchEntToShieldFast = (Func)delegates["MatchEntToShieldFast"]; + _matchEntToShieldFastExt = (Func, MyTuple>?>)delegates["MatchEntToShieldFastExt"]; + _isShieldBlock = (Func)delegates["IsShieldBlock"]; + _getClosestShield = (Func)delegates["GetClosestShield"]; + _getDistanceToShield = (Func)delegates["GetDistanceToShield"]; + _getClosestShieldPoint = (Func)delegates["GetClosestShieldPoint"]; + _getShieldInfo = (Func>)delegates["GetShieldInfo"]; + _getModulationInfo = (Func>)delegates["GetModulationInfo"]; + _getFaceInfo = (Func>)delegates["GetFaceInfo"]; + _addAtacker = (Action)delegates["AddAttacker"]; + _isBlockProtected = (Func)delegates["IsBlockProtected"]; + _getFacesFast = (Func>)delegates["GetFacesFast"]; + _getLastAttackers = (Action>>)delegates["GetLastAttackers"]; + } + + public Vector3D? RayAttackShield(IMyTerminalBlock block, RayD ray, long attackerId, float damage, bool energy, bool drawParticle) => + _rayAttackShield?.Invoke(block, ray, attackerId, damage, energy, drawParticle) ?? null; + + public Vector3D? LineAttackShield(IMyTerminalBlock block, LineD line, long attackerId, float damage, bool energy, bool drawParticle) => + _lineAttackShield?.Invoke(block, line, attackerId, damage, energy, drawParticle) ?? null; + + public MyTuple IntersectEntToShieldFast(List entities, RayD ray, bool onlyIfOnline, bool enemyOnly, long requesterId, float maxRange) => + _intersectEntToShieldFast?.Invoke(entities, ray, onlyIfOnline, enemyOnly, requesterId, maxRange) ?? new MyTuple(false, float.MaxValue); + + public bool PointAttackShield(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, bool energy, bool drawParticle, bool posMustBeInside = false) => + _pointAttackShield?.Invoke(block, pos, attackerId, damage, energy, drawParticle, posMustBeInside) ?? false; + + public float? PointAttackShieldExt(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, bool energy, bool drawParticle, bool posMustBeInside = false) => + _pointAttackShieldExt?.Invoke(block, pos, attackerId, damage, energy, drawParticle, posMustBeInside) ?? null; + + public float? PointAttackShieldCon(IMyTerminalBlock block, Vector3D pos, long attackerId, float damage, float optionalDamage, bool energy, bool drawParticle, bool posMustBeInside = false) => + _pointAttackShieldCon?.Invoke(block, pos, attackerId, damage, optionalDamage, energy, drawParticle, posMustBeInside) ?? null; + + public void SetShieldHeat(IMyTerminalBlock block, int value) => _setShieldHeat?.Invoke(block, value); + + public void OverLoadShield(IMyTerminalBlock block) => _overLoad?.Invoke(block); + + public void SetCharge(IMyTerminalBlock block, float value) => _setCharge.Invoke(block, value); + + public Vector3D? RayIntersectShield(IMyTerminalBlock block, RayD ray) => _rayIntersectShield?.Invoke(block, ray) ?? null; + + public Vector3D? LineIntersectShield(IMyTerminalBlock block, LineD line) => _lineIntersectShield?.Invoke(block, line) ?? null; + + public bool PointInShield(IMyTerminalBlock block, Vector3D pos) => _pointInShield?.Invoke(block, pos) ?? false; + + public float GetShieldPercent(IMyTerminalBlock block) => _getShieldPercent?.Invoke(block) ?? -1; + + public int GetShieldHeat(IMyTerminalBlock block) => _getShieldHeat?.Invoke(block) ?? -1; + + public float GetChargeRate(IMyTerminalBlock block) => _getChargeRate?.Invoke(block) ?? -1; + + public float HpToChargeRatio(IMyTerminalBlock block) => _hpToChargeRatio?.Invoke(block) ?? -1; + + public float GetMaxCharge(IMyTerminalBlock block) => _getMaxCharge?.Invoke(block) ?? -1; + + public float GetCharge(IMyTerminalBlock block) => _getCharge?.Invoke(block) ?? -1; + + public float GetPowerUsed(IMyTerminalBlock block) => _getPowerUsed?.Invoke(block) ?? -1; + + public float GetPowerCap(IMyTerminalBlock block) => _getPowerCap?.Invoke(block) ?? -1; + + public float GetMaxHpCap(IMyTerminalBlock block) => _getMaxHpCap?.Invoke(block) ?? -1; + + public bool IsShieldUp(IMyTerminalBlock block) => _isShieldUp?.Invoke(block) ?? false; + + public string ShieldStatus(IMyTerminalBlock block) => _shieldStatus?.Invoke(block) ?? string.Empty; + + public bool EntityBypass(IMyTerminalBlock block, IMyEntity entity, bool remove = false) => _entityBypass?.Invoke(block, entity, remove) ?? false; + + public bool GridHasShield(IMyCubeGrid grid) => _gridHasShield?.Invoke(grid) ?? false; + + public bool GridShieldOnline(IMyCubeGrid grid) => _gridShieldOnline?.Invoke(grid) ?? false; + + public bool ProtectedByShield(IMyEntity entity) => _protectedByShield?.Invoke(entity) ?? false; + + public IMyTerminalBlock GetShieldBlock(IMyEntity entity) => _getShieldBlock?.Invoke(entity) ?? null; + + public IMyTerminalBlock MatchEntToShieldFast(IMyEntity entity, bool onlyIfOnline) => _matchEntToShieldFast?.Invoke(entity, onlyIfOnline) ?? null; + + public MyTuple, MyTuple>? MatchEntToShieldFastExt(MyEntity entity, bool onlyIfOnline) => _matchEntToShieldFastExt?.Invoke(entity, onlyIfOnline) ?? null; + + public bool IsShieldBlock(IMyTerminalBlock block) => _isShieldBlock?.Invoke(block) ?? false; + + public IMyTerminalBlock GetClosestShield(Vector3D pos) => _getClosestShield?.Invoke(pos) ?? null; + + public double GetDistanceToShield(IMyTerminalBlock block, Vector3D pos) => _getDistanceToShield?.Invoke(block, pos) ?? -1; + + public Vector3D? GetClosestShieldPoint(IMyTerminalBlock block, Vector3D pos) => _getClosestShieldPoint?.Invoke(block, pos) ?? null; + + public MyTuple GetShieldInfo(MyEntity entity) => _getShieldInfo?.Invoke(entity) ?? new MyTuple(); + + public MyTuple GetModulationInfo(MyEntity entity) => _getModulationInfo?.Invoke(entity) ?? new MyTuple(); + + public MyTuple GetFaceInfo(IMyTerminalBlock block, Vector3D pos, bool posMustBeInside = false) => _getFaceInfo?.Invoke(block, pos, posMustBeInside) ?? new MyTuple(); + + public void AddAttacker(long attacker) => _addAtacker?.Invoke(attacker); + + public bool IsBlockProtected(IMySlimBlock block) => _isBlockProtected?.Invoke(block) ?? false; + + public MyTuple GetFacesFast(MyEntity entity) => _getFacesFast?.Invoke(entity) ?? new MyTuple(); + + public void GetLastAttackers(MyEntity entity, ICollection> collection) => _getLastAttackers?.Invoke(entity, collection); + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Utils.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Utils.cs new file mode 100644 index 00000000..f2d18a87 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/Utils.cs @@ -0,0 +1,264 @@ +using System; +using Sandbox.Definitions; +using Sandbox.Game; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.ModAPI; +using VRageMath; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class Utils + { + /// + /// Is the block damaged/incomplete/projected + /// + public static bool NeedRepair(this IMySlimBlock target, bool functionalOnly) + { + //I use target.HasDeformation && target.MaxDeformation > X) as I had several times both situations, a landing gear reporting HasDeformation or a block reporting target.MaxDeformation > 0.1 both weren't repairable and caused welding this blocks forever! + //Now I had the case that target.HasDeformation = true and target.MaxDeformation=0 and the block was deformed -> I removed the double Check + //target.IsFullyDismounted is equals to target.IsDestroyed + var neededIntegrityLevel = functionalOnly ? target.MaxIntegrity * ((MyCubeBlockDefinition)target.BlockDefinition).CriticalIntegrityRatio : target.MaxIntegrity; + return !target.IsDestroyed && (target.FatBlock == null || !target.FatBlock.Closed) && (target.Integrity < neededIntegrityLevel || target.HasDeformation); + } + + /// + /// Is the grid a projected grid + /// + public static bool IsProjected(this IMyCubeGrid target) + { + var cubeGrid = target as MyCubeGrid; + return cubeGrid?.Projector != null; + } + + /// + /// Is the block a projected block + /// + public static bool IsProjected(this IMySlimBlock target) + { + var cubeGrid = target.CubeGrid as MyCubeGrid; + return cubeGrid?.Projector != null; + } + + /// + /// Is the block a projected block + /// + public static bool IsProjected(this IMySlimBlock target, out IMyProjector projector) + { + var cubeGrid = target.CubeGrid as MyCubeGrid; + projector = cubeGrid?.Projector; + return projector != null; + } + + /// + /// Could the projected block be build + /// !GUI Thread! + /// + /// + /// + public static bool CanBuild(this IMySlimBlock target, bool gui) + { + var cubeGrid = target.CubeGrid as MyCubeGrid; + if (cubeGrid?.Projector == null) return false; + + var projector = ((IMyProjector)cubeGrid.Projector); + return projector.CanBuild(target, gui) == BuildCheckResult.OK; + } + + /// + /// The inventory is filled to X percent + /// + /// + /// + public static float IsFilledToPercent(this IMyInventory inventory) + { + return Math.Max((float)inventory.CurrentVolume / (float)inventory.MaxVolume, (float)inventory.CurrentMass / (float)((MyInventory)inventory).MaxMass); + } + + /// + /// Checks if block is inside the given BoundingBox + /// + /// + /// + /// + public static bool IsInRange(this IMySlimBlock block, ref MyOrientedBoundingBoxD areaBox, out double distance) + { + Vector3 halfExtents; + block.ComputeScaledHalfExtents(out halfExtents); + var matrix = block.CubeGrid.WorldMatrix; + matrix.Translation = block.CubeGrid.GridIntegerToWorld(block.Position); + var box = new MyOrientedBoundingBoxD(new BoundingBoxD(-halfExtents, halfExtents), matrix); + var inRange = areaBox.Intersects(ref box); + distance = inRange ? (areaBox.Center - box.Center).Length() : 0; + return inRange; + } + + /// + /// Get the block name for GUI + /// + /// + /// + public static string BlockName(this IMySlimBlock slimBlock) + { + if (slimBlock != null) + { + var terminalBlock = slimBlock.FatBlock as IMyTerminalBlock; + if (terminalBlock != null) + { + return + $"{(terminalBlock.CubeGrid != null ? terminalBlock.CubeGrid.DisplayName : "Unknown Grid")}.{terminalBlock.CustomName}"; + } + else + { + return + $"{(slimBlock.CubeGrid != null ? slimBlock.CubeGrid.DisplayName : "Unknown Grid")}.{slimBlock.BlockDefinition.DisplayNameText}"; + } + } + else return "(none)"; + } + + public static string BlockName(this VRage.Game.ModAPI.Ingame.IMySlimBlock slimBlock) + { + if (slimBlock != null) + { + var terminalBlock = slimBlock.FatBlock as Sandbox.ModAPI.Ingame.IMyTerminalBlock; + if (terminalBlock != null) + { + return + $"{(terminalBlock.CubeGrid != null ? terminalBlock.CubeGrid.DisplayName : "Unknown Grid")}.{terminalBlock.CustomName}"; + } + else + { + return + $"{(slimBlock.CubeGrid != null ? slimBlock.CubeGrid.DisplayName : "Unknown Grid")}.{slimBlock.BlockDefinition.ToString()}"; + } + } + else return "(none)"; + } + + /// + /// Check the ownership of the grid + /// + /// + /// + /// + public static VRage.Game.MyRelationsBetweenPlayerAndBlock GetUserRelationToOwner(this IMyCubeGrid cubeGrid, long userId, bool ignoreCubeGridList = false) + { + var enemies = false; + var neutral = false; + try + { + if (cubeGrid.BigOwners != null && cubeGrid.BigOwners.Count != 0) + { + foreach (var key in cubeGrid.BigOwners) + { + var relation = MyIDModule.GetRelationPlayerBlock(key, userId, VRage.Game.MyOwnershipShareModeEnum.Faction); + if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Owner || relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.FactionShare) + { + return relation; + } + else if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Enemies) + { + enemies = true; + } + else if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Neutral) + { + neutral = true; + } + } + } + else if (!ignoreCubeGridList) + { + //E.G. the case if a landing gear is directly attatched to piston/rotor (with no ownable block in the same subgrid) and the gear gets connected to something + var cubegridsList = MyAPIGateway.GridGroups.GetGroup(cubeGrid, GridLinkTypeEnum.Mechanical); + if (cubegridsList != null) + { + foreach (var cubeGrid1 in cubegridsList) + { + if (cubeGrid1 == cubeGrid) continue; + var relation = cubeGrid1.GetUserRelationToOwner(userId, true); //Do not recurse as this list is already complete + if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Owner || relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.FactionShare) + { + return relation; + } + else if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Enemies) + { + enemies = true; + } + else if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.Neutral) + { + neutral = true; + } + } + } + } + } + catch + { + //The list BigOwners could change while iterating -> a silent catch + } + + if (enemies) + { + return VRage.Game.MyRelationsBetweenPlayerAndBlock.Enemies; + } + + if (neutral) + { + return VRage.Game.MyRelationsBetweenPlayerAndBlock.Neutral; + } + + return VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership; + } + + /// + /// Return relation between player and grid, in case of 'NoOwnership' check the grid owner. + /// + /// + /// + /// + public static VRage.Game.MyRelationsBetweenPlayerAndBlock GetUserRelationToOwner(this IMySlimBlock slimBlock, long userId) + { + if (slimBlock == null) + { + return VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership; + } + var fatBlock = slimBlock.FatBlock; + if (fatBlock != null) + { + var relation = fatBlock.GetUserRelationToOwner(userId); + if (relation == VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership) + { + relation = GetUserRelationToOwner(slimBlock.CubeGrid, userId); + return relation; + } + else + { + return relation; + } + } + else + { + var relation = GetUserRelationToOwner(slimBlock.CubeGrid, userId); + return relation; + } + } + + public static VRage.MyFixedPoint AsFloorMyFixedPoint(this float value) + { + return new VRage.MyFixedPoint() { RawValue = (long)(value * 1000000L) }; + } + + public static int CompareDistance(double a, double b) + { + var diff = a - b; + return Math.Abs(diff) < 0.00001 ? 0 : diff > 0 ? 1 : -1; + } + + public static bool IsCharacterPlayerAndActive(IMyCharacter character) + { + return character != null && character.IsPlayer && !character.Closed && character.InScene && !character.IsDead; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsFaction.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsFaction.cs new file mode 100644 index 00000000..81e11f72 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsFaction.cs @@ -0,0 +1,44 @@ +using Sandbox.Game; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class UtilsFaction + { + public static int DamageAmount = 5; + + public static void DamageReputationWithPlayerFaction(long sourcePlayerId, long targetPlayerId) + { + if (MyAPIGateway.Session == null) + return; + + var targetFaction = MyAPIGateway.Session.Factions.TryGetPlayerFaction(targetPlayerId); + if (targetFaction != null) + { + var player = UtilsPlayer.GetPlayer(sourcePlayerId); + if (player == null) + return; + + var myFaction = GetPlayerFaction(player.IdentityId); + if (myFaction == null || (myFaction != null && myFaction.FactionId != targetFaction.FactionId)) + { + var currentReputation = MyVisualScriptLogicProvider.GetRelationBetweenPlayerAndFaction(sourcePlayerId, targetFaction.Tag); + if (currentReputation > -1500) + { + var newReputation = currentReputation - DamageAmount; + if (newReputation < -1500) + newReputation = -1500; + + MyVisualScriptLogicProvider.SetRelationBetweenPlayerAndFaction(sourcePlayerId, targetFaction.Tag, newReputation); + } + } + } + } + + public static IMyFaction GetPlayerFaction(long playerId) + { + return MyAPIGateway.Session.Factions.TryGetPlayerFaction(playerId); + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsInventory.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsInventory.cs new file mode 100644 index 00000000..8dddfc53 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsInventory.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sandbox.Definitions; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using MyInventoryItem = VRage.Game.ModAPI.Ingame.MyInventoryItem; +using MyItemType = VRage.Game.ModAPI.Ingame.MyItemType; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class UtilsInventory + { + public delegate bool ExcludeInventory(IMyInventory destInventory, IMyInventory srcInventory, ref MyInventoryItem srcItem); + + /// + /// Check if all inventories are empty + /// + /// + /// + public static bool InventoriesEmpty(this IMyEntity entity) + { + if (!entity.HasInventory) return true; + for (var i1 = 0; i1 < entity.InventoryCount; ++i1) + { + var srcInventory = entity.GetInventory(i1); + if (!srcInventory.Empty()) return false; + } + return true; + } + + /// + /// Push all components into destinations + /// + public static bool PushComponents(this IMyInventory srcInventory, List destinations, ExcludeInventory exclude) + { + var moved = false; + lock (destinations) + { + var srcItems = new List(); + srcInventory.GetItems(srcItems); + for (var srcItemIndex = srcItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var srcItem = srcItems[srcItemIndex]; + moved = TryTransferItemTo(srcInventory, destinations, srcItemIndex, srcItem, true, exclude) || moved; + } + + if (!moved) + { + srcItems.Clear(); + srcInventory.GetItems(srcItems); + for (var srcItemIndex = srcItems.Count - 1; srcItemIndex >= 0; srcItemIndex--) + { + var srcItem = srcItems[srcItemIndex]; + moved = TryTransferItemTo(srcInventory, destinations, srcItemIndex, srcItem, false, exclude) || moved; + } + } + } + return moved; + } + + /// + /// Push given items into destinations + /// + public static bool PushComponents(this IMyInventory srcInventory, List destinations, ExcludeInventory exclude, int srcItemIndex, MyInventoryItem srcItem) + { + var moved = false; + lock (destinations) + { + moved = TryTransferItemTo(srcInventory, destinations, srcItemIndex, srcItem, true, exclude); + if (!moved) + { + moved = TryTransferItemTo(srcInventory, destinations, srcItemIndex, srcItem, false, exclude); + } + } + return moved; + } + + /// + /// As long as ComputeAmountThatFits is not available for modding we have to try + /// + public static VRage.MyFixedPoint MaxItemsAddable(this IMyInventory destInventory, VRage.MyFixedPoint maxNeeded, MyItemType itemType) + { + if (destInventory.CanItemsBeAdded(maxNeeded, itemType)) + { + return maxNeeded; + } + + var maxPossible = 0; + var currentStep = Math.Max((int)maxNeeded / 2, 1); + var currentTry = 0; + while (currentStep > 0) + { + currentTry = maxPossible + currentStep; + if (destInventory.CanItemsBeAdded(currentTry, itemType)) + { + maxPossible = currentTry; + } + else + { + if (currentStep <= 1) break; + } + if (currentStep > 1) currentStep = currentStep / 2; + } + return maxPossible; + } + + /// + /// As long as ComputeAmountThatFits is not available for modding we have to try + /// + public static VRage.MyFixedPoint MaxFractionItemsAddable(this IMyInventory destInventory, VRage.MyFixedPoint maxNeeded, MyItemType itemType) + { + if (destInventory.CanItemsBeAdded(maxNeeded, itemType)) + { + return maxNeeded; + } + + VRage.MyFixedPoint maxPossible = 0; + var currentStep = (VRage.MyFixedPoint)((float)maxNeeded / 2); + VRage.MyFixedPoint currentTry = 0; + while (currentStep > VRage.MyFixedPoint.SmallestPossibleValue) + { + currentTry = maxPossible + currentStep; + if (destInventory.CanItemsBeAdded(currentTry, itemType)) + { + maxPossible = currentTry; + } + currentStep = (VRage.MyFixedPoint)((float)currentStep / 2); + } + return maxPossible; + } + + /// + /// Moves as many as possible from srcInventory to destinations + /// + private static bool TryTransferItemTo(IMyInventory srcInventory, List destinations, int srcItemIndex, MyInventoryItem srcItem, bool all, ExcludeInventory exclude) + { + var moved = false; + if (all) + { + foreach (var destInventory in destinations) + { + if (exclude != null && exclude(destInventory, srcInventory, ref srcItem)) continue; + if (destInventory.CanItemsBeAdded(srcItem.Amount, srcItem.Type) && srcInventory.CanTransferItemTo(destInventory, srcItem.Type)) + { + moved = srcInventory.TransferItemTo(destInventory, srcItemIndex, null, true, srcItem.Amount, false); + if (moved) break; + } + } + return moved; + } + + foreach (var destInventory in destinations) + { + if (exclude != null && exclude(destInventory, srcInventory, ref srcItem)) continue; + if (srcInventory.CanTransferItemTo(destInventory, srcItem.Type)) + { + var amount = destInventory.MaxItemsAddable(srcItem.Amount, srcItem.Type); + if (amount > 0) + { + moved = srcInventory.TransferItemTo(destInventory, srcItemIndex, null, true, amount, true) || moved; + if (srcItem.Amount <= 0) break; + } + } + } + return moved; + } + + /// + /// Add maxNeeded amount of items into inventory. + /// -If not maxNeeded could be added as amany as possible is added and the added amout is returned + /// -If maxNeeded is less than MyFixedPoint can handle 0 is returned + /// + public static float AddMaxItems(this IMyInventory destInventory, float maxNeeded, MyObjectBuilder_PhysicalObject objectBuilder) + { + var maxNeededFP = (VRage.MyFixedPoint)maxNeeded; + return (float)destInventory.AddMaxItems(maxNeededFP, objectBuilder); + } + + public static VRage.MyFixedPoint AddMaxItems(this IMyInventory destInventory, VRage.MyFixedPoint maxNeededFP, MyObjectBuilder_PhysicalObject objectBuilder) + { + var contentId = objectBuilder.GetObjectId(); + if (maxNeededFP <= 0) + { + return 0; //Amount to small + } + + var maxPossible = destInventory.MaxFractionItemsAddable(maxNeededFP, contentId); + if (maxPossible > 0) + { + destInventory.AddItems(maxPossible, objectBuilder); + return maxPossible; + } + else + { + return 0; + } + } + + public static VRage.MyFixedPoint RemoveMaxItems(this IMyInventory srcInventory, VRage.MyFixedPoint maxRemoveFP, MyObjectBuilder_PhysicalObject objectBuilder) + { + var contentId = objectBuilder.GetObjectId(); + VRage.MyFixedPoint removedAmount = 0; + if (!srcInventory.ContainItems(maxRemoveFP, objectBuilder)) + { + maxRemoveFP = srcInventory.GetItemAmount(contentId); + } + if (maxRemoveFP > 0) + { + srcInventory.RemoveItemsOfType(maxRemoveFP, contentId, MyItemFlags.None, false); + removedAmount = maxRemoveFP; + } + return maxRemoveFP; + } + + /// + /// Retrieve the total amount of componets to build a blueprint + /// (blueprint loaded inside projector) + /// + /// + /// + public static int NeededComponents4Blueprint(Sandbox.ModAPI.Ingame.IMyProjector srcProjector, Dictionary componentList) + { + var projector = srcProjector as IMyProjector; + if (componentList == null || projector == null || !projector.IsProjecting) return -1; + + //Add buildable blocks + var projectedCubeGrid = projector.ProjectedGrid; + if (projectedCubeGrid != null) + { + var projectedBlocks = new List(); + projectedCubeGrid.GetBlocks(projectedBlocks); + foreach (var block in projectedBlocks) + { + var blockDefinition = block.BlockDefinition as MyCubeBlockDefinition; + foreach (var component in blockDefinition.Components) + { + if (componentList.ContainsKey(component.Definition.Id)) componentList[component.Definition.Id] += component.Count; + else componentList[component.Definition.Id] = component.Count; + } + } + } + return componentList.Count(); + } + + public enum IntegrityLevel + { + Create, + Functional, + Complete + } + + /// + /// Retrieve the amount of components to build the block to the given index + /// + /// + /// + /// integrity level + public static void GetMissingComponents(this IMySlimBlock block, Dictionary componentList, IntegrityLevel level) + { + var blockDefinition = block.BlockDefinition as MyCubeBlockDefinition; + if (blockDefinition.Components == null || blockDefinition.Components.Length == 0) return; + + if (level == IntegrityLevel.Create) + { + var component = blockDefinition.Components[0]; + componentList.Add(component.Definition.Id.SubtypeName, 1); + } + else + { + if (block.IsProjected()) + { + var maxIdx = level == IntegrityLevel.Functional ? blockDefinition.CriticalGroup + 1 : blockDefinition.Components.Length; + for (var idx = 0; idx < maxIdx; idx++) + { + var component = blockDefinition.Components[idx]; + if (componentList.ContainsKey(component.Definition.Id.SubtypeName)) componentList[component.Definition.Id.SubtypeName] += component.Count; + else componentList.Add(component.Definition.Id.SubtypeName, component.Count); + } + } + else + { + block.GetMissingComponents(componentList); + if (level == IntegrityLevel.Functional) + { + for (var idx = blockDefinition.CriticalGroup + 1; idx < blockDefinition.Components.Length; idx++) + { + var component = blockDefinition.Components[idx]; + if (componentList.ContainsKey(component.Definition.Id.SubtypeName)) + { + var amount = componentList[component.Definition.Id.SubtypeName]; + if (amount <= component.Count) componentList.Remove(component.Definition.Id.SubtypeName); + else componentList[component.Definition.Id.SubtypeName] -= component.Count; + } + } + } + } + } + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsPlayer.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsPlayer.cs new file mode 100644 index 00000000..87023595 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsPlayer.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class UtilsPlayer + { + public static IMyPlayer GetPlayer(long identityId) + { + var players = GetAllPlayers(); + var player = players.FirstOrDefault(c => c.IdentityId == identityId); + return player; + } + + public static long GetOwner(IMyCubeGrid grid) + { + var gridOwnerList = grid.BigOwners; + var ownerCnt = gridOwnerList.Count; + var gridOwner = 0L; + + if (ownerCnt > 0 && gridOwnerList[0] != 0) + return gridOwnerList[0]; + else if (ownerCnt > 1) + return gridOwnerList[1]; + + return gridOwner; + } + + public static List GetAllPlayers() + { + var list = new List(); + MyAPIGateway.Players.GetPlayers(list); + return list; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsProductionBlock.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsProductionBlock.cs new file mode 100644 index 00000000..78838ab4 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsProductionBlock.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sandbox.Definitions; +using Sandbox.ModAPI; +using VRage.ModAPI; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class UtilsProductionBlock + { + /// + /// Ensure that the requested amout of material is available inside production block. + /// Available means already in inventory or in production queue. + /// + /// + /// + /// + /// + public static int EnsureQueued(IEnumerable entityIds, VRage.Game.MyDefinitionId materialId, int amount) + { + if (amount <= 0) return 0; + if (!entityIds.Any()) return -1; + + var blueprintDefinition = MyDefinitionManager.Static.TryGetBlueprintDefinitionByResultId(materialId); + if (blueprintDefinition == null) return 0; + + var queueSizes = new List>(); + var queueSizeAvg = 0; + foreach (var entityId in entityIds) + { + IMyEntity entity; + if (!MyAPIGateway.Entities.TryGetEntityById(entityId, out entity)) continue; + var productionBlock = entity as IMyAssembler; + if (productionBlock == null || productionBlock.Mode != Sandbox.ModAPI.Ingame.MyAssemblerMode.Assembly) continue; + + int queueSize; + var amountAvail = AvailableAmount(productionBlock, materialId, blueprintDefinition, out queueSize); + + if (productionBlock.CanUseBlueprint(blueprintDefinition)) + { + queueSizes.Add(new KeyValuePair(productionBlock, queueSize)); + queueSizeAvg += queueSize; + } + + amount -= amountAvail; + if (amount <= 0) return 0; //Allready enought available + } + + var cnt = queueSizes.Count; + if (cnt <= 0) return -1; //No production blocks or none could handle this material + + queueSizeAvg = queueSizeAvg / cnt; + queueSizes.Sort((a, b) => a.Value - b.Value); + + var queued = amount; + + //First run fill to avg + if (queueSizeAvg > 0) + { + foreach (var entry in queueSizes) + { + var space = queueSizeAvg - entry.Value; + if (space > 0) + { + space = Math.Min(space, amount); + entry.Key.AddQueueItem(blueprintDefinition, space); + amount -= space; + if (amount <= 0) return queued; + } + } + } + + //Second run spread the rest + var amountPerBlock = (int)Math.Ceiling((decimal)amount / cnt); + foreach (var entry in queueSizes) + { + var space = Math.Min(amountPerBlock, amount); + entry.Key.AddQueueItem(blueprintDefinition, space); + amount -= space; + if (amount <= 0) return queued; + } + + return 0; + } + + /// + /// Gives the 'available' amount inventory + queued + /// queueSize total amount of queued items indicator for workload + /// + private static int AvailableAmount(IMyProductionBlock productionBlock, VRage.Game.MyDefinitionId materialId, VRage.Game.MyDefinitionBase blueprintDefinition, out int queueSize) + { + var queue = productionBlock.GetQueue(); + var inventory = productionBlock.OutputInventory; + var tempInventoryItems = new List(); + inventory?.GetItems(tempInventoryItems); + var amount = 0; + queueSize = 0; + foreach (var item in tempInventoryItems) + { + if ((VRage.Game.MyDefinitionId)item.Type == materialId) + { + amount += (int)item.Amount; + } + } + + if (queue != null) + { + foreach (var item in queue) + { + queueSize += (int)item.Amount; + if (item.Blueprint.Id.Equals(blueprintDefinition.Id)) + { + amount += (int)item.Amount; + } + } + } + + return amount; + } + } +} diff --git a/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsSynchronization.cs b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsSynchronization.cs new file mode 100644 index 00000000..ee90fe8d --- /dev/null +++ b/DeltaVBaR/Data/Scripts/SKO-Nanobot-BuildAndRepair-System/UtilsSynchronization.cs @@ -0,0 +1,205 @@ +using System.Collections.Generic; +using ProtoBuf; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRage.ObjectBuilders; +using VRageMath; + +namespace SKONanobotBuildAndRepairSystem +{ + public static class UtilsSynchronization + { + public static uint RotateLeft(uint x, int n) + { + return (x << n) | (x >> (32 - n)); + } + } + + [ProtoContract(UseProtoMembersOnly = true)] + public class SyncEntityId + { + [ProtoMember(1)] + public long EntityId { get; set; } + + [ProtoMember(2)] + public long GridId { get; set; } + + [ProtoMember(3)] + public Vector3I? Position { get; set; } + + [ProtoMember(4)] + public BoundingBoxD? Box { get; set; } + + public override string ToString() + { + return $"EntityId={EntityId}, GridId={GridId}, Position={Position}, Box={Box}"; + } + + public static SyncEntityId GetSyncId(object item) + { + if (item == null) return null; + var slimBlock = item as IMySlimBlock; + if (slimBlock != null) + { + if (slimBlock.FatBlock != null) + { + return new SyncEntityId() { EntityId = slimBlock.FatBlock.EntityId, GridId = slimBlock.CubeGrid?.EntityId ?? 0, Position = slimBlock.Position }; + } + else if (slimBlock.CubeGrid != null) + { + return new SyncEntityId() { EntityId = 0, GridId = slimBlock.CubeGrid.EntityId, Position = slimBlock.Position }; + } + } + + var voxelBase = item as IMyVoxelBase; + if (voxelBase != null) + { + return new SyncEntityId() { Box = voxelBase.WorldAABB }; + } + + var entity = item as IMyEntity; + if (entity != null) + { + return new SyncEntityId() { EntityId = entity.EntityId }; + } + + var position = item as Vector3D?; + if (position != null) + { + return new SyncEntityId() { Position = new Vector3I((int)position.Value.X, (int)position.Value.Y, (int)position.Value.Z) }; + } + + return null; + } + + public static object GetItem(SyncEntityId id) + { + if (id == null) return null; + + if (id.EntityId != 0) + { + IMyEntity entity; + if (MyAPIGateway.Entities.TryGetEntityById(id.EntityId, out entity)) + { + return entity; + } + } + if (id.GridId != 0 && id.Position != null) + { + IMyEntity entity; + if (MyAPIGateway.Entities.TryGetEntityById(id.GridId, out entity)) + { + var grid = entity as IMyCubeGrid; + return grid?.GetCubeBlock(id.Position.Value); + } + } + if (id.Position != null) + { + return id.Position; + } + if (id.Box != null) + { + IMyEntity entity; + var box = id.Box.Value; + if ((entity = MyAPIGateway.Session.VoxelMaps.GetVoxelMapWhoseBoundingBoxIntersectsBox(ref box, null)) != null) + { + return entity; + } + } + return null; + } + + public static IMySlimBlock GetItemAsSlimBlock(SyncEntityId id) + { + var item = GetItem(id); + var slimBlock = item as IMySlimBlock; + if (slimBlock != null) return slimBlock; + + var block = item as IMyCubeBlock; + return block?.SlimBlock; + } + + public static T GetItemAs(SyncEntityId id) where T : class + { + return GetItem(id) as T; + } + + public override bool Equals(object obj) + { + var syncObj = obj as SyncEntityId; + if (obj == null || syncObj == null) + { + return false; + } + return EntityId == syncObj.EntityId && GridId == syncObj.GridId && + Position.Equals(syncObj.Position) && Box.Equals(syncObj.Box); + } + + public override int GetHashCode() + { + return EntityId.GetHashCode() + (GridId.GetHashCode() << 8) + ((Position.HasValue ? Position.Value.GetHashCode() : 0) << 16) + ((Box.HasValue ? Box.Value.GetHashCode() : 0) << 24); + } + } + + [ProtoContract(UseProtoMembersOnly = true)] + public class SyncComponents + { + [ProtoMember(1)] + public SerializableDefinitionId Component { get; set; } + + [ProtoMember(2)] + public int Amount { get; set; } + } + + [ProtoContract(UseProtoMembersOnly = true)] + public class SyncTargetEntityData + { + [ProtoMember(1)] + public SyncEntityId Entity { get; set; } + + [ProtoMember(2)] + public double Distance { get; set; } + } + + /// + /// List including Hash Values to detect changes + /// + /// + public abstract class HashList : List + { + private long _CurrentHash; + private long _LastHash; + private int _CurrentCount; + public long CurrentHash { get { return _CurrentHash; } protected set { _CurrentHash = value; } } + public long LastHash { get { return _LastHash; } set { _LastHash = value; } } + public int CurrentCount { get { return _CurrentCount; } protected set { _CurrentCount = value; } } + + public abstract void RebuildHash(); + + public abstract List GetSyncList(); + + public void ChangeHash() + { + CurrentHash++; + } + } + + /// + /// List including Hash Values to detect changes + /// + /// + public abstract class HashDictionary : Dictionary + { + private long _CurrentHash; + private long _LastHash; + private int _CurrentCount; + public long CurrentHash { get { return _CurrentHash; } protected set { _CurrentHash = value; } } + public long LastHash { get { return _LastHash; } set { _LastHash = value; } } + public int CurrentCount { get { return _CurrentCount; } protected set { _CurrentCount = value; } } + + public abstract void RebuildHash(); + + public abstract List GetSyncList(); + } +} diff --git a/DeltaVBaR/Data/Scripts/concatrecursivelyandminify.bat b/DeltaVBaR/Data/Scripts/concatrecursivelyandminify.bat new file mode 100644 index 00000000..9bbffa34 --- /dev/null +++ b/DeltaVBaR/Data/Scripts/concatrecursivelyandminify.bat @@ -0,0 +1,60 @@ +@echo off +setlocal enabledelayedexpansion +chcp 65001 +REM ^^ this is to change the encoding to UTF-8, apparently + +echo Starting operation... + +set TEMP_OUTPUT=temp_concatenated.txt +set FINAL_OUTPUT=minified_output.txt +set /a COUNT=0 +set /a ERRORS=0 + +if exist "%TEMP_OUTPUT%" ( + echo Existing temporary file found. Deleting... + del "%TEMP_OUTPUT%" +) + +if exist "%FINAL_OUTPUT%" ( + echo Existing output file found. Deleting... + del "%FINAL_OUTPUT%" +) + +for /r %%i in (*.cs) do ( + echo Processing: "%%i" + type "%%i" >> "%TEMP_OUTPUT%" + if errorlevel 1 ( + echo Error processing "%%i". + set /a ERRORS+=1 + ) else ( + set /a COUNT+=1 + ) +) + +echo Concatenation completed. +echo Total files processed: %COUNT% +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the concatenation. +) else ( + echo No errors encountered during concatenation. +) + +echo Minifying concatenated file... +csmin < "%TEMP_OUTPUT%" > "%FINAL_OUTPUT%" +if errorlevel 1 ( + echo Error occurred during minification. + set /a ERRORS+=1 +) else ( + echo Minification completed successfully. +) + +echo Cleaning up temporary file... +del "%TEMP_OUTPUT%" + +echo Operation completed. +if %ERRORS% gtr 0 ( + echo There were %ERRORS% errors during the entire operation. +) else ( + echo No errors encountered during the entire operation. +) +pause \ No newline at end of file diff --git a/DeltaVBaR/Data/TransparentMaterials.sbc b/DeltaVBaR/Data/TransparentMaterials.sbc new file mode 100644 index 00000000..85d0d6de --- /dev/null +++ b/DeltaVBaR/Data/TransparentMaterials.sbc @@ -0,0 +1,27 @@ + + + + + + TransparentMaterialDefinition + WelderGrid + + false + 1 + false + 0 + false + 1 + Textures\Particles\WelderGrid.dds + false + + 0 + 0 + + + 1 + 1 + + + + diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem.mwm new file mode 100644 index 00000000..3480984a Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr1.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr1.mwm new file mode 100644 index 00000000..04ee0b3a Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr1.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr2.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr2.mwm new file mode 100644 index 00000000..2751e5db Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr2.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr3.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr3.mwm new file mode 100644 index 00000000..683bc7f1 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_Constr3.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD1.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD1.mwm new file mode 100644 index 00000000..16380f7b Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD1.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD2.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD2.mwm new file mode 100644 index 00000000..98b727b9 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD2.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD3.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD3.mwm new file mode 100644 index 00000000..19edeffb Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdLargeNanobotBuildAndRepairSystem_LOD3.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem.mwm new file mode 100644 index 00000000..a8d3d661 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr1.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr1.mwm new file mode 100644 index 00000000..53f49c11 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr1.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr2.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr2.mwm new file mode 100644 index 00000000..8f0973c0 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr2.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr3.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr3.mwm new file mode 100644 index 00000000..3cb3943f Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_Constr3.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD1.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD1.mwm new file mode 100644 index 00000000..900cca4c Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD1.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD2.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD2.mwm new file mode 100644 index 00000000..82e8ae8f Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD2.mwm differ diff --git a/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD3.mwm b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD3.mwm new file mode 100644 index 00000000..6265a342 Binary files /dev/null and b/DeltaVBaR/Models/NanobotBuildAndRepairSystem/SELtdSmallNanobotBuildAndRepairSystem_LOD3.mwm differ diff --git a/DeltaVBaR/Sounds/ArcBaRUnable2d.xwm b/DeltaVBaR/Sounds/ArcBaRUnable2d.xwm new file mode 100644 index 00000000..ca5f2a53 Binary files /dev/null and b/DeltaVBaR/Sounds/ArcBaRUnable2d.xwm differ diff --git a/DeltaVBaR/Sounds/ArcBaRUnable3d.xwm b/DeltaVBaR/Sounds/ArcBaRUnable3d.xwm new file mode 100644 index 00000000..d1b1a6c9 Binary files /dev/null and b/DeltaVBaR/Sounds/ArcBaRUnable3d.xwm differ diff --git a/DeltaVBaR/Sounds/RealBaRUnable2d.wav b/DeltaVBaR/Sounds/RealBaRUnable2d.wav new file mode 100644 index 00000000..81420cf0 Binary files /dev/null and b/DeltaVBaR/Sounds/RealBaRUnable2d.wav differ diff --git a/DeltaVBaR/Sounds/RealBaRUnable3d.wav b/DeltaVBaR/Sounds/RealBaRUnable3d.wav new file mode 100644 index 00000000..9969f0fc Binary files /dev/null and b/DeltaVBaR/Sounds/RealBaRUnable3d.wav differ diff --git a/DeltaVBaR/Textures/Icons/SELtdNanobotBuildAndRepairSystem.dds b/DeltaVBaR/Textures/Icons/SELtdNanobotBuildAndRepairSystem.dds new file mode 100644 index 00000000..64455356 Binary files /dev/null and b/DeltaVBaR/Textures/Icons/SELtdNanobotBuildAndRepairSystem.dds differ diff --git a/DeltaVBaR/Textures/Models/DirectionArrows_add.dds b/DeltaVBaR/Textures/Models/DirectionArrows_add.dds new file mode 100644 index 00000000..bbcf8ce0 Binary files /dev/null and b/DeltaVBaR/Textures/Models/DirectionArrows_add.dds differ diff --git a/DeltaVBaR/Textures/Models/DirectionArrows_alpha.dds b/DeltaVBaR/Textures/Models/DirectionArrows_alpha.dds new file mode 100644 index 00000000..a3814c5e Binary files /dev/null and b/DeltaVBaR/Textures/Models/DirectionArrows_alpha.dds differ diff --git a/DeltaVBaR/Textures/Models/DirectionArrows_cm.dds b/DeltaVBaR/Textures/Models/DirectionArrows_cm.dds new file mode 100644 index 00000000..871a5078 Binary files /dev/null and b/DeltaVBaR/Textures/Models/DirectionArrows_cm.dds differ diff --git a/DeltaVBaR/Textures/Models/DirectionArrows_ng.dds b/DeltaVBaR/Textures/Models/DirectionArrows_ng.dds new file mode 100644 index 00000000..76e8f4c5 Binary files /dev/null and b/DeltaVBaR/Textures/Models/DirectionArrows_ng.dds differ diff --git a/DeltaVBaR/Textures/Models/WarningWelding_add.dds b/DeltaVBaR/Textures/Models/WarningWelding_add.dds new file mode 100644 index 00000000..bbcf8ce0 Binary files /dev/null and b/DeltaVBaR/Textures/Models/WarningWelding_add.dds differ diff --git a/DeltaVBaR/Textures/Models/WarningWelding_cm.dds b/DeltaVBaR/Textures/Models/WarningWelding_cm.dds new file mode 100644 index 00000000..36491590 Binary files /dev/null and b/DeltaVBaR/Textures/Models/WarningWelding_cm.dds differ diff --git a/DeltaVBaR/Textures/Models/WarningWelding_ng.dds b/DeltaVBaR/Textures/Models/WarningWelding_ng.dds new file mode 100644 index 00000000..76e8f4c5 Binary files /dev/null and b/DeltaVBaR/Textures/Models/WarningWelding_ng.dds differ diff --git a/DeltaVBaR/Textures/Particles/WelderGrid.dds b/DeltaVBaR/Textures/Particles/WelderGrid.dds new file mode 100644 index 00000000..ca2da1ac Binary files /dev/null and b/DeltaVBaR/Textures/Particles/WelderGrid.dds differ diff --git a/DeltaVBaR/metadata.mod b/DeltaVBaR/metadata.mod new file mode 100644 index 00000000..0a020fdf --- /dev/null +++ b/DeltaVBaR/metadata.mod @@ -0,0 +1,4 @@ + + + 1.0 + \ No newline at end of file diff --git a/DeltaVBaR/modinfo.sbmi b/DeltaVBaR/modinfo.sbmi new file mode 100644 index 00000000..7bd7d554 --- /dev/null +++ b/DeltaVBaR/modinfo.sbmi @@ -0,0 +1,11 @@ + + + 76561198071098415 + 0 + + + 3366634953 + Steam + + + \ No newline at end of file diff --git a/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav b/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav new file mode 100644 index 00000000..938ff2cc Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav differ diff --git a/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav.old b/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav.old new file mode 100644 index 00000000..cb3dca36 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/cbar_hit1A.wav.old differ diff --git a/DeltaVEquipment/Audio/Arcade/cn.wav b/DeltaVEquipment/Audio/Arcade/cn.wav new file mode 100644 index 00000000..66971b58 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/cn.wav differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstorm.wav b/DeltaVEquipment/Audio/Arcade/metalstorm.wav new file mode 100644 index 00000000..33970b31 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstorm.wav differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstorm.wav.9mmorsomething b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.9mmorsomething new file mode 100644 index 00000000..58527ba1 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.9mmorsomething differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstorm.wav.OLD b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.OLD new file mode 100644 index 00000000..f9231c27 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.OLD differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstorm.wav.alternate b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.alternate new file mode 100644 index 00000000..ad599a37 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstorm.wav.alternate differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstorm.wav2 b/DeltaVEquipment/Audio/Arcade/metalstorm.wav2 new file mode 100644 index 00000000..bc7761ff Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstorm.wav2 differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstormdistance.wav b/DeltaVEquipment/Audio/Arcade/metalstormdistance.wav new file mode 100644 index 00000000..434b19e1 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstormdistance.wav differ diff --git a/DeltaVEquipment/Audio/Arcade/metalstormreload.wav b/DeltaVEquipment/Audio/Arcade/metalstormreload.wav new file mode 100644 index 00000000..9d3987d1 Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/metalstormreload.wav differ diff --git a/DeltaVEquipment/Audio/Arcade/shieldhit1.wav b/DeltaVEquipment/Audio/Arcade/shieldhit1.wav new file mode 100644 index 00000000..2f1bc3ab Binary files /dev/null and b/DeltaVEquipment/Audio/Arcade/shieldhit1.wav differ diff --git a/DeltaVEquipment/Audio/BALLER/ballercomplete.wav b/DeltaVEquipment/Audio/BALLER/ballercomplete.wav new file mode 100644 index 00000000..36afcdee Binary files /dev/null and b/DeltaVEquipment/Audio/BALLER/ballercomplete.wav differ diff --git a/DeltaVEquipment/Audio/BALLER/dodgeball.wav b/DeltaVEquipment/Audio/BALLER/dodgeball.wav new file mode 100644 index 00000000..38a635b0 Binary files /dev/null and b/DeltaVEquipment/Audio/BALLER/dodgeball.wav differ diff --git a/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit1.wav b/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit1.wav new file mode 100644 index 00000000..a249214b Binary files /dev/null and b/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit1.wav differ diff --git a/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit2.wav b/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit2.wav new file mode 100644 index 00000000..9ec209b0 Binary files /dev/null and b/DeltaVEquipment/Audio/Crunch/HeavyCannonDistantHit2.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/ObviousFlyby.wav b/DeltaVEquipment/Audio/Nuke/ObviousFlyby.wav new file mode 100644 index 00000000..d8d482e2 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/ObviousFlyby.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/SLAMCloseNuked.wav b/DeltaVEquipment/Audio/Nuke/SLAMCloseNuked.wav new file mode 100644 index 00000000..389800c4 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/SLAMCloseNuked.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/SLAMFarNuked.wav b/DeltaVEquipment/Audio/Nuke/SLAMFarNuked.wav new file mode 100644 index 00000000..157ae5df Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/SLAMFarNuked.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/SLAMTorpMissileImpact.wav b/DeltaVEquipment/Audio/Nuke/SLAMTorpMissileImpact.wav new file mode 100644 index 00000000..bd3dab71 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/SLAMTorpMissileImpact.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/SLAMcraft_launch_01.wav b/DeltaVEquipment/Audio/Nuke/SLAMcraft_launch_01.wav new file mode 100644 index 00000000..ee7f7cfa Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/SLAMcraft_launch_01.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/pipeimpact.wav b/DeltaVEquipment/Audio/Nuke/pipeimpact.wav new file mode 100644 index 00000000..2c80b2a4 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/pipeimpact.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/slamoffshoottravel.wav b/DeltaVEquipment/Audio/Nuke/slamoffshoottravel.wav new file mode 100644 index 00000000..dc5438a5 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/slamoffshoottravel.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/slamtravsoundmeme2.wav b/DeltaVEquipment/Audio/Nuke/slamtravsoundmeme2.wav new file mode 100644 index 00000000..3fec1062 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/slamtravsoundmeme2.wav differ diff --git a/DeltaVEquipment/Audio/Nuke/slamtravsoundmemefinal.wav b/DeltaVEquipment/Audio/Nuke/slamtravsoundmemefinal.wav new file mode 100644 index 00000000..c93b9fc6 Binary files /dev/null and b/DeltaVEquipment/Audio/Nuke/slamtravsoundmemefinal.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/150mmArtillery_SHOT.wav b/DeltaVEquipment/Audio/Realistic/150mmArtillery_SHOT.wav new file mode 100644 index 00000000..dda69317 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/150mmArtillery_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/20mmPDG_LOOP.wav b/DeltaVEquipment/Audio/Realistic/20mmPDG_LOOP.wav new file mode 100644 index 00000000..23e18297 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/20mmPDG_LOOP.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/220mmMissile_SHOT.wav b/DeltaVEquipment/Audio/Realistic/220mmMissile_SHOT.wav new file mode 100644 index 00000000..3f1a131f Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/220mmMissile_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/25mmRotaryGun_SHOT.wav b/DeltaVEquipment/Audio/Realistic/25mmRotaryGun_SHOT.wav new file mode 100644 index 00000000..092db873 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/25mmRotaryGun_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/300mmFlak_SHOT.wav b/DeltaVEquipment/Audio/Realistic/300mmFlak_SHOT.wav new file mode 100644 index 00000000..2c48faf0 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/300mmFlak_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/30mmChaingun_SHOT.wav b/DeltaVEquipment/Audio/Realistic/30mmChaingun_SHOT.wav new file mode 100644 index 00000000..0a23a15f Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/30mmChaingun_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/30mmStorm_SHOT.wav b/DeltaVEquipment/Audio/Realistic/30mmStorm_SHOT.wav new file mode 100644 index 00000000..d60221f9 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/30mmStorm_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/400mm_SHOT.wav b/DeltaVEquipment/Audio/Realistic/400mm_SHOT.wav new file mode 100644 index 00000000..e9a6c060 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/400mm_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/BadIdea1.wav b/DeltaVEquipment/Audio/Realistic/BadIdea1.wav new file mode 100644 index 00000000..1a2d6533 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/BadIdea1.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/FOB1000_SHOT.wav b/DeltaVEquipment/Audio/Realistic/FOB1000_SHOT.wav new file mode 100644 index 00000000..6a30ed70 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/FOB1000_SHOT.wav differ diff --git a/DeltaVEquipment/Audio/Realistic/FOB1500_SHOT.wav b/DeltaVEquipment/Audio/Realistic/FOB1500_SHOT.wav new file mode 100644 index 00000000..3773dae4 Binary files /dev/null and b/DeltaVEquipment/Audio/Realistic/FOB1500_SHOT.wav differ diff --git a/DeltaVEquipment/Data/Audio_MS.sbc b/DeltaVEquipment/Data/Audio_MS.sbc new file mode 100644 index 00000000..778e62c8 --- /dev/null +++ b/DeltaVEquipment/Data/Audio_MS.sbc @@ -0,0 +1,123 @@ + + + + + + + + MyObjectBuilder_AudioDefinition + metalstorm + + SHOT + 0.1 + 2100 + 0 + + + + HeavyFight + 15 + false + 32 + 0.1 + 5 + + + Audio\Arcade\metalstorm.wav + + + + + + + + MyObjectBuilder_AudioDefinition + metalstormdistant + + SHOT + 0.1 + 2100 + 0 + HeavyFight + 15 + false + 32 + 0.1 + 5 + + + Audio\Arcade\metalstorm.wav + + + + + + + + MyObjectBuilder_AudioDefinition + cbar + + SHOT + 0.1 + 1000 + 0 + HeavyFight + 15 + false + 0.1 + 5 + + + Audio\Arcade\cbar_hit1A.wav + + + + + + + + MyObjectBuilder_AudioDefinition + shit + + SHOT + 0.1 + 1000 + 0 + HeavyFight + 15 + false + 0.1 + 5 + + + Audio\Arcade\shieldhit1.wav + + + + + + + + MyObjectBuilder_AudioDefinition + metalstormreload + + SHOT + 0.1 + 250 + 10 + 0 + HeavyFight + 15 + false + 1 + 5 + + + Audio\Arcade\metalstormreload.wav + + + + + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningLaserTurret.sbc b/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningLaserTurret.sbc new file mode 100644 index 00000000..7c9adcd5 --- /dev/null +++ b/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningLaserTurret.sbc @@ -0,0 +1,52 @@ + + + + + + ConveyorSorter + DeltaV_MiningLaserTurret + + DeltaV_MiningLaserTurret + Textures\GUI\Icons\AstronautBackpack.dds + + Le Beam + + Large + TriangleMesh + +
+ + Models\Cubes\large\DeltaV_MiningLaserTurret.mwm + + + + + + + + + + + + + + + + + + + + + + DeltaV_MiningLaserTurret + Light + 80 + 1 + 213 + ParticleWeapExpl + Defense + Default + 750 + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningMetalStorm.sbc b/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningMetalStorm.sbc new file mode 100644 index 00000000..1452e98e --- /dev/null +++ b/DeltaVEquipment/Data/CubeBlocks/DeltaV_MiningMetalStorm.sbc @@ -0,0 +1,52 @@ + + + + + + ConveyorSorter + DeltaV_MiningMetalStorm + + DeltaV_MiningMetalStorm + Textures\GUI\Icons\AstronautBackpack.dds + + METAL STOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOORM + + Large + TriangleMesh + +
+ + Models\Cubes\large\DeltaV_MiningMetalStorm.mwm + + + + + + + + + + + + + + + + + + + + + + DeltaV_MiningMetalStorm + Light + 80 + 1 + 213 + ParticleWeapExpl + Defense + Default + 750 + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/CubeBlocks/FixedLaser.sbc b/DeltaVEquipment/Data/CubeBlocks/FixedLaser.sbc new file mode 100644 index 00000000..d1bb13aa --- /dev/null +++ b/DeltaVEquipment/Data/CubeBlocks/FixedLaser.sbc @@ -0,0 +1,24 @@ + + + + + + ConveyorSorter + FixedLaser + + Fixed Laser + Fixed Laser + Textures\GUI\Icons\Cubes\FixedLaser.dds + Large + TriangleMesh + + + Models\FixedLaser.mwm + + + + + FixedLaser + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/CubeBlocks/FixedMicrowave.sbc b/DeltaVEquipment/Data/CubeBlocks/FixedMicrowave.sbc new file mode 100644 index 00000000..9bd89122 --- /dev/null +++ b/DeltaVEquipment/Data/CubeBlocks/FixedMicrowave.sbc @@ -0,0 +1,121 @@ + + + + + + SmallMissileLauncher + FixedMicrowave + + Microwave Cannon + Textures\GUI\Icons\AstronautBackpack.dds + Shoots your friendly neighbourhood APFSDSFSDSFSDS + Large + false + TriangleMesh + + + Models\FixedMicrowave.mwm + + + + + + + + +
+ 6 + Light + + Defense + 0.006 + Damage_WeapExpl_Damaged + ParticleWeapExpl + BlockDestroyedExplosion_Small + WepSmallWarheadExpl + + + 150 + + + + + + WeaponDefinition + MicrowaveWep + + + 0 + 0 + WepPlayRifleNoAmmo + + + + 4000 + + + + + + + + + AmmoMagazine + MicrowaveMag + + Ammo Microwave + Textures\Icons\Magazines\GrenadeCrate.png + + 0.25 + 0.2 + 0.2 + 999999 + 60 + 30 + Models\Magazines\GrenadeCrate.mwm + Ammo + 1 + + 100 + 1000 + 100 + 2000 + true + + + + + + AmmoDefinition + MicrowaveAmmo + + + 1000 + 0 + 4000 + true + 15000 + LargeShell + 1.2 + CauseOfDeath_HeavyWeapons + + + 1000 + 3 + Models\Weapons\LargeCalibreShell.mwm + 0 + 1000 + false + 0 + 4000 + LargeCalibreGun_Tracer + true + 50 + 80 + 0.25 + 0.85 + 3000 + + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/CubeBlocks/FixedRailgun.sbc b/DeltaVEquipment/Data/CubeBlocks/FixedRailgun.sbc new file mode 100644 index 00000000..4384acf1 --- /dev/null +++ b/DeltaVEquipment/Data/CubeBlocks/FixedRailgun.sbc @@ -0,0 +1,53 @@ + + + + + + ConveyorSorter + FixedRailgun + + Railgun Cannon + Textures\GUI\Icons\Cubes\SC_AR_Nimrod.dds + Shoots your friendly neighbourhood APFSDSFSDSFSDS + Large + TriangleMesh + +
+ + Models\FixedRailgun.mwm + + + + + + + + + + + + + + + + + + + + + + + + + SC_AR_Nimrod + Light + 80 + 1 + 213 + ParticleWeapExpl + Defense + Default + 750 + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/DeltaV_RailgunParticle.sbc b/DeltaVEquipment/Data/DeltaV_RailgunParticle.sbc new file mode 100644 index 00000000..6c5ef7fd --- /dev/null +++ b/DeltaVEquipment/Data/DeltaV_RailgunParticle.sbc @@ -0,0 +1,449 @@ + + + + + + 1669879675 + 5 + 0 + false + false + 0 + 5 + 0 + + + GPU + + + + 32 + 16 + 0 + + + + 448 + + + 32 + + + + + + + + + + + 0 + 0.5 + 1 + 1 + + + + + + + 0 + 0.5 + 1 + 1 + + + + + + + 0 + 0.5 + 1 + 1 + + + + + + + 0 + 0.5 + 1 + 1 + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + + + 0.5 + + + + + + + 0 + 0 + 0 + + + + + + + + + + 0 + + + + + + + 0 + 0 + -1 + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 0 + + + + + + + + + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + + + + + 0 + + + + + + + + + + 5 + + + + + 5 + + + + + 5 + + + + + 5 + + + + + + + + + 0.5 + + + 1 + + + 2.28 + + + 0.015 + + + true + + + + + + 200 + + + + + + Atlas_D_01 + + + 1 + + + false + + + false + + + false + + + false + + + 1 + + + 0 + + + + 0 + 0 + 0 + + + + 0 + + + 0 + + + true + + + 0.75 + + + 0 + + + true + + + 0 + + + + 90 + 0 + 0 + + + + + 0 + 0 + 0 + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + + + + + + 0 + + + + + + + + + + 10 + + + + + 10 + + + + + 10 + + + + + 10 + + + + + + + + + 5 + + + false + + + true + + + 1 + + + 0.1 + + + 1 + + + 0 + + + 0 + + + true + + + 0 + + + + + + 8000 + 1 + + + \ No newline at end of file diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionCollector.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionCollector.cs new file mode 100644 index 00000000..95348722 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionCollector.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using DeltaVEquipment.ProjectileBases; +using DeltaVEquipment.WeaponBases; +using ProtoBuf; +using Sandbox.ModAPI; + +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + internal DefinitionContainer Container = new DefinitionContainer(); + + internal void LoadWeaponDefinitions(params WeaponDefinitionBase[] defs) + { + List serialWeapons = new List(); + foreach (var weapon in defs) + serialWeapons.Add(MyAPIGateway.Utilities.SerializeToBinary(weapon)); + Container.SerializedWeaponDefs = serialWeapons.ToArray(); + Container.WeaponDefs = defs; + } + internal void LoadAmmoDefinitions(params ProjectileDefinitionBase[] defs) + { + List serialAmmos = new List(); + foreach (var ammo in defs) + serialAmmos.Add(MyAPIGateway.Utilities.SerializeToBinary(ammo)); + Container.SerializedAmmoDefs = serialAmmos.ToArray(); + Container.AmmoDefs = defs; + } + + /// + /// Load all definitions for DefinitionSender + /// + /// + internal static DefinitionContainer GetBaseDefinitions() + { + return new HeartDefinitions().Container; + } + } + + [ProtoContract] + internal class DefinitionContainer + { + [ProtoMember(1)] + public byte[][] SerializedWeaponDefs { get; set; } + [ProtoMember(2)] + public byte[][] SerializedAmmoDefs { get; set; } + + public WeaponDefinitionBase[] WeaponDefs { get; set; } + public ProjectileDefinitionBase[] AmmoDefs { get; set; } + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionSender.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionSender.cs new file mode 100644 index 00000000..61a60980 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/DefinitionSender.cs @@ -0,0 +1,53 @@ +using Sandbox.ModAPI; +using VRage.Game.Components; +using VRage.Utils; + +namespace DeltaVEquipment +{ + [MySessionComponentDescriptor(MyUpdateOrder.BeforeSimulation, Priority = int.MaxValue)] + internal class DefinitionSender : MySessionComponentBase + { + const int DefinitionMessageId = 8643; + + byte[] serializedStorage; + DefinitionContainer storedDef = null; + + public override void LoadData() + { + //if (!MyAPIGateway.Session.IsServer) + // return; + HeartApi.LoadData(ModContext, InitAndSendDefinitions); // Doing it this way because we don't get async stuff :( + + MyAPIGateway.Utilities.RegisterMessageHandler(DefinitionMessageId, InputHandler); + } + + private void InitAndSendDefinitions() + { + storedDef = HeartDefinitions.GetBaseDefinitions(); + serializedStorage = MyAPIGateway.Utilities.SerializeToBinary(storedDef); + HeartApi.LogWriteLine($"Packaged definitions & preparing to send."); + + MyAPIGateway.Utilities.SendModMessage(DefinitionMessageId, serializedStorage); + foreach (var def in storedDef.AmmoDefs) + def.LiveMethods.RegisterMethods(def.Name); + HeartApi.LogWriteLine($"Sent definitions & returning to sleep."); + } + + private void InputHandler(object o) + { + if (o is bool && (bool)o && storedDef != null) + { + MyAPIGateway.Utilities.SendModMessage(DefinitionMessageId, serializedStorage); + foreach (var def in storedDef.AmmoDefs) + def.LiveMethods.RegisterMethods(def.Name); + MyLog.Default.WriteLineAndConsole($"OrreryDefinition [{ModContext.ModName}]: Sent definitions & returning to sleep."); + } + } + + protected override void UnloadData() + { + HeartApi.UnloadData(); + MyAPIGateway.Utilities.UnregisterMessageHandler(DefinitionMessageId, InputHandler); + } + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/HeartApi.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/HeartApi.cs new file mode 100644 index 00000000..0a2abd11 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/HeartApi.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using DeltaVEquipment.ProjectileBases; +using DeltaVEquipment.WeaponBases; +using Sandbox.ModAPI; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRage.Utils; +using VRageMath; + +namespace DeltaVEquipment +{ + public class HeartApi + { + public static HeartApi I; + + #region API Loading + private const long HeartApiChannel = 8644; // https://xkcd.com/221/ + private Dictionary methodMap; + private Action onLoad; + private IMyModContext modContext; + + public static void LoadData(IMyModContext modContext, Action onLoad = null) + { + if (I != null && Inited) + return; + I = new HeartApi + { + onLoad = onLoad, + modContext = modContext + }; + MyAPIGateway.Utilities.RegisterMessageHandler(HeartApiChannel, I.RecieveApiMethods); + MyAPIGateway.Utilities.SendModMessage(HeartApiChannel, true); + MyLog.Default.WriteLineAndConsole("Orrery Combat Framework: HeartAPI awaiting methods."); + } + + public static void UnloadData() + { + if (I == null) + return; + + MyAPIGateway.Utilities.UnregisterMessageHandler(HeartApiChannel, I.RecieveApiMethods); + I = null; + } + + private void RecieveApiMethods(object data) + { + try + { + if (data == null) + return; + + if (!Inited && data is Dictionary) + { + methodMap = (Dictionary)data; + + // Standard + SetApiMethod("LogWriteLine", ref logWriteLine); + SetApiMethod("GetNetworkLoad", ref getNetworkLoad); + + // Projectile LiveMethods + SetApiMethod("AddOnProjectileSpawn", ref addOnProjectileSpawn); + SetApiMethod("AddOnProjectileImpact", ref addOnImpact); + SetApiMethod("AddOnEndOfLife", ref addOnEndOfLife); + //SetApiMethod("AddOnGuidanceStage", ref addOnGuidanceStage); // TODO: Cannot pass type Guidance or type ProjectileDefinitionBase! + + // Projectile Generics + SetApiMethod("GetProjectileDefinitionId", ref getProjectileDefinitionId); + SetApiMethod("GetProjectileDefinition", ref getProjectileDefinition); + SetApiMethod("RegisterProjectileDefinition", ref registerProjectileDefinition); + SetApiMethod("UpdateProjectileDefinition", ref updateProjectileDefinition); + SetApiMethod("RemoveProjectileDefinition", ref removeProjectileDefinition); + SetApiMethod("SpawnProjectile", ref spawnProjectile); + SetApiMethod("GetProjectileInfo", ref getProjectileInfo); + SetApiMethod("GetProjectileDefinitionId", ref getProjectileDefinitionId); + + // Weapon Generics + SetApiMethod("BlockHasWeapon", ref blockHasWeapon); + SetApiMethod("SubtypeHasDefinition", ref subtypeHasDefinition); + SetApiMethod("GetWeaponDefinitions", ref getWeaponDefinitions); + SetApiMethod("GetWeaponDefinition", ref getWeaponDefinition); + SetApiMethod("RegisterWeaponDefinition", ref registerWeaponDefinition); + SetApiMethod("UpdateWeaponDefinition", ref updateWeaponDefinition); + SetApiMethod("RemoveWeaponDefinition", ref removeWeaponDefinition); + + + Inited = true; + LogWriteLine($"HeartAPI inited."); + onLoad?.Invoke(); + } + } + catch (Exception ex) + { + MyLog.Default.WriteLineAndConsole($"Orrery Combat Framework: [{modContext.ModName}] ERR: Failed to init HeartAPI! {ex}"); + logWriteLine?.Invoke($"ERR: Failed to init HeartAPI! {ex}"); + } + + methodMap = null; + } + + private void SetApiMethod(string name, ref T method) where T : class + { + if (!methodMap.ContainsKey(name)) + throw new Exception("Method Map does not contain method " + name); + Delegate del = methodMap[name]; + if (del.GetType() != typeof(T)) + throw new Exception($"Method {name} type mismatch! [MapMethod: {del.GetType().Name} | ApiMethod: {typeof(T).Name}]"); + method = methodMap[name] as T; + } + + #endregion + + public static bool Inited = false; + + #region Projectiles + + private Action> addOnProjectileSpawn; + /// + /// Adds an action triggered on projectile spawn. + /// + /// + /// + public static void AddOnProjectileSpawn(string projectileDefinition, Action onSpawn) => I?.addOnProjectileSpawn?.Invoke(projectileDefinition, onSpawn); + + private Action> addOnImpact; + /// + /// Adds an action triggered on projectile impact. + /// + /// + /// + public static void AddOnImpact(string projectileDefinition, Action onImpact) => I?.addOnImpact?.Invoke(projectileDefinition, onImpact); + + private Action> addOnEndOfLife; + /// + /// Adds an action triggered on projectile end-of-life. + /// + public static void AddOnEndOfLife(string projectileDefinition, Action onEndOfLife) => I?.addOnEndOfLife?.Invoke(projectileDefinition, onEndOfLife); + + //private Action> addOnGuidanceStage; + /// + /// Adds an action triggered when a projectile's guidance stages. + /// + //public static void AddOnGuidanceStage(string projectileDefinition, Action onStage) => I?.addOnGuidanceStage?.Invoke(projectileDefinition, onStage); + + + private Func getProjectileDefinitionId; + public static int GetProjectileDefinitionId(string projectileName) => I?.getProjectileDefinitionId?.Invoke(projectileName) ?? -1; + + private Func getProjectileDefinition; + public static ProjectileDefinitionBase GetProjectileDefinition(int projectileDefId) + { + byte[] serialized = I?.getProjectileDefinition?.Invoke(projectileDefId); + if (serialized == null) + return null; + return MyAPIGateway.Utilities.SerializeFromBinary(serialized); + } + + private Func registerProjectileDefinition; + public static int RegisterProjectileDefinition(ProjectileDefinitionBase definition) => I?.registerProjectileDefinition?.Invoke(MyAPIGateway.Utilities.SerializeToBinary(definition)) ?? -1; + + private Func updateProjectileDefinition; + public static bool UpdateProjectileDefinition(int definitionId, ProjectileDefinitionBase definition) => I?.updateProjectileDefinition?.Invoke(definitionId, MyAPIGateway.Utilities.SerializeToBinary(definition)) ?? false; + + private Action removeProjectileDefinition; + public static void RemoveProjectileDefinition(int definitionId) => I?.removeProjectileDefinition?.Invoke(definitionId); + + private Func spawnProjectile; + public static uint SpawnProjectile(int definitionId, Vector3D position, Vector3D direction, long firerId, Vector3D initialVelocity) => I?.spawnProjectile?.Invoke(definitionId, position, direction, firerId, initialVelocity) ?? uint.MaxValue; + + + + public static List SpawnProjectilesInCone(int definitionId, Vector3D position, Vector3D direction, int count, double angleRads) + { + List spawned = new List(); + Random random = new Random(); + for (int i = 0; i < count; i++) + spawned.Add(SpawnProjectile(definitionId, position, direction.Rotate(Vector3D.CalculatePerpendicularVector(direction).Rotate(direction, Math.PI * 2 * random.NextDouble()), angleRads * random.NextDouble()), 0, Vector3D.Zero)); + + return spawned; + } + + private Func getProjectileInfo; + /// + /// DetailLevel 0 = full + /// DetailLevel 1 = Position, Direction, Id, IsActive, Timestamp + /// DetailLevel 2 = Id, IsActive, Timestamp + /// + /// + /// + /// + public static NSerializableProjectile GetProjectileInfo(uint projectileId, int detailLevel) + { + byte[] serialized = I?.getProjectileInfo?.Invoke(projectileId, detailLevel); + if (serialized == null) + return null; + return MyAPIGateway.Utilities.SerializeFromBinary(serialized); + } + + #endregion + + #region Weapons + + private Func blockHasWeapon; + public static bool BlockHasWeapon(MyEntity block) => I?.blockHasWeapon?.Invoke(block) ?? false; + + private Func subtypeHasDefinition; + public static bool SubtypeHasDefinition(string subtype) => I?.subtypeHasDefinition?.Invoke(subtype) ?? false; + + private Func getWeaponDefinitions; + public static string[] GetWeaponDefinitions() => I?.getWeaponDefinitions?.Invoke(); + + private Func getWeaponDefinition; + public static WeaponDefinitionBase GetWeaponDefinition(string subtype) + { + byte[] serialized = I?.getWeaponDefinition?.Invoke(subtype); + if (serialized == null || serialized.Length == 0) + return null; + + return MyAPIGateway.Utilities.SerializeFromBinary(serialized); + } + + private Func registerWeaponDefinition; + public static bool RegisterWeaponDefinition(WeaponDefinitionBase definition) + { + if (definition == null) + return false; + + byte[] serialized = MyAPIGateway.Utilities.SerializeToBinary(definition); + + if (serialized == null || serialized.Length == 0) + return false; + + return I?.registerWeaponDefinition?.Invoke(serialized) ?? false; + } + + private Func updateWeaponDefinition; + public static bool UpdateWeaponDefinition(WeaponDefinitionBase definition) + { + if (definition == null) + return false; + + byte[] serialized = MyAPIGateway.Utilities.SerializeToBinary(definition); + + if (serialized == null || serialized.Length == 0) + return false; + + return I?.updateWeaponDefinition?.Invoke(serialized) ?? false; + } + + private Action removeWeaponDefinition; + public static void RemoveWeaponDefinition(string definitionType) => I?.removeWeaponDefinition?.Invoke(definitionType); + + #endregion + + #region Standard + + private Action logWriteLine; + /// + /// Prints a line to the HeartModule log. + /// + /// + public static void LogWriteLine(string text) => I?.logWriteLine?.Invoke($"[{I.modContext.ModName}] {text}"); + + private Func getNetworkLoad; + /// + /// Returns current Orrery network load, in bytes per second. + /// + /// + public static int GetNetworkLoad() => I?.getNetworkLoad?.Invoke() ?? -1; + + #endregion + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/ProjectileDefinitionBase.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/ProjectileDefinitionBase.cs new file mode 100644 index 00000000..f7de8423 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/ProjectileDefinitionBase.cs @@ -0,0 +1,225 @@ +using System; +using ProtoBuf; +using Sandbox.Game.Entities; +using VRage.Game.Entity; +using VRage.Utils; +using VRageMath; + +namespace DeltaVEquipment.ProjectileBases +{ + /// + /// Standard serializable projectile definition. + /// + [ProtoContract] + public class ProjectileDefinitionBase + { + public ProjectileDefinitionBase() { } + + [ProtoMember(1)] public string Name = ""; + [ProtoMember(2)] public Ungrouped Ungrouped; + [ProtoMember(3)] public Damage Damage; + [ProtoMember(4)] public PhysicalProjectile PhysicalProjectile; + [ProtoMember(5)] public Visual Visual; + [ProtoMember(6)] public Audio Audio; + [ProtoMember(7)] public Guidance[] Guidance = new Guidance[0]; + [ProtoMember(8)] public Networking Networking; + [ProtoIgnore] public LiveMethods LiveMethods = new LiveMethods(); + } + + [ProtoContract] + public struct Ungrouped + { + /// + /// Power draw during reload, in MW + /// + [ProtoMember(1)] public float ReloadPowerUsage; // TODO + /// + /// Recoil of projectile, in Newtons + /// + [ProtoMember(2)] public int Recoil; + /// + /// Impulse of projectile, in Newtons + /// + [ProtoMember(3)] public int Impulse; + /// + /// Number of shots in single reload. + /// + [ProtoMember(4)] public int ShotsPerMagazine; + /// + /// The item that needs to get consumed for the magazine to reload. Leave blank to not consume anything. The weapon model should probably have a conveyor port. + /// + [ProtoMember(5)] public string MagazineItemToConsume; + /// + /// The order in which projectiles are synced. + /// + [ProtoMember(6)] public ushort SyncPriority; + } + + [ProtoContract] + public struct Networking + { + /// + /// The networking mode of the projectile. + /// + [ProtoMember(1)] public NetworkingModeEnum NetworkingMode; + /// + /// Set this to true if the projectile should constantly be updated over the network. + /// + [ProtoMember(2)] public bool DoConstantSync; + /// + /// Higher numbers take precedence over lower ones. + /// + [ProtoMember(3)] public ushort NetworkPriority; + + public enum NetworkingModeEnum + { + /// + /// Projectiles are not synced between server and client. Use this for hitscans. + /// + NoNetworking, + /// + /// Projectiles are synced in a 'light' manner. This should be your default. + /// + FireEvent, + /// + /// Projectiles are hard-synced between server and client. Use this for projectiles that *have* to be accurate. + /// + FullSync + } + } + + [ProtoContract] + public struct Damage + { + [ProtoMember(1)] public float SlimBlockDamageMod; + [ProtoMember(2)] public float FatBlockDamageMod; + [ProtoMember(3)] public float BaseDamage; + [ProtoMember(4)] public float AreaDamage; + [ProtoMember(5)] public float DamageToProjectiles; + [ProtoMember(6)] public int MaxImpacts; + [ProtoMember(7)] public float AreaRadius; + [ProtoMember(8)] public float DamageToProjectilesRadius; + } + + /// + /// Projectile information for non-hitscan projectiles. + /// + [ProtoContract] + public struct PhysicalProjectile + { + [ProtoMember(1)] public float Velocity; + [ProtoMember(2)] public float Acceleration; + [ProtoMember(3)] public float Health; // TODO // <= 0 for un-targetable + /// + /// Max range of projectile, relative to first firing. For hitscans, max hitscan length. + /// + [ProtoMember(4)] public float MaxTrajectory; + [ProtoMember(5)] public float MaxLifetime; + /// + /// Disables velocity updates, and changes several behaviors. Call (Projectile).UpdateBeam() to recycle and lower performance impact. + /// + [ProtoMember(6)] public bool IsHitscan; + /// + /// The size of the projectile in meters. Used for point defense hit checking. + /// + [ProtoMember(7)] public float ProjectileSize; + [ProtoMember(8)] public float VelocityVariance; + /// + /// How much the weapon's ShotInaccuracy will be multiplied by for this ammo. 0 to ignore. + /// + [ProtoMember(9)] public float AccuracyVarianceMultiplier; + [ProtoMember(10)] public float GravityInfluenceMultiplier; + } + + [ProtoContract] + public struct Visual + { + [ProtoMember(1)] public string Model; + [ProtoMember(2)] public MyStringId TrailTexture; + [ProtoMember(7)] public float TrailLength; + [ProtoMember(9)] public float TrailWidth; + [ProtoMember(8)] public Vector4 TrailColor; + [ProtoMember(3)] public float TrailFadeTime; + [ProtoMember(4)] public string AttachedParticle; + [ProtoMember(5)] public string ImpactParticle; + [ProtoMember(6)] public float VisibleChance; + public bool HasModel => !Model?.Equals("") ?? false; + public bool HasTrail => TrailTexture != null && TrailLength > 0 && TrailWidth > 0 && TrailColor != null && TrailColor != Vector4.Zero; + public bool HasAttachedParticle => !AttachedParticle?.Equals("") ?? false; + public bool HasImpactParticle => !ImpactParticle?.Equals("") ?? false; + } + + [ProtoContract] + public struct Audio + { + [ProtoMember(1)] public string TravelSound; + [ProtoMember(2)] public float TravelMaxDistance; + [ProtoMember(3)] public float TravelVolume; + [ProtoMember(4)] public string ImpactSound; + [ProtoMember(5)] public float SoundChance; + + public bool HasTravelSound => !TravelSound?.Equals("") ?? false && SoundChance > 0 && TravelMaxDistance > 0 && TravelVolume > 0; + public bool HasImpactSound => !ImpactSound?.Equals("") ?? false && SoundChance > 0; + public MySoundPair TravelSoundPair => new MySoundPair(TravelSound); + public MySoundPair ImpactSoundPair => new MySoundPair(ImpactSound); + } + + [ProtoContract] + public struct Guidance + { + [ProtoMember(1)] public float TriggerTime; + [ProtoMember(2)] public float ActiveDuration; // Ignore if -1 or greater than next + [ProtoMember(3)] public bool UseAimPrediction; + [ProtoMember(4)] public float MaxTurnRate; + [ProtoMember(6)] public IffEnum Iff; // 1 is TargetSelf, 2 is TargetEnemies, 4 is TargetFriendlies + [ProtoMember(7)] public bool DoRaycast; + [ProtoMember(8)] public float CastCone; + [ProtoMember(9)] public float CastDistance; + [ProtoMember(10)] public float Velocity; + /// + /// Random offset from target, in meters. + /// + [ProtoMember(11)] public float Inaccuracy; + /// + /// Maximum G-force the projectile can sustain. + /// + [ProtoMember(12)] public float MaxGs; + [ProtoMember(13)] public DefinitionPid? Pid; + } + + [ProtoContract] + public struct DefinitionPid + { + /// + /// Direct response to error + /// + [ProtoMember(1)] public float KProportional; + /// + /// Response to historical error + /// + [ProtoMember(2)] public float KIntegral; + /// + /// Damping factor + /// + [ProtoMember(3)] public float KDerivative; + } + + public class LiveMethods // TODO: OnGuidanceStage && DistanceToTarget + { + public void RegisterMethods(string definitionName) + { + if (!HeartApi.Inited) + throw new Exception("HeartAPI has not inited yet!"); + HeartApi.AddOnProjectileSpawn(definitionName, OnSpawn); + HeartApi.AddOnEndOfLife(definitionName, OnEndOfLife); + HeartApi.AddOnImpact(definitionName, OnImpact); + //HeartApi.AddOnGuidanceStage(definitionName, OnGuidanceStage); + HeartApi.LogWriteLine("Registered LiveMethods on projectile " + definitionName); + } + + public Action OnSpawn; + public Action OnImpact; + public Action OnEndOfLife; + //public Action OnGuidanceStage; + } +} \ No newline at end of file diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/n_SerializableProjectile.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/n_SerializableProjectile.cs new file mode 100644 index 00000000..3096b7b2 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/ProjectileBases/n_SerializableProjectile.cs @@ -0,0 +1,27 @@ +using ProtoBuf; +using VRageMath; + +namespace DeltaVEquipment.ProjectileBases +{ + /// + /// Used for syncing between server and clients, and in the API. + /// + [ProtoContract] + public class NSerializableProjectile + { + // ProtoMember IDs are high to avoid collisions + [ProtoMember(22)] public bool IsActive = true; + [ProtoMember(23)] public uint Id; + [ProtoMember(211)] public long Timestamp; + + // All non-required values are nullable + [ProtoMember(24)] public int? DefinitionId; + [ProtoMember(25)] public Vector3D? Position; + [ProtoMember(26)] public Vector3? Direction; + [ProtoMember(27)] public Vector3? InheritedVelocity; + [ProtoMember(28)] public float? Velocity; + //[ProtoMember(29)] public int? RemainingImpacts; + //[ProtoMember(210)] public Dictionary OverridenValues; + [ProtoMember(212)] public long? Firer; + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/TargetingEnums.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/TargetingEnums.cs new file mode 100644 index 00000000..0d55195d --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/TargetingEnums.cs @@ -0,0 +1,25 @@ +using System; + +namespace DeltaVEquipment +{ + [Flags] + public enum IffEnum + { + None = 0, + TargetSelf = 1, + TargetEnemies = 2, + TargetFriendlies = 4, + TargetNeutrals = 8, + TargetUnique = 16, + } + + [Flags] + public enum TargetTypeEnum + { + None = 0, + TargetGrids = 1, + TargetProjectiles = 2, + TargetCharacters = 4, + TargetUnique = 8, + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/WeaponBases/WeaponDefinitionBase.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/WeaponBases/WeaponDefinitionBase.cs new file mode 100644 index 00000000..27a669db --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/Communication/WeaponBases/WeaponDefinitionBase.cs @@ -0,0 +1,134 @@ +using System; +using ProtoBuf; + +namespace DeltaVEquipment.WeaponBases +{ + /// + /// Standard serializable weapon definition. Add onto definition base using the partial modifier. + /// + [ProtoContract] + public partial class WeaponDefinitionBase + { + public WeaponDefinitionBase() { } + + [ProtoMember(2)] public Targeting Targeting; + [ProtoMember(3)] public Assignments Assignments; + [ProtoMember(4)] public Hardpoint Hardpoint; + [ProtoMember(5)] public Loading Loading; + [ProtoMember(6)] public Audio Audio; + [ProtoMember(7)] public Visuals Visuals; + } + + [ProtoContract] + public struct Targeting + { + /// + /// The furthest target a turret can shoot. + /// + [ProtoMember(1)] public float MaxTargetingRange; + /// + /// The closest target a turret can shoot. + /// + [ProtoMember(2)] public float MinTargetingRange; + /// + /// Can the turret fire by itself? Tracks regardless. + /// + [ProtoMember(3)] public bool CanAutoShoot; + [ProtoMember(4)] public IffEnum DefaultIff; + [ProtoMember(5)] public TargetTypeEnum AllowedTargetTypes; + /// + /// Time until the turret is forced to find a new target + /// + [ProtoMember(6)] public float RetargetTime; // TODO + [ProtoMember(7)] public float AimTolerance; + } + + [ProtoContract] + public struct Assignments + { + [ProtoMember(1)] public string BlockSubtype; + [ProtoMember(2)] public string MuzzleSubpart; + [ProtoMember(3)] public string ElevationSubpart; + [ProtoMember(4)] public string AzimuthSubpart; + [ProtoMember(5)] public float DurabilityModifier; + [ProtoMember(6)] public string InventoryIconName; + [ProtoMember(7)] public string[] Muzzles; + + public bool HasElevation => !ElevationSubpart?.Equals("") ?? false; + public bool HasAzimuth => !AzimuthSubpart?.Equals("") ?? false; + public bool HasMuzzleSubpart => !MuzzleSubpart?.Equals("") ?? false; + public bool IsTurret => HasAzimuth && HasElevation; + } + + [ProtoContract] + public struct Hardpoint + { + // ALL VALUES IN RADIANS + [ProtoMember(1)] public float AzimuthRate; + [ProtoMember(2)] public float ElevationRate; + [ProtoMember(3)] public float MaxAzimuth; + [ProtoMember(4)] public float MinAzimuth; + [ProtoMember(5)] public float MaxElevation; + [ProtoMember(6)] public float MinElevation; + [ProtoMember(7)] public float IdlePower; + [ProtoMember(8)] public float ShotInaccuracy; + [ProtoMember(9)] public bool LineOfSightCheck; + [ProtoMember(10)] public bool ControlRotation; + + [ProtoMember(11)] public float HomeAzimuth; + [ProtoMember(12)] public float HomeElevation; + + public bool CanRotateFull => MaxAzimuth >= -(float)Math.PI && MinAzimuth <= -(float)Math.PI; + public bool CanElevateFull => MaxElevation >= -(float)Math.PI && MinElevation <= -(float)Math.PI; + } + + [ProtoContract] + public struct Loading + { + [ProtoMember(10)] public string[] Ammos; + + [ProtoMember(1)] public int RateOfFire; // Shots per second + [ProtoMember(2)] public int BarrelsPerShot; + [ProtoMember(3)] public int ProjectilesPerBarrel; + [ProtoMember(4)] public float ReloadTime; // Seconds + [ProtoMember(6)] public int MagazinesToLoad; // Like an autoloader clip. + /// + /// The maximum number of times the gun can reload. + /// + [ProtoMember(7)] public int MaxReloads; + [ProtoMember(8)] public float DelayUntilFire; // Seconds + [ProtoMember(9)] public Resource[] Resources; // TODO + [ProtoMember(11)] public float RateOfFireVariance; // +- in variance of ROF + + [ProtoContract] + public struct Resource // TODO + { + [ProtoMember(1)] public string ResourceType; + [ProtoMember(2)] public float ResourceGeneration; // Per second + [ProtoMember(3)] public float ResourceStorage; + [ProtoMember(4)] public float ResourcePerShot; + [ProtoMember(5)] public float MinResourceBeforeFire; + // TODO: Action OnConsume + } + } + + [ProtoContract] + public struct Audio + { + [ProtoMember(1)] public string PreShootSound; + [ProtoMember(2)] public string ShootSound; + [ProtoMember(3)] public string ReloadSound; + [ProtoMember(4)] public string RotationSound; // TODO + } + + [ProtoContract] + public struct Visuals + { + [ProtoMember(1)] public string ShootParticle; + [ProtoMember(2)] public bool ContinuousShootParticle; // TODO + [ProtoMember(3)] public string ReloadParticle; // TODO + + public bool HasShootParticle => !ShootParticle?.Equals("") ?? false; + public bool HasReloadParticle => !ReloadParticle?.Equals("") ?? false; + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVAmmos.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVAmmos.cs new file mode 100644 index 00000000..ab91c737 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVAmmos.cs @@ -0,0 +1,239 @@ +using DeltaVEquipment.ProjectileBases; +using VRage.Utils; + +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + ProjectileDefinitionBase ExampleAmmoProjectile => new ProjectileDefinitionBase + { + Name = "ExampleAmmoProjectile", + Ungrouped = new Ungrouped + { + ReloadPowerUsage = 10, + Recoil = 5000, + Impulse = 5000, + ShotsPerMagazine = 100, + MagazineItemToConsume = "", + }, + Networking = new Networking + { + NetworkingMode = Networking.NetworkingModeEnum.FireEvent, + DoConstantSync = false, + NetworkPriority = 0, + }, + Damage = new Damage + { + SlimBlockDamageMod = 1, + FatBlockDamageMod = 1, + BaseDamage = 5000, + AreaDamage = 0, + AreaRadius = 0, + MaxImpacts = 1, + DamageToProjectiles = 0.4f, + DamageToProjectilesRadius = 0.2f, + }, + PhysicalProjectile = new PhysicalProjectile + { + Velocity = 800, + VelocityVariance = 0, + Acceleration = 0, + Health = 0, + MaxTrajectory = 4000, + MaxLifetime = -1, + IsHitscan = false, + GravityInfluenceMultiplier = 0.01f, + ProjectileSize = 0.5f, + }, + Visual = new Visual + { + //Model = "Models\\Weapons\\Projectile_Missile.mwm", + TrailTexture = MyStringId.GetOrCompute("WeaponLaser"), + TrailFadeTime = 0f, + TrailLength = 8, + TrailWidth = 0.5f, + TrailColor = new VRageMath.Vector4(61, 24, 24, 200), + //AttachedParticle = "Smoke_Missile", + ImpactParticle = "MaterialHit_Metal", + VisibleChance = 1f, + }, + Audio = new Audio + { + TravelSound = "", + TravelVolume = 100, + TravelMaxDistance = 1000, + ImpactSound = "WepSmallWarheadExpl", + SoundChance = 0.1f, + }, + LiveMethods = new LiveMethods + { + // OnImpact = (projectileInfo, hitPosition, hitNormal, hitEntity) => + // { + // if (hitEntity == null) + // return; + // HeartApi.SpawnProjectilesInCone(HeartApi.GetProjectileDefinitionId(ExampleAmmoMissile.Name), hitPosition - hitNormal * 50, hitNormal, 10, 0.1f); + // } + } + }; + + ProjectileDefinitionBase DeltaVMiningLaserAmmoBeam => new ProjectileDefinitionBase + { + Name = "DeltaVMiningLaserAmmoBeam", + Ungrouped = new Ungrouped + { + ReloadPowerUsage = 0, + Recoil = 0, + Impulse = 0, + ShotsPerMagazine = 360, + MagazineItemToConsume = "", + }, + Networking = new Networking + { + NetworkingMode = Networking.NetworkingModeEnum.NoNetworking, + DoConstantSync = false, + NetworkPriority = 0, + }, + Damage = new Damage + { + SlimBlockDamageMod = 1, + FatBlockDamageMod = 1, + BaseDamage = 1000, + AreaDamage = 0, + AreaRadius = 0, + MaxImpacts = 1, + }, + PhysicalProjectile = new PhysicalProjectile + { + Velocity = 800, + VelocityVariance = 0, + Acceleration = 0, + Health = 1, + MaxTrajectory = 4000, + MaxLifetime = -1, + IsHitscan = true, + }, + Visual = new Visual + { + //Model = "Models\\Weapons\\Projectile_Missile.mwm", + TrailTexture = MyStringId.GetOrCompute("WeaponLaser"), + TrailFadeTime = 0f, + TrailLength = 8, + TrailWidth = 0.5f, + TrailColor = new VRageMath.Vector4(61, 24, 24, 200), + //AttachedParticle = "Smoke_Missile", + ImpactParticle = "", + VisibleChance = 1f, + }, + Audio = new Audio + { + TravelSound = "", + TravelVolume = 100, + TravelMaxDistance = 1000, + ImpactSound = "", + SoundChance = 0.1f, + }, + Guidance = new Guidance[] + { + //new Guidance() + //{ + // TriggerTime = 0, + // ActiveDuration = -1, + // UseAimPrediction = false, + // MaxTurnRate = -1.5f, + // IFF = 2, + // DoRaycast = false, + // CastCone = 0.5f, + // CastDistance = 1000, + // Velocity = 50f, + //}, + //new Guidance() + //{ + // TriggerTime = 1f, + // ActiveDuration = -1f, + // UseAimPrediction = false, + // MaxTurnRate = 3.14f, + // IFF = 2, + // DoRaycast = false, + // CastCone = 0.5f, + // CastDistance = 1000, + // Velocity = -1f, + //} + }, + LiveMethods = new LiveMethods + { + + } + }; + + ProjectileDefinitionBase DeltaVMiningMetalStormProjectile => new ProjectileDefinitionBase + { + Name = "DeltaVMiningMetalStormProjectile", + Ungrouped = new Ungrouped + { + ReloadPowerUsage = 10, + Recoil = 5000, + Impulse = 5000, + ShotsPerMagazine = 32, + MagazineItemToConsume = "", + }, + Networking = new Networking + { + NetworkingMode = Networking.NetworkingModeEnum.FireEvent, + DoConstantSync = false, + NetworkPriority = 0, + }, + Damage = new Damage + { + SlimBlockDamageMod = 1, + FatBlockDamageMod = 1, + BaseDamage = 500, + AreaDamage = 0, + AreaRadius = 0, + MaxImpacts = 1, + DamageToProjectiles = 0.4f, + DamageToProjectilesRadius = 0.2f, + }, + PhysicalProjectile = new PhysicalProjectile + { + Velocity = 1000, + VelocityVariance = 0, + Acceleration = 0, + Health = 0, + MaxTrajectory = 4000, + MaxLifetime = -1, + IsHitscan = false, + ProjectileSize = 0.5f, + }, + Visual = new Visual + { + //Model = "Models\\Weapons\\Projectile_Missile.mwm", + TrailTexture = MyStringId.GetOrCompute("ProjectileTrailLine"), + TrailFadeTime = 0f, + TrailLength = 5, + TrailWidth = 0.1f, + TrailColor = new VRageMath.Vector4(25, 2, 0, 1), + //AttachedParticle = "Smoke_Missile", + ImpactParticle = "MaterialHit_Metal", + VisibleChance = 1f, + }, + Audio = new Audio + { + TravelSound = "", + TravelVolume = 100, + TravelMaxDistance = 1000, + ImpactSound = "cbar", + SoundChance = 0.1f, + }, + LiveMethods = new LiveMethods + { + // OnImpact = (projectileInfo, hitPosition, hitNormal, hitEntity) => + // { + // if (hitEntity == null) + // return; + // HeartApi.SpawnProjectilesInCone(HeartApi.GetProjectileDefinitionId(ExampleAmmoMissile.Name), hitPosition - hitNormal * 50, hitNormal, 10, 0.1f); + // } + } + }; + + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVAmmoRailgun.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVAmmoRailgun.cs new file mode 100644 index 00000000..6a53ddcc --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVAmmoRailgun.cs @@ -0,0 +1,83 @@ +using DeltaVEquipment.ProjectileBases; +using VRage.Utils; +using VRageMath; + +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + ProjectileDefinitionBase DeltaVAmmoRailgun => new ProjectileDefinitionBase + { + Name = "DeltaV Railgun Ammo", + Ungrouped = new Ungrouped + { + ReloadPowerUsage = 10, + Recoil = 1000000, + Impulse = 1000000, + ShotsPerMagazine = 1, + MagazineItemToConsume = "", + }, + Networking = new Networking + { + NetworkingMode = Networking.NetworkingModeEnum.FireEvent, + DoConstantSync = false, + NetworkPriority = 0, + }, + Damage = new Damage + { + SlimBlockDamageMod = 1, + FatBlockDamageMod = 1, + BaseDamage = 50000, + AreaDamage = 0, + AreaRadius = 0, + MaxImpacts = 3, + DamageToProjectiles = 0f, + DamageToProjectilesRadius = 0f, + }, + PhysicalProjectile = new PhysicalProjectile + { + Velocity = 800, + VelocityVariance = 0, + Acceleration = 0, + Health = 0, + MaxTrajectory = 4000, + MaxLifetime = -1, + IsHitscan = false, + GravityInfluenceMultiplier = 0.01f, + ProjectileSize = 0.5f, + }, + Visual = new Visual + { + //Model = "Models\\Weapons\\Projectile_Missile.mwm", + TrailTexture = MyStringId.GetOrCompute("WeaponLaser"), + TrailFadeTime = 0f, + TrailLength = 8, + TrailWidth = 0.5f, + TrailColor = new Vector4(61, 24, 24, 200), + AttachedParticle = "DeltaV_RailgunParticle", + ImpactParticle = "MaterialHit_Metal", + VisibleChance = 1f, + }, + Audio = new Audio + { + TravelSound = "", + TravelVolume = 100, + TravelMaxDistance = 1000, + ImpactSound = "WepSmallWarheadExpl", + SoundChance = 0.1f, + }, + Guidance = new Guidance[] + { + }, + LiveMethods = new LiveMethods + { + // OnImpact = (projectileInfo, hitPosition, hitNormal, hitEntity) => + // { + // if (hitEntity == null) + // return; + // HeartApi.SpawnProjectilesInCone(HeartApi.GetProjectileDefinitionId(ExampleAmmoMissile.Name), hitPosition - hitNormal * 50, hitNormal, 10, 0.1f); + // } + } + }; + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVRailgunFixed.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVRailgunFixed.cs new file mode 100644 index 00000000..d833e513 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaVRailgun/DeltaVRailgunFixed.cs @@ -0,0 +1,88 @@ +using System; +using DeltaVEquipment.WeaponBases; + +namespace DeltaVEquipment +{ + internal partial class HeartDefinitions + { + private WeaponDefinitionBase DeltaVRailgunFixed => new WeaponDefinitionBase + { + Targeting = new Targeting + { + MinTargetingRange = 0, + MaxTargetingRange = 4000, + CanAutoShoot = true, + RetargetTime = -1, + AimTolerance = 0.0175f, + }, + Assignments = new Assignments + { + BlockSubtype = "FixedRailgun", + MuzzleSubpart = "", + ElevationSubpart = "", + AzimuthSubpart = "", + DurabilityModifier = 1, + InventoryIconName = "", + Muzzles = new[] + { + "muzzle_projectile_1" + } + }, + Hardpoint = new Hardpoint + { + AzimuthRate = 0.01f, + ElevationRate = 0.01f, + MaxAzimuth = (float)Math.PI, + MinAzimuth = (float)-Math.PI, + MaxElevation = (float)Math.PI / 4, + MinElevation = (float)-Math.PI / 4, + IdlePower = 0, + ShotInaccuracy = 0f, + LineOfSightCheck = true, + ControlRotation = true, + }, + Loading = new Loading + { + Ammos = new[] + { + DeltaVAmmoRailgun.Name + }, + + RateOfFire = 1, + RateOfFireVariance = 0f, + BarrelsPerShot = 1, + ProjectilesPerBarrel = 1, + ReloadTime = 6, + DelayUntilFire = 0, + MagazinesToLoad = 3, + + MaxReloads = -1, + + //Resources = new[] + //{ + // new Loading.Resource + // { + // ResourceType = "Heat", + // ResourceGeneration = 5, //fug this doesn't work yet + // ResourceStorage = 100, + // ResourcePerShot = 1, + // MinResourceBeforeFire = 10 + // } + //}, + }, + Audio = new Audio + { + PreShootSound = "", + ShootSound = "", + ReloadSound = "PunisherNewReload", + RotationSound = "WepTurretGatlingRotate" + }, + Visuals = new Visuals + { + ShootParticle = "Muzzle_Flash_Autocannon", + ContinuousShootParticle = false, + ReloadParticle = "" + } + }; + } +} \ No newline at end of file diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningLaserTurret.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningLaserTurret.cs new file mode 100644 index 00000000..6d889d26 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningLaserTurret.cs @@ -0,0 +1,79 @@ +using System; +using DeltaVEquipment.WeaponBases; + +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + WeaponDefinitionBase DeltaVMiningLaserTurret => new WeaponDefinitionBase + { + Targeting = new Targeting + { + MaxTargetingRange = 1000, + MinTargetingRange = 0, + CanAutoShoot = true, + RetargetTime = 0, + AimTolerance = 0.0175f, + DefaultIff = IffEnum.TargetEnemies | IffEnum.TargetNeutrals, + AllowedTargetTypes = TargetTypeEnum.TargetGrids | TargetTypeEnum.TargetCharacters, + }, + Assignments = new Assignments + { + BlockSubtype = "DeltaV_MiningLaserTurret", + MuzzleSubpart = "MiningLaserTurretBarrels", + ElevationSubpart = "MiningLaserTurretBarrels", + AzimuthSubpart = "MiningLaserTurretBase", + DurabilityModifier = 1, + InventoryIconName = "", + Muzzles = new string[] + { + "muzzle_01", + }, + }, + Hardpoint = new Hardpoint + { + AzimuthRate = 0.5f, + ElevationRate = 0.5f, + MaxAzimuth = (float)Math.PI, + MinAzimuth = (float)-Math.PI, + MaxElevation = (float)Math.PI, + MinElevation = -0.1745f, + HomeAzimuth = 0, + HomeElevation = 0, + IdlePower = 10, + ShotInaccuracy = 0.0025f, + LineOfSightCheck = true, + ControlRotation = true, + }, + Loading = new Loading + { + Ammos = new string[] + { + DeltaVMiningLaserAmmoBeam.Name, + }, + + RateOfFire = 60, + BarrelsPerShot = 1, + ProjectilesPerBarrel = 1, + ReloadTime = 1, + DelayUntilFire = 0, + MagazinesToLoad = 1, + + MaxReloads = -1, + }, + Audio = new Audio + { + PreShootSound = "", + ShootSound = "", + ReloadSound = "PunisherNewReload", + RotationSound = "WepTurretGatlingRotate", + }, + Visuals = new Visuals + { + ShootParticle = "Muzzle_Flash_Autocannon", + ContinuousShootParticle = false, + ReloadParticle = "", + }, + }; + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningMetalStorm.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningMetalStorm.cs new file mode 100644 index 00000000..81cc048d --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/DeltaV_MiningMetalStorm.cs @@ -0,0 +1,110 @@ +using System; +using DeltaVEquipment.WeaponBases; + +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + WeaponDefinitionBase DeltaVMiningMetalStorm => new WeaponDefinitionBase + { + Targeting = new Targeting + { + MaxTargetingRange = 1000, + MinTargetingRange = 0, + CanAutoShoot = true, + RetargetTime = 0, + AimTolerance = 0.0175f, + DefaultIff = IffEnum.TargetEnemies | IffEnum.TargetNeutrals, + AllowedTargetTypes = TargetTypeEnum.TargetGrids | TargetTypeEnum.TargetCharacters, + }, + Assignments = new Assignments + { + BlockSubtype = "DeltaV_MiningMetalStorm", + MuzzleSubpart = "MetalStormBarrels", + ElevationSubpart = "MetalStormBarrels", + AzimuthSubpart = "MetalStormBase", + DurabilityModifier = 1, + InventoryIconName = "", + Muzzles = new string[] + { + "muzzle_storm_01", + "muzzle_storm_02", + "muzzle_storm_03", + "muzzle_storm_04", + "muzzle_storm_05", + "muzzle_storm_06", + "muzzle_storm_07", + "muzzle_storm_08", + "muzzle_storm_09", + "muzzle_storm_10", + "muzzle_storm_11", + "muzzle_storm_12", + "muzzle_storm_13", + "muzzle_storm_14", + "muzzle_storm_15", + "muzzle_storm_16", + "muzzle_storm_17", + "muzzle_storm_18", + "muzzle_storm_19", + "muzzle_storm_20", + "muzzle_storm_21", + "muzzle_storm_22", + "muzzle_storm_23", + "muzzle_storm_24", + "muzzle_storm_25", + "muzzle_storm_26", + "muzzle_storm_27", + "muzzle_storm_28", + "muzzle_storm_29", + "muzzle_storm_30", + "muzzle_storm_31", + "muzzle_storm_32" + }, + }, + Hardpoint = new Hardpoint + { + AzimuthRate = 0.5f, + ElevationRate = 0.5f, + MaxAzimuth = (float)Math.PI, + MinAzimuth = (float)-Math.PI, + MaxElevation = (float)Math.PI, + MinElevation = -0.1745f, + HomeAzimuth = 0, + HomeElevation = 0, + IdlePower = 10, + ShotInaccuracy = 0.0025f, + LineOfSightCheck = true, + ControlRotation = true, + }, + Loading = new Loading + { + Ammos = new string[] + { + DeltaVMiningMetalStormProjectile.Name, + }, + + RateOfFire = 60, + BarrelsPerShot = 1, + ProjectilesPerBarrel = 1, + ReloadTime = 1, + DelayUntilFire = 0, + MagazinesToLoad = 1, + + MaxReloads = -1, + }, + Audio = new Audio + { + PreShootSound = "", + ShootSound = "metalstorm", + ReloadSound = "PunisherNewReload", + RotationSound = "WepTurretGatlingRotate", + }, + Visuals = new Visuals + { + ShootParticle = "Muzzle_Flash_Autocannon", + ContinuousShootParticle = false, + ReloadParticle = "", + }, + }; + } +} diff --git a/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/HeartDefinitions.cs b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/HeartDefinitions.cs new file mode 100644 index 00000000..1490e204 --- /dev/null +++ b/DeltaVEquipment/Data/Scripts/OrreryCombatFramework/HeartDefinitions.cs @@ -0,0 +1,11 @@ +namespace DeltaVEquipment +{ + partial class HeartDefinitions + { + internal HeartDefinitions() + { + LoadWeaponDefinitions(DeltaVMiningLaserTurret, DeltaVRailgunFixed, DeltaVMiningMetalStorm); //todo tell the user that they forgot to add stuff here when they get an error + LoadAmmoDefinitions(DeltaVMiningLaserAmmoBeam, DeltaVAmmoRailgun, DeltaVMiningMetalStormProjectile); + } + } +} diff --git a/DeltaVEquipment/Data/TransparentMaterials.sbc b/DeltaVEquipment/Data/TransparentMaterials.sbc new file mode 100644 index 00000000..2d793aaa --- /dev/null +++ b/DeltaVEquipment/Data/TransparentMaterials.sbc @@ -0,0 +1,49 @@ + + + + + + TransparentMaterialDefinition + GenericGlass + + false + 0.0 + 0.0 + true + 1.0 + Textures\Particles\SquareWindowDirtInside_ca.dds + + 1.1 + 1.1 + 1.1 + 0.3 + + + 0.0 + 0.0 + 0.0 + 0.5 + + + 0.0 + 0.0 + 0.0 + 2.0 + + + 0.1 + 0.1 + 0.1 + 0.1 + + 0.5 + 0.8 + 0.3 + 0.7 + 0.55 + Textures\Models\Cubes\Chrome_ng.dds + 12.0 + true + + + \ No newline at end of file diff --git a/DeltaVEquipment/DeltaVEquipment.csproj.DotSettings b/DeltaVEquipment/DeltaVEquipment.csproj.DotSettings new file mode 100644 index 00000000..385f8765 --- /dev/null +++ b/DeltaVEquipment/DeltaVEquipment.csproj.DotSettings @@ -0,0 +1,6 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurret.mwm b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurret.mwm new file mode 100644 index 00000000..6075bc7b Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurret.mwm differ diff --git a/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBarrels.mwm b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBarrels.mwm new file mode 100644 index 00000000..73844992 Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBarrels.mwm differ diff --git a/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBase.mwm b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBase.mwm new file mode 100644 index 00000000..a20c050b Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningLaserTurretBase.mwm differ diff --git a/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningMetalStorm.mwm b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningMetalStorm.mwm new file mode 100644 index 00000000..0183a8b0 Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/DeltaV_MiningMetalStorm.mwm differ diff --git a/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBarrels.mwm b/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBarrels.mwm new file mode 100644 index 00000000..1dc6451e Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBarrels.mwm differ diff --git a/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBase.mwm b/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBase.mwm new file mode 100644 index 00000000..20399eb9 Binary files /dev/null and b/DeltaVEquipment/Models/Cubes/large/Deltav_MiningMetalStormBase.mwm differ diff --git a/DeltaVEquipment/Models/FixedLaser.mwm b/DeltaVEquipment/Models/FixedLaser.mwm new file mode 100644 index 00000000..2a441604 Binary files /dev/null and b/DeltaVEquipment/Models/FixedLaser.mwm differ diff --git a/DeltaVEquipment/Models/FixedMicrowave.mwm b/DeltaVEquipment/Models/FixedMicrowave.mwm new file mode 100644 index 00000000..9bdf3608 Binary files /dev/null and b/DeltaVEquipment/Models/FixedMicrowave.mwm differ diff --git a/DeltaVEquipment/Models/FixedRailgun.mwm b/DeltaVEquipment/Models/FixedRailgun.mwm new file mode 100644 index 00000000..c9f0c6a6 Binary files /dev/null and b/DeltaVEquipment/Models/FixedRailgun.mwm differ diff --git a/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_add.dds b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_add.dds new file mode 100644 index 00000000..fba8c55d Binary files /dev/null and b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_add.dds differ diff --git a/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_cm.dds b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_cm.dds new file mode 100644 index 00000000..92884923 Binary files /dev/null and b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_cm.dds differ diff --git a/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_ng.dds b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_ng.dds new file mode 100644 index 00000000..68c041ee Binary files /dev/null and b/DeltaVEquipment/Textures/Custom/enenra/EmissiveSpectrumAtlas_ng.dds differ diff --git a/DeltaVEquipment/Textures/GUI/Icons/Cubes/FixedLaser.dds b/DeltaVEquipment/Textures/GUI/Icons/Cubes/FixedLaser.dds new file mode 100644 index 00000000..79042a26 Binary files /dev/null and b/DeltaVEquipment/Textures/GUI/Icons/Cubes/FixedLaser.dds differ diff --git a/DeltaVEquipment/Textures/GUI/Icons/Cubes/SC_AR_Nimrod.dds b/DeltaVEquipment/Textures/GUI/Icons/Cubes/SC_AR_Nimrod.dds new file mode 100644 index 00000000..6cc4d9f7 Binary files /dev/null and b/DeltaVEquipment/Textures/GUI/Icons/Cubes/SC_AR_Nimrod.dds differ diff --git a/DeltaVEquipment/deltavequipment.blend b/DeltaVEquipment/deltavequipment.blend new file mode 100644 index 00000000..fbe89943 Binary files /dev/null and b/DeltaVEquipment/deltavequipment.blend differ diff --git a/DeltaVEquipment/dv_massdriverturret.blend b/DeltaVEquipment/dv_massdriverturret.blend new file mode 100644 index 00000000..b91994fe Binary files /dev/null and b/DeltaVEquipment/dv_massdriverturret.blend differ diff --git a/DeltaVEquipment/dv_massdriverturret.blend1 b/DeltaVEquipment/dv_massdriverturret.blend1 new file mode 100644 index 00000000..91c502a9 Binary files /dev/null and b/DeltaVEquipment/dv_massdriverturret.blend1 differ diff --git a/DeltaVEquipment/dv_metalstorm.blend b/DeltaVEquipment/dv_metalstorm.blend new file mode 100644 index 00000000..b2a05c29 Binary files /dev/null and b/DeltaVEquipment/dv_metalstorm.blend differ diff --git a/DeltaVEquipment/dv_metalstorm.blend1 b/DeltaVEquipment/dv_metalstorm.blend1 new file mode 100644 index 00000000..7f60ee00 Binary files /dev/null and b/DeltaVEquipment/dv_metalstorm.blend1 differ diff --git a/DeltaVEquipment/dv_mininglaserturret.blend b/DeltaVEquipment/dv_mininglaserturret.blend new file mode 100644 index 00000000..4ae94e8a Binary files /dev/null and b/DeltaVEquipment/dv_mininglaserturret.blend differ diff --git a/DeltaVEquipment/dv_mininglaserturret.blend1 b/DeltaVEquipment/dv_mininglaserturret.blend1 new file mode 100644 index 00000000..56b73e8c Binary files /dev/null and b/DeltaVEquipment/dv_mininglaserturret.blend1 differ diff --git a/DeltaVEquipment/dv_miningmicrowaveturret.blend b/DeltaVEquipment/dv_miningmicrowaveturret.blend new file mode 100644 index 00000000..5f3c11ee Binary files /dev/null and b/DeltaVEquipment/dv_miningmicrowaveturret.blend differ diff --git a/DeltaVEquipment/dv_miningmicrowaveturret.blend1 b/DeltaVEquipment/dv_miningmicrowaveturret.blend1 new file mode 100644 index 00000000..c2036f71 Binary files /dev/null and b/DeltaVEquipment/dv_miningmicrowaveturret.blend1 differ diff --git a/DeltaVEquipment/metadata.mod b/DeltaVEquipment/metadata.mod new file mode 100644 index 00000000..0a020fdf --- /dev/null +++ b/DeltaVEquipment/metadata.mod @@ -0,0 +1,4 @@ + + + 1.0 + \ No newline at end of file diff --git a/DeltaVEquipment/modinfo.sbmi b/DeltaVEquipment/modinfo.sbmi new file mode 100644 index 00000000..baf2c5af --- /dev/null +++ b/DeltaVEquipment/modinfo.sbmi @@ -0,0 +1,11 @@ + + + 76561198071098415 + 0 + + + 3346575895 + Steam + + + \ No newline at end of file diff --git a/DeltaVFactionQuestLog/Aristeas_copy to DS and client.bat b/DeltaVFactionQuestLog/Aristeas_copy to DS and client.bat new file mode 100644 index 00000000..81d17d26 --- /dev/null +++ b/DeltaVFactionQuestLog/Aristeas_copy to DS and client.bat @@ -0,0 +1,31 @@ +@echo off +rem Testing mods in DS by yourself can be done without the need to re-publish every time. +rem You can simply update the files that are on your machine! +rem This will only work for you, anyone else joining the server will of course download the mod from the workshop. + +rem To use: +rem 1. Copy this .bat file in the ROOT folder of your local mod (e.g. %appdata%/SpaceEngineers/Mods/YourLocalMod/) + +rem 2. Edit this variable if applicable (do not add quotes or end with backslash). +set STEAM_PATH=C:\Program Files (x86)\Steam + +rem 3. Edit this with your mod's workshop id. +set WORKSHOP_ID=3373765186 + +rem Now you can run it every time you want to update the mod on DS and client. + + + +rem Don't edit the below unless you really need different paths. +rem NOTE: don't add quotes and don't end with a backslash! + +set CLIENT_PATH=%STEAM_PATH%\steamapps\workshop\content\244850\%WORKSHOP_ID% +set DS_PATH=%APPDATA%\SpaceEngineersDedicated\content\244850\%WORKSHOP_ID% + +rmdir "%CLIENT_PATH%" /S /Q +rmdir "%DS_PATH%" /S /Q + +robocopy.exe .\ "%DS_PATH%" *.* /S /xd .git bin obj .vs ignored /xf *.lnk *.git* *.bat *.zip *.7z *.blend* *.md *.log *.sln *.csproj *.csproj.user *.ruleset modinfo.sbmi +robocopy.exe "%DS_PATH%" "%CLIENT_PATH%" *.* /S + +pause \ No newline at end of file diff --git a/DeltaVFactionQuestLog/Data/Scripts/DeltaVQuestLog/Commands/CommandHandler.cs b/DeltaVFactionQuestLog/Data/Scripts/DeltaVQuestLog/Commands/CommandHandler.cs new file mode 100644 index 00000000..c0737856 --- /dev/null +++ b/DeltaVFactionQuestLog/Data/Scripts/DeltaVQuestLog/Commands/CommandHandler.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Sandbox.ModAPI; +using VRage.Utils; + +namespace Invalid.DeltaVQuestLog.Commands +{ + /// + /// Parses commands from chat and triggers relevant methods. + /// + public class CommandHandler + { + public static CommandHandler I; + + private readonly Dictionary _commands = new Dictionary + { + ["help"] = new Command( + "Faction Objectives", + "Displays command help.", + message => I.ShowHelp()), + + ["add"] = new Command( + "Faction Objectives", + "Add a new objective (leaders only)", + CommandMethods.HandleAddObjective), + ["list"] = new Command( + "Faction Objectives", + "Lists all objectives.", + CommandMethods.HandleListObjectives), + ["show"] = new Command( + "Faction Objectives", + "Show the quest log locally.", + CommandMethods.HandleShowObjectives), + ["remove"] = new Command( + "Faction Objectives", + "Remove an objective (leaders only).", + CommandMethods.HandleRemoveObjective), + ["broadcast"] = new Command( + "Faction Objectives", + "Show the quest log to all members for