mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 13:37:47 +03:00
266 lines
10 KiB
C#
266 lines
10 KiB
C#
using System.Linq;
|
|
using Content.Server.GameTicking;
|
|
using Content.Server.Procedural;
|
|
using Content.Server.Salvage;
|
|
using Content.Server.Salvage.Magnet;
|
|
using Content.Server.Shuttles.Systems;
|
|
using Content.Server.Station.Systems;
|
|
using Content.Shared._White.CCVar;
|
|
using Content.Shared.Salvage.Magnet;
|
|
using Content.Shared.Shuttles.Components;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Random;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
using Robust.Shared.EntitySerialization.Systems;
|
|
using Content.Shared.Salvage;
|
|
|
|
namespace Content.Server._White.Procedural.Systems;
|
|
|
|
public sealed class StartingAsteroidFieldSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly SalvageSystem _salvage = default!;
|
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
|
[Dependency] private readonly MapLoaderSystem _mapLoader = default!;
|
|
[Dependency] private readonly MapSystem _map = default!;
|
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
|
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad, after: [typeof(StationSystem),]);
|
|
}
|
|
|
|
private const float SpawningBoxSize = 1000f;
|
|
|
|
private void OnPostGameMapLoad(PostGameMapLoad ev)
|
|
{
|
|
if (!_config.GetCVar(WhiteCVars.AsteroidFieldEnabled))
|
|
{
|
|
Log.Info("Asteroid field disabled. Skipping generation.");
|
|
return;
|
|
}
|
|
|
|
var min = _config.GetCVar(WhiteCVars.AsteroidFieldDistanceMin);
|
|
var max = _config.GetCVar(WhiteCVars.AsteroidFieldDistanceMax);
|
|
var asteroids = _config.GetCVar(WhiteCVars.AsteroidFieldAsteroidCount);
|
|
var derelicts = _config.GetCVar(WhiteCVars.AsteroidFieldDerelictCount);
|
|
|
|
var worldPos = _random.NextAngle().ToWorldVec() * _random.NextFloat(min, max);
|
|
|
|
if (!_config.GetCVar(WhiteCVars.AsteroidFieldSpawnBeacon))
|
|
Log.Info($"Asteroid beacon spawning disabled. Skipping.");
|
|
else
|
|
{
|
|
var path = _config.GetCVar(WhiteCVars.AsteroidFieldBeaconGridPath);
|
|
|
|
if (!_mapLoader.TryLoadGrid(ev.Map, path, out var grid, offset:worldPos, rot:_random.NextAngle()))
|
|
Log.Info($"Failed to load asteroid beacon ({path}).");
|
|
else
|
|
{
|
|
var beaconName = Loc.GetString(_config.GetCVar(WhiteCVars.AsteroidFieldBeaconName));
|
|
_metaData.SetEntityName(grid.Value, beaconName);
|
|
Log.Info($"Successfully loaded asteroid beacon and named it \"{beaconName}\".");
|
|
}
|
|
}
|
|
|
|
Log.Info($"Starting asteroid field generation at position {worldPos}...");
|
|
_ = SpawnAsteroidField(ev.Map, worldPos, asteroids, derelicts);
|
|
}
|
|
|
|
// todo: rewrite grid spawn logic instead of copypasting salvage magnet code?
|
|
private async Task SpawnAsteroidField(MapId mapId, Vector2 worldPos, int asteroidAmount, int derelictAmount)
|
|
{
|
|
List<MapId> asteroidMaps = new();
|
|
List<MapId> derelictMaps = new();
|
|
List<MapId> maps = new();
|
|
List<Task> loadTasks = new();
|
|
|
|
for (int i = 0; i < asteroidAmount; i++)
|
|
if (_map.CreateMap(out var asteroidMapId, false).Valid)
|
|
asteroidMaps.Add(asteroidMapId);
|
|
|
|
if (asteroidAmount != asteroidMaps.Count)
|
|
{
|
|
Log.Error($"Failed to create {asteroidAmount-asteroidMaps.Count} maps out of {asteroidAmount}. {asteroidMaps.Count} will instead be generated.");
|
|
asteroidAmount = asteroidMaps.Count;
|
|
}
|
|
|
|
for (int i = 0; i < derelictAmount; i++)
|
|
if (_map.CreateMap(out var derelictMapId, false).Valid)
|
|
derelictMaps.Add(derelictMapId);
|
|
|
|
if (derelictAmount != derelictMaps.Count)
|
|
{
|
|
Log.Error($"Failed to create {derelictAmount - derelictMaps.Count} maps out of {derelictAmount}. {derelictMaps.Count} will instead be generated.");
|
|
derelictAmount = derelictMaps.Count;
|
|
}
|
|
|
|
var total = asteroidAmount + derelictAmount;
|
|
int c = 1;
|
|
|
|
for (int i = 0; i < asteroidAmount; i++)
|
|
{
|
|
var asteroidMap = asteroidMaps[i];
|
|
var seed = _random.Next();
|
|
|
|
var asteroid = (AsteroidOffering) _salvage.GetSalvageOffering(seed, SharedSalvageSystem.SalvageMagnetOfferingTypeEnum.Asteroid);
|
|
var grid = _mapManager.CreateGridEntity(asteroidMap);
|
|
|
|
Log.Debug($"Queuing asteroid generation. ({i+1}/{asteroidAmount})");
|
|
|
|
var task = _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid, grid, Vector2i.Zero, seed);
|
|
|
|
var finishTask = task.ContinueWith(_ =>
|
|
{
|
|
Log.Info($"Finished generating asteroid {c}/{asteroidAmount}.");
|
|
c++;
|
|
});
|
|
|
|
loadTasks.Add(finishTask);
|
|
maps.Add(asteroidMap);
|
|
}
|
|
|
|
|
|
for (int i = 0; i < derelictAmount; i++)
|
|
{
|
|
var derelictMap = derelictMaps[i];
|
|
var seed = _random.Next();
|
|
|
|
var salvage = (SalvageOffering) _salvage.GetSalvageOffering(seed, SharedSalvageSystem.SalvageMagnetOfferingTypeEnum.Salvage);
|
|
|
|
Log.Debug($"Generating derelict... ({i+1}/{derelictAmount})");
|
|
|
|
_mapLoader.TryLoadGrid(derelictMap, salvage.SalvageMap.MapPath, out _, offset: new (0, 0));
|
|
|
|
maps.Add(derelictMap);
|
|
}
|
|
|
|
await Task.WhenAll(loadTasks);
|
|
Log.Debug($"Asteroid field maps generated. Total asteroid field counts: {asteroidAmount} asteroids, {derelictAmount} derelicts, {total} total.");
|
|
|
|
_random.Shuffle(maps);
|
|
foreach (var map in maps)
|
|
{
|
|
if (!_map.TryGetMap(map, out var mapUid))
|
|
continue;
|
|
|
|
Box2? bounds = null;
|
|
var mapXform = Transform(mapUid.Value);
|
|
|
|
if (mapXform.ChildCount == 0)
|
|
{
|
|
Log.Error($"Map {map} used for asteroid field generation had zero children. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
var mapChildren = mapXform.ChildEnumerator;
|
|
|
|
while (mapChildren.MoveNext(out var mapChild))
|
|
{
|
|
// If something went awry in dungeon.
|
|
if (!TryComp<MapGridComponent>(mapChild, out var childGrid))
|
|
{
|
|
Log.Error($"Map child {ToPrettyString(mapChild)} of map {map} used for asteroid field generation had no MapGridComponent. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
var childAABB = _transform.GetWorldMatrix(mapChild).TransformBox(childGrid.LocalAABB);
|
|
bounds = bounds?.Union(childAABB) ?? childAABB;
|
|
|
|
//// Update mass scanner names as relevant.
|
|
//if (offering is AsteroidOffering)
|
|
//{
|
|
// //_metaData.SetEntityName(mapChild, Loc.GetString("salvage-asteroid-name"));
|
|
// _gravity.EnableGravity(mapChild);
|
|
//}
|
|
}
|
|
|
|
var attachedBounds = new Box2Rotated(Box2.CenteredAround(worldPos, new(SpawningBoxSize)));
|
|
var worldAngle = _random.NextAngle();
|
|
|
|
if (!TryGetPlacementLocation(mapId, attachedBounds, bounds!.Value, worldAngle, out var spawnLocation, out var spawnAngle))
|
|
{
|
|
Log.Error($"Failed to find free space the asteroid field. Consider tweaking asteroid field size.");
|
|
_map.DeleteMap(map);
|
|
continue;
|
|
}
|
|
|
|
mapChildren = mapXform.ChildEnumerator;
|
|
|
|
while (mapChildren.MoveNext(out var mapChild))
|
|
{
|
|
var childXform = Transform(mapChild);
|
|
var localPos = childXform.LocalPosition;
|
|
|
|
_transform.SetParent(mapChild, childXform, _map.GetMap(spawnLocation.MapId));
|
|
_transform.SetWorldPositionRotation(mapChild, spawnLocation.Position + localPos, spawnAngle, childXform);
|
|
|
|
if (HasComp<MapGridComponent>(mapChild))
|
|
{
|
|
_shuttle.Disable(mapChild);
|
|
_shuttle.SetIFFFlag(mapChild, IFFFlags.HideLabel);
|
|
}
|
|
|
|
// Handle mob restrictions
|
|
var children = childXform.ChildEnumerator;
|
|
|
|
while (children.MoveNext(out var child))
|
|
{
|
|
if (!TryComp<SalvageMobRestrictionsComponent>(child, out var salvageMob))
|
|
continue;
|
|
|
|
salvageMob.LinkedEntity = mapChild;
|
|
}
|
|
}
|
|
|
|
_map.DeleteMap(map);
|
|
}
|
|
Log.Info($"Finished generating asteroid field at {worldPos}.");
|
|
}
|
|
|
|
private bool TryGetPlacementLocation(MapId mapId, Box2Rotated attachedBounds, Box2 bounds, Angle worldAngle, out MapCoordinates coords, out Angle angle, int iter = 200, float step = 0.10f)
|
|
{
|
|
var attachedAABB = attachedBounds.CalcBoundingBox();
|
|
var minDistance = (attachedAABB.Height < attachedAABB.Width ? attachedAABB.Width : attachedAABB.Height) / 2f;
|
|
var minActualDistance = bounds.Height < bounds.Width ? minDistance + bounds.Width / 2f : minDistance + bounds.Height / 2f;
|
|
|
|
var attachedCenter = attachedAABB.Center;
|
|
var fraction = step;
|
|
|
|
for (var i = 0; i < iter; i++)
|
|
{
|
|
var randomPos = attachedCenter +
|
|
worldAngle.ToVec() * (minActualDistance * fraction);
|
|
var finalCoords = new MapCoordinates(randomPos, mapId);
|
|
|
|
angle = _random.NextAngle();
|
|
var box2 = Box2.CenteredAround(finalCoords.Position, bounds.Size);
|
|
var box2Rot = new Box2Rotated(box2, angle, finalCoords.Position);
|
|
|
|
// This doesn't stop it from spawning on top of random things in space
|
|
// Might be better like this, ghosts could stop it before
|
|
if (_mapManager.FindGridsIntersecting(finalCoords.MapId, box2Rot).Any())
|
|
{
|
|
// Bump it further and further just in case.
|
|
fraction += step;
|
|
continue;
|
|
}
|
|
|
|
coords = finalCoords;
|
|
return true;
|
|
}
|
|
|
|
angle = Angle.Zero;
|
|
coords = MapCoordinates.Nullspace;
|
|
return false;
|
|
}
|
|
}
|