diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSpawner.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSpawner.cs index e235c28..c18fea3 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSpawner.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSpawner.cs @@ -97,6 +97,11 @@ public class AsteroidSpawner { private ConcurrentQueue _updateQueue = new ConcurrentQueue(); private const int UpdatesPerTick = 50;// update rate of the roids + private ConcurrentQueue _pendingRemovals = new ConcurrentQueue(); + private const int REMOVAL_BATCH_SIZE = 50; + private const double REMOVAL_BATCH_INTERVAL = 1.0; // seconds + private DateTime _lastRemovalBatch = DateTime.MinValue; + private RealGasGiantsApi _realGasGiantsApi; private Dictionary _newAsteroidTimestamps = new Dictionary(); //JUST ONE MORE BRO JUST ONE MORE DICTIONARY WE GOTTA STORE THE DATA BRO WE MIGHT FORGET!!! @@ -701,7 +706,7 @@ public void UpdateTick() { MergeZones(); UpdateZones(); SendZoneUpdates(); - + try { List players = new List(); MyAPIGateway.Players.GetPlayers(players); @@ -749,6 +754,11 @@ public void UpdateTick() { CleanupOrphanedAsteroids(); } } + + if (MyAPIGateway.Session.IsServer) { + ProcessPendingRemovals(); + } + } catch (Exception ex) { Log.Exception(ex, typeof(AsteroidSpawner), "Error in UpdateTick"); @@ -1153,52 +1163,22 @@ public void SendNetworkMessages() { private void RemoveAsteroid(AsteroidEntity asteroid) { if (asteroid == null) return; - try { - var existingEntity = MyEntities.GetEntityById(asteroid.EntityId) as AsteroidEntity; - if (existingEntity == null || existingEntity.MarkedForClose) { - AsteroidEntity removedFromBag; - _asteroids.TryTake(out removedFromBag); - _messageCache.Clear(); // Clear pending messages - return; - } - if (existingEntity != asteroid) { - Log.Warning($"Entity mismatch for asteroid {asteroid.EntityId} - skipping removal"); - return; + try { + if (MyAPIGateway.Session.IsServer) { + _pendingRemovals.Enqueue(asteroid.EntityId); } - AsteroidEntity removedAsteroid; - if (_asteroids.TryTake(out removedAsteroid)) { - // Send removal message for any server (dedicated or not) - if (MyAPIGateway.Session.IsServer) { - var message = new AsteroidNetworkMessage( - asteroid.PositionComp.GetPosition(), - asteroid.Properties.Diameter, - Vector3D.Zero, - Vector3D.Zero, - asteroid.Type, - false, - asteroid.EntityId, - true, // isRemoval - false, - Quaternion.Identity - ); - byte[] messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); - MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); - Log.Info($"Sent removal message for asteroid {asteroid.EntityId}"); - } + MyEntities.Remove(asteroid); + asteroid.Close(); - MyEntities.Remove(asteroid); - asteroid.Close(); - _messageCache.Clear(); // Clear pending messages after removal - Log.Info($"Successfully removed asteroid {asteroid.EntityId} from world"); - } + AsteroidEntity removed; + _asteroids.TryTake(out removed); } catch (Exception ex) { Log.Exception(ex, typeof(AsteroidSpawner), $"Error removing asteroid {asteroid?.EntityId}"); } } - public void SendPositionUpdates() { if (!MyAPIGateway.Session.IsServer) return; @@ -1418,5 +1398,24 @@ private bool IsPlayerMovingTooFast(IMyPlayer player) { return isTooFast; } + + private void ProcessPendingRemovals() { + if ((DateTime.UtcNow - _lastRemovalBatch).TotalSeconds < REMOVAL_BATCH_INTERVAL) + return; + + var removalBatch = new List(); + long entityId; + + while (removalBatch.Count < REMOVAL_BATCH_SIZE && _pendingRemovals.TryDequeue(out entityId)) { + removalBatch.Add(entityId); + } + + if (removalBatch.Count > 0) { + var packet = new AsteroidBatchUpdatePacket { Removals = removalBatch }; + byte[] data = MyAPIGateway.Utilities.SerializeToBinary(packet); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, data); + _lastRemovalBatch = DateTime.UtcNow; + } + } } } \ No newline at end of file diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs index cf1d77a..8dbb490 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs @@ -439,27 +439,14 @@ private void OnSecureMessageReceived(ushort handlerId, byte[] message, ulong ste return; } - if (handlerId == 32001) // Zone updates - { - ProcessZoneMessage(message); - return; - } - - // Try to deserialize as container first try { - var container = - MyAPIGateway.Utilities.SerializeFromBinary(message); - if (container != null && container.Messages != null) { - Log.Info($"Received batch update with {container.Messages.Length} asteroids"); - foreach (var asteroidMessage in container.Messages) { - ProcessClientMessage(asteroidMessage); - } - + var batchPacket = MyAPIGateway.Utilities.SerializeFromBinary(message); + if (batchPacket != null) { + ProcessBatchMessage(batchPacket); return; } } catch { - // If container deserialization fails, try individual message var asteroidMessage = MyAPIGateway.Utilities.SerializeFromBinary(message); if (!MyAPIGateway.Session.IsServer) { ProcessClientMessage(asteroidMessage); @@ -621,6 +608,21 @@ private void ProcessClientMessage(AsteroidNetworkMessage message) { } } + // Add a separate method for batch updates + private void ProcessBatchMessage(AsteroidBatchUpdatePacket packet) { + try { + if (packet.Removals != null && packet.Removals.Count > 0) { + foreach (long entityId in packet.Removals) { + RemoveAsteroidOnClient(entityId); + } + } + + // Process other batch data as needed... + } + catch (Exception ex) { + Log.Exception(ex, typeof(MainSession), "Error processing batch update"); + } + } private float GetQuaternionAngleDifference(Quaternion a, Quaternion b) { // Get the dot product between the quaternions float dot = a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W; diff --git a/Dynamic Asteroids/Data/Scripts/minified_output.txt b/Dynamic Asteroids/Data/Scripts/minified_output.txt new file mode 100644 index 0000000..f8538f0 --- /dev/null +++ b/Dynamic Asteroids/Data/Scripts/minified_output.txt @@ -0,0 +1 @@ +using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;using ProtoBuf;using Sandbox.ModAPI;using System.Collections.Generic;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids{[ProtoInclude(10,typeof(AsteroidUpdatePacket))][ProtoInclude(11,typeof(AsteroidRemovalPacket))][ProtoInclude(12,typeof(AsteroidSpawnPacket))][ProtoInclude(13,typeof(AsteroidBatchUpdatePacket))][ProtoInclude(14,typeof(ZoneUpdatePacket))]public abstract partial class PacketBase{}[ProtoContract]public class AsteroidState{[ProtoMember(1)]public Vector3D Position{get;set;}[ProtoMember(2)]public Vector3D Velocity{get;set;}[ProtoMember(3)]public Quaternion Rotation{get;set;}[ProtoMember(4)]public float Size{get;set;}[ProtoMember(5)]public AsteroidType Type{get;set;}[ProtoMember(6)]public long EntityId{get;set;}[ProtoMember(7)]public Vector3D AngularVelocity{get;set;}public AsteroidState(){}public AsteroidState(AsteroidEntity asteroid){Position=asteroid.PositionComp.GetPosition();Velocity=asteroid.Physics.LinearVelocity;AngularVelocity=asteroid.Physics.AngularVelocity;Rotation=Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix);Size=asteroid.Properties.Diameter;Type=asteroid.Type;EntityId=asteroid.EntityId;}public bool HasChanged(AsteroidEntity asteroid){return Vector3D.DistanceSquared(Position,asteroid.PositionComp.GetPosition())>0.01||Vector3D.DistanceSquared(Velocity,asteroid.Physics.LinearVelocity)>0.01;}}[ProtoContract]public class AsteroidUpdatePacket:PacketBase{[ProtoMember(1)]public ListStates{get;set;}=new List();}[ProtoContract]public class AsteroidSpawnPacket:PacketBase{[ProtoMember(1)]public Vector3D Position{get;set;}[ProtoMember(2)]public float Size{get;set;}[ProtoMember(3)]public Vector3D Velocity{get;set;}[ProtoMember(4)]public Vector3D AngularVelocity{get;set;}[ProtoMember(5)]public AsteroidType Type{get;set;}[ProtoMember(6)]public long EntityId{get;set;}[ProtoMember(7)]public Quaternion Rotation{get;set;}public AsteroidSpawnPacket(){}public AsteroidSpawnPacket(AsteroidEntity asteroid){Position=asteroid.PositionComp.GetPosition();Size=asteroid.Properties.Diameter;Velocity=asteroid.Physics.LinearVelocity;AngularVelocity=asteroid.Physics.AngularVelocity;Type=asteroid.Type;EntityId=asteroid.EntityId;Rotation=Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix);}}[ProtoContract]public class AsteroidRemovalPacket:PacketBase{[ProtoMember(1)]public long EntityId{get;set;}}[ProtoContract]public class AsteroidBatchUpdatePacket:PacketBase{[ProtoMember(1)]public ListUpdates{get;set;}=new List();[ProtoMember(2)]public ListRemovals{get;set;}=new List();[ProtoMember(3)]public ListSpawns{get;set;}=new List();}[ProtoContract]public class ZoneData{[ProtoMember(1)]public Vector3D Center{get;set;}[ProtoMember(2)]public double Radius{get;set;}[ProtoMember(3)]public long PlayerId{get;set;}[ProtoMember(4)]public bool IsActive{get;set;}[ProtoMember(5)]public bool IsMerged{get;set;}[ProtoMember(6)]public double CurrentSpeed{get;set;}}[ProtoContract]public class ZoneUpdatePacket:PacketBase{[ProtoMember(1)]public ListZones{get;set;}=new List();}[ProtoContract]public class AsteroidPacketData{[ProtoMember(1)]public Vector3D Position{get;set;}[ProtoMember(2)]public Vector3D Velocity{get;set;}[ProtoMember(3)]public Vector3D AngularVelocity{get;set;}[ProtoMember(4)]public float Size{get;set;}[ProtoMember(5)]public AsteroidType Type{get;set;}[ProtoMember(6)]public long EntityId{get;set;}[ProtoMember(7)]public Quaternion Rotation{get;set;}[ProtoMember(8)]public bool IsRemoval{get;set;}[ProtoMember(9)]public bool IsInitialCreation{get;set;}public AsteroidPacketData(){}public AsteroidPacketData(AsteroidEntity asteroid,bool isRemoval=false,bool isInitialCreation=false){Position=asteroid.PositionComp.GetPosition();Velocity=asteroid.Physics.LinearVelocity;AngularVelocity=asteroid.Physics.AngularVelocity;Size=asteroid.Properties.Diameter;Type=asteroid.Type;EntityId=asteroid.EntityId;Rotation=Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix);IsRemoval=isRemoval;IsInitialCreation=isInitialCreation;}}[ProtoContract]public class AsteroidBatchPacket:PacketBase{[ProtoMember(1)]public ListMessages{get;set;}=new List();public AsteroidBatchPacket(){}public AsteroidBatchPacket(IEnumerableasteroids){Messages=new List();foreach(var asteroid in asteroids){Messages.Add(new AsteroidPacketData(asteroid));}}}public static class NetworkHandler{public static void SendAsteroidUpdate(AsteroidEntity asteroid){var packet=new AsteroidBatchPacket();packet.Messages.Add(new AsteroidPacketData(asteroid));byte[]data=MyAPIGateway.Utilities.SerializeToBinary(packet);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,data);}public static void SendAsteroidRemoval(long entityId){var packet=new AsteroidRemovalPacket{EntityId=entityId};byte[]data=MyAPIGateway.Utilities.SerializeToBinary(packet);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,data);}}[ProtoContract]public class AsteroidNetworkMessageContainer{[ProtoMember(2)]public AsteroidNetworkMessage[]Messages{get;set;}public AsteroidNetworkMessageContainer(){}public AsteroidNetworkMessageContainer(AsteroidNetworkMessage[]messages){Messages=messages;}}[ProtoContract]public class AsteroidNetworkMessage{[ProtoMember(1)]public double PosX;[ProtoMember(2)]public double PosY;[ProtoMember(3)]public double PosZ;[ProtoMember(4)]public float Size;[ProtoMember(5)]public double VelX;[ProtoMember(6)]public double VelY;[ProtoMember(7)]public double VelZ;[ProtoMember(8)]public double AngVelX;[ProtoMember(9)]public double AngVelY;[ProtoMember(10)]public double AngVelZ;[ProtoMember(11)]public int Type;[ProtoMember(13)]public long EntityId;[ProtoMember(14)]public bool IsRemoval;[ProtoMember(15)]public bool IsInitialCreation;[ProtoMember(16)]public float RotX;[ProtoMember(17)]public float RotY;[ProtoMember(18)]public float RotZ;[ProtoMember(19)]public float RotW;public AsteroidNetworkMessage(){}public AsteroidNetworkMessage(Vector3D position,float size,Vector3D initialVelocity,Vector3D angularVelocity,AsteroidType type,bool isSubChunk,long entityId,bool isRemoval,bool isInitialCreation,Quaternion rotation){PosX=position.X;PosY=position.Y;PosZ=position.Z;Size=size;VelX=initialVelocity.X;VelY=initialVelocity.Y;VelZ=initialVelocity.Z;AngVelX=angularVelocity.X;AngVelY=angularVelocity.Y;AngVelZ=angularVelocity.Z;Type=(int)type;EntityId=entityId;IsRemoval=isRemoval;IsInitialCreation=isInitialCreation;RotX=rotation.X;RotY=rotation.Y;RotZ=rotation.Z;RotW=rotation.W;}public Vector3D GetPosition()=>new Vector3D(PosX,PosY,PosZ);public Vector3D GetVelocity()=>new Vector3D(VelX,VelY,VelZ);public Vector3D GetAngularVelocity()=>new Vector3D(AngVelX,AngVelY,AngVelZ);public AsteroidType GetType()=>(AsteroidType)Type;public Quaternion GetRotation()=>new Quaternion(RotX,RotY,RotZ,RotW);}[ProtoContract]public class SettingsSyncMessage:PacketBase{[ProtoMember(1)]public bool EnableLogging{get;set;}}}using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;using RealGasGiants;using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.IO;using System.Linq;using VRage.Game;using VRage.Game.Entity;using VRage.Game.ModAPI;using VRage.ModAPI;using VRageMath;using static DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidZone;using static DynamicAsteroids.Data.Scripts.DynamicAsteroids.MainSession;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids{public class AsteroidZone{public Vector3D Center{get;set;}public double Radius{get;set;}public int AsteroidCount{get;set;}public bool IsMerged{get;set;}public long EntityId{get;set;}public HashSetContainedAsteroids{get;private set;}public bool IsMarkedForRemoval{get;set;}public DateTime LastActiveTime{get;set;}public double CurrentSpeed{get;set;}public HashSetTransferringAsteroids{get;private set;}=new HashSet();public DateTime CreationTime{get;private set;}=DateTime.UtcNow;public HashSetTransferredFromOtherZone{get;private set;}=new HashSet();public int TotalAsteroidCount=>ContainedAsteroids.Count+TransferredFromOtherZone.Count;public const double MINIMUM_ZONE_LIFETIME=5.0;private const float ASTEROID_GRACE_PERIOD=5f;public AsteroidZone(Vector3D center,double radius){Center=center;Radius=radius;AsteroidCount=0;IsMerged=false;EntityId=0;ContainedAsteroids=new HashSet();IsMarkedForRemoval=false;LastActiveTime=DateTime.UtcNow;CurrentSpeed=0;}public enum ZoneState{Active,PendingRemoval,Removed}public ZoneState State{get;set;}=ZoneState.Active;public DateTime?RemovalStartTime{get;private set;}public const double REMOVAL_TRANSITION_TIME=5.0;public void MarkForRemoval(){if(State==ZoneState.Active){State=ZoneState.PendingRemoval;RemovalStartTime=DateTime.UtcNow;IsMarkedForRemoval=true;}}public bool IsPointInZone(Vector3D point){return Vector3D.DistanceSquared(Center,point)<=Radius*Radius;}public bool CanBeRemoved(){return(DateTime.UtcNow-CreationTime).TotalSeconds>=MINIMUM_ZONE_LIFETIME;}public bool IsDisabledDueToSpeed(){return CurrentSpeed>AsteroidSettings.ZoneSpeedThreshold;}}public class AsteroidSpawner{private ConcurrentBag_asteroids;private bool _canSpawnAsteroids=false;private DateTime _worldLoadTime;private Random rand;private List_despawnedAsteroids=new List();private ConcurrentQueue_networkMessages=new ConcurrentQueue();public ConcurrentDictionaryplayerZones=new ConcurrentDictionary();private ConcurrentDictionaryplayerMovementData=new ConcurrentDictionary();private ConcurrentQueue_updateQueue=new ConcurrentQueue();private const int UpdatesPerTick=50;private RealGasGiantsApi _realGasGiantsApi;private Dictionary_newAsteroidTimestamps=new Dictionary();private const double NEW_ASTEROID_GRACE_PERIOD=5.0;private class ZoneCache{public AsteroidZone Zone{get;set;}public DateTime LastUpdateTime{get;set;}const int CacheExpirationSeconds=5;public bool IsExpired(){return(DateTime.UtcNow-LastUpdateTime).TotalSeconds>CacheExpirationSeconds;}}private class AsteroidStateCache{private ConcurrentBag_dirtyStates=new ConcurrentBag();private const int SaveInterval=300;private DateTime _lastSaveTime=DateTime.UtcNow;private ConcurrentDictionary_stateCache=new ConcurrentDictionary();private ConcurrentBag_dirtyAsteroids=new ConcurrentBag();private const double POSITION_CHANGE_THRESHOLD=1;private const double ROTATION_CHANGE_THRESHOLD=0.01;private const double ACCELERATION_THRESHOLD=0.1;public void UpdateState(AsteroidEntity asteroid){if(asteroid==null||asteroid.Physics==null)return;Vector3D currentPosition=asteroid.PositionComp.GetPosition();Vector3D currentVelocity=asteroid.Physics.LinearVelocity;Vector3D currentAngularVelocity=asteroid.Physics.AngularVelocity;Quaternion currentRotation=Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix);lock(_stateCache){AsteroidState cachedState;if(_stateCache.TryGetValue(asteroid.EntityId,out cachedState)){bool needsUpdate=false;if(cachedState.Velocity!=null){Vector3D acceleration=(currentVelocity-cachedState.Velocity)/MyEngineConstants.UPDATE_STEP_SIZE_IN_SECONDS;if(acceleration.LengthSquared()>ACCELERATION_THRESHOLD*ACCELERATION_THRESHOLD){needsUpdate=true;Log.Info($"Asteroid {asteroid.EntityId} updating due to acceleration: {acceleration.Length():F2} m/s²");}}float rotationDiff=GetQuaternionAngleDifference(currentRotation,cachedState.Rotation);if(rotationDiff>ROTATION_CHANGE_THRESHOLD){needsUpdate=true;Log.Info($"Asteroid {asteroid.EntityId} updating due to rotation change: {MathHelper.ToDegrees(rotationDiff):F2}°");}double positionDeltaSquared=Vector3D.DistanceSquared(cachedState.Position,currentPosition);if(positionDeltaSquared>POSITION_CHANGE_THRESHOLD*POSITION_CHANGE_THRESHOLD*4){needsUpdate=true;Log.Info($"Asteroid {asteroid.EntityId} updating due to position change: {Math.Sqrt(positionDeltaSquared):F2}m");}if(needsUpdate){_stateCache[asteroid.EntityId]=new AsteroidState(asteroid);_dirtyAsteroids.Add(asteroid.EntityId);}}else{_stateCache[asteroid.EntityId]=new AsteroidState(asteroid);_dirtyAsteroids.Add(asteroid.EntityId);Log.Info($"Initial state cache for asteroid {asteroid.EntityId}");}}}private float GetQuaternionAngleDifference(Quaternion a,Quaternion b){float dot=a.X*b.X+a.Y*b.Y+a.Z*b.Z+a.W*b.W;dot=MathHelper.Clamp(dot,-1f,1f);return 2f*(float)Math.Acos(Math.Abs(dot));}public ListGetDirtyAsteroids(){return _dirtyAsteroids.Select(id=>_stateCache[id]).ToList();}public AsteroidState GetState(long asteroidId){AsteroidState state;_stateCache.TryGetValue(asteroidId,out state);return state;}public ListGetDirtyStates(){return _stateCache.Where(kvp=>_dirtyStates.Contains(kvp.Key)).Select(kvp=>kvp.Value).ToList();}public void ClearDirtyStates(){long id;while(_dirtyAsteroids.TryTake(out id)){}}public bool ShouldSave(){return(DateTime.UtcNow-_lastSaveTime).TotalSeconds>=SaveInterval&&!_dirtyStates.IsEmpty;}}private class NetworkMessageCache{private ConcurrentDictionary_messageCache=new ConcurrentDictionary();private ConcurrentQueue_messageQueue=new ConcurrentQueue();public void Clear(){_messageCache.Clear();AsteroidNetworkMessage message;while(_messageQueue.TryDequeue(out message)){}}public void AddMessage(AsteroidNetworkMessage message){if(_messageCache.TryAdd(message.EntityId,message)){_messageQueue.Enqueue(message);}}public void ProcessMessages(ActionsendAction){int processedCount=0;const int MAX_MESSAGES_PER_UPDATE=50;AsteroidNetworkMessage message;while(processedCountMAX_MESSAGES_PER_UPDATE*2){Log.Info($"Clearing {_messageQueue.Count} pending messages due to backup");Clear();}}}private ConcurrentDictionary_zoneCache=new ConcurrentDictionary();private AsteroidStateCache _stateCache=new AsteroidStateCache();private NetworkMessageCache _messageCache=new NetworkMessageCache();public AsteroidSpawner(RealGasGiantsApi realGasGiantsApi){_realGasGiantsApi=realGasGiantsApi;}private class PlayerMovementData{public Vector3D LastPosition{get;set;}public DateTime LastUpdateTime{get;set;}public double Speed{get;set;}public QueueSpeedHistory{get;private set;}private const int SPEED_HISTORY_LENGTH=10;private const double SPEED_SPIKE_THRESHOLD=2.0;public PlayerMovementData(){SpeedHistory=new Queue(SPEED_HISTORY_LENGTH);}public void AddSpeedSample(double speed){SpeedHistory.Enqueue(speed);if(SpeedHistory.Count>SPEED_HISTORY_LENGTH)SpeedHistory.Dequeue();}public double GetSmoothedSpeed(){if(SpeedHistory.Count==0)return Speed;double avg=SpeedHistory.Average();var validSpeeds=SpeedHistory.Where(s=>s<=avg*SPEED_SPIKE_THRESHOLD);return validSpeeds.Any()?validSpeeds.Average():avg;}}public void Init(int seed){if(!MyAPIGateway.Session.IsServer)return;Log.Info("Initializing AsteroidSpawner");_asteroids=new ConcurrentBag();_worldLoadTime=DateTime.UtcNow;rand=new Random(seed);AsteroidSettings.Seed=seed;}public IEnumerableGetAsteroids(){return _asteroids;}public void AddAsteroid(AsteroidEntity asteroid){_asteroids.Add(asteroid);_newAsteroidTimestamps[asteroid.EntityId]=DateTime.UtcNow;Log.Info($"Added new asteroid {asteroid.EntityId} with grace period");}public bool TryRemoveAsteroid(AsteroidEntity asteroid){return _asteroids.TryTake(out asteroid);}public bool ContainsAsteroid(long asteroidId){return _asteroids.Any(a=>a.EntityId==asteroidId);}private void LoadAsteroidsInRange(Vector3D playerPosition,AsteroidZone zone){int skippedCount=0;int respawnedCount=0;ListskippedPositions=new List();ListrespawnedPositions=new List();foreach(AsteroidState state in _despawnedAsteroids.ToArray()){if(!zone.IsPointInZone(state.Position))continue;bool tooClose=_asteroids.Any(a=>Vector3D.DistanceSquared(a.PositionComp.GetPosition(),state.Position)0){Log.Info($"Skipped respawn of {skippedCount} asteroids due to proximity to other asteroids or duplicate ID.");}if(respawnedCount>0){Log.Info($"Respawned {respawnedCount} asteroids at positions: {string.Join(", ",respawnedPositions.Select(p=>p.ToString()))}");}}public void Close(){if(!MyAPIGateway.Session.IsServer)return;try{Log.Info("Closing AsteroidSpawner");_zoneCache.Clear();playerZones.Clear();playerMovementData.Clear();if(_asteroids!=null){foreach(AsteroidEntity asteroid in _asteroids){try{MyEntities.Remove(asteroid);asteroid.Close();}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error closing individual asteroid");}}_asteroids=null;}_updateQueue=new ConcurrentQueue();_networkMessages=new ConcurrentQueue();}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error in Close method");}}public void AssignMergedZonesToPlayers(ListmergedZones){const double REASSIGNMENT_THRESHOLD=100.0;const double OVERLAP_BUFFER=50.0;try{Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);var newPlayerZones=new ConcurrentDictionary();var zoneAssignments=new ConcurrentDictionary>();foreach(IMyPlayer player in players){Vector3D playerPos=player.GetPosition();PlayerMovementData movementData;if(!playerMovementData.TryGetValue(player.IdentityId,out movementData)){movementData=new PlayerMovementData{LastPosition=playerPos,LastUpdateTime=DateTime.UtcNow,Speed=0};playerMovementData[player.IdentityId]=movementData;}if(IsPlayerMovingTooFast(player)){continue;}AsteroidZone bestZone=null;double bestDistance=double.MaxValue;foreach(AsteroidZone zone in mergedZones){double distance=Vector3D.Distance(playerPos,zone.Center);if(!(distance();}zoneAssignments[bestZone].Add(player.IdentityId);}foreach(var zoneAssignment in zoneAssignments){AsteroidZone zone=zoneAssignment.Key;var playerIds=zoneAssignment.Value;if(playerIds.Count>AsteroidSettings.MaxPlayersPerZone){OptimizeZoneAssignments(zone,playerIds,newPlayerZones,mergedZones);}}foreach(var kvp in newPlayerZones){long playerId=kvp.Key;AsteroidZone newZone=kvp.Value;AsteroidZone oldZone;if(playerZones.TryGetValue(playerId,out oldZone)){IMyPlayer player=players.FirstOrDefault(p=>p.IdentityId==playerId);if(player!=null){Vector3D playerPos=player.GetPosition();double distanceToOldZone=Vector3D.Distance(playerPos,oldZone.Center);double distanceToNewZone=Vector3D.Distance(playerPos,newZone.Center);if(distanceToNewZone>distanceToOldZone*2){Log.Info($"Possible teleportation detected for player {player.DisplayName}. Reassigning zone.");HandlePlayerTeleportation(player,newZone,oldZone);}}}playerZones[playerId]=newZone;}CleanupUnassignedZones();}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error in AssignMergedZonesToPlayers");}}private void HandlePlayerTeleportation(IMyPlayer player,AsteroidZone newZone,AsteroidZone oldZone){Vector3D playerPos=player.GetPosition();playerMovementData[player.IdentityId]=new PlayerMovementData{LastPosition=playerPos,LastUpdateTime=DateTime.UtcNow,Speed=0};if(oldZone!=null){var potentialOrphans=_asteroids.Where(a=>oldZone.IsPointInZone(a.PositionComp.GetPosition())&&!newZone.IsPointInZone(a.PositionComp.GetPosition())).ToList();foreach(var asteroid in potentialOrphans){Log.Info($"Handling asteroid {asteroid.EntityId} during teleport transition");if(Vector3D.Distance(asteroid.PositionComp.GetPosition(),newZone.Center)<=newZone.Radius*1.5){newZone.ContainedAsteroids.Add(asteroid.EntityId);Log.Info($"Transferred asteroid {asteroid.EntityId} to new zone");}else{RemoveAsteroid(asteroid);Log.Info($"Removed asteroid {asteroid.EntityId} during zone transition");}}}if(!playerZones.Values.Contains(oldZone)){CleanupZone(oldZone);}Log.Info($"Player {player.DisplayName} teleported from {oldZone?.Center} to {newZone.Center}");}private AsteroidZone FindAlternateZone(Vector3D position,AsteroidZone excludeZone,ListmergedZones){return mergedZones.Where(z=>z!=excludeZone&&Vector3D.Distance(position,z.Center)<=z.Radius).OrderBy(z=>Vector3D.Distance(position,z.Center)).FirstOrDefault();}private void CleanupUnassignedZones(){var assignedZones=new HashSet(playerZones.Values);foreach(AsteroidZone zone in playerZones.Values.ToList()){if(!assignedZones.Contains(zone)){CleanupZone(zone);}}}private void CleanupZone(AsteroidZone zone){if(zone==null)return;var asteroidsToRemove=_asteroids.Where(a=>a!=null&&!a.MarkedForClose&&zone.IsPointInZone(a.PositionComp.GetPosition())).ToList();if(asteroidsToRemove.Count>0){Log.Info($"Cleaning up {asteroidsToRemove.Count} asteroids from abandoned zone at {zone.Center}");foreach(var asteroid in asteroidsToRemove){RemoveAsteroid(asteroid);}}}private void AssignZonesToPlayers(){Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(IMyPlayer player in players){Vector3D playerPosition=player.GetPosition();PlayerMovementData data;if(playerMovementData.TryGetValue(player.IdentityId,out data)){if(data.Speed>AsteroidSettings.ZoneSpeedThreshold){Log.Info($"Player {player.DisplayName} moving too fast ({data.Speed:F2} m/s), skipping zone assignment.");continue;}}AsteroidZone existingZone;if(playerZones.TryGetValue(player.IdentityId,out existingZone)){existingZone.LastActiveTime=DateTime.UtcNow;if(!existingZone.IsPointInZone(playerPosition)){existingZone.MarkForRemoval();if(existingZone.State==ZoneState.Removed){var newZone=new AsteroidZone(playerPosition,AsteroidSettings.ZoneRadius);playerZones[player.IdentityId]=newZone;}}}else if(data?.Speed<=AsteroidSettings.ZoneSpeedThreshold){playerZones[player.IdentityId]=new AsteroidZone(playerPosition,AsteroidSettings.ZoneRadius);}}}private void OptimizeZoneAssignments(AsteroidZone zone,HashSetplayerIds,ConcurrentDictionarynewPlayerZones,ListmergedZones){Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);var playerDistances=playerIds.Select(id=>players.FirstOrDefault(p=>p.IdentityId==id)).Where(player=>player!=null).Select(player=>new{PlayerId=player.IdentityId,Distance=Vector3D.Distance(player.GetPosition(),zone.Center)}).OrderBy(p=>p.Distance).ToList();for(int i=AsteroidSettings.MaxPlayersPerZone;ip.IdentityId==playerId);if(player==null)continue;AsteroidZone alternateZone=FindAlternateZone(player.GetPosition(),zone,mergedZones);if(alternateZone!=null){newPlayerZones[playerId]=alternateZone;}}}public void MergeZones(){ListmergedZones=new List();foreach(AsteroidZone zone in playerZones.Values){bool merged=false;foreach(AsteroidZone mergedZone in mergedZones){double distance=Vector3D.Distance(zone.Center,mergedZone.Center);double combinedRadius=zone.Radius+mergedZone.Radius;if(!(distance<=combinedRadius))continue;Vector3D newCenter=(zone.Center+mergedZone.Center)/2;double newRadius=Math.Max(zone.Radius,mergedZone.Radius)+distance/2;mergedZone.Center=newCenter;mergedZone.Radius=newRadius;mergedZone.AsteroidCount+=zone.AsteroidCount;merged=true;break;}if(!merged){mergedZones.Add(new AsteroidZone(zone.Center,zone.Radius){AsteroidCount=zone.AsteroidCount});}}AssignMergedZonesToPlayers(mergedZones);}public void UpdateZones(){Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);DictionaryupdatedZones=new Dictionary();HashSetoldZones=new HashSet(playerZones.Values);foreach(IMyPlayer player in players){Vector3D playerPosition=player.GetPosition();PlayerMovementData data;if(playerMovementData.TryGetValue(player.IdentityId,out data)){if(AsteroidSettings.DisableZoneWhileMovingFast&&IsPlayerMovingTooFast(player)){continue;}}bool playerInZone=false;foreach(AsteroidZone zone in playerZones.Values){if(zone.IsPointInZone(playerPosition)){playerInZone=true;oldZones.Remove(zone);updatedZones[player.IdentityId]=zone;break;}}if(!playerInZone){AsteroidZone newZone=new AsteroidZone(playerPosition,AsteroidSettings.ZoneRadius);updatedZones[player.IdentityId]=newZone;}}foreach(var oldZone in oldZones){Log.Info($"Zone at {oldZone.Center} is no longer active, cleaning up asteroids");CleanupZone(oldZone);}playerZones=new ConcurrentDictionary(updatedZones);if(!MyAPIGateway.Utilities.IsDedicated){MainSession.I.UpdateClientZones(playerZones.ToDictionary(kvp=>kvp.Key,kvp=>kvp.Value));}}private AsteroidZone GetCachedZone(long playerId,Vector3D playerPosition){ZoneCache cache;if(_zoneCache.TryGetValue(playerId,out cache)&&!cache.IsExpired()){if(cache.Zone.IsPointInZone(playerPosition))return cache.Zone;}AsteroidZone zone=new AsteroidZone(playerPosition,AsteroidSettings.ZoneRadius);_zoneCache.AddOrUpdate(playerId,new ZoneCache{Zone=zone,LastUpdateTime=DateTime.UtcNow},(key,oldCache)=>new ZoneCache{Zone=zone,LastUpdateTime=DateTime.UtcNow});return zone;}private int _spawnIntervalTimer=0;private int _updateIntervalTimer=0;private DateTime _lastCleanupTime=DateTime.MinValue;private bool _isCleanupRunning=false;private const double CLEANUP_COOLDOWN_SECONDS=10.0;public void UpdateTick(){if(!MyAPIGateway.Session.IsServer)return;AssignZonesToPlayers();MergeZones();UpdateZones();SendZoneUpdates();try{Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(IMyPlayer player in players){AsteroidZone zone=GetCachedZone(player.IdentityId,player.GetPosition());if(zone!=null){LoadAsteroidsInRange(player.GetPosition(),zone);}}if(_updateIntervalTimer<=0){UpdateAsteroids(playerZones.Values.ToList());ProcessAsteroidUpdates();_updateIntervalTimer=AsteroidSettings.UpdateInterval;}else{_updateIntervalTimer--;}if(_spawnIntervalTimer>0){_spawnIntervalTimer--;}else{SpawnAsteroids(playerZones.Values.ToList());_spawnIntervalTimer=AsteroidSettings.SpawnInterval;}SendPositionUpdates();if(_updateIntervalTimer%600==0){try{ValidateAsteroidTracking();}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error triggering validation");}}if(!_isCleanupRunning&&(DateTime.UtcNow-_lastCleanupTime).TotalSeconds>=CLEANUP_COOLDOWN_SECONDS){if(_asteroids.Count>0){Log.Info($"Starting cleanup check");CleanupOrphanedAsteroids();}}}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error in UpdateTick");}}private void UpdateAsteroids(Listzones){var asteroidsToRemove=new List();var currentAsteroids=_asteroids.ToList();var activeZones=zones.Where(z=>!z.IsMarkedForRemoval).ToList();foreach(var zone in zones){zone.TransferredFromOtherZone.Clear();}foreach(var asteroid in currentAsteroids){if(asteroid==null||asteroid.MarkedForClose)continue;Vector3D asteroidPosition=asteroid.PositionComp.GetPosition();bool inAnyZone=false;AsteroidZone primaryZone=null;double closestDistance=double.MaxValue;foreach(var zone in activeZones){if(zone.IsPointInZone(asteroidPosition)){double distance=Vector3D.DistanceSquared(asteroidPosition,zone.Center);if(distancez.IsMarkedForRemoval&&z.IsPointInZone(asteroidPosition)&&!z.CanBeRemoved());if(removingZone==null){asteroidsToRemove.Add(asteroid);}}}const int REMOVAL_BATCH_SIZE=50;for(int i=0;iGetDistanceToClosestPlayer(a.Position));int updatesProcessed=0;foreach(AsteroidState asteroidState in dirtyAsteroids){if(updatesProcessed>=UpdatesPerTick)break;AsteroidNetworkMessage message=new AsteroidNetworkMessage(asteroidState.Position,asteroidState.Size,asteroidState.Velocity,Vector3D.Zero,asteroidState.Type,false,asteroidState.EntityId,false,true,asteroidState.Rotation);_messageCache.AddMessage(message);updatesProcessed++;}}private double GetDistanceToClosestPlayer(Vector3D position){double minDistance=double.MaxValue;foreach(AsteroidZone zone in playerZones.Values){double distance=Vector3D.DistanceSquared(zone.Center,position);if(distancezones){if(!MyAPIGateway.Session.IsServer)return;int totalSpawnAttempts=0;if(AsteroidSettings.MaxAsteroidCount==0){Log.Info("Asteroid spawning is disabled.");return;}int totalAsteroidsSpawned=0;int totalZoneSpawnAttempts=0;ListskippedPositions=new List();ListspawnedPositions=new List();UpdatePlayerMovementData();foreach(AsteroidZone zone in zones){int asteroidsSpawned=0;int zoneSpawnAttempts=0;Log.Info($"Attempting spawn in zone at {zone.Center}, current count: {zone.TotalAsteroidCount}/{AsteroidSettings.MaxAsteroidsPerZone}");if(zone.TotalAsteroidCount>=AsteroidSettings.MaxAsteroidsPerZone){Log.Info($"Zone at {zone.Center} is at capacity ({zone.TotalAsteroidCount} asteroids)");continue;}bool skipSpawning=false;Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(IMyPlayer player in players){Vector3D playerPosition=player.GetPosition();if(!zone.IsPointInZone(playerPosition))continue;PlayerMovementData data;if(!playerMovementData.TryGetValue(player.IdentityId,out data))continue;if(IsPlayerMovingTooFast(player)){skipSpawning=true;Log.Info($"Skipping asteroid spawning for player {player.DisplayName} due to high speed: {data.Speed:F2} m/s > {AsteroidSettings.ZoneSpeedThreshold} m/s");break;}}if(skipSpawning){continue;}while(zone.AsteroidCountAsteroidSettings.MinimumRingInfluenceForSpawn){validPosition=true;isInRing=true;Log.Info($"Position {newPosition} is within a gas giant ring (influence: {ringInfluence})");}}if(!isInRing){validPosition=IsValidSpawnPosition(newPosition,zones);}}while(!validPosition&&zoneSpawnAttempts=AsteroidSettings.MaxZoneAttempts||totalSpawnAttempts>=AsteroidSettings.MaxTotalAttempts)break;Vector3D newVelocity;if(!AsteroidSettings.CanSpawnAsteroidAtPoint(newPosition,out newVelocity,isInRing)){continue;}if(IsNearVanillaAsteroid(newPosition)){Log.Info($"Position {newPosition} is near a vanilla asteroid, skipping.");skippedPositions.Add(newPosition);continue;}if(AsteroidSettings.MaxAsteroidCount!=-1&&_asteroids.Count>=AsteroidSettings.MaxAsteroidCount){Log.Warning($"Maximum asteroid count of {AsteroidSettings.MaxAsteroidCount} reached. No more asteroids will be spawned until existing ones are removed.");return;}if(zone.AsteroidCount>=AsteroidSettings.MaxAsteroidsPerZone){Log.Info($"Zone at {zone.Center} has reached its maximum asteroid count ({AsteroidSettings.MaxAsteroidsPerZone}). Skipping further spawning in this zone.");break;}float spawnChance=isInRing?MathHelper.Lerp(0.1f,1f,ringInfluence)*AsteroidSettings.MaxRingAsteroidDensityMultiplier:1f;if(MainSession.I.Rand.NextDouble()>spawnChance){Log.Info($"Asteroid spawn skipped due to density scaling (spawn chance: {spawnChance})");continue;}AsteroidType type=AsteroidSettings.GetAsteroidType(newPosition);float size=AsteroidSettings.GetAsteroidSize(newPosition);if(isInRing){size*=MathHelper.Lerp(0.5f,1f,ringInfluence);}Quaternion rotation=Quaternion.CreateFromYawPitchRoll((float)MainSession.I.Rand.NextDouble()*MathHelper.TwoPi,(float)MainSession.I.Rand.NextDouble()*MathHelper.TwoPi,(float)MainSession.I.Rand.NextDouble()*MathHelper.TwoPi);AsteroidEntity asteroid=AsteroidEntity.CreateAsteroid(newPosition,size,newVelocity,type,rotation);if(asteroid==null)continue;_asteroids.Add(asteroid);zone.AsteroidCount++;spawnedPositions.Add(newPosition);_messageCache.AddMessage(new AsteroidNetworkMessage(newPosition,size,newVelocity,Vector3D.Zero,type,false,asteroid.EntityId,false,true,rotation));asteroidsSpawned++;Log.Info($"Spawned asteroid at {newPosition} with size {size} and type {type}");}totalAsteroidsSpawned+=asteroidsSpawned;totalZoneSpawnAttempts+=zoneSpawnAttempts;}if(!AsteroidSettings.EnableLogging)return;if(skippedPositions.Count>0){Log.Info($"Skipped spawning asteroids due to proximity to vanilla asteroids. Positions: {string.Join(", ",skippedPositions.Select(p=>p.ToString()))}");}if(spawnedPositions.Count>0){Log.Info($"Spawned asteroids at positions: {string.Join(", ",spawnedPositions.Select(p=>p.ToString()))}");}}private void UpdatePlayerMovementData(){const double MIN_TIME_DELTA=0.016;const double MAX_TIME_DELTA=1.0;const double TELEPORT_THRESHOLD=1000.0;Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(IMyPlayer player in players){Vector3D currentPosition=player.GetPosition();DateTime currentTime=DateTime.UtcNow;PlayerMovementData data;if(!playerMovementData.TryGetValue(player.IdentityId,out data)){data=new PlayerMovementData{LastPosition=currentPosition,LastUpdateTime=currentTime,Speed=0};playerMovementData[player.IdentityId]=data;continue;}double timeDelta=(currentTime-data.LastUpdateTime).TotalSeconds;timeDelta=MathHelper.Clamp(timeDelta,MIN_TIME_DELTA,MAX_TIME_DELTA);double distance=Vector3D.Distance(currentPosition,data.LastPosition);if(distance>TELEPORT_THRESHOLD){Log.Info($"Player {player.DisplayName} likely teleported: {distance:F0}m");data.LastPosition=currentPosition;data.LastUpdateTime=currentTime;data.SpeedHistory.Clear();continue;}double instantaneousSpeed=distance/timeDelta;data.AddSpeedSample(instantaneousSpeed);data.Speed=data.GetSmoothedSpeed();data.LastPosition=currentPosition;data.LastUpdateTime=currentTime;if(Math.Abs(instantaneousSpeed-data.Speed)>AsteroidSettings.ZoneSpeedThreshold*0.1){Log.Info($"Player {player.DisplayName} speed: {data.Speed:F2} m/s (instant: {instantaneousSpeed:F2} m/s)");}}}private bool IsValidSpawnPosition(Vector3D position,Listzones){BoundingSphereD sphere=new BoundingSphereD(position,AsteroidSettings.MinDistanceFromPlayer);Listentities=new List();MyGamePruningStructure.GetAllTopMostEntitiesInSphere(ref sphere,entities,MyEntityQueryType.Both);foreach(MyEntity entity in entities){if(entity is IMyCharacter||entity is IMyShipController){return false;}}if(AsteroidSettings.EnableGasGiantRingSpawning&&_realGasGiantsApi!=null&&_realGasGiantsApi.IsReady){float ringInfluence=_realGasGiantsApi.GetRingInfluenceAtPositionGlobal(position);if(ringInfluence>AsteroidSettings.MinimumRingInfluenceForSpawn){return true;}}foreach(AsteroidSettings.SpawnableArea area in AsteroidSettings.ValidSpawnLocations){if(area.ContainsPoint(position))return true;}foreach(AsteroidZone zone in zones){if(zone.IsPointInZone(position)){if(zone.TotalAsteroidCount>=AsteroidSettings.MaxAsteroidsPerZone){Log.Info($"Zone at {zone.Center} is at capacity ({zone.TotalAsteroidCount} asteroids)");return false;}return true;}}return false;}public void SendNetworkMessages(){if(!MyAPIGateway.Session.IsServer||!MyAPIGateway.Utilities.IsDedicated)return;try{int messagesSent=0;_messageCache.ProcessMessages(message=>{if(message.EntityId==0){Log.Warning("Attempted to send message for asteroid with ID 0");return;}AsteroidEntity asteroid=MyEntities.GetEntityById(message.EntityId)as AsteroidEntity;if(asteroid==null){Log.Warning($"Attempted to send update for non-existent asteroid {message.EntityId}");return;}AsteroidNetworkMessage updateMessage=new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(),asteroid.Properties.Diameter,asteroid.Physics.LinearVelocity,asteroid.Physics.AngularVelocity,asteroid.Type,false,asteroid.EntityId,false,false,Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix));byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(updateMessage);if(messageBytes==null||messageBytes.Length==0){Log.Warning("Failed to serialize network message");return;}Log.Info($"Server: Sending position update for asteroid ID {updateMessage.EntityId}, "+$"Position: {updateMessage.GetPosition()}, "+$"Velocity: {updateMessage.GetVelocity()}, "+$"Data size: {messageBytes.Length} bytes");MyAPIGateway.Multiplayer.SendMessageToOthers(32000,messageBytes);messagesSent++;});if(messagesSent>0){Log.Info($"Server: Successfully sent {messagesSent} asteroid updates");}}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error sending network messages");}}private void RemoveAsteroid(AsteroidEntity asteroid){if(asteroid==null)return;try{var existingEntity=MyEntities.GetEntityById(asteroid.EntityId)as AsteroidEntity;if(existingEntity==null||existingEntity.MarkedForClose){AsteroidEntity removedFromBag;_asteroids.TryTake(out removedFromBag);_messageCache.Clear();return;}if(existingEntity!=asteroid){Log.Warning($"Entity mismatch for asteroid {asteroid.EntityId} - skipping removal");return;}AsteroidEntity removedAsteroid;if(_asteroids.TryTake(out removedAsteroid)){if(MyAPIGateway.Session.IsServer){var message=new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(),asteroid.Properties.Diameter,Vector3D.Zero,Vector3D.Zero,asteroid.Type,false,asteroid.EntityId,true,false,Quaternion.Identity);byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(message);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,messageBytes);Log.Info($"Sent removal message for asteroid {asteroid.EntityId}");}MyEntities.Remove(asteroid);asteroid.Close();_messageCache.Clear();Log.Info($"Successfully removed asteroid {asteroid.EntityId} from world");}}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),$"Error removing asteroid {asteroid?.EntityId}");}}public void SendPositionUpdates(){if(!MyAPIGateway.Session.IsServer)return;try{var batchPacket=new AsteroidBatchUpdatePacket();Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(AsteroidEntity asteroid in _asteroids){if(asteroid==null||asteroid.MarkedForClose)continue;bool isNearPlayer=false;Vector3D asteroidPos=asteroid.PositionComp.GetPosition();foreach(IMyPlayer player in players){double distSquared=Vector3D.DistanceSquared(player.GetPosition(),asteroidPos);if(distSquared<=AsteroidSettings.ZoneRadius*AsteroidSettings.ZoneRadius){isNearPlayer=true;break;}}if(!isNearPlayer)continue;_stateCache.UpdateState(asteroid);}var dirtyAsteroids=_stateCache.GetDirtyAsteroids();foreach(AsteroidState state in dirtyAsteroids){batchPacket.Updates.Add(state);}if(batchPacket.Updates.Count>0){byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(batchPacket);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,messageBytes);Log.Info($"Server: Sent batch update for {batchPacket.Updates.Count} changed asteroids");}_stateCache.ClearDirtyStates();}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error sending position updates");}}private bool IsNearVanillaAsteroid(Vector3D position){BoundingSphereD sphere=new BoundingSphereD(position,AsteroidSettings.MinDistanceFromVanillaAsteroids);Listentities=new List();MyGamePruningStructure.GetAllTopMostEntitiesInSphere(ref sphere,entities,MyEntityQueryType.Static);foreach(MyEntity entity in entities){IMyVoxelMap voxelMap=entity as IMyVoxelMap;if(voxelMap==null||voxelMap.StorageName.StartsWith("mod_"))continue;Log.Info($"Position {position} is near vanilla asteroid {voxelMap.StorageName}");return true;}return false;}private Vector3D RandVector(){double theta=rand.NextDouble()*2.0*Math.PI;double phi=Math.Acos(2.0*rand.NextDouble()-1.0);double sinPhi=Math.Sin(phi);return Math.Pow(rand.NextDouble(),1/3d)*new Vector3D(sinPhi*Math.Cos(theta),sinPhi*Math.Sin(theta),Math.Cos(phi));}public void SendZoneUpdates(){if(!MyAPIGateway.Session.IsServer)return;var zonePacket=new ZoneUpdatePacket();var mergedZoneIds=new HashSet();foreach(var zone1 in playerZones.Values){foreach(var zone2 in playerZones.Values){if(zone1!=zone2){double distance=Vector3D.Distance(zone1.Center,zone2.Center);if(distance<=zone1.Radius+zone2.Radius){mergedZoneIds.Add(zone1.EntityId);mergedZoneIds.Add(zone2.EntityId);}}}}foreach(var kvp in playerZones){zonePacket.Zones.Add(new ZoneData{Center=kvp.Value.Center,Radius=kvp.Value.Radius,PlayerId=kvp.Key,IsActive=true,IsMerged=mergedZoneIds.Contains(kvp.Key),CurrentSpeed=kvp.Value.CurrentSpeed});}byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(zonePacket);MyAPIGateway.Multiplayer.SendMessageToOthers(32001,messageBytes);}private void CleanupOrphanedAsteroids(){if(_isCleanupRunning)return;_isCleanupRunning=true;try{var currentZones=playerZones.Values.ToList();var asteroidsToRemove=new List();var trackedAsteroids=_asteroids.ToList();foreach(var asteroid in trackedAsteroids){if(asteroid==null||asteroid.MarkedForClose)continue;Vector3D asteroidPosition=asteroid.PositionComp.GetPosition();bool isInAnyZone=false;foreach(var zone in currentZones){if(zone.IsPointInZone(asteroidPosition)){isInAnyZone=true;break;}}if(!isInAnyZone){Log.Info($"Found orphaned asteroid {asteroid.EntityId} at {asteroidPosition}");asteroidsToRemove.Add(asteroid);}}if(asteroidsToRemove.Count>0){Log.Info($"Cleaning up {asteroidsToRemove.Count} orphaned asteroids");foreach(var asteroid in asteroidsToRemove){RemoveAsteroid(asteroid);}}_lastCleanupTime=DateTime.UtcNow;}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error in cleanup");}finally{_isCleanupRunning=false;}}private const int VALIDATION_BATCH_SIZE=100;private const double VALIDATION_MAX_TIME_MS=16.0;private int _lastValidatedIndex=0;private void ValidateAsteroidTracking(){try{var startTime=DateTime.UtcNow;var trackedIds=_asteroids.Select(a=>a.EntityId).ToHashSet();var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);var entityList=entities.Skip(_lastValidatedIndex).Take(VALIDATION_BATCH_SIZE).ToList();int untrackedCount=0;int processedCount=0;foreach(var entity in entityList){if((DateTime.UtcNow-startTime).TotalMilliseconds>VALIDATION_MAX_TIME_MS){Log.Warning($"Validation taking too long - processed {processedCount} entities before timeout");break;}var asteroid=entity as AsteroidEntity;if(asteroid!=null&&!asteroid.MarkedForClose){if(!trackedIds.Contains(asteroid.EntityId)){untrackedCount++;Log.Warning($"Found untracked asteroid {asteroid.EntityId} at {asteroid.PositionComp.GetPosition()}");}}processedCount++;}_lastValidatedIndex+=processedCount;if(_lastValidatedIndex>=entities.Count){_lastValidatedIndex=0;if(untrackedCount>0){Log.Warning($"Validation cycle complete: Found {untrackedCount} untracked asteroids in world");}}double elapsedMs=(DateTime.UtcNow-startTime).TotalMilliseconds;if(elapsedMs>5.0){Log.Info($"Asteroid validation took {elapsedMs:F2}ms to process {processedCount} entities");}}catch(Exception ex){Log.Exception(ex,typeof(AsteroidSpawner),"Error in asteroid validation");_lastValidatedIndex=0;}}private bool IsPlayerMovingTooFast(IMyPlayer player){PlayerMovementData data;if(!playerMovementData.TryGetValue(player.IdentityId,out data))return false;double smoothedSpeed=data.GetSmoothedSpeed();bool isTooFast=smoothedSpeed>AsteroidSettings.ZoneSpeedThreshold;if(isTooFast){Log.Info($"Player {player.DisplayName} moving too fast: {smoothedSpeed:F2} m/s (threshold: {AsteroidSettings.ZoneSpeedThreshold} m/s)");}return isTooFast;}}}using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using VRage.Game;using VRage.ModAPI;using VRage.Utils;using VRageMath;using VRageRender;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids{public partial class MainSession{private Queue_lastRemovedZones=new Queue(5);private HashSet_orphanedAsteroids=new HashSet();private int _orphanCheckTimer=0;private const int ORPHAN_CHECK_INTERVAL=60;private const double HITBOX_DISPLAY_DISTANCE=1000;public override void Draw(){try{if(MyAPIGateway.Session?.Player?.Character==null)return;Vector3D characterPosition=MyAPIGateway.Session.Player.Character.PositionComp.GetPosition();if(AsteroidSettings.EnableLogging){DrawPlayerZones(characterPosition);DrawNearestAsteroidDebug(characterPosition);DrawOrphanedAsteroids();}else{DrawNearbyAsteroidHitboxes(characterPosition);}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error in Draw");}}private void DrawNearbyAsteroidHitboxes(Vector3D characterPosition){try{var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities,e=>e is AsteroidEntity&&Vector3D.DistanceSquared(e.GetPosition(),characterPosition)<=HITBOX_DISPLAY_DISTANCE*HITBOX_DISPLAY_DISTANCE);foreach(AsteroidEntity asteroid in entities.Cast()){if(asteroid==null||asteroid.MarkedForClose)continue;float healthPercentage=asteroid.Properties.GetIntegrityPercentage()/100f;float instabilityPercentage=asteroid.Properties.GetInstabilityPercentage()/100f;Color baseColor=Color.Lerp(Color.Red,Color.Green,healthPercentage);Color hitboxColor=new Color(baseColor.R,baseColor.G,baseColor.B,15);Vector3D wobbleOffset=Vector3D.Zero;if(instabilityPercentage>0){double time=DateTime.Now.TimeOfDay.TotalSeconds;float wobbleAmount=instabilityPercentage*0.2f;wobbleOffset=new Vector3D(Math.Sin(time*5f)*wobbleAmount,Math.Cos(time*3f)*wobbleAmount,Math.Sin(time*4f)*wobbleAmount)*asteroid.Properties.Radius;}MatrixD worldMatrix=MatrixD.CreateTranslation(asteroid.PositionComp.GetPosition()+wobbleOffset);MySimpleObjectDraw.DrawTransparentSphere(ref worldMatrix,asteroid.Properties.Radius,ref hitboxColor,MySimpleObjectRasterizer.Wireframe,8,null,MyStringId.GetOrCompute("Square"),0.1f);if(instabilityPercentage>0.5f){float pulseIntensity=(float)Math.Sin(DateTime.Now.TimeOfDay.TotalSeconds*4f)*0.5f+0.5f;Color pulseColor=new Color(255,0,0,(byte)(15*pulseIntensity));MatrixD pulseMatrix=MatrixD.CreateTranslation(asteroid.PositionComp.GetPosition());float pulseRadius=asteroid.Properties.Radius*(1f+(0.1f*pulseIntensity));MySimpleObjectDraw.DrawTransparentSphere(ref pulseMatrix,pulseRadius,ref pulseColor,MySimpleObjectRasterizer.Wireframe,8,null,MyStringId.GetOrCompute("Square"),0.05f);}}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error drawing hitbox spheres");}}private void DrawPlayerZones(Vector3D characterPosition){foreach(var kvp in _clientZones){DrawZone(kvp.Key,kvp.Value,characterPosition);if(kvp.Value.IsMerged){DrawZoneMergeConnections(kvp.Value);}}foreach(var removedZone in _lastRemovedZones){Color fadeColor=Color.Red*0.5f;DrawZoneSphere(removedZone,fadeColor);}}private void DrawZone(long playerId,AsteroidZone zone,Vector3D characterPosition){bool isLocalPlayer=playerId==MyAPIGateway.Session.Player.IdentityId;bool playerInZone=zone.IsPointInZone(characterPosition);Color zoneColor=DetermineZoneColor(isLocalPlayer,playerInZone,zone.IsMerged);DrawZoneSphere(zone,zoneColor);DrawZoneInfo(zone,isLocalPlayer,playerInZone);}private void DrawZoneInfo(AsteroidZone zone,bool isLocalPlayer,bool playerInZone){Vector3D textPosition=zone.Center+new Vector3D(0,zone.Radius+100,0);string zoneInfo=$"Asteroids: {zone.ContainedAsteroids?.Count??0}\n"+$"Active: {!zone.IsMarkedForRemoval}\n"+$"Last Active: {(DateTime.UtcNow-zone.LastActiveTime).TotalSeconds:F1}s ago";MyTransparentGeometry.AddLineBillboard(MyStringId.GetOrCompute("Square"),Color.White,textPosition,Vector3.Right,(float)zone.Radius*0.1f,0.5f,MyBillboard.BlendTypeEnum.Standard);}private Color DetermineZoneColor(bool isLocalPlayer,bool playerInZone,bool isMerged){if(isLocalPlayer&&MyAPIGateway.Session?.Player!=null){AsteroidZone currentZone;if(_clientZones.TryGetValue(MyAPIGateway.Session.Player.IdentityId,out currentZone)){if(currentZone.IsMarkedForRemoval)return Color.Red;}}if(isLocalPlayer)return playerInZone?Color.Green:Color.Yellow;if(isMerged)return Color.Purple;return Color.Blue;}private void DrawZoneSphere(AsteroidZone zone,Color color){MatrixD worldMatrix=MatrixD.CreateTranslation(zone.Center);MySimpleObjectDraw.DrawTransparentSphere(ref worldMatrix,(float)zone.Radius,ref color,MySimpleObjectRasterizer.Wireframe,20,null,MyStringId.GetOrCompute("Square"),5f);}private void DrawZoneMergeConnections(AsteroidZone sourceZone){foreach(var targetZone in _clientZones.Values){if(targetZone.IsMerged&&targetZone!=sourceZone){double distance=Vector3D.Distance(sourceZone.Center,targetZone.Center);if(distance<=sourceZone.Radius+targetZone.Radius){Color connectionColor=(sourceZone.IsMarkedForRemoval||targetZone.IsMarkedForRemoval)?Color.Red:Color.Purple;Vector4 mergeLineColor=connectionColor.ToVector4();MySimpleObjectDraw.DrawLine(sourceZone.Center,targetZone.Center,MyStringId.GetOrCompute("Square"),ref mergeLineColor,2f);}}}}private void DrawNearestAsteroidDebug(Vector3D characterPosition){AsteroidEntity nearestAsteroid=FindNearestAsteroid(characterPosition);if(nearestAsteroid==null)return;DrawAsteroidClientPosition(nearestAsteroid);DrawAsteroidServerComparison(nearestAsteroid);}private void DrawAsteroidClientPosition(AsteroidEntity asteroid){Vector3D clientPosition=asteroid.PositionComp.GetPosition();MatrixD clientWorldMatrix=MatrixD.CreateTranslation(clientPosition);Color clientColor=Color.Red;MySimpleObjectDraw.DrawTransparentSphere(ref clientWorldMatrix,asteroid.Properties.Radius,ref clientColor,MySimpleObjectRasterizer.Wireframe,20);}private void DrawAsteroidServerComparison(AsteroidEntity asteroid){Vector3D serverPosition;Quaternion serverRotation;if(!_serverPositions.TryGetValue(asteroid.EntityId,out serverPosition)||!_serverRotations.TryGetValue(asteroid.EntityId,out serverRotation))return;DrawServerPositionSphere(asteroid,serverPosition);DrawPositionComparisonLine(asteroid.PositionComp.GetPosition(),serverPosition);DrawRotationComparison(asteroid,serverPosition,serverRotation);DisplayAsteroidDebugInfo(asteroid,serverPosition,serverRotation);}private void DrawServerPositionSphere(AsteroidEntity asteroid,Vector3D serverPosition){MatrixD serverWorldMatrix=MatrixD.CreateTranslation(serverPosition);Color serverColor=Color.Blue;MySimpleObjectDraw.DrawTransparentSphere(ref serverWorldMatrix,asteroid.Properties.Radius,ref serverColor,MySimpleObjectRasterizer.Wireframe,20);}private void DrawPositionComparisonLine(Vector3D clientPosition,Vector3D serverPosition){Vector4 lineColor=Color.Yellow.ToVector4();MySimpleObjectDraw.DrawLine(clientPosition,serverPosition,MyStringId.GetOrCompute("Square"),ref lineColor,0.1f);}private void DrawRotationComparison(AsteroidEntity asteroid,Vector3D serverPosition,Quaternion serverRotation){Vector3D clientForward=asteroid.WorldMatrix.Forward;Vector3D serverForward=MatrixD.CreateFromQuaternion(serverRotation).Forward;Vector3D clientPosition=asteroid.PositionComp.GetPosition();Vector4 clientDirColor=Color.Red.ToVector4();Vector4 serverDirColor=Color.Blue.ToVector4();MySimpleObjectDraw.DrawLine(clientPosition,clientPosition+clientForward*asteroid.Properties.Radius*2,MyStringId.GetOrCompute("Square"),ref clientDirColor,0.1f);MySimpleObjectDraw.DrawLine(serverPosition,serverPosition+serverForward*asteroid.Properties.Radius*2,MyStringId.GetOrCompute("Square"),ref serverDirColor,0.1f);}private void DisplayAsteroidDebugInfo(AsteroidEntity asteroid,Vector3D serverPosition,Quaternion serverRotation){float angleDifference=GetQuaternionAngleDifference(Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix),serverRotation);MyAPIGateway.Utilities.ShowNotification($"Asteroid {asteroid.EntityId}:\n"+$"Position diff: {Vector3D.Distance(asteroid.PositionComp.GetPosition(),serverPosition):F2}m\n"+$"Rotation diff: {MathHelper.ToDegrees(angleDifference):F1}°",16);}private void UpdateOrphanedAsteroidsList(){try{_orphanedAsteroids.Clear();var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);foreach(var entity in entities){var asteroid=entity as AsteroidEntity;if(asteroid==null)continue;Vector3D asteroidPosition=asteroid.PositionComp.GetPosition();bool isInAnyZone=false;foreach(var zoneKvp in _clientZones){if(zoneKvp.Value.IsPointInZone(asteroidPosition)){isInAnyZone=true;break;}}if(!isInAnyZone){_orphanedAsteroids.Add(asteroid);if(MyAPIGateway.Session.IsServer){var removalMessage=new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(),asteroid.Properties.Diameter,Vector3D.Zero,Vector3D.Zero,asteroid.Type,false,asteroid.EntityId,true,false,Quaternion.Identity);var messageBytes=MyAPIGateway.Utilities.SerializeToBinary(removalMessage);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,messageBytes);MyEntities.Remove(asteroid);asteroid.Close();}}}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error updating orphaned asteroids list");}}private void DrawOrphanedAsteroids(){foreach(var asteroid in _orphanedAsteroids){if(asteroid==null||asteroid.MarkedForClose)continue;try{Vector3D asteroidPosition=asteroid.PositionComp.GetPosition();MatrixD worldMatrix=MatrixD.CreateTranslation(asteroidPosition);Color orphanColor=new Color(255,0,0,128);MySimpleObjectDraw.DrawTransparentSphere(ref worldMatrix,asteroid.Properties.Radius*1.5f,ref orphanColor,MySimpleObjectRasterizer.Wireframe,4,null,MyStringId.GetOrCompute("Square"),2f);Vector3D textPosition=asteroidPosition+new Vector3D(0,asteroid.Properties.Radius*2,0);string orphanInfo=$"Orphaned Asteroid {asteroid.EntityId}\nType: {asteroid.Type}";MyTransparentGeometry.AddLineBillboard(MyStringId.GetOrCompute("Square"),Color.Red,textPosition,Vector3.Right,asteroid.Properties.Radius*0.2f,0.5f,MyBillboard.BlendTypeEnum.Standard);}catch(Exception ex){_orphanedAsteroids.Remove(asteroid);Log.Warning($"Error drawing orphaned asteroid {asteroid.EntityId}: {ex.Message}");}}}}}using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;using Sandbox.ModAPI;using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.IO;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids{internal class Log{private class LogEntry{public string Message{get;set;}public int Count{get;set;}public DateTime FirstOccurrence{get;set;}public DateTime LastOccurrence{get;set;}}private static Log I;private readonly TextWriter _writer;private readonly ConcurrentDictionary_cachedMessages;private readonly object _lockObject;private readonly int _flushIntervalSeconds;private DateTime _lastFlushTime;private Log(){var logFileName=MyAPIGateway.Session.IsServer?"DynamicAsteroids_Server.log":"DynamicAsteroids_Client.log";MyAPIGateway.Utilities.DeleteFileInGlobalStorage(logFileName);_writer=MyAPIGateway.Utilities.WriteFileInGlobalStorage(logFileName);_writer.WriteLine($" Dynamic Asteroids - {(MyAPIGateway.Session.IsServer?"Server":"Client")} Debug Log\n===========================================\n");_writer.WriteLine($"{DateTime.UtcNow:HH:mm:ss}: Logger initialized for {(MyAPIGateway.Session.IsServer?"Server":"Client")}");_writer.Flush();_cachedMessages=new ConcurrentDictionary();_flushIntervalSeconds=1;_lockObject=new object();_lastFlushTime=DateTime.UtcNow;}public static void Info(string message){if(AsteroidSettings.EnableLogging)I?.CacheLogMessage(message);}public static void Warning(string message){if(AsteroidSettings.EnableLogging)I?.WriteToFile("WARNING: "+message);}public static void Exception(Exception ex,Type callingType,string prefix=""){if(AsteroidSettings.EnableLogging)I?._LogException(ex,callingType,prefix);}public static void Init(){Close();I=new Log();}public static void Close(){if(I!=null){Info("Closing log writer.");I.FlushCache();I._writer.Close();}I=null;}public static void Update(){if(I!=null&&(DateTime.UtcNow-I._lastFlushTime).TotalSeconds>=I._flushIntervalSeconds){I.FlushCache();}}private void CacheLogMessage(string message){_cachedMessages.AddOrUpdate(message,new LogEntry{Message=message,Count=1,FirstOccurrence=DateTime.UtcNow,LastOccurrence=DateTime.UtcNow},(key,existing)=>{existing.Count++;existing.LastOccurrence=DateTime.UtcNow;return existing;});}private void FlushCache(){lock(_lockObject){DateTime currentTime=DateTime.UtcNow;var entriesToRemove=new List();foreach(var kvp in _cachedMessages){LogEntry entry=kvp.Value;if(!((currentTime-entry.LastOccurrence).TotalSeconds>=_flushIntervalSeconds))continue;string logMessage=entry.Count>1?$"[{currentTime:HH:mm:ss}]: Repeated {entry.Count} times in {(entry.LastOccurrence-entry.FirstOccurrence).TotalSeconds:F1}s: {entry.Message}":$"[{currentTime:HH:mm:ss}]: {entry.Message}";WriteToFile(logMessage);entriesToRemove.Add(kvp.Key);}foreach(var key in entriesToRemove){LogEntry removedEntry;_cachedMessages.TryRemove(key,out removedEntry);}_lastFlushTime=currentTime;}}private void WriteToFile(string message){_writer.WriteLine(message);_writer.Flush();}private void _LogException(Exception ex,Type callingType,string prefix=""){if(ex==null){WriteToFile("Null exception! CallingType: "+callingType.FullName);return;}string exceptionMessage=prefix+$"Exception in {callingType.FullName}! {ex.Message}\n{ex.StackTrace}\n{ex.InnerException}";WriteToFile(exceptionMessage);MyAPIGateway.Utilities.ShowNotification($"{ex.GetType().Name} in Dynamic Asteroids! Check logs for more info.",10000,"Red");}}}using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;using RealGasGiants;using Sandbox.Definitions;using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.Collections.Generic;using System.Linq;using Sandbox.Game;using VRage.Game;using VRage.Game.Components;using VRage.Game.ModAPI;using VRage.Input;using VRage.ModAPI;using VRage.Utils;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids{[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]public partial class MainSession:MySessionComponentBase{public static MainSession I;public Random Rand;private int seed;public AsteroidSpawner _spawner;private int _saveStateTimer;private int _networkMessageTimer;public RealGasGiantsApi RealGasGiantsApi{get;private set;}private int _testTimer=0;private KeenRicochetMissileBSWorkaroundHandler _missileHandler;private Dictionary_serverPositions=new Dictionary();private Dictionary_serverRotations=new Dictionary();private Dictionary_clientZones=new Dictionary();public override void LoadData(){I=this;Log.Init();Log.Info("Log initialized in LoadData method.");AsteroidSettings.LoadSettings();seed=AsteroidSettings.Seed;Rand=new Random(seed);RealGasGiantsApi=new RealGasGiantsApi();RealGasGiantsApi.Load();Log.Info("RealGasGiants API loaded in LoadData");AsteroidDamageHandler damageHandler=new AsteroidDamageHandler();_missileHandler=new KeenRicochetMissileBSWorkaroundHandler(damageHandler);if(MyAPIGateway.Session.IsServer){_spawner=new AsteroidSpawner(RealGasGiantsApi);_spawner.Init(seed);}MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(32000,OnSecureMessageReceived);MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(32001,OnSecureMessageReceived);MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(32002,OnSettingsSyncReceived);MyAPIGateway.Utilities.MessageEntered+=OnMessageEntered;}public override void BeforeStart(){MyVisualScriptLogicProvider.PlayerConnected+=OnPlayerConnected;Log.Info($"RealGasGiants API IsReady: {RealGasGiantsApi.IsReady}");}protected override void UnloadData(){try{Log.Info("Unloading data in MainSession");if(_spawner!=null){if(MyAPIGateway.Session.IsServer){var asteroidsToRemove=_spawner.GetAsteroids().ToList();foreach(var asteroid in asteroidsToRemove){try{MyEntities.Remove(asteroid);asteroid.Close();}catch(Exception removeEx){Log.Exception(removeEx,typeof(MainSession),"Error removing asteroid during unload");}}_spawner.Close();_spawner=null;}}MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(32000,OnSecureMessageReceived);MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(32001,OnSecureMessageReceived);MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(32002,OnSettingsSyncReceived);MyAPIGateway.Utilities.MessageEntered-=OnMessageEntered;MyVisualScriptLogicProvider.PlayerConnected-=OnPlayerConnected;if(RealGasGiantsApi!=null){RealGasGiantsApi.Unload();RealGasGiantsApi=null;}_missileHandler.Unload();AsteroidSettings.SaveSettings();Log.Close();I=null;}catch(Exception ex){MyLog.Default.WriteLine($"Error in UnloadData: {ex}");try{Log.Exception(ex,typeof(MainSession),"Error in UnloadData");}catch{}}}private void OnMessageEntered(string messageText,ref bool sendToOthers){IMyPlayer player=MyAPIGateway.Session.Player;if(player==null||!IsPlayerAdmin(player))return;if(!messageText.StartsWith("/dynamicasteroids")&&!messageText.StartsWith("/dn"))return;var args=messageText.Split(' ');if(args.Length<=1)return;switch(args[1].ToLower()){case"createspawnarea":double radius;if(args.Length==3&&double.TryParse(args[2],out radius)){CreateSpawnArea(radius);sendToOthers=false;}break;case"removespawnarea":if(args.Length==3){RemoveSpawnArea(args[2]);sendToOthers=false;}break;}}private bool IsPlayerAdmin(IMyPlayer player){return MyAPIGateway.Session.OnlineMode==MyOnlineModeEnum.OFFLINE||MyAPIGateway.Session.IsUserAdmin(player.SteamUserId);}private void CreateSpawnArea(double radius){IMyPlayer player=MyAPIGateway.Session.Player;if(player==null)return;Vector3D position=player.GetPosition();var name=$"Area_{position.GetHashCode()}";BoundingBoxD boundingBox=new BoundingBoxD(position-new Vector3D(radius),position+new Vector3D(radius));MyPlanet closestPlanet=MyGamePruningStructure.GetClosestPlanet(ref boundingBox);if(closestPlanet!=null){Log.Info($"Cannot create spawn area '{name}' at {position} with radius {radius}: Intersects with a planet.");MyAPIGateway.Utilities.ShowMessage("DynamicAsteroids",$"Cannot create spawn area '{name}' at {position} with radius {radius}: Intersects with a planet.");return;}AsteroidSettings.AddSpawnableArea(name,position,radius);Log.Info($"Created spawn area '{name}' at {position} with radius {radius}");MyAPIGateway.Utilities.ShowMessage("DynamicAsteroids",$"Created spawn area '{name}' at {position} with radius {radius}");}private void RemoveSpawnArea(string name){AsteroidSettings.RemoveSpawnableArea(name);Log.Info($"Removed spawn area '{name}'");MyAPIGateway.Utilities.ShowMessage("DynamicAsteroids",$"Removed spawn area '{name}'");}public override void UpdateAfterSimulation(){try{if(MyAPIGateway.Session.IsServer){_spawner?.UpdateTick();Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);foreach(IMyPlayer player in players){Vector3D playerPosition=player.GetPosition();AsteroidEntity nearestAsteroid=FindNearestAsteroid(playerPosition);if(nearestAsteroid!=null){}}if(_saveStateTimer>0)_saveStateTimer--;if(_networkMessageTimer>0)_networkMessageTimer--;else{_spawner?.SendNetworkMessages();_networkMessageTimer=AsteroidSettings.NetworkMessageInterval;Log.Info("Server: Sending network messages to clients.");}}if(AsteroidSettings.EnableMiddleMouseAsteroidSpawn&&MyAPIGateway.Input.IsNewKeyPressed(MyKeys.MiddleButton)&&MyAPIGateway.Session?.Player!=null){Vector3D position=MyAPIGateway.Session.Player.GetPosition();Vector3D velocity=MyAPIGateway.Session.Player.Character?.Physics?.LinearVelocity??Vector3D.Zero;AsteroidType type=DetermineAsteroidType();if(MyAPIGateway.Session.IsServer){var asteroid=AsteroidEntity.CreateAsteroid(position,Rand.Next(50),velocity,type);if(asteroid!=null&&_spawner!=null){_spawner.AddAsteroid(asteroid);var message=new AsteroidNetworkMessage(position,asteroid.Properties.Diameter,velocity,asteroid.Physics.AngularVelocity,type,false,asteroid.EntityId,false,true,Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix));byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(message);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,messageBytes);}}else{var request=new AsteroidNetworkMessage(position,50,velocity,Vector3D.Zero,type,false,0,false,true,Quaternion.Identity);byte[]messageBytes=MyAPIGateway.Utilities.SerializeToBinary(request);MyAPIGateway.Multiplayer.SendMessageToServer(32000,messageBytes);}Log.Info($"Asteroid spawn requested at {position} with velocity {velocity}");}if(MyAPIGateway.Session?.Player?.Character!=null){Vector3D characterPosition=MyAPIGateway.Session.Player.Character.PositionComp.GetPosition();AsteroidEntity nearestAsteroid=FindNearestAsteroid(characterPosition);if(nearestAsteroid!=null&&AsteroidSettings.EnableLogging){Vector3D angularVelocity=nearestAsteroid.Physics.AngularVelocity;string rotationString=$"({angularVelocity.X:F2}, {angularVelocity.Y:F2}, {angularVelocity.Z:F2})";string message=$"Nearest Asteroid: {nearestAsteroid.EntityId} ({nearestAsteroid.Type})\nRotation: {rotationString}";MyAPIGateway.Utilities.ShowNotification(message,1000/60);nearestAsteroid.DrawDebugSphere();}if(AsteroidSettings.EnableLogging){if(!MyAPIGateway.Session.IsServer){var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);int localAsteroidCount=entities.Count(e=>e is AsteroidEntity);if(AsteroidSettings.EnableLogging){MyAPIGateway.Utilities.ShowNotification($"Client Asteroids: {localAsteroidCount}",1000/60);}}}if(++_orphanCheckTimer>=ORPHAN_CHECK_INTERVAL){_orphanCheckTimer=0;UpdateOrphanedAsteroidsList();}}if(++_testTimer>=240){_testTimer=0;TestNearestGasGiant();}Log.Update();}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error in UpdateAfterSimulation: ");}}public void DebugMissiles(){if(!AsteroidSettings.EnableLogging)return;var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);int missileCount=0;foreach(var entity in entities){IMyMissile missile=entity as IMyMissile;if(missile!=null){var ammoDef=missile.AmmoDefinition as MyMissileAmmoDefinition;if(ammoDef!=null){MyAPIGateway.Utilities.ShowNotification($"Missile detected:\n"+$"Type: {ammoDef.Id.SubtypeName}\n",1000/60);}}}if(missileCount==0){MyAPIGateway.Utilities.ShowNotification("No missiles found in world",1000/60);}}private void TestNearestGasGiant(){if(RealGasGiantsApi==null||!RealGasGiantsApi.IsReady||MyAPIGateway.Session?.Player==null)return;if(!AsteroidSettings.EnableLogging)return;Vector3D playerPosition=MyAPIGateway.Session.Player.GetPosition();MyPlanet nearestGasGiant=FindNearestGasGiant(playerPosition);float ringInfluence=RealGasGiantsApi.GetRingInfluenceAtPositionGlobal(playerPosition);string message;if(nearestGasGiant!=null){var basicInfo=RealGasGiantsApi.GetGasGiantConfig_BasicInfo_Base(nearestGasGiant);if(basicInfo.Item1){double distance=Vector3D.Distance(playerPosition,nearestGasGiant.PositionComp.GetPosition())-basicInfo.Item2;message=$"Nearest Gas Giant:\n"+$"Distance: {distance:N0}m\n"+$"Radius: {basicInfo.Item2:N0}m\n"+$"Color: {basicInfo.Item3}\n"+$"Skin: {basicInfo.Item4}\n"+$"Day Length: {basicInfo.Item5:F2}s\n"+$"Current Ring Influence: {ringInfluence:F3}";}else{message="Failed to get gas giant info";}}else{message=$"Current Ring Influence: {ringInfluence:F3}";}if(AsteroidSettings.EnableLogging)MyAPIGateway.Utilities.ShowNotification(message,4000,"White");}private MyPlanet FindNearestGasGiant(Vector3D position){const double searchRadius=1000000000;MyPlanet nearestGasGiant=null;double nearestDistance=double.MaxValue;var gasGiants=RealGasGiantsApi.GetAtmoGasGiantsAtPosition(position);foreach(MyPlanet gasGiant in gasGiants){var basicInfo=RealGasGiantsApi.GetGasGiantConfig_BasicInfo_Base(gasGiant);if(!basicInfo.Item1)continue;float gasGiantRadius=basicInfo.Item2;Vector3D gasGiantCenter=gasGiant.PositionComp.GetPosition();double distance=Vector3D.Distance(position,gasGiantCenter)-gasGiantRadius;if(!(distance(message);if(container!=null&&container.Messages!=null){Log.Info($"Received batch update with {container.Messages.Length} asteroids");foreach(var asteroidMessage in container.Messages){ProcessClientMessage(asteroidMessage);}return;}}catch{var asteroidMessage=MyAPIGateway.Utilities.SerializeFromBinary(message);if(!MyAPIGateway.Session.IsServer){ProcessClientMessage(asteroidMessage);}else{ProcessServerMessage(asteroidMessage,steamId);}}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),$"Error processing received message");}}public class NetworkMessageVerification{public static bool ValidateMessage(AsteroidNetworkMessage message){if(ReferenceEquals(message,null))return false;if(message.EntityId==0&&!message.IsInitialCreation)return false;if(double.IsNaN(message.PosX)||double.IsNaN(message.PosY)||double.IsNaN(message.PosZ))return false;return true;}}private void ProcessServerMessage(AsteroidNetworkMessage message,ulong steamId){if(!NetworkMessageVerification.ValidateMessage(message)){Log.Warning($"Server received invalid message from client {steamId}");return;}if(message.IsInitialCreation&&message.EntityId==0){var asteroid=AsteroidEntity.CreateAsteroid(message.GetPosition(),message.Size,message.GetVelocity(),message.GetType());if(asteroid!=null&&_spawner!=null){_spawner.AddAsteroid(asteroid);var response=new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(),asteroid.Properties.Diameter,asteroid.Physics.LinearVelocity,asteroid.Physics.AngularVelocity,asteroid.Type,false,asteroid.EntityId,false,true,Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix));byte[]responseBytes=MyAPIGateway.Utilities.SerializeToBinary(response);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,responseBytes);}}}private void RemoveAsteroidOnClient(long entityId){Log.Info($"Client: Removing asteroid with ID {entityId}");AsteroidEntity asteroid=MyEntities.GetEntityById(entityId)as AsteroidEntity;if(asteroid!=null){try{MyEntities.Remove(asteroid);asteroid.Close();Log.Info($"Client: Successfully removed asteroid {entityId}");}catch(Exception ex){Log.Exception(ex,typeof(MainSession),$"Error removing asteroid {entityId} on client");}}else{Log.Warning($"Client: Could not find asteroid with ID {entityId} to remove");}}private void CreateNewAsteroidOnClient(AsteroidNetworkMessage message){try{var asteroid=AsteroidEntity.CreateAsteroid(message.GetPosition(),message.Size,message.GetVelocity(),message.GetType(),message.GetRotation(),message.EntityId);if(asteroid!=null){if(asteroid.Physics!=null){asteroid.Physics.LinearVelocity=message.GetVelocity();asteroid.Physics.AngularVelocity=message.GetAngularVelocity();}Log.Info($"Client: Successfully created asteroid {message.EntityId} with server rotation");}else{Log.Warning($"Client: Failed to create asteroid {message.EntityId}");}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error creating asteroid on client");}}private void ProcessClientMessage(AsteroidNetworkMessage message){try{if(!NetworkMessageVerification.ValidateMessage(message)){Log.Warning($"Client received invalid message - ID: {message.EntityId}");return;}if(message.IsRemoval){RemoveAsteroidOnClient(message.EntityId);return;}AsteroidEntity existingAsteroid=MyEntities.GetEntityById(message.EntityId)as AsteroidEntity;if(message.IsInitialCreation){if(existingAsteroid!=null){Log.Warning($"Received creation message for existing asteroid {message.EntityId}");return;}CreateNewAsteroidOnClient(message);}else if(existingAsteroid!=null){UpdateExistingAsteroidOnClient(existingAsteroid,message);}else{Log.Warning($"Received update for non-existent asteroid {message.EntityId}");CreateNewAsteroidOnClient(new AsteroidNetworkMessage(message.GetPosition(),message.Size,message.GetVelocity(),message.GetAngularVelocity(),message.GetType(),false,message.EntityId,false,true,message.GetRotation()));}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),$"Error processing client message");}}private float GetQuaternionAngleDifference(Quaternion a,Quaternion b){float dot=a.X*b.X+a.Y*b.Y+a.Z*b.Z+a.W*b.W;dot=MathHelper.Clamp(dot,-1f,1f);return 2f*(float)Math.Acos(Math.Abs(dot));}private void UpdateServerPosition(long entityId,Vector3D position){_serverPositions[entityId]=position;}private const double DRIFT_TOLERANCE=0.1;private Dictionary_lastPhysicsResetTime=new Dictionary();private const double PHYSICS_RESET_COOLDOWN=1.0;private void UpdateExistingAsteroidOnClient(AsteroidEntity asteroid,AsteroidNetworkMessage message){try{Vector3D newPosition=message.GetPosition();Vector3D newVelocity=message.GetVelocity();Quaternion newRotation=message.GetRotation();Vector3D newAngularVelocity=message.GetAngularVelocity();Vector3D oldPosition=asteroid.PositionComp.GetPosition();Vector3D oldVelocity=asteroid.Physics?.LinearVelocity??Vector3D.Zero;Vector3D oldAngularVelocity=asteroid.Physics?.AngularVelocity??Vector3D.Zero;double positionDrift=Vector3D.Distance(oldPosition,newPosition);bool hasDrift=positionDrift>DRIFT_TOLERANCE;bool wasMoving=oldVelocity.LengthSquared()>0.01||oldAngularVelocity.LengthSquared()>0.01;bool isMoving=newVelocity.LengthSquared()>0.01||newAngularVelocity.LengthSquared()>0.01;bool stateChanged=wasMoving!=isMoving;_serverPositions[asteroid.EntityId]=newPosition;_serverRotations[asteroid.EntityId]=newRotation;if(stateChanged&&asteroid.Physics!=null){var oldPhysics=asteroid.Physics;asteroid.Physics=null;MatrixD newWorldMatrix=MatrixD.CreateFromQuaternion(newRotation);newWorldMatrix.Translation=newPosition;asteroid.WorldMatrix=newWorldMatrix;asteroid.PositionComp.SetPosition(newPosition);asteroid.Physics=oldPhysics;asteroid.Physics.Clear();asteroid.Physics.LinearVelocity=newVelocity;asteroid.Physics.AngularVelocity=newAngularVelocity;Log.Info($"Physics reset for asteroid {asteroid.EntityId} due to state change");}else{MatrixD newWorldMatrix=MatrixD.CreateFromQuaternion(newRotation);newWorldMatrix.Translation=newPosition;asteroid.WorldMatrix=newWorldMatrix;asteroid.PositionComp.SetPosition(newPosition);if(asteroid.Physics!=null){asteroid.Physics.LinearVelocity=newVelocity;asteroid.Physics.AngularVelocity=newAngularVelocity;}}if(hasDrift||stateChanged){Log.Info($"Client asteroid {asteroid.EntityId} update:"+$"\nDrift: {positionDrift:F2}m"+$"\nState: {(isMoving?"Moving":"Stopped")}"+$"\nVelocity: {newVelocity.Length():F2}m/s"+$"\nAngular velocity: {newAngularVelocity.Length():F3}rad/s");}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),$"Error updating client asteroid {asteroid.EntityId}");}}private AsteroidEntity FindNearestAsteroid(Vector3D characterPosition){if(characterPosition==null)return null;var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);AsteroidEntity nearestAsteroid=null;double minDistance=double.MaxValue;foreach(var entity in entities){AsteroidEntity asteroid=entity as AsteroidEntity;if(asteroid!=null){double distance=Vector3D.DistanceSquared(characterPosition,asteroid.PositionComp.GetPosition());if(distanceserverZones){if(!MyAPIGateway.Utilities.IsDedicated){_clientZones.Clear();foreach(var kvp in serverZones){_clientZones[kvp.Key]=kvp.Value;}}}private void ProcessZoneMessage(byte[]message){try{var zonePacket=MyAPIGateway.Utilities.SerializeFromBinary(message);if(zonePacket?.Zones==null)return;if(MyAPIGateway.Session.IsServer&&!MyAPIGateway.Utilities.IsDedicated){if(_spawner!=null){UpdateClientZones(_spawner.playerZones.ToDictionary(kvp=>kvp.Key,kvp=>kvp.Value));}return;}var previousZones=new Dictionary(_clientZones);_clientZones.Clear();foreach(var zoneData in zonePacket.Zones){var newZone=new AsteroidZone(zoneData.Center,zoneData.Radius){IsMarkedForRemoval=!zoneData.IsActive,IsMerged=zoneData.IsMerged,CurrentSpeed=zoneData.CurrentSpeed};_clientZones[zoneData.PlayerId]=newZone;previousZones.Remove(zoneData.PlayerId);}foreach(var removedZone in previousZones.Values){_lastRemovedZones.Enqueue(removedZone);while(_lastRemovedZones.Count>5)_lastRemovedZones.Dequeue();if(!MyAPIGateway.Session.IsServer){var entities=new HashSet();MyAPIGateway.Entities.GetEntities(entities);foreach(var entity in entities){var asteroid=entity as AsteroidEntity;if(asteroid!=null&&removedZone.IsPointInZone(asteroid.PositionComp.GetPosition())){RemoveAsteroidOnClient(asteroid.EntityId);}}}}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error processing zone packet");}}private void OnSettingsSyncReceived(ushort handlerId,byte[]data,ulong steamId,bool isFromServer){if(!isFromServer)return;try{var settings=MyAPIGateway.Utilities.SerializeFromBinary(data);if(settings!=null){AsteroidSettings.EnableLogging=settings.EnableLogging;Log.Info($"Received settings from server. Debug logging: {settings.EnableLogging}");}}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error processing settings sync");}}private void OnPlayerConnected(long identityId){MyAPIGateway.Utilities.InvokeOnGameThread(()=>{Listplayers=new List();MyAPIGateway.Players.GetPlayers(players);var player=players.FirstOrDefault(p=>p.IdentityId==identityId);if(player!=null){Log.Info($"Syncing settings to player {player.DisplayName}");SendSettingsToClient(player.SteamUserId);}},"SyncSettings");}private void SendSettingsToClient(ulong steamId){try{var settings=new SettingsSyncMessage{EnableLogging=AsteroidSettings.EnableLogging};byte[]data=MyAPIGateway.Utilities.SerializeToBinary(settings);MyAPIGateway.Multiplayer.SendMessageTo(32002,data,steamId);Log.Info($"Sent settings to client {steamId}");}catch(Exception ex){Log.Exception(ex,typeof(MainSession),"Error sending settings to client");}}}}using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.Collections.Generic;using System.Collections.Immutable;using VRage;using VRage.ModAPI;using VRageMath;namespace RealGasGiants{public class RealGasGiantsApi{private bool _apiInit;private Func_spawnGasGiant;private Func_setGasGiantConfig_name;private Func_setGasGiantConfig_basicInfo;private Func>_getGasGiantConfig_basicInfo_base;private Func>_getGasGiantConfig_basicInfo_gravity;private Func_setGasGiantConfig_atmoInfo;private Func>_getGasGiantConfig_atmoInfo;private Func_setGasGiantConfig_ringInfo;private Func>_getGasGiantConfig_ringInfo_base;private Func>_getGasGiantConfig_ringInfo_visual;private Func>_getGasGiantConfig_ringInfo_size;private Func_setGasGiantConfig_interiorInfo;private Func>_getGasGiantConfig_interiorInfo;private Func_setGasGiantConfig_resourceInfo;private Func>_getGasGiantConfig_resourceInfo_planet;private Func>_getGasGiantConfig_resourceInfo_ring;private Func>_getGasGiantSkinList;private Func>_getGasGiantRingSkinList;private Func>_getOverlapGasGiantsAtPosition;private Func>_getAtmoGasGiantsAtPosition;private Func_getShadowFactor;private Func_getAtmoDensity;private Func_getAtmoDensityGlobal;private Func_getRingInfluenceAtPosition;private Func_getRingInfluenceAtPositionGlobal;private Action _forceMainUpdate;private const long Channel=321421229679;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;}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(IReadOnlyDictionarydelegates){_apiInit=true;_spawnGasGiant=(Func)delegates["SpawnGasGiant"];_setGasGiantConfig_name=(Func)delegates["SetGasGiantConfig_Name"];_setGasGiantConfig_basicInfo=(Func)delegates["SetGasGiantConfig_BasicInfo"];_getGasGiantConfig_basicInfo_base=(Func>)delegates["GetGasGiantConfig_BasicInfo_Base"];_getGasGiantConfig_basicInfo_gravity=(Func>)delegates["GetGasGiantConfig_BasicInfo_Gravity"];_setGasGiantConfig_atmoInfo=(Func)delegates["SetGasGiantConfig_AtmoInfo"];_getGasGiantConfig_atmoInfo=(Func>)delegates["GetGasGiantConfig_AtmoInfo"];_setGasGiantConfig_ringInfo=(Func)delegates["SetGasGiantConfig_RingInfo"];_getGasGiantConfig_ringInfo_base=(Func>)delegates["GetGasGiantConfig_RingInfo_Base"];_getGasGiantConfig_ringInfo_visual=(Func>)delegates["GetGasGiantConfig_RingInfo_Visual"];_getGasGiantConfig_ringInfo_size=(Func>)delegates["GetGasGiantConfig_RingInfo_Size"];_setGasGiantConfig_interiorInfo=(Func)delegates["SetGasGiantConfig_InteriorInfo"];_getGasGiantConfig_interiorInfo=(Func>)delegates["GetGasGiantConfig_InteriorInfo"];_setGasGiantConfig_resourceInfo=(Func)delegates["SetGasGiantConfig_ResourceInfo"];_getGasGiantConfig_resourceInfo_planet=(Func>)delegates["GetGasGiantConfig_ResourceInfo_Planet"];_getGasGiantConfig_resourceInfo_ring=(Func>)delegates["GetGasGiantConfig_ResourceInfo_Ring"];_getGasGiantSkinList=(Func>)delegates["GetGasGiantSkinList"];_getGasGiantRingSkinList=(Func>)delegates["GetGasGiantRingSkinList"];_getOverlapGasGiantsAtPosition=(Func>)delegates["GetOverlapGasGiantsAtPosition"];_getAtmoGasGiantsAtPosition=(Func>)delegates["GetAtmoGasGiantsAtPosition"];_getShadowFactor=(Func)delegates["GetShadowFactor"];_getAtmoDensity=(Func)delegates["GetAtmoDensity"];_getAtmoDensityGlobal=(Func)delegates["GetAtmoDensityGlobal"];_getRingInfluenceAtPosition=(Func)delegates["GetRingInfluenceAtPosition"];_getRingInfluenceAtPositionGlobal=(Func)delegates["GetRingInfluenceAtPositionGlobal"];_forceMainUpdate=(Action)delegates["ForceMainUpdate"];}public MyPlanet SpawnGasGiant(Vector3D position,float radius,Vector3I planetColor,string planetSkin,float gravityStrength,float gravityFalloff,float dayLength)=>_spawnGasGiant?.Invoke(position,radius,planetColor,planetSkin,gravityStrength,gravityFalloff,dayLength)??null;public bool SetGasGiantConfig_Name(MyPlanet planet,string name)=>_setGasGiantConfig_name?.Invoke(planet,name)??false;public bool SetGasGiantConfig_BasicInfo(MyPlanet planet,float radius,Vector3I planetColor,string planetSkin,float gravityStrength,float gravityFalloff,float dayLength)=>_setGasGiantConfig_basicInfo?.Invoke(planet,radius,planetColor,planetSkin,gravityStrength,gravityFalloff,dayLength)??false;public MyTupleGetGasGiantConfig_BasicInfo_Base(MyPlanet planet)=>_getGasGiantConfig_basicInfo_base?.Invoke(planet)??new MyTuple();public MyTupleGetGasGiantConfig_BasicInfo_Gravity(MyPlanet planet)=>_getGasGiantConfig_basicInfo_gravity?.Invoke(planet)??new MyTuple();public bool SetGasGiantConfig_AtmoInfo(MyPlanet planet,float airDensity,float oxygenDensity,float windSpeed)=>_setGasGiantConfig_atmoInfo?.Invoke(planet,airDensity,oxygenDensity,windSpeed)??false;public MyTupleGetGasGiantConfig_AtmoInfo(MyPlanet planet)=>_getGasGiantConfig_atmoInfo?.Invoke(planet)??new MyTuple();public bool SetGasGiantConfig_RingInfo(MyPlanet planet,bool hasRing,string ringSkin,Vector3D ringNormal,Vector3I ringColor,float ringLightMult,float ringShadowMult,float ringInnerScale,float ringOuterScale,float ringLayerSpacingScale,float ringRotationPeriod,bool constrainNearbyAsteroidsToRing)=>_setGasGiantConfig_ringInfo?.Invoke(planet,hasRing,ringSkin,ringNormal,ringColor,ringLightMult,ringShadowMult,ringInnerScale,ringOuterScale,ringLayerSpacingScale,ringRotationPeriod,constrainNearbyAsteroidsToRing)??false;public MyTupleGetGasGiantConfig_RingInfo_Base(MyPlanet planet)=>_getGasGiantConfig_ringInfo_base?.Invoke(planet)??new MyTuple();public MyTupleGetGasGiantConfig_RingInfo_Visual(MyPlanet planet)=>_getGasGiantConfig_ringInfo_visual?.Invoke(planet)??new MyTuple();public MyTupleGetGasGiantConfig_RingInfo_Size(MyPlanet planet)=>_getGasGiantConfig_ringInfo_size?.Invoke(planet)??new MyTuple();public bool SetGasGiantConfig_InteriorInfo(MyPlanet planet,bool asteroidRemoval,bool pressureDamagePlayers,bool pressureDamageGrids)=>_setGasGiantConfig_interiorInfo?.Invoke(planet,asteroidRemoval,pressureDamagePlayers,pressureDamageGrids)??false;public MyTupleGetGasGiantConfig_InteriorInfo(MyPlanet planet)=>_getGasGiantConfig_interiorInfo?.Invoke(planet)??new MyTuple();public bool SetGasGiantConfig_ResourceInfo(MyPlanet planet,bool collectPlanetResources,string collectResourceUpperSubtypeId,float collectResourceUpperAmount,string collectResourceLowerSubtypeId,float collectResourceLowerAmount,bool collectRingResources,string collectResourceRingSubtypeId,float collectResourceRingAmount)=>_setGasGiantConfig_resourceInfo?.Invoke(planet,collectPlanetResources,collectResourceUpperSubtypeId,collectResourceUpperAmount,collectResourceLowerSubtypeId,collectResourceLowerAmount,collectRingResources,collectResourceRingSubtypeId,collectResourceRingAmount)??false;public MyTupleGetGasGiantConfig_ResourceInfo_Planet(MyPlanet planet)=>_getGasGiantConfig_resourceInfo_planet?.Invoke(planet)??new MyTuple();public MyTupleGetGasGiantConfig_ResourceInfo_Ring(MyPlanet planet)=>_getGasGiantConfig_resourceInfo_ring?.Invoke(planet)??new MyTuple();public ListGetGasGiantSkinList()=>_getGasGiantSkinList?.Invoke()??new List();public ListGetGasGiantRingSkinList()=>_getGasGiantRingSkinList?.Invoke()??new List();public ListGetOverlapGasGiantsAtPosition(Vector3D position)=>_getOverlapGasGiantsAtPosition?.Invoke(position)??new List();public ListGetAtmoGasGiantsAtPosition(Vector3D position)=>_getAtmoGasGiantsAtPosition?.Invoke(position)??new List();public float GetShadowFactor(IMyEntity entity)=>_getShadowFactor?.Invoke(entity)??0f;public float GetAtmoDensity(MyPlanet planet,Vector3D position)=>_getAtmoDensity?.Invoke(planet,position)??0f;public float GetAtmoDensityGlobal(Vector3D position)=>_getAtmoDensityGlobal?.Invoke(position)??0f;public float GetRingInfluenceAtPosition(MyPlanet planet,Vector3D position)=>_getRingInfluenceAtPosition?.Invoke(planet,position)??0f;public float GetRingInfluenceAtPositionGlobal(Vector3D position)=>_getRingInfluenceAtPositionGlobal?.Invoke(position)??0f;public void ForceMainUpdate()=>_forceMainUpdate?.Invoke();}}using Sandbox.Definitions;using Sandbox.Game;using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.Collections.Generic;using VRage.Game;using VRage.Game.Entity;using VRage.Game.ModAPI;using VRage.ObjectBuilders;using VRage.Utils;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities{public class AsteroidDamageHandler{private void CreateEffects(Vector3D position){MyVisualScriptLogicProvider.CreateParticleEffectAtPosition("roidbreakparticle1",position);MyVisualScriptLogicProvider.PlaySingleSoundAtPosition("roidbreak",position);}public bool DoDamage(AsteroidEntity asteroid,float damage,MyStringHash damageSource,bool sync,MyHitInfo?hitInfo=null,long attackerId=0,long realHitEntityId=0,bool shouldDetonateAmmo=true,MyStringHash?extraInfo=null){float massToRemove=damage*AsteroidSettings.KgLossPerDamage;float instabilityIncrease=damage*AsteroidSettings.InstabilityPerDamage;asteroid.AddInstability(instabilityIncrease);if(asteroid.Properties.ShouldSpawnChunk()){float chunkMass=asteroid.Properties.Mass*0.1f;SpawnDebrisAtImpact(asteroid,hitInfo?.Position??asteroid.PositionComp.GetPosition(),chunkMass);asteroid.Properties.ReduceMass(chunkMass);asteroid.Properties.ResetInstability();}if(hitInfo.HasValue){asteroid.Properties.ReduceMass(massToRemove);SpawnDebrisAtImpact(asteroid,hitInfo.Value.Position,massToRemove);}if(asteroid.Properties.IsDestroyed()){asteroid.OnDestroy();return true;}return true;}public void SpawnDebrisAtImpact(AsteroidEntity asteroid,Vector3D impactPosition,float massLost){MyPhysicalItemDefinition itemDefinition=MyDefinitionManager.Static.GetPhysicalItemDefinition(new MyDefinitionId(typeof(MyObjectBuilder_Ore),asteroid.Type.ToString()));var newObject=MyObjectBuilderSerializer.CreateNewObject(itemDefinition.Id.TypeId,itemDefinition.Id.SubtypeId.ToString())as MyObjectBuilder_PhysicalObject;float groupingRadius=10.0f;ListnearbyDebris=GetNearbyDebris(impactPosition,groupingRadius,newObject);if(nearbyDebris.Count>0){MyFloatingObject closestDebris=nearbyDebris[0];MyFloatingObjects.AddFloatingObjectAmount(closestDebris,(VRage.MyFixedPoint)massLost);}else{MyFloatingObjects.Spawn(new MyPhysicalInventoryItem((VRage.MyFixedPoint)massLost,newObject),impactPosition,Vector3D.Forward,Vector3D.Up,asteroid?.Physics,entity=>{MyFloatingObject debris=entity as MyFloatingObject;if(debris?.Physics!=null){debris.Physics.LinearVelocity=asteroid?.Physics?.LinearVelocity??Vector3D.Zero;debris.Physics.AngularVelocity=MyUtils.GetRandomVector3Normalized()*5;}});}}private ListGetNearbyDebris(Vector3D position,float radius,MyObjectBuilder_PhysicalObject itemType){ListnearbyDebris=new List();BoundingSphereD boundingSphereD=new BoundingSphereD(position,radius);foreach(var entity in MyAPIGateway.Entities.GetEntitiesInSphere(ref boundingSphereD)){MyFloatingObject floatingObj=entity as MyFloatingObject;if(floatingObj!=null&&floatingObj.Item.Content.GetType()==itemType.GetType()&&floatingObj.Item.Content.SubtypeName==itemType.SubtypeName){nearbyDebris.Add(floatingObj);}}return nearbyDebris;}private Vector3D RandVector(Random rand){var theta=rand.NextDouble()*2.0*Math.PI;var phi=Math.Acos(2.0*rand.NextDouble()-1.0);var sinPhi=Math.Sin(phi);return Math.Pow(rand.NextDouble(),1/3d)*new Vector3D(sinPhi*Math.Cos(theta),sinPhi*Math.Sin(theta),Math.Cos(phi));}}}using Sandbox.Game.Entities;using Sandbox.ModAPI;using System;using System.IO;using Sandbox.Game;using VRage.Game;using VRage.Game.Components;using VRage.Game.Entity;using VRage.Game.ModAPI;using VRage.Game.ModAPI.Interfaces;using VRage.ModAPI;using VRage.Utils;using VRageMath;using CollisionLayers=Sandbox.Engine.Physics.MyPhysics.CollisionLayers;using Color=VRageMath.Color;using VRage;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities{public enum AsteroidType{Ice,Stone,Iron,Nickel,Cobalt,Magnesium,Silicon,Silver,Gold,Platinum,Uraninite}public class AsteroidEntity:MyEntity,IMyDestroyableObject{private static readonly string[]IceAsteroidModels={@"Models\IceAsteroid_1.mwm",@"Models\IceAsteroid_2.mwm",@"Models\IceAsteroid_3.mwm",@"Models\IceAsteroid_4.mwm"};private static readonly string[]StoneAsteroidModels={@"Models\StoneAsteroid_1.mwm",@"Models\StoneAsteroid_2.mwm",@"Models\StoneAsteroid_3.mwm",@"Models\StoneAsteroid_4.mwm",@"Models\StoneAsteroid_5.mwm",@"Models\StoneAsteroid_6.mwm",@"Models\StoneAsteroid_7.mwm",@"Models\StoneAsteroid_8.mwm",@"Models\StoneAsteroid_9.mwm",@"Models\StoneAsteroid_10.mwm",@"Models\StoneAsteroid_11.mwm",@"Models\StoneAsteroid_12.mwm",@"Models\StoneAsteroid_13.mwm",@"Models\StoneAsteroid_14.mwm",@"Models\StoneAsteroid_15.mwm",@"Models\StoneAsteroid_16.mwm"};private static readonly string[]IronAsteroidModels={@"Models\OreAsteroid_Iron.mwm"};private static readonly string[]NickelAsteroidModels={@"Models\OreAsteroid_Nickel.mwm"};private static readonly string[]CobaltAsteroidModels={@"Models\OreAsteroid_Cobalt.mwm"};private static readonly string[]MagnesiumAsteroidModels={@"Models\OreAsteroid_Magnesium.mwm"};private static readonly string[]SiliconAsteroidModels={@"Models\OreAsteroid_Silicon.mwm"};private static readonly string[]SilverAsteroidModels={@"Models\OreAsteroid_Silver.mwm"};private static readonly string[]GoldAsteroidModels={@"Models\OreAsteroid_Gold.mwm"};private static readonly string[]PlatinumAsteroidModels={@"Models\OreAsteroid_Platinum.mwm"};private static readonly string[]UraniniteAsteroidModels={@"Models\OreAsteroid_Uraninite.mwm"};public AsteroidType Type{get;private set;}public string ModelString="";public AsteroidPhysicalProperties Properties{get;private set;}public float Integrity=>Properties.CurrentIntegrity;public bool IsUnstable()=>Properties.IsUnstable();public void UpdateInstability()=>Properties.UpdateInstability();public void AddInstability(float amount)=>Properties.AddInstability(amount);public bool UseDamageSystem=>true;public static AsteroidEntity CreateAsteroid(Vector3D position,float size,Vector3D initialVelocity,AsteroidType type,Quaternion?rotation=null,long?entityId=null){var ent=new AsteroidEntity();try{if(entityId.HasValue)ent.EntityId=entityId.Value;var massRange=AsteroidSettings.MinMaxMassByType[type];string ringDebugInfo;float distanceScale=AsteroidSettings.CalculateMassScaleByDistance(position,MainSession.I.RealGasGiantsApi,out ringDebugInfo);float randomFactor=(float)MainSession.I.Rand.NextDouble()*0.2f-0.1f;float finalMass=MathHelper.Lerp(massRange.MinMass,massRange.MaxMass,distanceScale+randomFactor);finalMass=MathHelper.Clamp(finalMass,massRange.MinMass,massRange.MaxMass);ent.Properties=AsteroidPhysicalProperties.CreateFromMass(finalMass,AsteroidPhysicalProperties.DEFAULT_DENSITY,ent);if(!rotation.HasValue&&MyAPIGateway.Session.IsServer){Vector3D randomAxis=RandVector();float randomAngle=(float)(MainSession.I.Rand.NextDouble()*Math.PI*2);rotation=Quaternion.CreateFromAxisAngle(randomAxis,randomAngle);}else if(!rotation.HasValue){rotation=Quaternion.Identity;}ent.Init(position,ent.Properties.Diameter,initialVelocity,type,rotation);MyEntities.Add(ent);if(!MyEntities.EntityExists(ent.EntityId)){Log.Warning($"Asteroid {ent.EntityId} failed to be added to the scene.");return null;}Log.Info($"Spawned ring asteroid {ent.EntityId}:"+$"\nType: {type}"+$"\nMass Range: {massRange.MinMass:N0}kg - {massRange.MaxMass:N0}kg"+$"\nFinal Mass: {finalMass:N0}kg"+$"\nFinal Diameter: {ent.Properties.Diameter:F2}m"+$"\nRandom Factor: {randomFactor:F3}"+$"\nPosition: {position}"+$"\nVelocity: {initialVelocity.Length():F1}m/s"+$"\n{ringDebugInfo}");return ent;}catch(Exception ex){Log.Exception(ex,typeof(AsteroidEntity),"Exception during asteroid creation");return null;}}private void Init(Vector3D position,float size,Vector3D initialVelocity,AsteroidType type,Quaternion?rotation){try{Type=type;ModelString=SelectModelForAsteroidType(type);Log.Info($"Initializing asteroid at {position} with size {size} and type {type}");float modelScale=size;Init(null,ModelString,null,modelScale);if(string.IsNullOrEmpty(ModelString)){Log.Warning($"Failed to assign model for asteroid type {type}");}PositionComp.SetPosition(position);if(rotation.HasValue){MatrixD worldMatrix=MatrixD.CreateFromQuaternion(rotation.Value);worldMatrix.Translation=position;WorldMatrix=worldMatrix;}CreatePhysics();if(Physics==null){Log.Warning($"Physics creation failed for asteroid {EntityId}");}Physics.LinearVelocity=initialVelocity;if(MyAPIGateway.Session.IsServer){SyncFlag=true;}Log.Info($"Asteroid {EntityId} initialized:"+$"\n - Position: {PositionComp.GetPosition()}"+$"\n - Velocity: {initialVelocity}"+$"\n - Model Scale: {modelScale}"+$"\n - Physics Radius: {Properties.Radius}");}catch(Exception ex){Log.Exception(ex,typeof(AsteroidEntity),"Failed to initialize AsteroidEntity");Flags&=~EntityFlags.Visible;}}private string SelectModelForAsteroidType(AsteroidType type){string modPath=MainSession.I.ModContext.ModPath;switch(type){case AsteroidType.Ice:return GetRandomModel(IceAsteroidModels,modPath);case AsteroidType.Stone:return GetRandomModel(StoneAsteroidModels,modPath);case AsteroidType.Iron:return GetRandomModel(IronAsteroidModels,modPath);case AsteroidType.Nickel:return GetRandomModel(NickelAsteroidModels,modPath);case AsteroidType.Cobalt:return GetRandomModel(CobaltAsteroidModels,modPath);case AsteroidType.Magnesium:return GetRandomModel(MagnesiumAsteroidModels,modPath);case AsteroidType.Silicon:return GetRandomModel(SiliconAsteroidModels,modPath);case AsteroidType.Silver:return GetRandomModel(SilverAsteroidModels,modPath);case AsteroidType.Gold:return GetRandomModel(GoldAsteroidModels,modPath);case AsteroidType.Platinum:return GetRandomModel(PlatinumAsteroidModels,modPath);case AsteroidType.Uraninite:return GetRandomModel(UraniniteAsteroidModels,modPath);default:Log.Info("Invalid AsteroidType, no model selected.");return string.Empty;}}private string GetRandomModel(string[]models,string modPath){if(models.Length==0){Log.Info("Model array is empty");return string.Empty;}int modelIndex=MainSession.I.Rand.Next(models.Length);Log.Info($"Selected model index: {modelIndex}");return Path.Combine(modPath,models[modelIndex]);}public void DrawDebugSphere(){Vector3D asteroidPosition=this.PositionComp.GetPosition();float radius=Properties.Radius;Color sphereColor=Color.Red;Color otherColor=Color.Yellow;MatrixD worldMatrix=MatrixD.CreateTranslation(asteroidPosition);MySimpleObjectDraw.DrawTransparentSphere(ref worldMatrix,radius,ref sphereColor,MySimpleObjectRasterizer.Wireframe,20);BoundingBoxD localBox=PositionComp.LocalAABB;MatrixD boxWorldMatrix=WorldMatrix;MySimpleObjectDraw.DrawTransparentBox(ref boxWorldMatrix,ref localBox,ref otherColor,MySimpleObjectRasterizer.Wireframe,1,0.1f);}public void OnDestroy(){if(!MyAPIGateway.Session.IsServer)return;MyVisualScriptLogicProvider.CreateParticleEffectAtPosition("roidbreakparticle1",PositionComp.GetPosition());MyVisualScriptLogicProvider.PlaySingleSoundAtPosition("roidbreak",PositionComp.GetPosition());var damageHandler=new AsteroidDamageHandler();damageHandler.SpawnDebrisAtImpact(this,PositionComp.GetPosition(),Properties.Mass);var finalRemovalMessage=new AsteroidNetworkMessage(PositionComp.GetPosition(),Properties.Diameter,Vector3D.Zero,Vector3D.Zero,Type,false,EntityId,true,false,Quaternion.Identity);var finalRemovalMessageBytes=MyAPIGateway.Utilities.SerializeToBinary(finalRemovalMessage);MyAPIGateway.Multiplayer.SendMessageToOthers(32000,finalRemovalMessageBytes);if(MainSession.I?._spawner!=null){MainSession.I._spawner.TryRemoveAsteroid(this);}MyEntities.Remove(this);Close();}public bool DoDamage(float damage,MyStringHash damageSource,bool sync,MyHitInfo?hitInfo=null,long attackerId=0,long realHitEntityId=0,bool shouldDetonateAmmo=true,MyStringHash?extraInfo=null){Log.Info($"DoDamage called with damage: {damage}, damageSource: {damageSource}, "+$"integrity before damage: {Properties.CurrentIntegrity}");var damageHandler=new AsteroidDamageHandler();return damageHandler.DoDamage(this,damage,damageSource,sync,hitInfo,attackerId,realHitEntityId,shouldDetonateAmmo,extraInfo);}public void CreatePhysics(){try{if(Physics!=null){Physics.Close();Physics=null;}Log.Info($"Creating physics for asteroid {EntityId}:"+$"\n - Mass: {Properties.Mass:N0}kg"+$"\n - Volume: {Properties.Volume:N0}m³"+$"\n - Radius: {Properties.Radius:F2}m");PhysicsSettings settings=MyAPIGateway.Physics.CreateSettingsForPhysics(this,MatrixD.CreateTranslation(this.PositionComp.GetPosition()),Vector3.Zero,linearDamping:0f,angularDamping:0.01f,rigidBodyFlags:RigidBodyFlag.RBF_DEFAULT,collisionLayer:CollisionLayers.NoVoxelCollisionLayer,isPhantom:false,mass:new ModAPIMass(Properties.Volume,Properties.Mass,Vector3.Zero,Properties.Mass*Matrix.Identity));MyAPIGateway.Physics.CreateSpherePhysics(settings,Properties.Radius);if(MyAPIGateway.Session.IsServer){const float initialMaxSpin=0.2f;Vector3D randomSpin=RandVector()*initialMaxSpin;this.Physics.AngularVelocity=randomSpin;Log.Info($"Server: Set initial spin for asteroid {EntityId}: {randomSpin}");}}catch(Exception ex){Log.Exception(ex,typeof(AsteroidEntity),$"Error creating physics for asteroid {EntityId}");}}private static Vector3D RandVector(){var theta=MainSession.I.Rand.NextDouble()*2.0*Math.PI;var phi=Math.Acos(2.0*MainSession.I.Rand.NextDouble()-1.0);var sinPhi=Math.Sin(phi);return Math.Pow(MainSession.I.Rand.NextDouble(),1/3d)*new Vector3D(sinPhi*Math.Cos(theta),sinPhi*Math.Sin(theta),Math.Cos(phi));}}}using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using VRage.Game;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities{public class AsteroidPhysicalProperties{public float Mass{get;private set;}public float Volume{get;private set;}public float Radius{get;private set;}public float Diameter{get;private set;}public float Density{get;private set;}public float MaximumIntegrity{get;private set;}public float CurrentIntegrity{get;private set;}public float MaxInstability{get;private set;}public float CurrentInstability{get;private set;}public float InstabilityThreshold{get;private set;}private const float CHUNK_THRESHOLD=0.1f;private float _lastChunkThreshold=0f;public const float DEFAULT_DENSITY=917.0f;private AsteroidEntity ParentEntity{get;set;}public AsteroidPhysicalProperties(float diameter,float density=DEFAULT_DENSITY,AsteroidEntity parentEntity=null){ParentEntity=parentEntity;Diameter=diameter;Radius=diameter/2.0f;Density=density;Volume=(4.0f/3.0f)*MathHelper.Pi*(float)Math.Pow(Radius,3);Mass=Volume*Density;MaxInstability=Mass*AsteroidSettings.InstabilityPerMass;InstabilityThreshold=MaxInstability*AsteroidSettings.InstabilityThresholdPercent;CurrentInstability=0;Log.Info($"Created asteroid with:"+$"\n - Diameter: {Diameter:F2}m"+$"\n - Mass: {Mass:N0}kg"+$"\n - Volume: {Volume:N0}m³");}public void AddInstability(float amount){CurrentInstability=Math.Min(MaxInstability,CurrentInstability+amount);}public float GetIntegrityPercentage()=>(CurrentIntegrity/MaximumIntegrity)*100f;public float GetInstabilityPercentage()=>(CurrentInstability/MaxInstability)*100f;public bool IsDestroyed()=>Mass<=0;public bool IsUnstable()=>CurrentInstability>=InstabilityThreshold;public void UpdateInstability(){float previousInstability=CurrentInstability;CurrentInstability=Math.Max(0,CurrentInstability-(AsteroidSettings.InstabilityDecayRate*MyEngineConstants.UPDATE_STEP_SIZE_IN_SECONDS));if(Math.Abs(previousInstability-CurrentInstability)>0.01f){Log.Info($"Instability decay: {previousInstability:F2} -> {CurrentInstability:F2} "+$"(-{previousInstability-CurrentInstability:F2})");}}public void ReduceMass(float damageAmount){float massToRemove=damageAmount*AsteroidSettings.KgLossPerDamage;Mass=Math.Max(0,Mass-massToRemove);}public static AsteroidPhysicalProperties CreateFromMass(float targetMass,float density=DEFAULT_DENSITY,AsteroidEntity parentEntity=null){float volume=targetMass/density;float radius=(float)Math.Pow((3.0f*volume)/(4.0f*MathHelper.Pi),1.0f/3.0f);float diameter=radius*2.0f;Log.Info($"Creating asteroid from mass:"+$"\n - Target Mass: {targetMass:N0}kg"+$"\n - Calculated Diameter: {diameter:F2}m"+$"\n - Calculated Volume: {volume:N0}m³");return new AsteroidPhysicalProperties(diameter,density,parentEntity);}public bool ShouldSpawnChunk(){float currentInstabilityPercent=CurrentInstability/MaxInstability;float currentThreshold=(float)Math.Floor(currentInstabilityPercent/CHUNK_THRESHOLD)*CHUNK_THRESHOLD;if(currentThreshold>_lastChunkThreshold){_lastChunkThreshold=currentThreshold;return true;}return false;}public void ResetInstability(){CurrentInstability=0f;_lastChunkThreshold=0f;}}}using RealGasGiants;using Sandbox.ModAPI;using System;using System.Collections.Generic;using System.IO;using System.Linq;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities{public static class AsteroidSettings{public static bool EnableLogging=false;public static bool EnableMiddleMouseAsteroidSpawn=false;public static bool EnableVanillaAsteroidSpawnLatching=false;public static bool EnableGasGiantRingSpawning=false;public static float MinimumRingInfluenceForSpawn=0.1f;public static double RingAsteroidVelocityBase=50.0;public static float MaxRingAsteroidDensityMultiplier=1f;public static double VanillaAsteroidSpawnLatchingRadius=10000;public static bool DisableZoneWhileMovingFast=true;public static double ZoneSpeedThreshold=2000.0;public static int SaveStateInterval=600;public static int NetworkMessageInterval=60;public static int SpawnInterval=6;public static int UpdateInterval=60;public static int MaxAsteroidCount=20000;public static int MaxAsteroidsPerZone=100;public static int MaxTotalAttempts=100;public static int MaxZoneAttempts=50;public static double ZoneRadius=10000.0;public static int AsteroidVelocityBase=0;public static double VelocityVariability=0;public static double AngularVelocityVariability=0;public static double MinDistanceFromVanillaAsteroids=1000;public static double MinDistanceFromPlayer=3000;public static int Seed=69420;public static bool IgnorePlanets=true;public static double IceWeight=97.4;public static double StoneWeight=1.0;public static double IronWeight=0.4;public static double NickelWeight=0.2;public static double CobaltWeight=0.2;public static double MagnesiumWeight=0.2;public static double SiliconWeight=0.2;public static double SilverWeight=0.1;public static double GoldWeight=0.1;public static double PlatinumWeight=0.1;public static double UraniniteWeight=0.1;public static float MinAsteroidSize=50f;public static float MaxAsteroidSize=250f;public static float InstabilityPerMass=0.1f;public static float InstabilityThresholdPercent=0.8f;public static float InstabilityDecayRate=0.1f;public static float InstabilityFromDamage=1.0f;public static float KgLossPerDamage=0.01f;public static int MaxPlayersPerZone=64;public static float ChunkMassPercent=0.1f;public static float ChunkEjectionVelocity=5.0f;public static float ChunkVelocityRandomization=2.0f;public static float InstabilityPerDamage=0.1f;public struct MassRange{public float MinMass;public float MaxMass;public MassRange(float minMass,float maxMass){MinMass=minMass;MaxMass=maxMass;}}public static readonly DictionaryMinMaxMassByType=new Dictionary{{AsteroidType.Ice,new MassRange(10000f,5000000000f)},{AsteroidType.Stone,new MassRange(8000f,4000000000f)},{AsteroidType.Iron,new MassRange(5000f,3000000000f)},{AsteroidType.Nickel,new MassRange(4000f,2500000000f)},{AsteroidType.Cobalt,new MassRange(3000f,2000000000f)},{AsteroidType.Magnesium,new MassRange(2000f,1500000000f)},{AsteroidType.Silicon,new MassRange(5000f,3500000000f)},{AsteroidType.Silver,new MassRange(2000f,1000000000f)},{AsteroidType.Gold,new MassRange(1000f,800000000f)},{AsteroidType.Platinum,new MassRange(500f,500000000f)},{AsteroidType.Uraninite,new MassRange(300f,200000000f)}};public static ListValidSpawnLocations=new List();public static bool CanSpawnAsteroidAtPoint(Vector3D point,out Vector3D velocity,bool isInRing=false){if(isInRing){velocity=Vector3D.Zero;return true;}foreach(SpawnableArea area in ValidSpawnLocations){if(!area.ContainsPoint(point))continue;velocity=area.VelocityAtPoint(point);return true;}velocity=Vector3D.Zero;return false;}private static Random rand=new Random(Seed);public static AsteroidType GetAsteroidType(Vector3D position){double totalWeight=IceWeight+StoneWeight+IronWeight+NickelWeight+CobaltWeight+MagnesiumWeight+SiliconWeight+SilverWeight+GoldWeight+PlatinumWeight+UraniniteWeight;double randomValue=rand.NextDouble()*totalWeight;if(randomValuea.Name.Equals(name,StringComparison.OrdinalIgnoreCase));if(area==null)return;ValidSpawnLocations.Remove(area);SaveSettings();}public static float CalculateMassScaleByDistance(Vector3D position,RealGasGiantsApi gasGiantsApi,out string debugInfo){const float SPAWN_TOLERANCE=1.25f;debugInfo="";if(gasGiantsApi==null||!gasGiantsApi.IsReady){debugInfo="GasGiants API not ready";return 0f;}var gasGiants=gasGiantsApi.GetAtmoGasGiantsAtPosition(Vector3D.Zero);if(!gasGiants.Any()){debugInfo="No gas giants found at origin";return 0f;}var nearestGasGiant=gasGiants.First();var basicInfo=gasGiantsApi.GetGasGiantConfig_BasicInfo_Base(nearestGasGiant);if(!basicInfo.Item1){debugInfo="Failed to get gas giant info";return 0f;}float gasGiantRadiusMeters=basicInfo.Item2*1000f;var gasGiantCenter=nearestGasGiant.PositionComp.GetPosition();var ringInfo=gasGiantsApi.GetGasGiantConfig_RingInfo_Size(nearestGasGiant);if(!ringInfo.Item1){debugInfo="Failed to get ring info";return 0f;}float innerRingRadiusMeters=gasGiantRadiusMeters*ringInfo.Item3;float outerRingRadiusMeters=gasGiantRadiusMeters*ringInfo.Item4;float extendedSpawnRadiusMeters=outerRingRadiusMeters*SPAWN_TOLERANCE;double distanceFromCenterMeters=Vector3D.Distance(position,gasGiantCenter);debugInfo=$"Ring metrics:"+$"\n - Gas Giant: {basicInfo.Item4} at {gasGiantCenter}"+$"\n - Gas Giant Radius: {gasGiantRadiusMeters/1000f:N0}km"+$"\n - Inner Ring: {innerRingRadiusMeters/1000f:N0}km"+$"\n - Outer Ring: {outerRingRadiusMeters/1000f:N0}km"+$"\n - Extended Spawn Range: {extendedSpawnRadiusMeters/1000f:N0}km"+$"\n - Distance: {distanceFromCenterMeters/1000f:N0}km";if(distanceFromCenterMeters>=extendedSpawnRadiusMeters||distanceFromCenterMeters<=gasGiantRadiusMeters){debugInfo+="\n - Outside valid spawn range";return 0f;}if(distanceFromCenterMeters<=outerRingRadiusMeters){float scale=MathHelper.Lerp(1.0f,0.0f,(float)((distanceFromCenterMeters-innerRingRadiusMeters)/(outerRingRadiusMeters-innerRingRadiusMeters)));debugInfo+=$"\n - Scale Factor: {scale:F3}";return scale;}debugInfo+="\n - Scale Factor: 0.000 (Extended region)";return 0f;}public class SpawnableArea{public string Name{get;set;}public Vector3D CenterPosition{get;set;}public double Radius{get;set;}public bool ContainsPoint(Vector3D point){double distanceSquared=(point-CenterPosition).LengthSquared();return distanceSquared<=Radius*Radius;}public Vector3D VelocityAtPoint(Vector3D point){return(point-CenterPosition).Normalized()*AsteroidSettings.AsteroidVelocityBase;}}}}using Sandbox.Definitions;using Sandbox.ModAPI;using System;using VRage.Game.ModAPI;using VRage.Utils;using VRageMath;namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities{public class KeenRicochetMissileBSWorkaroundHandler{private static IMyMissiles _missileAPI;private static bool _isInitialized=false;private static AsteroidDamageHandler _damageHandler;public KeenRicochetMissileBSWorkaroundHandler(AsteroidDamageHandler damageHandler){_damageHandler=damageHandler;InitializeMissileAPI();}private void InitializeMissileAPI(){if(!_isInitialized){_missileAPI=MyAPIGateway.Missiles;if(_missileAPI!=null){_missileAPI.OnMissileCollided+=OnMissileCollided;_isInitialized=true;Log.Info("Initialized Keen missile ricochet workaround.");}else{Log.Warning("Failed to initialize Keen missile ricochet workaround - API not available.");}}}private float CalculateMissileDamage(IMyMissile missile){var missileDefinition=missile.AmmoDefinition as MyMissileAmmoDefinition;if(missileDefinition==null)return 0;if(missile.HealthPool>0){float damage=missile.HealthPool;Log.Info($"Using missile health pool as damage: {damage}");return damage;}else if(missileDefinition.MissileExplosionDamage>0){return missileDefinition.MissileExplosionDamage;}return 0;}private void OnMissileCollided(IMyMissile missile){try{if(missile?.CollidedEntity==null)return;var asteroid=missile.CollidedEntity as AsteroidEntity;if(asteroid==null)return;float damage=CalculateMissileDamage(missile);if(damage<=0)return;Vector3D impactPosition=missile.CollisionPoint??missile.PositionComp.GetPosition();var hitInfo=new MyHitInfo{Position=impactPosition,Normal=missile.CollisionNormal,Velocity=missile.LinearVelocity};_damageHandler.DoDamage(asteroid,damage,MyStringHash.GetOrCompute("Missile"),true,hitInfo,missile.Owner);_missileAPI.Remove(missile.EntityId);}catch(Exception ex){Log.Exception(ex,typeof(KeenRicochetMissileBSWorkaroundHandler),"Error in Keen missile ricochet workaround");}}public void Unload(){if(_isInitialized&&_missileAPI!=null){_missileAPI.OnMissileCollided-=OnMissileCollided;_isInitialized=false;Log.Info("Cleaned up Keen missile ricochet workaround.");}}}}