From 3327c2998f28c5a0e47ffaddb4c5c45b996e7e4e Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Mon, 19 Dec 2022 21:38:34 -0500 Subject: [PATCH] Adds Special Respawn, spawns a new disk if the old one was deleted (#12762) * Spawns a new disk if one was deleted * Adds tiledef to space checks, also adds a way to randomly check for a respawn point on station * Removes unused method * Cuts down on tile checks needed by checking surrounding tiles first * Fixes up an issue where the coords wouldn't spawn * Removes disk system, adds special respawning system and comp * Fixes spelling error * Fixes linter issue * Maybe fixes the linter with a ref event? * Empty commit to rerun tests * Maybe fix? * check for deleted grid * Moves shutdown code to terminating code * Customtypeseralizer * changes name of datafield * Removes owning station references * Trying the queue event again --- Content.Server/Nuke/NukeSystem.cs | 31 ++- .../Respawn/SpecialRespawnSystem.cs | 184 ++++++++++++++++++ Content.Shared/Nuke/NukeDiskComponent.cs | 5 +- .../Respawn/SharedSpecialRespawnSystem.cs | 6 + .../Respawn/SpecialRespawnComponent.cs | 41 ++++ .../Entities/Objects/Misc/dat_fukken_disk.yml | 2 + 6 files changed, 251 insertions(+), 18 deletions(-) create mode 100644 Content.Server/Respawn/SpecialRespawnSystem.cs create mode 100644 Content.Shared/Respawn/SharedSpecialRespawnSystem.cs create mode 100644 Content.Shared/Respawn/SpecialRespawnComponent.cs diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 509417c628..2c870f504b 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,8 +1,5 @@ -using System.Text; using Content.Server.AlertLevel; using Content.Server.Audio; -using Content.Server.Chat; -using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Server.Coordinates.Helpers; using Content.Server.DoAfter; @@ -19,7 +16,6 @@ using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Random; -using Robust.Shared.Timing; namespace Content.Server.Nuke { @@ -48,6 +44,7 @@ namespace Content.Server.Nuke public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnMapInit); @@ -105,9 +102,8 @@ namespace Content.Server.Nuke var originStation = _stationSystem.GetOwningStation(uid); if (originStation != null) - { nuke.OriginStation = originStation; - } + else { var transform = Transform(uid); @@ -124,7 +120,8 @@ namespace Content.Server.Nuke private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args) { - if (!component.Initialized) return; + if (!component.Initialized) + return; if (args.Container.ID != component.DiskSlot.ID) return; @@ -229,9 +226,8 @@ namespace Content.Server.Nuke return; if (component.Status == NukeStatus.AWAIT_ARM && Transform(uid).Anchored) - { ArmBomb(uid, component); - } + else { if (args.Session.AttachedEntity is not { } user) @@ -303,10 +299,9 @@ namespace Content.Server.Nuke nuke.RemainingTime = 0; ActivateBomb(uid, nuke); } + else - { UpdateUserInterface(uid, nuke); - } } private void UpdateStatus(EntityUid uid, NukeComponent? component = null) @@ -456,9 +451,7 @@ namespace Content.Server.Nuke // let people know that a nuclear bomb was armed in their vicinity instead. // Otherwise, you could set every station to whatever AlertLevelOnActivate is. if (stationUid != null) - { _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnActivate, true, true, true, true); - } var nukeXform = Transform(uid); var pos = nukeXform.MapPosition; @@ -493,9 +486,7 @@ namespace Content.Server.Nuke var stationUid = _stationSystem.GetOwningStation(uid); if (stationUid != null) - { _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true); - } // warn a crew var announcement = Loc.GetString("nuke-component-announcement-unarmed"); @@ -614,11 +605,17 @@ namespace Content.Server.Nuke /// /// Raised directed on the nuke when its disarm doafter is successful. /// - public sealed class NukeDisarmSuccessEvent : EntityEventArgs {} + public sealed class NukeDisarmSuccessEvent : EntityEventArgs + { + + } /// /// Raised directed on the nuke when its disarm doafter is cancelled. /// - public sealed class NukeDisarmCancelledEvent : EntityEventArgs {} + public sealed class NukeDisarmCancelledEvent : EntityEventArgs + { + + } } diff --git a/Content.Server/Respawn/SpecialRespawnSystem.cs b/Content.Server/Respawn/SpecialRespawnSystem.cs new file mode 100644 index 0000000000..ec3b3b8537 --- /dev/null +++ b/Content.Server/Respawn/SpecialRespawnSystem.cs @@ -0,0 +1,184 @@ +using Content.Server.Administration.Logs; +using Content.Server.Atmos.EntitySystems; +using Content.Server.GameTicking; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; +using Content.Shared.Database; +using Content.Shared.Maps; +using Content.Shared.Respawn; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Respawn; + +public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRunLevelChanged); + SubscribeLocalEvent(OnSpecialRespawnSetup); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnTermination); + } + + private void OnRunLevelChanged(GameRunLevelChangedEvent ev) + { + //Try to compensate for restartroundnow command + if (ev.Old == GameRunLevel.InRound && ev.New == GameRunLevel.PreRoundLobby) + OnRoundEnd(); + + switch (ev.New) + { + case GameRunLevel.PostRound: + OnRoundEnd(); + break; + } + } + + private void OnSpecialRespawnSetup(SpecialRespawnSetupEvent ev) + { + if (!TryComp(ev.Entity, out var comp)) + return; + + var xform = Transform(ev.Entity); + + if (xform.GridUid != null) + comp.StationMap = (xform.MapUid, xform.GridUid); + } + + private void OnRoundEnd() + { + var specialRespawnQuery = EntityQuery(); + + //Turn respawning off so the entity doesn't respawn during reset + foreach (var entity in specialRespawnQuery) + { + entity.Respawn = false; + } + } + + private void OnStartup(EntityUid uid, SpecialRespawnComponent component, ComponentStartup args) + { + var ev = new SpecialRespawnSetupEvent(uid); + QueueLocalEvent(ev); + } + + private void OnTermination(EntityUid uid, SpecialRespawnComponent component, ref EntityTerminatingEvent args) + { + var entityMapUid = component.StationMap.Item1; + var entityGridUid = component.StationMap.Item2; + + if (!component.Respawn || !HasComp(entityGridUid) || entityMapUid == null) + return; + + if (!_mapManager.TryGetGrid(entityGridUid, out var grid) || MetaData(entityGridUid.Value).EntityLifeStage >= EntityLifeStage.Terminating) + return; + + if (TryFindRandomTile(entityGridUid.Value, entityMapUid.Value, 10, out var coords)) + Respawn(component.Prototype, coords); + + //If the above fails, spawn at the center of the grid on the station + else + { + var xform = Transform(entityGridUid.Value); + var pos = xform.Coordinates; + var mapPos = xform.MapPosition; + var circle = new Circle(mapPos.Position, 2); + + var found = false; + + foreach (var tile in grid.GetTilesIntersecting(circle)) + { + if (tile.IsSpace(_tileDefinitionManager) || tile.IsBlockedTurf(true) || !_atmosphere.IsTileMixtureProbablySafe(entityGridUid, entityMapUid.Value, grid.TileIndicesFor(mapPos))) + continue; + + pos = tile.GridPosition(); + found = true; + + if (found) + break; + } + + Respawn(component.Prototype, pos); + } + } + + /// + /// Respawn the entity and log it. + /// + /// The prototype being spawned + /// The place where it will be spawned + private void Respawn(string prototype, EntityCoordinates coords) + { + var entity = Spawn(prototype, coords); + var name = MetaData(entity).EntityName; + _adminLog.Add(LogType.Respawn, LogImpact.High, $"{name} was deleted and was respawned at {coords.ToMap(EntityManager)}"); + } + + /// + /// Try to find a random safe tile on the supplied grid + /// + /// The grid that you're looking for a safe tile on + /// The map that you're looking for a safe tile on + /// The maximum amount of attempts it should try before it gives up + /// If successful, the coordinates of the safe tile + /// + public bool TryFindRandomTile(EntityUid targetGrid, EntityUid targetMap, int maxAttempts, out EntityCoordinates targetCoords) + { + targetCoords = EntityCoordinates.Invalid; + + if (!_mapManager.TryGetGrid(targetGrid, out var grid)) + return false; + + var xform = Transform(targetGrid); + + if (!grid.TryGetTileRef(xform.Coordinates, out var tileRef)) + return false; + + var tile = tileRef.GridIndices; + + var found = false; + var (gridPos, _, gridMatrix) = xform.GetWorldPositionRotationMatrix(); + var gridBounds = gridMatrix.TransformBox(grid.LocalAABB); + + //Obviously don't put anything ridiculous in here + for (var i = 0; i < maxAttempts; i++) + { + var randomX = _random.Next((int) gridBounds.Left, (int) gridBounds.Right); + var randomY = _random.Next((int) gridBounds.Bottom, (int) gridBounds.Top); + + tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y); + var mapPos = grid.GridTileToWorldPos(tile); + var mapTarget = grid.WorldToTile(mapPos); + var circle = new Circle(mapPos, 2); + + foreach (var newTileRef in grid.GetTilesIntersecting(circle)) + { + if (newTileRef.IsSpace(_tileDefinitionManager) || newTileRef.IsBlockedTurf(true) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget)) + continue; + + found = true; + targetCoords = grid.GridTileToLocal(tile); + break; + } + + //Found a safe tile, no need to continue + if (found) + break; + } + + if (!found) + return false; + + return true; + } +} diff --git a/Content.Shared/Nuke/NukeDiskComponent.cs b/Content.Shared/Nuke/NukeDiskComponent.cs index 3f75aeddaf..6a42998f7d 100644 --- a/Content.Shared/Nuke/NukeDiskComponent.cs +++ b/Content.Shared/Nuke/NukeDiskComponent.cs @@ -6,4 +6,7 @@ namespace Content.Shared.Nuke; /// Used for tracking the nuke disk - isn't a tag for pinpointer purposes. /// [RegisterComponent, NetworkedComponent] -public sealed class NukeDiskComponent : Component {} +public sealed class NukeDiskComponent : Component +{ + +} diff --git a/Content.Shared/Respawn/SharedSpecialRespawnSystem.cs b/Content.Shared/Respawn/SharedSpecialRespawnSystem.cs new file mode 100644 index 0000000000..6c738e4489 --- /dev/null +++ b/Content.Shared/Respawn/SharedSpecialRespawnSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Respawn; + +public abstract class SharedSpecialRespawnSystem : EntitySystem +{ + +} diff --git a/Content.Shared/Respawn/SpecialRespawnComponent.cs b/Content.Shared/Respawn/SpecialRespawnComponent.cs new file mode 100644 index 0000000000..739fa4b50a --- /dev/null +++ b/Content.Shared/Respawn/SpecialRespawnComponent.cs @@ -0,0 +1,41 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Respawn; + +/// +/// This is to be used where you need some item respawned on station if it was deleted somehow in round +/// Items like the nuke disk. +/// +[RegisterComponent, NetworkedComponent] +public sealed class SpecialRespawnComponent: Component +{ + [ViewVariables] + [DataField("stationMap")] + public (EntityUid?, EntityUid?) StationMap; + + /// + /// Checks if the entityentity should respawn on the station grid + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("respawn")] + public bool Respawn = true; + + /// + /// The prototypeID of the entity to be respawned + /// + [ViewVariables] + [DataField("prototype", required:true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string Prototype = ""; +} + +public sealed class SpecialRespawnSetupEvent : EntityEventArgs +{ + public EntityUid Entity; + + public SpecialRespawnSetupEvent(EntityUid entity) + { + Entity = entity; + } +} diff --git a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml index 37049a2fce..f1b804811d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml @@ -5,6 +5,8 @@ description: A nuclear auth disk, capable of arming a nuke if used along with a code. Note from nanotrasen reads "THIS IS YOUR MOST IMPORTANT POSESSION, SECURE DAT FUKKEN DISK!" components: - type: NukeDisk + - type: SpecialRespawn + prototype: NukeDisk - type: Sprite netsync: false sprite: Objects/Misc/nukedisk.rsi