* Fix for the salvage ice labs map. (#26928)
* done
* more work
* Automatic changelog update
* Update Credits (#26938)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
* Fix cryostorage identifying unknown characters as captain (#26927)
Fixed cryostorage getting captain's record for unknown jobs.
Also localized Unknown job string.
* Automatic changelog update
* Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai. (#26939)
* Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai.
* Update Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
---------
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
* Automatic changelog update
* Bug fix: Force cancellation of RCD constructions if the construction type is changed (#26935)
Force cancellation of RCD constructions if the construction type is changed
* Fix standart -> standard and dressfilled test fail (#26942)
Fix standart -> standard
* Add Ability to stop sound when MobState is Dead (#26905)
* Add stopsWhenEntityDead to sound components
* Convert component
* Review
* Fix dupe sub
---------
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
* Automatic changelog update
* Fix rockets and lasers looking like they have nothing loaded (#26933)
* Automatic changelog update
* You can now see paper on crates (with color!) (#26834)
* Implement changes on not-cooked branch
* Made it work
* Fix update appearance calls
* Fix extra indents, clean-up code, fix tests hopefully
* Fix hammy cagecrate
* Fix messing up the yml, add artifact crate specific labels back in
* Visual Studio hates yml, sad
* Seperate the colors for cargonia
* sorry json
* make label move with artifact door
* Apply suggestion changes
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
* Fix remaining crate offsets, add a few for livestock and graves (why are you labeling graves) and coffin label sprites (why are you labeling coffins??)
---------
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
* Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled (#25826)
* Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled
* merge conflicts
---------
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
* Automatic changelog update
* Add two-message overload to PopupPredicted (#26907)
Added two-message overload to PopupPredicted
* Update submodule to 218.0.0 (#26945)
* Autism pins! (#25597)
* hee hee he ha ha
* added gold varients, forgive me for my spritework
* maints loot, copying from past PRs
* Trying to fix RSI
* speedran these sprites in break time, pictures will be later
* Fixed/Tweaked glows
* consensus
* gregregation
* dam copiryte
* oops i forgot to delete 2 fields hope this works
* Automatic changelog update
* Fix database round start date issues (#26838)
How can ONE DATABASE COLUMN have so many cursed issues I don't know, but it certainly pissed off the devil in its previous life.
The start_date column on round entities in the database was added by https://github.com/space-wizards/space-station-14/pull/21153. For some reason, this PR gave the column a nonsensical default value instead of making it nullable. This default value causes the code from #25280 to break. It actually trips an assert though that's not what the original issue report ran into.
This didn't get noticed on wizden servers because we at some point backfilled the start_date column based on the stored admin logs.
So I change the database model to make this column nullable, updated the C# code to match, and made the existing migration set the invalid values to be NULL instead. Cool.
Wait how's SQLite handle in this scenario anyways? Well actually turns out the column was *completely broken* in the first place!
The code for inserting into the round table was copy pasted between SQLite and PostgreSQL, with the only difference being that the SQLite key manually assigned the primary key instead of letting SQLite AUTOINCREMENT it. And then the code to give a start_date value was only added to the PostgreSQL version (which is actually in the base class already). So for SQLite that column's been filled up with the same invalid default the whole time.
Why was the code manually assigning a PK? I checked the SQLite docs for AUTOINCREMENT[1], and the behavior seems appropriate.
I removed the SQLite-specific code path and it just seems to work regardless. The migration just sets the old values to NULL too.
BUT WAIT, THERE'S MORE!
Turns out just doing the migration on SQLite is a pain in the ass! EF Core has to create a new table to apply the nullability change, because SQLite doesn't support proper ALTER COLUMN. This causes the generated SQL commands to be weird and the UPDATE for the migration goes BEFORE the nullability change... I ended up having to make TWO migrations for SQLite. Yay.
Fixes #26800
[1]: https://www.sqlite.org/autoinc.html
* Fix options menu crashing in replays (#26911)
Not having the nullable set properly is annoying but fixing that would probably be a significant amount of work.
* Greyscale color clothing (#26943)
* greyscales color gloves, color jumpsuits, and shoes
* remove popbob
* fix test fails
* Automatic changelog update
* WT550 Buffs + Burst Mode for WT550 & C-20R (#26886)
* Slightly increased WT550 Firerate, drastically reduced recoil, and given it the option to fire in 5 round bursts.
* Given the C-20 a 5 round burst aswell
* Automatic changelog update
* make holoparasites actually holographic (#26862)
it's over
* Automatic changelog update
* Add character sheets to board game crate (#26926)
add character sheets to board game crate
* Automatic changelog update
* Game server admin API (#26880)
* Reapply "Game server api" (#26871)
This reverts commit 3aee197923.
* Rewrite 75% of the code it's good now
* Wield recoil components (#26915)
* WieldRecoilComponents
* WieldRecoilComponents
* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs
---------
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
* Clown shoes make you waddle, as God intended (#26338)
* Clown shoes make you waddle, as God intended
* OOPS
* Toned down, client system name fix
* Tidy namespacing for @deltanedas
* Refactor to handle prediction better, etc.
* Resolve PR comments.
* Automatic changelog update
* Use round time instead of server time for criminal history (#26949)
make criminal records computer use round time for history instead of the server time
* Rotate and Offset station CCVar nuke (#26175)
* no content
* add noRot to Europa
* bruh. and this
* yay
* fix
* Update debug.yml
---------
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: superjj18 <gagnonjake@gmail.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: GreaseMonk <1354802+GreaseMonk@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Terraspark4941 <terraspark4941@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: BramvanZijp <56019239+BramvanZijp@users.noreply.github.com>
Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
Co-authored-by: Tyzemol <85772526+Tyzemol@users.noreply.github.com>
Co-authored-by: Froffy025 <78222136+Froffy025@users.noreply.github.com>
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com>
Co-authored-by: ilya.mikheev.coder <imc-ext+github@ilyamikcoder.com>
631 lines
21 KiB
C#
631 lines
21 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Server.Chat.Systems;
|
|
using Content.Server.GameTicking;
|
|
using Content.Server.Station.Components;
|
|
using Content.Server.Station.Events;
|
|
using Content.Shared.Fax;
|
|
using Content.Shared.Station;
|
|
using JetBrains.Annotations;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared.Collections;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server.Station.Systems;
|
|
|
|
/// <summary>
|
|
/// System that manages stations.
|
|
/// A station is, by default, just a name, optional map prototype, and optional grids.
|
|
/// For jobs, look at StationJobSystem. For spawning, look at StationSpawningSystem.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed class StationSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
|
[Dependency] private readonly ILogManager _logManager = default!;
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
|
[Dependency] private readonly MapSystem _map = default!;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
|
|
private bool _randomStationOffset;
|
|
private bool _randomStationRotation;
|
|
private float _maxRandomStationOffset;
|
|
|
|
/// <inheritdoc/>
|
|
public override void Initialize()
|
|
{
|
|
_sawmill = _logManager.GetSawmill("station");
|
|
|
|
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd);
|
|
SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad);
|
|
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationAdd);
|
|
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
|
|
SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted);
|
|
SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent);
|
|
|
|
_player.PlayerStatusChanged += OnPlayerStatusChanged;
|
|
}
|
|
|
|
private void OnStationSplitEvent(EntityUid uid, StationMemberComponent component, ref PostGridSplitEvent args)
|
|
{
|
|
AddGridToStation(component.Station, args.Grid); // Add the new grid as a member.
|
|
}
|
|
|
|
private void OnStationGridDeleted(EntityUid uid, StationMemberComponent component, ComponentShutdown args)
|
|
{
|
|
if (!TryComp<StationDataComponent>(component.Station, out var stationData))
|
|
return;
|
|
|
|
stationData.Grids.Remove(uid);
|
|
}
|
|
|
|
public override void Shutdown()
|
|
{
|
|
base.Shutdown();
|
|
_player.PlayerStatusChanged -= OnPlayerStatusChanged;
|
|
}
|
|
|
|
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
|
{
|
|
if (e.NewStatus == SessionStatus.Connected)
|
|
{
|
|
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationNames()), e.Session);
|
|
}
|
|
}
|
|
|
|
#region Event handlers
|
|
|
|
private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentStartup args)
|
|
{
|
|
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationNames()), Filter.Broadcast());
|
|
|
|
var metaData = MetaData(uid);
|
|
RaiseLocalEvent(new StationInitializedEvent(uid));
|
|
_sawmill.Info($"Set up station {metaData.EntityName} ({uid}).");
|
|
|
|
}
|
|
|
|
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
|
|
{
|
|
foreach (var grid in component.Grids)
|
|
{
|
|
RemComp<StationMemberComponent>(grid);
|
|
}
|
|
|
|
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationNames()), Filter.Broadcast());
|
|
}
|
|
|
|
private void OnPostGameMapLoad(PostGameMapLoad ev)
|
|
{
|
|
var dict = new Dictionary<string, List<EntityUid>>();
|
|
|
|
void AddGrid(string station, EntityUid grid)
|
|
{
|
|
if (dict.ContainsKey(station))
|
|
{
|
|
dict[station].Add(grid);
|
|
}
|
|
else
|
|
{
|
|
dict[station] = new List<EntityUid> {grid};
|
|
}
|
|
}
|
|
|
|
// Iterate over all BecomesStation
|
|
foreach (var grid in ev.Grids)
|
|
{
|
|
// We still setup the grid
|
|
if (!TryComp<BecomesStationComponent>(grid, out var becomesStation))
|
|
continue;
|
|
|
|
AddGrid(becomesStation.Id, grid);
|
|
}
|
|
|
|
if (!dict.Any())
|
|
{
|
|
// Oh jeez, no stations got loaded.
|
|
// We'll yell about it, but the thing this used to do with creating a dummy is kinda pointless now.
|
|
_sawmill.Error($"There were no station grids for {ev.GameMap.ID}!");
|
|
}
|
|
|
|
foreach (var (id, gridIds) in dict)
|
|
{
|
|
StationConfig stationConfig;
|
|
|
|
if (ev.GameMap.Stations.ContainsKey(id))
|
|
stationConfig = ev.GameMap.Stations[id];
|
|
else
|
|
{
|
|
_sawmill.Error($"The station {id} in map {ev.GameMap.ID} does not have an associated station config!");
|
|
continue;
|
|
}
|
|
|
|
InitializeNewStation(stationConfig, gridIds, ev.StationName);
|
|
}
|
|
}
|
|
|
|
private void OnRoundEnd(GameRunLevelChangedEvent eventArgs)
|
|
{
|
|
if (eventArgs.New != GameRunLevel.PreRoundLobby)
|
|
return;
|
|
|
|
var query = EntityQueryEnumerator<StationDataComponent>();
|
|
while (query.MoveNext(out var station, out _))
|
|
{
|
|
QueueDel(station);
|
|
}
|
|
}
|
|
|
|
#endregion Event handlers
|
|
|
|
/// <summary>
|
|
/// Gets the largest member grid from a station.
|
|
/// </summary>
|
|
public EntityUid? GetLargestGrid(StationDataComponent component)
|
|
{
|
|
EntityUid? largestGrid = null;
|
|
Box2 largestBounds = new Box2();
|
|
|
|
foreach (var gridUid in component.Grids)
|
|
{
|
|
if (!TryComp<MapGridComponent>(gridUid, out var grid) ||
|
|
grid.LocalAABB.Size.LengthSquared() < largestBounds.Size.LengthSquared())
|
|
continue;
|
|
|
|
largestBounds = grid.LocalAABB;
|
|
largestGrid = gridUid;
|
|
}
|
|
|
|
return largestGrid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the total number of tiles contained in the station's grids.
|
|
/// </summary>
|
|
public int GetTileCount(StationDataComponent component)
|
|
{
|
|
var count = 0;
|
|
foreach (var gridUid in component.Grids)
|
|
{
|
|
if (!TryComp<MapGridComponent>(gridUid, out var grid))
|
|
continue;
|
|
|
|
count += _map.GetAllTiles(gridUid, grid).Count();
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to retrieve a filter for everything in the station the source is on.
|
|
/// </summary>
|
|
/// <param name="source">The entity to use to find the station.</param>
|
|
/// <param name="range">The range around the station</param>
|
|
/// <returns></returns>
|
|
public Filter GetInOwningStation(EntityUid source, float range = 32f)
|
|
{
|
|
var station = GetOwningStation(source);
|
|
|
|
if (TryComp<StationDataComponent>(station, out var data))
|
|
{
|
|
return GetInStation(data);
|
|
}
|
|
|
|
return Filter.Empty();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a filter for everything in a particular station or near its member grids.
|
|
/// </summary>
|
|
public Filter GetInStation(StationDataComponent dataComponent, float range = 32f)
|
|
{
|
|
// Could also use circles if you wanted.
|
|
var bounds = new ValueList<Box2>(dataComponent.Grids.Count);
|
|
var filter = Filter.Empty();
|
|
var mapIds = new ValueList<MapId>();
|
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
|
|
foreach (var gridUid in dataComponent.Grids)
|
|
{
|
|
if (!TryComp(gridUid, out MapGridComponent? grid) ||
|
|
!xformQuery.TryGetComponent(gridUid, out var xform))
|
|
continue;
|
|
|
|
var mapId = xform.MapID;
|
|
var position = _transform.GetWorldPosition(xform, xformQuery);
|
|
var bound = grid.LocalAABB.Enlarged(range).Translated(position);
|
|
|
|
bounds.Add(bound);
|
|
if (!mapIds.Contains(mapId))
|
|
{
|
|
mapIds.Add(xform.MapID);
|
|
}
|
|
}
|
|
|
|
foreach (var session in Filter.GetAllPlayers(_player))
|
|
{
|
|
var entity = session.AttachedEntity;
|
|
if (entity == null || !xformQuery.TryGetComponent(entity, out var xform))
|
|
continue;
|
|
|
|
var mapId = xform.MapID;
|
|
|
|
if (!mapIds.Contains(mapId))
|
|
continue;
|
|
|
|
var position = _transform.GetWorldPosition(xform, xformQuery);
|
|
|
|
foreach (var bound in bounds)
|
|
{
|
|
if (!bound.Contains(position))
|
|
continue;
|
|
|
|
filter.AddPlayer(session);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new station with the given information.
|
|
/// </summary>
|
|
/// <param name="stationConfig">The game map prototype used, if any.</param>
|
|
/// <param name="gridIds">All grids that should be added to the station.</param>
|
|
/// <param name="name">Optional override for the station name.</param>
|
|
/// <remarks>This is for ease of use, manually spawning the entity works just fine.</remarks>
|
|
/// <returns>The initialized station.</returns>
|
|
public EntityUid InitializeNewStation(StationConfig stationConfig, IEnumerable<EntityUid>? gridIds, string? name = null)
|
|
{
|
|
// Use overrides for setup.
|
|
var station = EntityManager.SpawnEntity(stationConfig.StationPrototype, MapCoordinates.Nullspace, stationConfig.StationComponentOverrides);
|
|
|
|
|
|
|
|
if (name is not null)
|
|
RenameStation(station, name, false);
|
|
|
|
DebugTools.Assert(HasComp<StationDataComponent>(station), "Stations should have StationData in their prototype.");
|
|
|
|
var data = Comp<StationDataComponent>(station);
|
|
name ??= MetaData(station).EntityName;
|
|
|
|
var entry = gridIds ?? Array.Empty<EntityUid>();
|
|
|
|
foreach (var grid in entry)
|
|
{
|
|
AddGridToStation(station, grid, null, data, name);
|
|
}
|
|
|
|
if (TryComp<StationRandomTransformComponent>(station, out var random))
|
|
{
|
|
Angle? rotation = null;
|
|
Vector2? offset = null;
|
|
|
|
if (random.MaxStationOffset != null)
|
|
offset = _random.NextVector2(-random.MaxStationOffset.Value, random.MaxStationOffset.Value);
|
|
|
|
if (random.EnableStationRotation)
|
|
rotation = _random.NextAngle();
|
|
|
|
foreach (var grid in entry)
|
|
{
|
|
//planetary maps give an error when trying to change from position or rotation.
|
|
//This is still the case, but it will be irrelevant after the https://github.com/space-wizards/space-station-14/pull/26510
|
|
if (rotation != null && offset != null)
|
|
{
|
|
var pos = _transform.GetWorldPosition(grid);
|
|
_transform.SetWorldPositionRotation(grid, pos + offset.Value, rotation.Value);
|
|
continue;
|
|
}
|
|
if (rotation != null)
|
|
{
|
|
_transform.SetWorldRotation(grid, rotation.Value);
|
|
continue;
|
|
}
|
|
if (offset != null)
|
|
{
|
|
var pos = _transform.GetWorldPosition(grid);
|
|
_transform.SetWorldPosition(grid, pos + offset.Value);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
var ev = new StationPostInitEvent((station, data));
|
|
RaiseLocalEvent(station, ref ev, true);
|
|
|
|
return station;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the given grid to a station.
|
|
/// </summary>
|
|
/// <param name="mapGrid">Grid to attach.</param>
|
|
/// <param name="station">Station to attach the grid to.</param>
|
|
/// <param name="gridComponent">Resolve pattern, grid component of mapGrid.</param>
|
|
/// <param name="stationData">Resolve pattern, station data component of station.</param>
|
|
/// <param name="name">The name to assign to the grid if any.</param>
|
|
/// <exception cref="ArgumentException">Thrown when mapGrid or station are not a grid or station, respectively.</exception>
|
|
public void AddGridToStation(EntityUid station, EntityUid mapGrid, MapGridComponent? gridComponent = null, StationDataComponent? stationData = null, string? name = null)
|
|
{
|
|
if (!Resolve(mapGrid, ref gridComponent))
|
|
throw new ArgumentException("Tried to initialize a station on a non-grid entity!", nameof(mapGrid));
|
|
if (!Resolve(station, ref stationData))
|
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
_metaData.SetEntityName(mapGrid, name);
|
|
|
|
var stationMember = EnsureComp<StationMemberComponent>(mapGrid);
|
|
stationMember.Station = station;
|
|
stationData.Grids.Add(mapGrid);
|
|
|
|
RaiseLocalEvent(station, new StationGridAddedEvent(mapGrid, false), true);
|
|
|
|
_sawmill.Info($"Adding grid {mapGrid} to station {Name(station)} ({station})");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the given grid from a station.
|
|
/// </summary>
|
|
/// <param name="station">Station to remove the grid from.</param>
|
|
/// <param name="mapGrid">Grid to remove</param>
|
|
/// <param name="gridComponent">Resolve pattern, grid component of mapGrid.</param>
|
|
/// <param name="stationData">Resolve pattern, station data component of station.</param>
|
|
/// <exception cref="ArgumentException">Thrown when mapGrid or station are not a grid or station, respectively.</exception>
|
|
public void RemoveGridFromStation(EntityUid station, EntityUid mapGrid, MapGridComponent? gridComponent = null, StationDataComponent? stationData = null)
|
|
{
|
|
if (!Resolve(mapGrid, ref gridComponent))
|
|
throw new ArgumentException("Tried to initialize a station on a non-grid entity!", nameof(mapGrid));
|
|
if (!Resolve(station, ref stationData))
|
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
|
|
|
RemComp<StationMemberComponent>(mapGrid);
|
|
stationData.Grids.Remove(mapGrid);
|
|
|
|
RaiseLocalEvent(station, new StationGridRemovedEvent(mapGrid), true);
|
|
_sawmill.Info($"Removing grid {mapGrid} from station {Name(station)} ({station})");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renames the given station.
|
|
/// </summary>
|
|
/// <param name="station">Station to rename.</param>
|
|
/// <param name="name">The new name to apply.</param>
|
|
/// <param name="loud">Whether or not to announce the rename.</param>
|
|
/// <param name="stationData">Resolve pattern, station data component of station.</param>
|
|
/// <param name="metaData">Resolve pattern, metadata component of station.</param>
|
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
|
public void RenameStation(EntityUid station, string name, bool loud = true, StationDataComponent? stationData = null, MetaDataComponent? metaData = null)
|
|
{
|
|
if (!Resolve(station, ref stationData, ref metaData))
|
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
|
|
|
var oldName = metaData.EntityName;
|
|
_metaData.SetEntityName(station, name, metaData);
|
|
|
|
if (loud)
|
|
{
|
|
_chatSystem.DispatchStationAnnouncement(station, $"The station {oldName} has been renamed to {name}.");
|
|
}
|
|
|
|
RaiseLocalEvent(station, new StationRenamedEvent(oldName, name), true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes the given station.
|
|
/// </summary>
|
|
/// <param name="station">Station to delete.</param>
|
|
/// <param name="stationData">Resolve pattern, station data component of station.</param>
|
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
|
public void DeleteStation(EntityUid station, StationDataComponent? stationData = null)
|
|
{
|
|
if (!Resolve(station, ref stationData))
|
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
|
|
|
QueueDel(station);
|
|
}
|
|
|
|
public EntityUid? GetOwningStation(EntityUid? entity, TransformComponent? xform = null)
|
|
{
|
|
if (entity == null)
|
|
return null;
|
|
|
|
return GetOwningStation(entity.Value, xform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the station that "owns" the given entity (essentially, the station the grid it's on is attached to)
|
|
/// </summary>
|
|
/// <param name="entity">Entity to find the owner of.</param>
|
|
/// <param name="xform">Resolve pattern, transform of the entity.</param>
|
|
/// <returns>The owning station, if any.</returns>
|
|
/// <remarks>
|
|
/// This does not remember what station an entity started on, it simply checks where it is currently located.
|
|
/// </remarks>
|
|
public EntityUid? GetOwningStation(EntityUid entity, TransformComponent? xform = null)
|
|
{
|
|
if (!Resolve(entity, ref xform))
|
|
throw new ArgumentException("Tried to use an abstract entity!", nameof(entity));
|
|
|
|
if (TryComp<StationDataComponent>(entity, out _))
|
|
{
|
|
// We are the station, just return ourselves.
|
|
return entity;
|
|
}
|
|
|
|
if (TryComp<MapGridComponent>(entity, out _))
|
|
{
|
|
// We are the station, just check ourselves.
|
|
return CompOrNull<StationMemberComponent>(entity)?.Station;
|
|
}
|
|
|
|
if (xform.GridUid == EntityUid.Invalid)
|
|
{
|
|
Log.Debug("Unable to get owning station - GridUid invalid.");
|
|
return null;
|
|
}
|
|
|
|
return CompOrNull<StationMemberComponent>(xform.GridUid)?.Station;
|
|
}
|
|
|
|
public List<EntityUid> GetStations()
|
|
{
|
|
var stations = new List<EntityUid>();
|
|
var query = EntityQueryEnumerator<StationDataComponent>();
|
|
while (query.MoveNext(out var uid, out _))
|
|
{
|
|
stations.Add(uid);
|
|
}
|
|
|
|
return stations;
|
|
}
|
|
|
|
public HashSet<EntityUid> GetStationsSet()
|
|
{
|
|
var stations = new HashSet<EntityUid>();
|
|
var query = EntityQueryEnumerator<StationDataComponent>();
|
|
while (query.MoveNext(out var uid, out _))
|
|
{
|
|
stations.Add(uid);
|
|
}
|
|
|
|
return stations;
|
|
}
|
|
|
|
public List<(string Name, NetEntity Entity)> GetStationNames()
|
|
{
|
|
var stations = GetStationsSet();
|
|
var stats = new List<(string Name, NetEntity Station)>();
|
|
|
|
foreach (var weh in stations)
|
|
{
|
|
stats.Add((MetaData(weh).EntityName, GetNetEntity(weh)));
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the first station that has a grid in a certain map.
|
|
/// If the map has no stations, null is returned instead.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If there are multiple stations on a map it is probably arbitrary which one is returned.
|
|
/// </remarks>
|
|
public EntityUid? GetStationInMap(MapId map)
|
|
{
|
|
var query = EntityQueryEnumerator<StationDataComponent>();
|
|
while (query.MoveNext(out var uid, out var data))
|
|
{
|
|
foreach (var gridUid in data.Grids)
|
|
{
|
|
if (Transform(gridUid).MapID == map)
|
|
{
|
|
return uid;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Broadcast event fired when a station is first set up.
|
|
/// This is the ideal point to add components to it.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed class StationInitializedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Station this event is for.
|
|
/// </summary>
|
|
public EntityUid Station;
|
|
|
|
public StationInitializedEvent(EntityUid station)
|
|
{
|
|
Station = station;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Directed event fired on a station when a grid becomes a member of the station.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed class StationGridAddedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// ID of the grid added to the station.
|
|
/// </summary>
|
|
public EntityUid GridId;
|
|
|
|
/// <summary>
|
|
/// Indicates that the event was fired during station setup,
|
|
/// so that it can be ignored if StationInitializedEvent was already handled.
|
|
/// </summary>
|
|
public bool IsSetup;
|
|
|
|
public StationGridAddedEvent(EntityUid gridId, bool isSetup)
|
|
{
|
|
GridId = gridId;
|
|
IsSetup = isSetup;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Directed event fired on a station when a grid is no longer a member of the station.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed class StationGridRemovedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// ID of the grid removed from the station.
|
|
/// </summary>
|
|
public EntityUid GridId;
|
|
|
|
public StationGridRemovedEvent(EntityUid gridId)
|
|
{
|
|
GridId = gridId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Directed event fired on a station when it is renamed.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed class StationRenamedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Prior name of the station.
|
|
/// </summary>
|
|
public string OldName;
|
|
|
|
/// <summary>
|
|
/// New name of the station.
|
|
/// </summary>
|
|
public string NewName;
|
|
|
|
public StationRenamedEvent(string oldName, string newName)
|
|
{
|
|
OldName = oldName;
|
|
NewName = newName;
|
|
}
|
|
}
|
|
|