Files
OldThink/Content.Server/Salvage/SpawnSalvageMissionJob.cs
SlamBamActionman bed9e9ac6a Coordinates Disks & Shuttle FTL Travel (#23240)
* Adds the CentComm Disk and configures it to work with direct-use shuttles

* Added functionality for drone shuttles (i.e. cargo shuttle)

* Adds support for pods, and a disk console object for disks to be inserted into. Also sprites.

* Added the disk to HoP's locker

* Removed leftover logs & comments

* Fix for integration test

* Apply suggestions from code review (formatting & proper DataField)

Co-authored-by: 0x6273 <0x40@keemail.me>

* Fix integration test & changes based on code review

* Includes Disk Cases to contain Coordinate Disks, which are now CDs instead of Floppy Disks

* Check pods & non-evac shuttles for CentCom travel, even in FTL

* Import

* Remove CentCom travel restrictions & pod disk consoles

* Major changes that changes the coordinates disk system to work with salvage expeditions

* Missed CC diskcase removal

* Fix build

* Review suggestions and changes

* Major additional changes after merge

* Minor tag miss

* Integration test fix

* review

---------

Co-authored-by: 0x6273 <0x40@keemail.me>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 15:50:00 +11:00

344 lines
12 KiB
C#

using System.Collections;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Robust.Shared.CPUJob.JobQueues;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Parallax;
using Content.Server.Procedural;
using Content.Server.Salvage.Expeditions;
using Content.Server.Salvage.Expeditions.Structure;
using Content.Shared.Atmos;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Dataset;
using Content.Shared.Gravity;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Physics;
using Content.Shared.Procedural;
using Content.Shared.Procedural.Loot;
using Content.Shared.Random;
using Content.Shared.Salvage;
using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers;
using Content.Shared.Shuttles.Components;
using Content.Shared.Storage;
using Robust.Shared.Collections;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.Shuttles.Components;
using Content.Shared.Coordinates;
using Content.Shared.Shuttles.Components;
namespace Content.Server.Salvage;
public sealed class SpawnSalvageMissionJob : Job<bool>
{
private readonly IEntityManager _entManager;
private readonly IGameTiming _timing;
private readonly IMapManager _mapManager;
private readonly IPrototypeManager _prototypeManager;
private readonly AnchorableSystem _anchorable;
private readonly BiomeSystem _biome;
private readonly DungeonSystem _dungeon;
private readonly MetaDataSystem _metaData;
private readonly SharedTransformSystem _xforms;
public readonly EntityUid Station;
public readonly EntityUid? CoordinatesDisk;
private readonly SalvageMissionParams _missionParams;
private readonly ISawmill _sawmill;
public SpawnSalvageMissionJob(
double maxTime,
IEntityManager entManager,
IGameTiming timing,
ILogManager logManager,
IMapManager mapManager,
IPrototypeManager protoManager,
AnchorableSystem anchorable,
BiomeSystem biome,
DungeonSystem dungeon,
MetaDataSystem metaData,
SharedTransformSystem xform,
EntityUid station,
EntityUid? coordinatesDisk,
SalvageMissionParams missionParams,
CancellationToken cancellation = default) : base(maxTime, cancellation)
{
_entManager = entManager;
_timing = timing;
_mapManager = mapManager;
_prototypeManager = protoManager;
_anchorable = anchorable;
_biome = biome;
_dungeon = dungeon;
_metaData = metaData;
_xforms = xform;
Station = station;
CoordinatesDisk = coordinatesDisk;
_missionParams = missionParams;
_sawmill = logManager.GetSawmill("salvage_job");
#if !DEBUG
_sawmill.Level = LogLevel.Info;
#endif
}
protected override async Task<bool> Process()
{
_sawmill.Debug("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}");
var mapId = _mapManager.CreateMap();
var mapUid = _mapManager.GetMapEntityId(mapId);
_mapManager.AddUninitializedMap(mapId);
MetaDataComponent? metadata = null;
var grid = _entManager.EnsureComponent<MapGridComponent>(mapUid);
var random = new Random(_missionParams.Seed);
var destComp = _entManager.AddComponent<FTLDestinationComponent>(mapUid);
destComp.BeaconsOnly = true;
destComp.RequireCoordinateDisk = true;
destComp.Enabled = true;
_metaData.SetEntityName(mapUid, SharedSalvageSystem.GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), _missionParams.Seed));
_entManager.AddComponent<FTLBeaconComponent>(mapUid);
// Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity
if (CoordinatesDisk.HasValue)
{
var cd = _entManager.EnsureComponent<ShuttleDestinationCoordinatesComponent>(CoordinatesDisk.Value);
cd.Destination = mapUid;
_entManager.Dirty(CoordinatesDisk.Value, cd);
}
// Setup mission configs
// As we go through the config the rating will deplete so we'll go for most important to least important.
var difficultyId = "Moderate";
var difficultyProto = _prototypeManager.Index<SalvageDifficultyPrototype>(difficultyId);
var mission = _entManager.System<SharedSalvageSystem>()
.GetMission(difficultyProto, _missionParams.Seed);
var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
if (missionBiome.BiomePrototype != null)
{
var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
var biomeSystem = _entManager.System<BiomeSystem>();
biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
biomeSystem.SetSeed(mapUid, biome, mission.Seed);
_entManager.Dirty(mapUid, biome);
// Gravity
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
gravity.Enabled = true;
_entManager.Dirty(mapUid, gravity, metadata);
// Atmos
var air = _prototypeManager.Index<SalvageAirMod>(mission.Air);
// copy into a new array since the yml deserialization discards the fixed length
var moles = new float[Atmospherics.AdjustedNumberOfGases];
air.Gases.CopyTo(moles, 0);
var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
_entManager.System<AtmosphereSystem>().SetMapSpace(mapUid, air.Space, atmos);
_entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos);
if (mission.Color != null)
{
var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
lighting.AmbientLightColor = mission.Color.Value;
_entManager.Dirty(mapUid, lighting);
}
}
_mapManager.DoMapInitialize(mapId);
_mapManager.SetMapPaused(mapId, true);
// Setup expedition
var expedition = _entManager.AddComponent<SalvageExpeditionComponent>(mapUid);
expedition.Station = Station;
expedition.EndTime = _timing.CurTime + mission.Duration;
expedition.MissionParams = _missionParams;
var landingPadRadius = 24;
var minDungeonOffset = landingPadRadius + 4;
// We'll use the dungeon rotation as the spawn angle
var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed);
var maxDungeonOffset = minDungeonOffset + 12;
var dungeonOffsetDistance = minDungeonOffset + (maxDungeonOffset - minDungeonOffset) * random.NextFloat();
var dungeonOffset = new Vector2(0f, dungeonOffsetDistance);
dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
var dungeonConfig = _prototypeManager.Index<DungeonConfigPrototype>(dungeonMod.Proto);
var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
_missionParams.Seed));
// Aborty
if (dungeon.Rooms.Count == 0)
{
return false;
}
expedition.DungeonLocation = dungeonOffset;
List<Vector2i> reservedTiles = new();
foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false))
{
if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
continue;
reservedTiles.Add(tile.GridIndices);
}
var budgetEntries = new List<IBudgetEntry>();
/*
* GUARANTEED LOOT
*/
// We'll always add this loot if possible
// mainly used for ore layers.
foreach (var lootProto in _prototypeManager.EnumeratePrototypes<SalvageLootPrototype>())
{
if (!lootProto.Guaranteed)
continue;
await SpawnDungeonLoot(lootProto, mapUid);
}
// Handle boss loot (when relevant).
// Handle mob loot.
// Handle remaining loot
/*
* MOB SPAWNS
*/
var mobBudget = difficultyProto.MobBudget;
var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
var randomSystem = _entManager.System<RandomSystem>();
foreach (var entry in faction.MobGroups)
{
budgetEntries.Add(entry);
}
var probSum = budgetEntries.Sum(x => x.Prob);
while (mobBudget > 0f)
{
var entry = randomSystem.GetBudgetEntry(ref mobBudget, ref probSum, budgetEntries, random);
if (entry == null)
break;
await SpawnRandomEntry(grid, entry, dungeon, random);
}
var allLoot = _prototypeManager.Index<SalvageLootPrototype>(SharedSalvageSystem.ExpeditionsLootProto);
var lootBudget = difficultyProto.LootBudget;
foreach (var rule in allLoot.LootRules)
{
switch (rule)
{
case RandomSpawnsLoot randomLoot:
budgetEntries.Clear();
foreach (var entry in randomLoot.Entries)
{
budgetEntries.Add(entry);
}
probSum = budgetEntries.Sum(x => x.Prob);
while (lootBudget > 0f)
{
var entry = randomSystem.GetBudgetEntry(ref lootBudget, ref probSum, budgetEntries, random);
if (entry == null)
break;
_sawmill.Debug($"Spawning dungeon loot {entry.Proto}");
await SpawnRandomEntry(grid, entry, dungeon, random);
}
break;
default:
throw new NotImplementedException();
}
}
return true;
}
private async Task SpawnRandomEntry(MapGridComponent grid, IBudgetEntry entry, Dungeon dungeon, Random random)
{
await SuspendIfOutOfTime();
var availableRooms = new ValueList<DungeonRoom>(dungeon.Rooms);
var availableTiles = new List<Vector2i>();
while (availableRooms.Count > 0)
{
availableTiles.Clear();
var roomIndex = random.Next(availableRooms.Count);
var room = availableRooms.RemoveSwap(roomIndex);
availableTiles.AddRange(room.Tiles);
while (availableTiles.Count > 0)
{
var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count));
if (!_anchorable.TileFree(grid, tile, (int) CollisionGroup.MachineLayer,
(int) CollisionGroup.MachineLayer))
{
continue;
}
var uid = _entManager.SpawnAtPosition(entry.Proto, grid.GridTileToLocal(tile));
_entManager.RemoveComponent<GhostRoleComponent>(uid);
_entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
return;
}
}
// oh noooooooooooo
}
private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
{
for (var i = 0; i < loot.LootRules.Count; i++)
{
var rule = loot.LootRules[i];
switch (rule)
{
case BiomeMarkerLoot biomeLoot:
{
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
{
_biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype);
}
}
break;
case BiomeTemplateLoot biomeLoot:
{
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
{
_biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
}
}
break;
}
}
}
}