diff --git a/Content.Server/_Citadel/Shipyard/Commands/PurchaseShuttleCommand.cs b/Content.Server/_Citadel/Shipyard/Commands/PurchaseShuttleCommand.cs new file mode 100644 index 0000000000..09c8d4e1b8 --- /dev/null +++ b/Content.Server/_Citadel/Shipyard/Commands/PurchaseShuttleCommand.cs @@ -0,0 +1,46 @@ +using Content.Server.Administration; +using Content.Server.Maps; +using Content.Server.Shipyard.Systems; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Shipyard.Commands; + +/// +/// Purchases a shuttle and docks it to a station. +/// +[AdminCommand(AdminFlags.Fun)] +public sealed class PurchaseShuttleCommand : IConsoleCommand +{ + [Dependency] private readonly IEntitySystemManager _entityManager = default!; + public string Command => "purchaseshuttle"; + public string Description => "Spawns and docks a specified shuttle from a grid file"; + public string Help => $"{Command} "; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (!int.TryParse(args[0], out var stationId)) + { + shell.WriteError($"{args[0]} is not a valid integer."); + return; + } + + var shuttlePath = args[1]; + var system = _entityManager.GetEntitySystem(); + var station = new EntityUid(stationId); + system.PurchaseShuttle(station, shuttlePath); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + switch (args.Length) + { + case 1: + return CompletionResult.FromHint(Loc.GetString("station-id")); + case 2: + var opts = CompletionHelper.PrototypeIDs(); + return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/_Citadel/Shipyard/Commands/SellShuttleCommand.cs b/Content.Server/_Citadel/Shipyard/Commands/SellShuttleCommand.cs new file mode 100644 index 0000000000..2d6b150339 --- /dev/null +++ b/Content.Server/_Citadel/Shipyard/Commands/SellShuttleCommand.cs @@ -0,0 +1,49 @@ +using Content.Server.Administration; +using Content.Server.Shipyard.Systems; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Shipyard.Commands; + +/// +/// Sells a shuttle docked to a station. +/// +[AdminCommand(AdminFlags.Fun)] +public sealed class SellShuttleCommand : IConsoleCommand +{ + [Dependency] private readonly IEntitySystemManager _entityManager = default!; + + public string Command => "sellshuttle"; + public string Description => "Appraises and sells a selected grid connected to selected station"; + public string Help => $"{Command} "; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (!EntityUid.TryParse(args[0], out var stationId)) + { + shell.WriteError($"{args[0]} is not a valid entity uid."); + return; + } + + if (!EntityUid.TryParse(args[1], out var shuttleId)) + { + shell.WriteError($"{args[0]} is not a valid entity uid."); + return; + }; + + var system = _entityManager.GetEntitySystem(); + system.SellShuttle(stationId, shuttleId); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + switch (args.Length) + { + case 1: + return CompletionResult.FromHint(Loc.GetString("station-id")); + case 2: + return CompletionResult.FromHint(Loc.GetString("shuttle-id")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/_Citadel/Shipyard/Systems/ShipyardSystem.cs b/Content.Server/_Citadel/Shipyard/Systems/ShipyardSystem.cs new file mode 100644 index 0000000000..9caca90bc5 --- /dev/null +++ b/Content.Server/_Citadel/Shipyard/Systems/ShipyardSystem.cs @@ -0,0 +1,189 @@ +using System; +using Content.Server.Shuttles.Systems; +using Content.Server.Shuttles.Components; +using Content.Server.Station.Components; +using Content.Server.Cargo.Systems; +using Content.Server.Station.Systems; +using Content.Shared.MobState.Components; +using Content.Shared.GameTicking; +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.Shipyard.Systems +{ + + public sealed partial class ShipyardSystem : EntitySystem + { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly PricingSystem _pricing = default!; + [Dependency] private readonly ShuttleSystem _shuttle = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly CargoSystem _cargo = default!; + [Dependency] private readonly MapLoaderSystem _map = default!; + + public MapId? ShipyardMap { get; private set; } + private float _shuttleIndex; + private const float ShuttleSpawnBuffer = 1f; + private ISawmill _sawmill = default!; + + public override void Initialize() + { + _sawmill = Logger.GetSawmill("shipyard"); + SubscribeLocalEvent(OnShipyardStartup); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnShipyardStartup(EntityUid uid, BecomesStationComponent component, ComponentStartup args) + { + SetupShipyard(); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + CleanupShipyard(); + } + + /// + /// Adds a ship to the shipyard, calculates its price, and attempts to ftl-dock it to the given station + /// + /// The ID of the station to dock the shuttle to + /// The path to the grid file to load. Must be a grid file! + public void PurchaseShuttle(EntityUid? stationUid, string shuttlePath) + { + if (!TryComp(stationUid, out var stationData) || !TryComp(AddShuttle(shuttlePath), out var shuttle)) + return; + + var targetGrid = _station.GetLargestGrid(stationData); + + if (targetGrid == null) + return; + + var price = _pricing.AppraiseGrid(shuttle.Owner, null); + + //can do FTLTravel later instead if we want to open that door + _shuttle.TryFTLDock(shuttle, targetGrid.Value); + + _sawmill.Info($"Shuttle {shuttlePath} was purchased at {targetGrid} for {price}"); + } + + /// + /// Loads a paused shuttle into the ShipyardMap from a file path + /// + /// The path to the grid file to load. Must be a grid file! + /// Returns the EntityUid of the shuttle + private EntityUid? AddShuttle(string shuttlePath) + { + if (ShipyardMap == null) + return null; + + var loadOptions = new MapLoadOptions() + { + Offset = (500f + _shuttleIndex, 0f) + }; + + if (!_map.TryLoad(ShipyardMap.Value, shuttlePath.ToString(), out var gridList, loadOptions) || gridList == null) + { + _sawmill.Error($"Unable to spawn shuttle {shuttlePath}"); + return null; + }; + + _shuttleIndex += _mapManager.GetGrid(gridList[0]).LocalAABB.Width + ShuttleSpawnBuffer; + var actualGrids = new List(); + var gridQuery = GetEntityQuery(); + + foreach (var ent in gridList) + { + if (!gridQuery.HasComponent(ent)) + continue; + + actualGrids.Add(ent); + }; + + //only dealing with 1 grid at a time for now, until more is known about multi-grid drifting + if (actualGrids.Count != 1) + { + _sawmill.Error($"Unable to spawn shuttle {shuttlePath}"); + return null; + }; + + return actualGrids[0]; + } + + /// + /// Checks a shuttle to make sure that it is docked to the given station, and that there are no lifeforms aboard. Then it appraises the grid, outputs to the server log, and deletes the grid + /// + /// The ID of the station that the shuttle is docked to + /// The grid ID of the shuttle to be appraised and sold + public void SellShuttle(EntityUid stationUid, EntityUid shuttleUid) + { + if (!TryComp(stationUid, out var stationGrid) || !HasComp(shuttleUid) || !TryComp(shuttleUid, out var xform) || ShipyardMap == null) + return; + + var targetGrid = _station.GetLargestGrid(stationGrid); + + if (targetGrid == null) + return; + + var gridDocks = _shuttle.GetDocks((EntityUid) targetGrid); + var shuttleDocks = _shuttle.GetDocks(shuttleUid); + var isDocked = false; + + foreach (var shuttleDock in shuttleDocks) + { + foreach (var gridDock in gridDocks) + { + if (shuttleDock.DockedWith == gridDock.Owner) + { + isDocked = true; + break; + }; + }; + if (isDocked) + break; + }; + + if (!isDocked) + { + _sawmill.Warning($"shuttle is not docked to that station"); + return; + }; + + var mobQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + + if (_cargo.FoundOrganics(shuttleUid, mobQuery, xformQuery)) + { + _sawmill.Warning($"organics on board"); + return; + }; + + //just yeet and delete for now. Might want to split it into another function later to send back to the shipyard map first to pause for something + var price = _pricing.AppraiseGrid(shuttleUid); + _mapManager.DeleteGrid(shuttleUid); + _sawmill.Info($"Sold shuttle {shuttleUid} for {price}"); + } + + private void CleanupShipyard() + { + if (ShipyardMap == null || !_mapManager.MapExists(ShipyardMap.Value)) + { + ShipyardMap = null; + return; + }; + + _mapManager.DeleteMap(ShipyardMap.Value); + } + + private void SetupShipyard() + { + if (ShipyardMap != null && _mapManager.MapExists(ShipyardMap.Value)) + return; + + ShipyardMap = _mapManager.CreateMap(); + + _mapManager.SetMapPaused(ShipyardMap.Value, true); + } + } +}