diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidEntity.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidEntity.cs index c6966fd0..b99849be 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidEntity.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidEntity.cs @@ -39,30 +39,30 @@ public enum AsteroidType 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" - }; + @"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" - }; + @"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" }; @@ -74,14 +74,12 @@ public class AsteroidEntity : MyEntity, IMyDestroyableObject private static readonly string[] PlatinumAsteroidModels = { @"Models\OreAsteroid_Platinum.mwm" }; private static readonly string[] UraniniteAsteroidModels = { @"Models\OreAsteroid_Uraninite.mwm" }; - private void CreateEffects(Vector3D position) { MyVisualScriptLogicProvider.CreateParticleEffectAtPosition("roidbreakparticle1", position); MyVisualScriptLogicProvider.PlaySingleSoundAtPosition("roidbreak", position); } - public static AsteroidEntity CreateAsteroid(Vector3D position, float size, Vector3D initialVelocity, AsteroidType type) { var ent = new AsteroidEntity(); @@ -89,6 +87,90 @@ public static AsteroidEntity CreateAsteroid(Vector3D position, float size, Vecto return ent; } + private void Init(Vector3D position, float size, Vector3D initialVelocity, AsteroidType type) + { + try + { + Log.Info("Initializing asteroid entity"); + string modPath = Path.Combine(MainSession.I.ModContext.ModPath, ""); + Type = type; + switch (type) + { + case AsteroidType.Ice: + ModelString = Path.Combine(modPath, IceAsteroidModels[MainSession.I.Rand.Next(IceAsteroidModels.Length)]); + break; + case AsteroidType.Stone: + ModelString = Path.Combine(modPath, StoneAsteroidModels[MainSession.I.Rand.Next(StoneAsteroidModels.Length)]); + break; + case AsteroidType.Iron: + ModelString = Path.Combine(modPath, IronAsteroidModels[MainSession.I.Rand.Next(IronAsteroidModels.Length)]); + break; + case AsteroidType.Nickel: + ModelString = Path.Combine(modPath, NickelAsteroidModels[MainSession.I.Rand.Next(NickelAsteroidModels.Length)]); + break; + case AsteroidType.Cobalt: + ModelString = Path.Combine(modPath, CobaltAsteroidModels[MainSession.I.Rand.Next(CobaltAsteroidModels.Length)]); + break; + case AsteroidType.Magnesium: + ModelString = Path.Combine(modPath, MagnesiumAsteroidModels[MainSession.I.Rand.Next(MagnesiumAsteroidModels.Length)]); + break; + case AsteroidType.Silicon: + ModelString = Path.Combine(modPath, SiliconAsteroidModels[MainSession.I.Rand.Next(SiliconAsteroidModels.Length)]); + break; + case AsteroidType.Silver: + ModelString = Path.Combine(modPath, SilverAsteroidModels[MainSession.I.Rand.Next(SilverAsteroidModels.Length)]); + break; + case AsteroidType.Gold: + ModelString = Path.Combine(modPath, GoldAsteroidModels[MainSession.I.Rand.Next(GoldAsteroidModels.Length)]); + break; + case AsteroidType.Platinum: + ModelString = Path.Combine(modPath, PlatinumAsteroidModels[MainSession.I.Rand.Next(PlatinumAsteroidModels.Length)]); + break; + case AsteroidType.Uraninite: + ModelString = Path.Combine(modPath, UraniniteAsteroidModels[MainSession.I.Rand.Next(UraniniteAsteroidModels.Length)]); + break; + } + + Size = size; + _integrity = AsteroidSettings.BaseIntegrity + Size; + + Log.Info($"Attempting to load model: {ModelString}"); + + Init(null, ModelString, null, Size); + + if (string.IsNullOrEmpty(ModelString)) + Flags &= ~EntityFlags.Visible; + + Save = false; + NeedsWorldMatrix = true; + + PositionComp.LocalAABB = new BoundingBox(-Vector3.Half * Size, Vector3.Half * Size); + + // Apply random rotation + var randomRotation = MatrixD.CreateFromQuaternion(Quaternion.CreateFromYawPitchRoll((float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi, (float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi, (float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi)); + WorldMatrix = randomRotation * MatrixD.CreateWorld(position, Vector3D.Forward, Vector3D.Up); + WorldMatrix.Orthogonalize(); // Normalize the matrix to prevent rotation spazzing + + MyEntities.Add(this); + + CreatePhysics(); + Physics.LinearVelocity = initialVelocity + RandVector() * AsteroidSettings.VelocityVariability; + Physics.AngularVelocity = RandVector() * AsteroidSettings.GetRandomAngularVelocity(MainSession.I.Rand); // Set initial angular velocity + + Log.Info($"Asteroid model {ModelString} loaded successfully with initial angular velocity: {Physics.AngularVelocity}"); + + if (MyAPIGateway.Session.IsServer) + { + SyncFlag = true; + } + } + catch (Exception ex) + { + Log.Exception(ex, typeof(AsteroidEntity), $"Failed to load model: {ModelString}"); + Flags &= ~EntityFlags.Visible; + } + } + public float Size; public string ModelString = ""; public AsteroidType Type; @@ -211,16 +293,6 @@ public void OnDestroy() public bool DoDamage(float damage, MyStringHash damageSource, bool sync, MyHitInfo? hitInfo = null, long attackerId = 0, long realHitEntityId = 0, bool shouldDetonateAmmo = true, MyStringHash? extraInfo = null) { - // Define the explosion damage type - var explosionDamageType = MyStringHash.GetOrCompute("Explosion"); - - // Check if the damage source is explosion - //if (damageSource == explosionDamageType) - //{ - // Log.Info($"Ignoring explosion damage for asteroid. Damage source: {damageSource.String}"); - // return false; // Ignore the damage - //} - _integrity -= damage; Log.Info($"DoDamage called with damage: {damage}, damageSource: {damageSource.String}, attackerId: {attackerId}, realHitEntityId: {realHitEntityId}, new integrity: {_integrity}"); @@ -242,90 +314,6 @@ public bool DoDamage(float damage, MyStringHash damageSource, bool sync, MyHitIn public bool UseDamageSystem => true; - private void Init(Vector3D position, float size, Vector3D initialVelocity, AsteroidType type) - { - try - { - Log.Info("Initializing asteroid entity"); - string modPath = Path.Combine(MainSession.I.ModContext.ModPath, ""); - Type = type; - switch (type) - { - case AsteroidType.Ice: - ModelString = Path.Combine(modPath, IceAsteroidModels[MainSession.I.Rand.Next(IceAsteroidModels.Length)]); - break; - case AsteroidType.Stone: - ModelString = Path.Combine(modPath, StoneAsteroidModels[MainSession.I.Rand.Next(StoneAsteroidModels.Length)]); - break; - case AsteroidType.Iron: - ModelString = Path.Combine(modPath, IronAsteroidModels[MainSession.I.Rand.Next(IronAsteroidModels.Length)]); - break; - case AsteroidType.Nickel: - ModelString = Path.Combine(modPath, NickelAsteroidModels[MainSession.I.Rand.Next(NickelAsteroidModels.Length)]); - break; - case AsteroidType.Cobalt: - ModelString = Path.Combine(modPath, CobaltAsteroidModels[MainSession.I.Rand.Next(CobaltAsteroidModels.Length)]); - break; - case AsteroidType.Magnesium: - ModelString = Path.Combine(modPath, MagnesiumAsteroidModels[MainSession.I.Rand.Next(MagnesiumAsteroidModels.Length)]); - break; - case AsteroidType.Silicon: - ModelString = Path.Combine(modPath, SiliconAsteroidModels[MainSession.I.Rand.Next(SiliconAsteroidModels.Length)]); - break; - case AsteroidType.Silver: - ModelString = Path.Combine(modPath, SilverAsteroidModels[MainSession.I.Rand.Next(SilverAsteroidModels.Length)]); - break; - case AsteroidType.Gold: - ModelString = Path.Combine(modPath, GoldAsteroidModels[MainSession.I.Rand.Next(GoldAsteroidModels.Length)]); - break; - case AsteroidType.Platinum: - ModelString = Path.Combine(modPath, PlatinumAsteroidModels[MainSession.I.Rand.Next(PlatinumAsteroidModels.Length)]); - break; - case AsteroidType.Uraninite: - ModelString = Path.Combine(modPath, UraniniteAsteroidModels[MainSession.I.Rand.Next(UraniniteAsteroidModels.Length)]); - break; - } - - Size = size; - _integrity = AsteroidSettings.BaseIntegrity + Size; - - Log.Info($"Attempting to load model: {ModelString}"); - - Init(null, ModelString, null, Size); - - if (string.IsNullOrEmpty(ModelString)) - Flags &= ~EntityFlags.Visible; - - Save = false; - NeedsWorldMatrix = true; - - PositionComp.LocalAABB = new BoundingBox(-Vector3.Half * Size, Vector3.Half * Size); - - // Apply random rotation - var randomRotation = MatrixD.CreateFromQuaternion(Quaternion.CreateFromYawPitchRoll((float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi, (float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi, (float)MainSession.I.Rand.NextDouble() * MathHelper.TwoPi)); - WorldMatrix = randomRotation * MatrixD.CreateWorld(position, Vector3D.Forward, Vector3D.Up); - WorldMatrix.Orthogonalize(); // Normalize the matrix to prevent rotation spazzing - - MyEntities.Add(this); - - CreatePhysics(); - Physics.LinearVelocity = initialVelocity + RandVector() * AsteroidSettings.VelocityVariability; - Physics.AngularVelocity = RandVector() * AsteroidSettings.GetRandomAngularVelocity(MainSession.I.Rand); // Set initial angular velocity - - Log.Info($"Asteroid model {ModelString} loaded successfully with initial angular velocity: {Physics.AngularVelocity}"); - - if (MyAPIGateway.Session.IsServer) - { - SyncFlag = true; - } - } - catch (Exception ex) - { - Log.Exception(ex, typeof(AsteroidEntity), $"Failed to load model: {ModelString}"); - Flags &= ~EntityFlags.Visible; - } - } - private void CreatePhysics() { float mass = 10000 * Size * Size * Size; @@ -347,7 +335,7 @@ private void CreatePhysics() Physics.Enabled = true; Physics.Activate(); } - + private Vector3D RandVector() { var theta = MainSession.I.Rand.NextDouble() * 2.0 * Math.PI; diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidSpawner.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidSpawner.cs index 15c87497..aa7ed4c4 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidSpawner.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidEntities/AsteroidSpawner.cs @@ -1,162 +1,249 @@ -using System; -using System.Collections.Generic; +using DynamicAsteroids.AsteroidEntities; +using DynamicAsteroids; using Sandbox.ModAPI; using SC.SUGMA; +using System.Collections.Generic; +using System; using VRage.Game.ModAPI; using VRage.ModAPI; using VRageMath; +using System.Linq; +using Sandbox.Game.Entities; -namespace DynamicAsteroids.AsteroidEntities +public class AsteroidSpawner { - public class AsteroidSpawner + public List _asteroids; + private bool _canSpawnAsteroids = false; + private DateTime _worldLoadTime; + private Random rand; + private List _despawnedAsteroids = new List(); + + public void Init(int seed) + { + if (!MyAPIGateway.Session.IsServer) + return; + + Log.Info("Initializing AsteroidSpawner"); + _asteroids = new List(AsteroidSettings.MaxAsteroidCount); + _worldLoadTime = DateTime.UtcNow; + rand = new Random(seed); + AsteroidSettings.Seed = seed; + } + + public void SaveAsteroidState() { - public List _asteroids; - private const double MinDistanceFromVanillaAsteroids = 1000; // 1 km - private const double MinDistanceFromPlayer = 3000; // Minimum distance from the player to spawn new asteroids - private bool _canSpawnAsteroids = false; - private DateTime _worldLoadTime; + if (!MyAPIGateway.Session.IsServer) + return; - public void Init() + var asteroidStates = _asteroids.Select(asteroid => new AsteroidState { - if (!MyAPIGateway.Session.IsServer) - return; + Position = asteroid.PositionComp.GetPosition(), + Size = asteroid.Size, + Type = asteroid.Type + }).ToList(); + + asteroidStates.AddRange(_despawnedAsteroids); // Include despawned asteroids - Log.Info("Initializing AsteroidSpawner"); - _asteroids = new List(AsteroidSettings.MaxAsteroidCount); - _worldLoadTime = DateTime.UtcNow; + var stateBytes = MyAPIGateway.Utilities.SerializeToBinary(asteroidStates); + using (var writer = MyAPIGateway.Utilities.WriteBinaryFileInLocalStorage("asteroid_states.dat", typeof(AsteroidSpawner))) + { + writer.Write(stateBytes, 0, stateBytes.Length); } + } - public void Close() + public void LoadAsteroidState() + { + if (!MyAPIGateway.Session.IsServer) + return; + + _asteroids.Clear(); // Clear existing asteroids to avoid double loading + + if (MyAPIGateway.Utilities.FileExistsInLocalStorage("asteroid_states.dat", typeof(AsteroidSpawner))) { - if (!MyAPIGateway.Session.IsServer) - return; + byte[] stateBytes; + using (var reader = MyAPIGateway.Utilities.ReadBinaryFileInLocalStorage("asteroid_states.dat", typeof(AsteroidSpawner))) + { + stateBytes = reader.ReadBytes((int)reader.BaseStream.Length); + } + + var asteroidStates = MyAPIGateway.Utilities.SerializeFromBinary>(stateBytes); - Log.Info("Closing AsteroidSpawner"); - _asteroids?.Clear(); + foreach (var state in asteroidStates) + { + var asteroid = AsteroidEntity.CreateAsteroid(state.Position, state.Size, Vector3D.Zero, state.Type); + _asteroids.Add(asteroid); + MyEntities.Add(asteroid); // Ensure the asteroid is added to the game world + } } + } - public void UpdateTick() + private void LoadAsteroidsInRange(Vector3D playerPosition) + { + foreach (var state in _despawnedAsteroids.ToArray()) { - if (!MyAPIGateway.Session.IsServer) - return; + double distanceSquared = Vector3D.DistanceSquared(state.Position, playerPosition); - // Check if 10 seconds have passed since the world loaded - if (!_canSpawnAsteroids) + if (distanceSquared < AsteroidSettings.AsteroidSpawnRadius * AsteroidSettings.AsteroidSpawnRadius) { - if ((DateTime.UtcNow - _worldLoadTime).TotalSeconds < 10) + bool tooClose = _asteroids.Any(a => Vector3D.DistanceSquared(a.PositionComp.GetPosition(), state.Position) < AsteroidSettings.MinDistanceFromPlayer * AsteroidSettings.MinDistanceFromPlayer); + + if (tooClose) { - return; + Log.Info($"Skipping respawn of asteroid at {state.Position} due to proximity to other asteroids"); + continue; } - _canSpawnAsteroids = true; + + Log.Info($"Respawning asteroid at {state.Position} due to player re-entering range"); + var asteroid = AsteroidEntity.CreateAsteroid(state.Position, state.Size, Vector3D.Zero, state.Type); + _asteroids.Add(asteroid); + + var message = new AsteroidNetworkMessage(state.Position, state.Size, Vector3D.Zero, Vector3D.Zero, state.Type, false, asteroid.EntityId, false, true); + var messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); + + _despawnedAsteroids.Remove(state); } + } + } - try - { - List players = new List(); - MyAPIGateway.Players.GetPlayers(players); + public void Close() + { + if (!MyAPIGateway.Session.IsServer) + return; - foreach (var player in players) - { - Vector3D playerPosition = player.GetPosition(); + SaveAsteroidState(); + Log.Info("Closing AsteroidSpawner"); + _asteroids?.Clear(); + } - foreach (var asteroid in _asteroids.ToArray()) - { - double distanceSquared = Vector3D.DistanceSquared(asteroid.PositionComp.GetPosition(), playerPosition); + public void UpdateTick() + { + if (!MyAPIGateway.Session.IsServer) + return; - // Remove asteroids that are outside the spherical spawn radius - if (distanceSquared > AsteroidSettings.AsteroidSpawnRadius * AsteroidSettings.AsteroidSpawnRadius) - { - Log.Info($"Removing asteroid at {asteroid.PositionComp.GetPosition()} due to distance from player"); - _asteroids.Remove(asteroid); + // Check if 10 seconds have passed since the world loaded + if (!_canSpawnAsteroids) + { + if ((DateTime.UtcNow - _worldLoadTime).TotalSeconds < 10) + { + return; + } + _canSpawnAsteroids = true; + } - var removalMessage = new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(), asteroid.Size, Vector3D.Zero, Vector3D.Zero, asteroid.Type, false, asteroid.EntityId, true, false); - var removalMessageBytes = MyAPIGateway.Utilities.SerializeToBinary(removalMessage); - MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); + try + { + List players = new List(); + MyAPIGateway.Players.GetPlayers(players); - asteroid.Close(); - continue; - } - } + foreach (var player in players) + { + Vector3D playerPosition = player.GetPosition(); - int asteroidsSpawned = 0; - int spawnAttempts = 0; - int maxAttempts = 50; // Limit the number of attempts to find valid positions + // Load asteroids in range + LoadAsteroidsInRange(playerPosition); - while (_asteroids.Count < AsteroidSettings.MaxAsteroidCount && asteroidsSpawned < 10) - { - if (spawnAttempts >= maxAttempts) - { - Log.Info("Reached maximum spawn attempts, breaking out of loop to prevent freeze"); - break; - } + foreach (var asteroid in _asteroids.ToArray()) + { + double distanceSquared = Vector3D.DistanceSquared(asteroid.PositionComp.GetPosition(), playerPosition); - Vector3D newPosition; - do + // Remove asteroids that are outside the spherical spawn radius + if (distanceSquared > AsteroidSettings.AsteroidSpawnRadius * AsteroidSettings.AsteroidSpawnRadius) + { + Log.Info($"Removing asteroid at {asteroid.PositionComp.GetPosition()} due to distance from player"); + _despawnedAsteroids.Add(new AsteroidState { - newPosition = playerPosition + RandVector() * AsteroidSettings.AsteroidSpawnRadius; - spawnAttempts++; - } while (Vector3D.DistanceSquared(newPosition, playerPosition) < MinDistanceFromPlayer * MinDistanceFromPlayer && spawnAttempts < maxAttempts); + Position = asteroid.PositionComp.GetPosition(), + Size = asteroid.Size, + Type = asteroid.Type + }); + _asteroids.Remove(asteroid); + + var removalMessage = new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(), asteroid.Size, Vector3D.Zero, Vector3D.Zero, asteroid.Type, false, asteroid.EntityId, true, false); + var removalMessageBytes = MyAPIGateway.Utilities.SerializeToBinary(removalMessage); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); + + asteroid.Close(); + continue; + } + } - if (spawnAttempts >= maxAttempts) - break; + int asteroidsSpawned = 0; + int spawnAttempts = 0; + int maxAttempts = 50; // Limit the number of attempts to find valid positions - Vector3D newVelocity; - if (!AsteroidSettings.CanSpawnAsteroidAtPoint(newPosition, out newVelocity)) - continue; + while (_asteroids.Count < AsteroidSettings.MaxAsteroidCount && asteroidsSpawned < 10) + { + if (spawnAttempts >= maxAttempts) + { + Log.Info("Reached maximum spawn attempts, breaking out of loop to prevent freeze"); + break; + } - if (IsNearVanillaAsteroid(newPosition)) - { - Log.Info("Skipped spawning asteroid due to proximity to vanilla asteroid."); - continue; - } + Vector3D newPosition; + do + { + newPosition = playerPosition + RandVector() * AsteroidSettings.AsteroidSpawnRadius; + spawnAttempts++; + } while (Vector3D.DistanceSquared(newPosition, playerPosition) < AsteroidSettings.MinDistanceFromPlayer * AsteroidSettings.MinDistanceFromPlayer && spawnAttempts < maxAttempts); - AsteroidType type = AsteroidSettings.GetRandomAsteroidType(MainSession.I.Rand); - float size = AsteroidSettings.GetRandomAsteroidSize(MainSession.I.Rand); + if (spawnAttempts >= maxAttempts) + break; - Log.Info($"Spawning asteroid at {newPosition} with velocity {newVelocity} of type {type}"); - var asteroid = AsteroidEntity.CreateAsteroid(newPosition, size, newVelocity, type); - _asteroids.Add(asteroid); - asteroidsSpawned++; + Vector3D newVelocity; + if (!AsteroidSettings.CanSpawnAsteroidAtPoint(newPosition, out newVelocity)) + continue; - var message = new AsteroidNetworkMessage(newPosition, size, newVelocity, Vector3D.Zero, type, false, asteroid.EntityId, false, true); - var messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); - MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); + if (IsNearVanillaAsteroid(newPosition)) + { + Log.Info("Skipped spawning asteroid due to proximity to vanilla asteroid."); + continue; } - MyAPIGateway.Utilities.ShowNotification($"Active Asteroids: {_asteroids.Count}", 1000 / 60); + AsteroidType type = AsteroidSettings.GetAsteroidType(newPosition); + float size = AsteroidSettings.GetAsteroidSize(newPosition); + + Log.Info($"Spawning asteroid at {newPosition} with velocity {newVelocity} of type {type}"); + var asteroid = AsteroidEntity.CreateAsteroid(newPosition, size, newVelocity, type); + _asteroids.Add(asteroid); + + var message = new AsteroidNetworkMessage(newPosition, size, newVelocity, Vector3D.Zero, type, false, asteroid.EntityId, false, true); + var messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); } - } - catch (Exception ex) - { - Log.Exception(ex, typeof(AsteroidSpawner)); + + MyAPIGateway.Utilities.ShowNotification($"Active Asteroids: {_asteroids.Count}", 1000 / 60); } } - - private bool IsNearVanillaAsteroid(Vector3D position) + catch (Exception ex) { - List voxelMaps = new List(); - MyAPIGateway.Session.VoxelMaps.GetInstances(voxelMaps, v => v is IMyVoxelMap && !v.StorageName.StartsWith("mod_")); + Log.Exception(ex, typeof(AsteroidSpawner)); + } + } + + private bool IsNearVanillaAsteroid(Vector3D position) + { + List voxelMaps = new List(); + MyAPIGateway.Session.VoxelMaps.GetInstances(voxelMaps, v => v is IMyVoxelMap && !v.StorageName.StartsWith("mod_")); - foreach (var voxelMap in voxelMaps) + foreach (var voxelMap in voxelMaps) + { + if (Vector3D.DistanceSquared(position, voxelMap.GetPosition()) < AsteroidSettings.MinDistanceFromVanillaAsteroids * AsteroidSettings.MinDistanceFromVanillaAsteroids) { - if (Vector3D.DistanceSquared(position, voxelMap.GetPosition()) < MinDistanceFromVanillaAsteroids * MinDistanceFromVanillaAsteroids) - { - Log.Info($"Position {position} is near vanilla asteroid {voxelMap.StorageName}"); - return true; - } + Log.Info($"Position {position} is near vanilla asteroid {voxelMap.StorageName}"); + return true; } - - return false; } - private 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)); - } + return false; + } - private float RandAsteroidSize => (float)(MainSession.I.Rand.NextDouble() * MainSession.I.Rand.NextDouble() * MainSession.I.Rand.NextDouble() * (AsteroidSettings.MaxAsteroidSize - AsteroidSettings.MinAsteroidSize)) + AsteroidSettings.MinAsteroidSize; + private Vector3D RandVector() + { + 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)); } + } diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSettings.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSettings.cs index e86113ec..7c3b947c 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSettings.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSettings.cs @@ -11,19 +11,22 @@ public static class AsteroidSettings public static int AsteroidVelocityBase = 0; public static double VelocityVariability = 0; public static double AngularVelocityVariability = 0; - //TODO: DespawnRangeFromPlayer - - public static double IceWeight = 0.80; - public static double StoneWeight = 0.11; - public static double IronWeight = 0.01; - public static double NickelWeight = 0.01; - public static double CobaltWeight = 0.01; - public static double MagnesiumWeight = 0.01; - public static double SiliconWeight = 0.01; - public static double SilverWeight = 0.01; - public static double GoldWeight = 0.01; - public static double PlatinumWeight = 0.01; - public static double UraniniteWeight = 0.01; + + public static double MinDistanceFromVanillaAsteroids = 1000; // 1 km + public static double MinDistanceFromPlayer = 1; // Minimum distance from the player to spawn new asteroids + public static int Seed = 69420; // Default seed, can be set dynamically + + public static double IceWeight = 99; + public static double StoneWeight = 0.5; // Represents silicate materials + public static double IronWeight = 0.25; + public static double NickelWeight = 0.05; + public static double CobaltWeight = 0.05; + public static double MagnesiumWeight = 0.05; + public static double SiliconWeight = 0.05; + public static double SilverWeight = 0.05; + public static double GoldWeight = 0.05; + public static double PlatinumWeight = 0.05; + public static double UraniniteWeight = 0.05; public static float BaseIntegrity = 1f; public static float MinAsteroidSize = 50f; @@ -82,8 +85,10 @@ public static bool PlayerCanSeeRings(Vector3D point) return false; } - public static AsteroidType GetRandomAsteroidType(Random rand) + public static AsteroidType GetAsteroidType(Vector3D position) { + Random rand = new Random(Seed + position.GetHashCode()); + double totalWeight = IceWeight + StoneWeight + IronWeight + NickelWeight + CobaltWeight + MagnesiumWeight + SiliconWeight + SilverWeight + GoldWeight + PlatinumWeight + UraniniteWeight; double randomValue = rand.NextDouble() * totalWeight; @@ -107,18 +112,18 @@ public static AsteroidType GetRandomAsteroidType(Random rand) if (randomValue < GoldWeight) return AsteroidType.Gold; randomValue -= GoldWeight; if (randomValue < PlatinumWeight) return AsteroidType.Platinum; - randomValue -= PlatinumWeight; return AsteroidType.Uraninite; } - public static double GetRandomAngularVelocity(Random rand) + public static float GetAsteroidSize(Vector3D position) { - return AngularVelocityVariability * rand.NextDouble(); + Random rand = new Random(Seed + position.GetHashCode()); + return MinAsteroidSize + (float)rand.NextDouble() * (MaxAsteroidSize - MinAsteroidSize); } - public static float GetRandomAsteroidSize(Random rand) + public static double GetRandomAngularVelocity(Random rand) { - return MinAsteroidSize + (float)rand.NextDouble() * (MaxAsteroidSize - MinAsteroidSize); + return AngularVelocityVariability * rand.NextDouble(); } public static double GetRandomSubChunkVelocity(Random rand) diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidState.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidState.cs new file mode 100644 index 00000000..3483a186 --- /dev/null +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidState.cs @@ -0,0 +1,23 @@ +using DynamicAsteroids.AsteroidEntities; +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRageMath; + + [ProtoContract] + public class AsteroidState + { + [ProtoMember(1)] + public Vector3D Position { get; set; } + + [ProtoMember(2)] + public float Size { get; set; } + + [ProtoMember(3)] + public AsteroidType Type { get; set; } + } + + diff --git a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs index 7b119896..de0edc10 100644 --- a/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs +++ b/Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs @@ -16,12 +16,10 @@ namespace DynamicAsteroids public class MainSession : MySessionComponentBase { public static MainSession I; - - public Random Rand = new Random(); - + public Random Rand; + private int seed; public AsteroidSpawner _spawner = new AsteroidSpawner(); - - #region Base Methods + private int _saveStateTimer; public override void LoadData() { @@ -33,7 +31,11 @@ public override void LoadData() Log.Info("Loading data in MainSession"); if (MyAPIGateway.Session.IsServer) { - _spawner.Init(); + seed = (int)DateTime.UtcNow.Ticks; // Example seed based on current time + AsteroidSettings.Seed = seed; + Rand = new Random(seed); + _spawner.Init(seed); + _spawner.LoadAsteroidState(); // Load asteroid states } MyAPIGateway.Multiplayer.RegisterMessageHandler(32000, OnMessageReceived); @@ -51,6 +53,7 @@ protected override void UnloadData() Log.Info("Unloading data in MainSession"); if (MyAPIGateway.Session.IsServer) { + _spawner.SaveAsteroidState(); // Save asteroid states _spawner.Close(); } @@ -72,6 +75,17 @@ public override void UpdateAfterSimulation() if (MyAPIGateway.Session.IsServer) { _spawner.UpdateTick(); + + // Save asteroid states periodically + if (_saveStateTimer > 0) + { + _saveStateTimer--; + } + else + { + _spawner.SaveAsteroidState(); + _saveStateTimer = 600; // Set to save every 10 seconds (600 ticks) + } } if (MyAPIGateway.Session?.Player?.Character != null && _spawner._asteroids != null) @@ -103,6 +117,7 @@ public override void UpdateAfterSimulation() Log.Exception(ex, typeof(MainSession)); } } + private void OnMessageReceived(byte[] message) { try @@ -175,6 +190,5 @@ private AsteroidType DetermineAsteroidType() return (AsteroidType)randValue; } - #endregion } } diff --git a/Dynamic Asteroids/Data/newfile.txt b/Dynamic Asteroids/Data/newfile.txt index 8e3e8234..b3038e00 100644 --- a/Dynamic Asteroids/Data/newfile.txt +++ b/Dynamic Asteroids/Data/newfile.txt @@ -61,17 +61,21 @@ namespace DynamicAsteroids public static double VelocityVariability = 0; public static double AngularVelocityVariability = 0; - public static double IceWeight = 0.80; - public static double StoneWeight = 0.11; - public static double IronWeight = 0.01; - public static double NickelWeight = 0.01; - public static double CobaltWeight = 0.01; - public static double MagnesiumWeight = 0.01; - public static double SiliconWeight = 0.01; - public static double SilverWeight = 0.01; - public static double GoldWeight = 0.01; - public static double PlatinumWeight = 0.01; - public static double UraniniteWeight = 0.01; + public static double MinDistanceFromVanillaAsteroids = 1000; // 1 km + public static double MinDistanceFromPlayer = 1; // Minimum distance from the player to spawn new asteroids + public static int Seed = 69420; // Default seed, can be set dynamically + + public static double IceWeight = 99; + public static double StoneWeight = 0.5; // Represents silicate materials + public static double IronWeight = 0.25; + public static double NickelWeight = 0.05; + public static double CobaltWeight = 0.05; + public static double MagnesiumWeight = 0.05; + public static double SiliconWeight = 0.05; + public static double SilverWeight = 0.05; + public static double GoldWeight = 0.05; + public static double PlatinumWeight = 0.05; + public static double UraniniteWeight = 0.05; public static float BaseIntegrity = 1f; public static float MinAsteroidSize = 50f; @@ -130,8 +134,10 @@ namespace DynamicAsteroids return false; } - public static AsteroidType GetRandomAsteroidType(Random rand) + public static AsteroidType GetAsteroidType(Vector3D position) { + Random rand = new Random(Seed + position.GetHashCode()); + double totalWeight = IceWeight + StoneWeight + IronWeight + NickelWeight + CobaltWeight + MagnesiumWeight + SiliconWeight + SilverWeight + GoldWeight + PlatinumWeight + UraniniteWeight; double randomValue = rand.NextDouble() * totalWeight; @@ -155,18 +161,18 @@ namespace DynamicAsteroids if (randomValue < GoldWeight) return AsteroidType.Gold; randomValue -= GoldWeight; if (randomValue < PlatinumWeight) return AsteroidType.Platinum; - randomValue -= PlatinumWeight; return AsteroidType.Uraninite; } - public static double GetRandomAngularVelocity(Random rand) + public static float GetAsteroidSize(Vector3D position) { - return AngularVelocityVariability * rand.NextDouble(); + Random rand = new Random(Seed + position.GetHashCode()); + return MinAsteroidSize + (float)rand.NextDouble() * (MaxAsteroidSize - MinAsteroidSize); } - public static float GetRandomAsteroidSize(Random rand) + public static double GetRandomAngularVelocity(Random rand) { - return MinAsteroidSize + (float)rand.NextDouble() * (MaxAsteroidSize - MinAsteroidSize); + return AngularVelocityVariability * rand.NextDouble(); } public static double GetRandomSubChunkVelocity(Random rand) @@ -294,12 +300,10 @@ namespace DynamicAsteroids { public static MainSession I; - public Random Rand = new Random(); - - private AsteroidSpawner _spawner = new AsteroidSpawner(); - - #region Base Methods + public Random Rand; + private int seed; + public AsteroidSpawner _spawner = new AsteroidSpawner(); public override void LoadData() { I = this; @@ -310,7 +314,11 @@ namespace DynamicAsteroids Log.Info("Loading data in MainSession"); if (MyAPIGateway.Session.IsServer) { - _spawner.Init(); + // Use a fixed seed for testing or a seed based on a consistent source + seed = (int)DateTime.UtcNow.Ticks; // Example seed based on current time + AsteroidSettings.Seed = seed; + Rand = new Random(seed); + _spawner.Init(seed); } MyAPIGateway.Multiplayer.RegisterMessageHandler(32000, OnMessageReceived); @@ -452,7 +460,6 @@ namespace DynamicAsteroids return (AsteroidType)randValue; } - #endregion } } using System; @@ -496,30 +503,30 @@ namespace DynamicAsteroids.AsteroidEntities 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" - }; + @"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" - }; + @"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" }; @@ -531,14 +538,12 @@ namespace DynamicAsteroids.AsteroidEntities private static readonly string[] PlatinumAsteroidModels = { @"Models\OreAsteroid_Platinum.mwm" }; private static readonly string[] UraniniteAsteroidModels = { @"Models\OreAsteroid_Uraninite.mwm" }; - private void CreateEffects(Vector3D position) { MyVisualScriptLogicProvider.CreateParticleEffectAtPosition("roidbreakparticle1", position); MyVisualScriptLogicProvider.PlaySingleSoundAtPosition("roidbreak", position); } - public static AsteroidEntity CreateAsteroid(Vector3D position, float size, Vector3D initialVelocity, AsteroidType type) { var ent = new AsteroidEntity(); @@ -580,6 +585,9 @@ namespace DynamicAsteroids.AsteroidEntities MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); } + // Remove from asteroid list + MainSession.I._spawner._asteroids.Remove(this); + Close(); return; } @@ -593,6 +601,9 @@ namespace DynamicAsteroids.AsteroidEntities var subChunk = CreateAsteroid(newPos, newSize, newVelocity, Type); subChunk.Physics.AngularVelocity = newAngularVelocity; + // Add sub-chunks to the asteroid list + MainSession.I._spawner._asteroids.Add(subChunk); + // Send a network message to clients if (MyAPIGateway.Utilities.IsDedicated || !MyAPIGateway.Session.IsServer) { @@ -610,10 +621,12 @@ namespace DynamicAsteroids.AsteroidEntities MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); } + // Remove from asteroid list + MainSession.I._spawner._asteroids.Remove(this); + Close(); } - private int GetRandomDropAmount(AsteroidType type) { switch (type) @@ -647,14 +660,33 @@ namespace DynamicAsteroids.AsteroidEntities public void OnDestroy() { - SplitAsteroid(); + try + { + SplitAsteroid(); + } + catch (Exception ex) + { + Log.Exception(ex, typeof(AsteroidEntity), "Exception in OnDestroy:"); + throw; // Rethrow the exception for the debugger + } } public bool DoDamage(float damage, MyStringHash damageSource, bool sync, MyHitInfo? hitInfo = null, long attackerId = 0, long realHitEntityId = 0, bool shouldDetonateAmmo = true, MyStringHash? extraInfo = null) { _integrity -= damage; + Log.Info($"DoDamage called with damage: {damage}, damageSource: {damageSource.String}, attackerId: {attackerId}, realHitEntityId: {realHitEntityId}, new integrity: {_integrity}"); + + if (hitInfo.HasValue) + { + var hit = hitInfo.Value; + Log.Info($"HitInfo - Position: {hit.Position}, Normal: {hit.Normal}, Velocity: {hit.Velocity}"); + } + if (Integrity < 0) + { + Log.Info("Integrity below 0, calling OnDestroy"); OnDestroy(); + } return true; } @@ -707,7 +739,7 @@ namespace DynamicAsteroids.AsteroidEntities } Size = size; - _integrity = AsteroidSettings.BaseIntegrity * Size; + _integrity = AsteroidSettings.BaseIntegrity + Size; Log.Info($"Attempting to load model: {ModelString}"); @@ -749,18 +781,21 @@ namespace DynamicAsteroids.AsteroidEntities private void CreatePhysics() { float mass = 10000 * Size * Size * Size; + float radius = Size / 2; // Assuming Size represents the diameter + PhysicsSettings settings = MyAPIGateway.Physics.CreateSettingsForPhysics( this, WorldMatrix, Vector3.Zero, linearDamping: 0f, // Remove damping angularDamping: 0f, // Remove damping - collisionLayer: CollisionLayers.DefaultCollisionLayer, + rigidBodyFlags: RigidBodyFlag.RBF_DEFAULT, + collisionLayer: CollisionLayers.NoVoxelCollisionLayer, isPhantom: false, mass: new ModAPIMass(PositionComp.LocalAABB.Volume(), mass, Vector3.Zero, mass * PositionComp.LocalAABB.Height * PositionComp.LocalAABB.Height / 6 * Matrix.Identity) ); - MyAPIGateway.Physics.CreateBoxPhysics(settings, PositionComp.LocalAABB.HalfExtents, 0); + MyAPIGateway.Physics.CreateSpherePhysics(settings, radius); Physics.Enabled = true; Physics.Activate(); } @@ -774,155 +809,163 @@ namespace DynamicAsteroids.AsteroidEntities } } } -using System; -using System.Collections.Generic; +using DynamicAsteroids.AsteroidEntities; +using DynamicAsteroids; using Sandbox.ModAPI; using SC.SUGMA; +using System.Collections.Generic; +using System; using VRage.Game.ModAPI; using VRage.ModAPI; using VRageMath; -namespace DynamicAsteroids.AsteroidEntities +public class AsteroidSpawner { - internal class AsteroidSpawner + public List _asteroids; + private bool _canSpawnAsteroids = false; + private DateTime _worldLoadTime; + private Random rand; + + public void Init(int seed) { - public List _asteroids; - private const double MinDistanceFromVanillaAsteroids = 1000; // 1 km - private const double MinDistanceFromPlayer = 3000; // Minimum distance from the player to spawn new asteroids - private bool _canSpawnAsteroids = false; - private DateTime _worldLoadTime; + if (!MyAPIGateway.Session.IsServer) + return; + + Log.Info("Initializing AsteroidSpawner"); + _asteroids = new List(AsteroidSettings.MaxAsteroidCount); + _worldLoadTime = DateTime.UtcNow; + rand = new Random(seed); + AsteroidSettings.Seed = seed; + } - public void Init() - { - if (!MyAPIGateway.Session.IsServer) - return; + public void Close() + { + if (!MyAPIGateway.Session.IsServer) + return; - Log.Info("Initializing AsteroidSpawner"); - _asteroids = new List(AsteroidSettings.MaxAsteroidCount); - _worldLoadTime = DateTime.UtcNow; - } + Log.Info("Closing AsteroidSpawner"); + _asteroids?.Clear(); + } - public void Close() + public void UpdateTick() + { + if (!MyAPIGateway.Session.IsServer) + return; + + // Check if 10 seconds have passed since the world loaded + if (!_canSpawnAsteroids) { - if (!MyAPIGateway.Session.IsServer) + if ((DateTime.UtcNow - _worldLoadTime).TotalSeconds < 10) + { return; - - Log.Info("Closing AsteroidSpawner"); - _asteroids?.Clear(); + } + _canSpawnAsteroids = true; } - public void UpdateTick() + try { - if (!MyAPIGateway.Session.IsServer) - return; + List players = new List(); + MyAPIGateway.Players.GetPlayers(players); - // Check if 10 seconds have passed since the world loaded - if (!_canSpawnAsteroids) + foreach (var player in players) { - if ((DateTime.UtcNow - _worldLoadTime).TotalSeconds < 10) - { - return; - } - _canSpawnAsteroids = true; - } + Vector3D playerPosition = player.GetPosition(); - try - { - List players = new List(); - MyAPIGateway.Players.GetPlayers(players); - - foreach (var player in players) + foreach (var asteroid in _asteroids.ToArray()) { - Vector3D playerPosition = player.GetPosition(); + double distanceSquared = Vector3D.DistanceSquared(asteroid.PositionComp.GetPosition(), playerPosition); - if (!AsteroidSettings.PlayerCanSeeRings(playerPosition)) + // Remove asteroids that are outside the spherical spawn radius + if (distanceSquared > AsteroidSettings.AsteroidSpawnRadius * AsteroidSettings.AsteroidSpawnRadius) { + Log.Info($"Removing asteroid at {asteroid.PositionComp.GetPosition()} due to distance from player"); + _asteroids.Remove(asteroid); + + var removalMessage = new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(), asteroid.Size, Vector3D.Zero, Vector3D.Zero, asteroid.Type, false, asteroid.EntityId, true, false); + var removalMessageBytes = MyAPIGateway.Utilities.SerializeToBinary(removalMessage); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); + + asteroid.Close(); continue; } + } - foreach (var asteroid in _asteroids.ToArray()) + int asteroidsSpawned = 0; + int spawnAttempts = 0; + int maxAttempts = 50; // Limit the number of attempts to find valid positions + + while (_asteroids.Count < AsteroidSettings.MaxAsteroidCount && asteroidsSpawned < 10) + { + if (spawnAttempts >= maxAttempts) { - if (Vector3D.DistanceSquared(asteroid.PositionComp.GetPosition(), playerPosition) > - AsteroidSettings.AsteroidSpawnRadius * AsteroidSettings.AsteroidSpawnRadius * 1.1) - { - Log.Info($"Removing asteroid at {asteroid.PositionComp.GetPosition()} due to distance from player"); - _asteroids.Remove(asteroid); - - var removalMessage = new AsteroidNetworkMessage(asteroid.PositionComp.GetPosition(), asteroid.Size, Vector3D.Zero, Vector3D.Zero, asteroid.Type, false, asteroid.EntityId, true, false); - var removalMessageBytes = MyAPIGateway.Utilities.SerializeToBinary(removalMessage); - MyAPIGateway.Multiplayer.SendMessageToOthers(32000, removalMessageBytes); - - asteroid.Close(); - continue; - } + Log.Info("Reached maximum spawn attempts, breaking out of loop to prevent freeze"); + break; } - int asteroidsSpawned = 0; - while (_asteroids.Count < AsteroidSettings.MaxAsteroidCount && asteroidsSpawned < 10) + Vector3D newPosition; + do { - Vector3D newPosition; - do - { - newPosition = playerPosition + RandVector() * AsteroidSettings.AsteroidSpawnRadius; - } while (Vector3D.DistanceSquared(newPosition, playerPosition) < MinDistanceFromPlayer * MinDistanceFromPlayer); - - Vector3D newVelocity; - if (!AsteroidSettings.CanSpawnAsteroidAtPoint(newPosition, out newVelocity)) - continue; - - if (IsNearVanillaAsteroid(newPosition)) - { - Log.Info("Skipped spawning asteroid due to proximity to vanilla asteroid."); - continue; - } - - AsteroidType type = AsteroidSettings.GetRandomAsteroidType(MainSession.I.Rand); - float size = AsteroidSettings.GetRandomAsteroidSize(MainSession.I.Rand); - - Log.Info($"Spawning asteroid at {newPosition} with velocity {newVelocity} of type {type}"); - var asteroid = AsteroidEntity.CreateAsteroid(newPosition, size, newVelocity, type); - _asteroids.Add(asteroid); - asteroidsSpawned++; - - var message = new AsteroidNetworkMessage(newPosition, size, newVelocity, Vector3D.Zero, type, false, asteroid.EntityId, false, true); - var messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); - MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); + newPosition = playerPosition + RandVector() * AsteroidSettings.AsteroidSpawnRadius; + spawnAttempts++; + } while (Vector3D.DistanceSquared(newPosition, playerPosition) < AsteroidSettings.MinDistanceFromPlayer * AsteroidSettings.MinDistanceFromPlayer && spawnAttempts < maxAttempts); + + if (spawnAttempts >= maxAttempts) + break; + + Vector3D newVelocity; + if (!AsteroidSettings.CanSpawnAsteroidAtPoint(newPosition, out newVelocity)) + continue; + + if (IsNearVanillaAsteroid(newPosition)) + { + Log.Info("Skipped spawning asteroid due to proximity to vanilla asteroid."); + continue; } - MyAPIGateway.Utilities.ShowNotification($"Active Asteroids: {_asteroids.Count}", 1000 / 60); + AsteroidType type = AsteroidSettings.GetAsteroidType(newPosition); + float size = AsteroidSettings.GetAsteroidSize(newPosition); + + Log.Info($"Spawning asteroid at {newPosition} with velocity {newVelocity} of type {type}"); + var asteroid = AsteroidEntity.CreateAsteroid(newPosition, size, newVelocity, type); + _asteroids.Add(asteroid); + + var message = new AsteroidNetworkMessage(newPosition, size, newVelocity, Vector3D.Zero, type, false, asteroid.EntityId, false, true); + var messageBytes = MyAPIGateway.Utilities.SerializeToBinary(message); + MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes); } - } - catch (Exception ex) - { - Log.Exception(ex, typeof(AsteroidSpawner)); + + MyAPIGateway.Utilities.ShowNotification($"Active Asteroids: {_asteroids.Count}", 1000 / 60); } } - - private bool IsNearVanillaAsteroid(Vector3D position) + catch (Exception ex) { - List voxelMaps = new List(); - MyAPIGateway.Session.VoxelMaps.GetInstances(voxelMaps, v => v is IMyVoxelMap && !v.StorageName.StartsWith("mod_")); + Log.Exception(ex, typeof(AsteroidSpawner)); + } + } - foreach (var voxelMap in voxelMaps) + private bool IsNearVanillaAsteroid(Vector3D position) + { + List voxelMaps = new List(); + MyAPIGateway.Session.VoxelMaps.GetInstances(voxelMaps, v => v is IMyVoxelMap && !v.StorageName.StartsWith("mod_")); + + foreach (var voxelMap in voxelMaps) + { + if (Vector3D.DistanceSquared(position, voxelMap.GetPosition()) < AsteroidSettings.MinDistanceFromVanillaAsteroids * AsteroidSettings.MinDistanceFromVanillaAsteroids) { - if (Vector3D.DistanceSquared(position, voxelMap.GetPosition()) < MinDistanceFromVanillaAsteroids * MinDistanceFromVanillaAsteroids) - { - Log.Info($"Position {position} is near vanilla asteroid {voxelMap.StorageName}"); - return true; - } + Log.Info($"Position {position} is near vanilla asteroid {voxelMap.StorageName}"); + return true; } - - return false; } - private 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)); - } + return false; + } - private float RandAsteroidSize => (float)(MainSession.I.Rand.NextDouble() * MainSession.I.Rand.NextDouble() * MainSession.I.Rand.NextDouble() * (AsteroidSettings.MaxAsteroidSize - AsteroidSettings.MinAsteroidSize)) + AsteroidSettings.MinAsteroidSize; + private Vector3D RandVector() + { + 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)); } } +