diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 86f7be6d7b0..0f54fd0da50 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -621,6 +621,7 @@ public void Update(EntityUid uid, PlantHolderComponent? component = null) RemovePlant(uid, component); component.ForceUpdate = true; Update(uid, component); + return; } CheckHealth(uid, component); diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 5830018c488..9b9a042641f 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -29,6 +29,8 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.Player; +using Content.Shared.Coordinates; namespace Content.Server.Explosion.EntitySystems { @@ -103,9 +105,15 @@ public override void Initialize() private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, TriggerEvent args) { - _audio.PlayPvs(component.Sound, uid); - if (component.RemoveOnTrigger) - RemCompDeferred(uid); + if (component.RemoveOnTrigger) // if the component gets removed when it's triggered + { + var xform = Transform(uid); + _audio.PlayPvs(component.Sound, xform.Coordinates); // play the sound at its last known coordinates + } + else // if the component doesn't get removed when triggered + { + _audio.PlayPvs(component.Sound, uid); // have the sound follow the entity itself + } } private void OnAnchorTrigger(EntityUid uid, AnchorOnTriggerComponent component, TriggerEvent args) diff --git a/Content.Server/GameTicking/GameTicker.Replays.cs b/Content.Server/GameTicking/GameTicker.Replays.cs index 7e1a553a856..f23482585cc 100644 --- a/Content.Server/GameTicking/GameTicker.Replays.cs +++ b/Content.Server/GameTicking/GameTicker.Replays.cs @@ -2,6 +2,10 @@ using Robust.Shared; using Robust.Shared.ContentPack; using Robust.Shared.Replays; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Utility; namespace Content.Server.GameTicking; @@ -10,12 +14,15 @@ public sealed partial class GameTicker { [Dependency] private readonly IReplayRecordingManager _replays = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; + [Dependency] private readonly ISerializationManager _serialman = default!; + private ISawmill _sawmillReplays = default!; private void InitializeReplays() { _replays.RecordingFinished += ReplaysOnRecordingFinished; + _replays.RecordingStopped += ReplaysOnRecordingStopped; } /// @@ -108,6 +115,20 @@ private void ReplaysOnRecordingFinished(ReplayRecordingFinished data) data.Directory.Rename(data.Path, state.MoveToPath.Value); } + private void ReplaysOnRecordingStopped(MappingDataNode metadata) + { + // Write round info like map and round end summery into the replay_final.yml file. Useful for external parsers. + + metadata["map"] = new ValueDataNode(_gameMapManager.GetSelectedMap()?.MapName); + metadata["gamemode"] = new ValueDataNode(CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty); + metadata["roundEndPlayers"] = _serialman.WriteValue(_replayRoundPlayerInfo); + metadata["roundEndText"] = new ValueDataNode(_replayRoundText); + metadata["server_id"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.ServerId)); + // These should be set to null to prepare them for the next round. + _replayRoundPlayerInfo = null; + _replayRoundText = null; + } + private ResPath GetAutoReplayPath() { var cfgValue = _cfg.GetCVar(CCVars.ReplayAutoRecordName); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 02a58ed23df..d113e1247f9 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -46,6 +46,10 @@ public sealed partial class GameTicker [ViewVariables] private GameRunLevel _runLevel; + private RoundEndMessageEvent.RoundEndPlayerInfo[]? _replayRoundPlayerInfo; + + private string? _replayRoundText; + [ViewVariables] public GameRunLevel RunLevel { @@ -373,11 +377,14 @@ public void ShowRoundEndScoreboard(string text = "") PlayerOOCName = contentPlayerData?.Name ?? "(IMPOSSIBLE: REGISTERED MIND WITH NO OWNER)", // Character name takes precedence over current entity name PlayerICName = playerIcName, + PlayerGuid = userId, PlayerNetEntity = GetNetEntity(entity), Role = antag ? roles.First(role => role.Antagonist).Name : roles.FirstOrDefault().Name ?? Loc.GetString("game-ticker-unknown-role"), Antag = antag, + JobPrototypes = roles.Where(role => !role.Antagonist).Select(role => role.Prototype).ToArray(), + AntagPrototypes = roles.Where(role => role.Antagonist).Select(role => role.Prototype).ToArray(), Observer = observer, Connected = connected }; @@ -391,6 +398,9 @@ public void ShowRoundEndScoreboard(string text = "") RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, RoundId, listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong, sound)); RaiseLocalEvent(new RoundEndedEvent(RoundId, roundDuration)); // Corvax + + _replayRoundPlayerInfo = listOfPlayerInfoFinal; + _replayRoundText = roundEndText; } private async void SendRoundEndDiscordMessage() diff --git a/Content.Shared/Species/Systems/NymphSystem.cs b/Content.Server/Species/Systems/NymphSystem.cs similarity index 75% rename from Content.Shared/Species/Systems/NymphSystem.cs rename to Content.Server/Species/Systems/NymphSystem.cs index 7acbf2e1528..8d0646ae8e4 100644 --- a/Content.Shared/Species/Systems/NymphSystem.cs +++ b/Content.Server/Species/Systems/NymphSystem.cs @@ -1,15 +1,15 @@ +using Content.Server.Mind; using Content.Shared.Species.Components; using Content.Shared.Body.Events; -using Content.Shared.Mind; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -namespace Content.Shared.Species; +namespace Content.Server.Species.Systems; public sealed partial class NymphSystem : EntitySystem { - [Dependency] protected readonly IPrototypeManager _protoManager = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly IPrototypeManager _protoManager= default!; + [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() @@ -31,11 +31,11 @@ private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, RemovedFromPa return; var coords = Transform(uid).Coordinates; - var nymph = EntityManager.SpawnEntity(entityProto.ID, coords); + var nymph = EntityManager.SpawnAtPosition(entityProto.ID, coords); if (comp.TransferMind == true && _mindSystem.TryGetMind(args.OldBody, out var mindId, out var mind)) _mindSystem.TransferTo(mindId, nymph, mind: mind); - EntityManager.QueueDeleteEntity(uid); + QueueDel(uid); } } diff --git a/Content.Shared/Administration/AdminFlags.cs b/Content.Shared/Administration/AdminFlags.cs index 05b7a45a464..f7d7efb1dbd 100644 --- a/Content.Shared/Administration/AdminFlags.cs +++ b/Content.Shared/Administration/AdminFlags.cs @@ -89,6 +89,11 @@ public enum AdminFlags : uint /// EditNotes = 1 << 14, + /// + /// Lets you Massban, on SS14.Admin + /// + MassBan = 1 << 15, + /// /// Dangerous host permissions like scsi. /// diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 7778588f97c..2677d499c2f 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -1,4 +1,5 @@ using Content.Shared.Roles; +using Robust.Shared.Network; using Robust.Shared.Replays; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Markdown.Mapping; @@ -144,18 +145,37 @@ public TickerJobsAvailableEvent(Dictionary stationNames, Dict } } - [Serializable, NetSerializable] - public sealed class RoundEndMessageEvent : EntityEventArgs + [Serializable, NetSerializable, DataDefinition] + public sealed partial class RoundEndMessageEvent : EntityEventArgs { - [Serializable, NetSerializable] - public struct RoundEndPlayerInfo + [Serializable, NetSerializable, DataDefinition] + public partial struct RoundEndPlayerInfo { + [DataField] public string PlayerOOCName; + + [DataField] public string? PlayerICName; + + [DataField, NonSerialized] + public NetUserId? PlayerGuid; + public string Role; + + [DataField, NonSerialized] + public string[] JobPrototypes; + + [DataField, NonSerialized] + public string[] AntagPrototypes; + public NetEntity? PlayerNetEntity; + + [DataField] public bool Antag; + + [DataField] public bool Observer; + public bool Connected; } diff --git a/Content.Shared/Roles/MindGetAllRolesEvent.cs b/Content.Shared/Roles/MindGetAllRolesEvent.cs index 9313d94edfd..69878739084 100644 --- a/Content.Shared/Roles/MindGetAllRolesEvent.cs +++ b/Content.Shared/Roles/MindGetAllRolesEvent.cs @@ -16,4 +16,5 @@ namespace Content.Shared.Roles; /// Name of the role. /// Whether or not this role makes this player an antagonist. /// The id associated with the role. -public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId); +/// The prototype ID of the role +public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype); diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 05d6ab9f37f..24db1d56774 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -27,16 +27,18 @@ public override void Initialize() private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) { var name = "game-ticker-unknown-role"; + var prototype = ""; string? playTimeTracker = null; if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job)) { name = job.Name; + prototype = job.ID; playTimeTracker = job.PlayTimeTracker; } name = Loc.GetString(name); - args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker)); + args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype)); } protected void SubscribeAntagEvents() where T : AntagonistRoleComponent @@ -44,13 +46,15 @@ protected void SubscribeAntagEvents() where T : AntagonistRoleComponent SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) => { var name = "game-ticker-unknown-role"; + var prototype = ""; if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag)) { name = antag.Name; + prototype = antag.ID; } name = Loc.GetString(name); - args.Roles.Add(new RoleInfo(component, name, true, null)); + args.Roles.Add(new RoleInfo(component, name, true, null, prototype)); }); SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => args.IsAntagonist = true); diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d24496ce4ab..e700e7f24a3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Emisse - changes: - - message: The Grand Lottery now has much more prizes in store! - type: Tweak - id: 5456 - time: '2023-12-27T12:31:03.0000000+00:00' - url: https://api.github.com/repos/space-wizards/space-station-14/pulls/23048 - author: TheShuEd changes: - message: The thief will temporarily not steal structures for the time being, as @@ -3877,3 +3870,10 @@ id: 5955 time: '2024-02-17T05:02:12.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/25326 +- author: Dygon + changes: + - message: Diona nymphs aren't deleted immediately after spawning anymore. + type: Fix + id: 5956 + time: '2024-02-17T16:38:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/25344 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index 90cb751e40a..0b1a7ecb77a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -67,7 +67,7 @@ sprite: Objects/Weapons/Grenades/flashbang.rsi - type: FlashOnTrigger range: 7 - - type: EmitSoundOnTrigger + - type: SoundOnTrigger sound: path: "/Audio/Effects/flash_bang.ogg" - type: DeleteOnTrigger