diff --git a/Content.Server/StationEvents/Components/VentCrittersRuleComponent.cs b/Content.Server/StationEvents/Components/VentCrittersRuleComponent.cs index f280468fc34..f6dced22b76 100644 --- a/Content.Server/StationEvents/Components/VentCrittersRuleComponent.cs +++ b/Content.Server/StationEvents/Components/VentCrittersRuleComponent.cs @@ -1,17 +1,60 @@ -using Content.Server.StationEvents.Events; +// SPDX-FileCopyrightText: 2023 DrSmugleaf +// SPDX-FileCopyrightText: 2023 Nemanja +// SPDX-FileCopyrightText: 2023 Nim +// SPDX-FileCopyrightText: 2023 Slava0135 +// SPDX-FileCopyrightText: 2023 metalgearsloth +// SPDX-FileCopyrightText: 2025 Jakumba +// SPDX-FileCopyrightText: 2025 empty0set +// SPDX-FileCopyrightText: 2025 sleepyyapril +// +// SPDX-License-Identifier: MIT + +using Content.Server.StationEvents.Events; +using Content.Shared.EntityTable.EntitySelectors; using Content.Shared.Storage; +using Robust.Shared.Audio; +using Robust.Shared.Map; // DeltaV namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(VentCrittersRule))] public sealed partial class VentCrittersRuleComponent : Component { - [DataField("entries")] - public List Entries = new(); + // DeltaV: Replaced by Table + //[DataField("entries")] + //public List Entries = new(); + + /// + /// DeltaV: Table of possible entities to spawn. + /// + [DataField(required: true)] + public EntityTableSelector Table = default!; /// /// At least one special entry is guaranteed to spawn /// [DataField("specialEntries")] public List SpecialEntries = new(); + + + /// + /// DeltaV: Base minimum number of critters to spawn. + /// + [DataField] + public int Min = 3; + + /// + /// DeltaV: Base maximum number of critters to spawn. + /// + [DataField] + public int Max = 4; + + /// + /// DeltaV: Min and max get multiplied by the player count then divided by this. + /// + [DataField] + public int PlayerRatio = 20; + + [DataField("sound")] + public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/_DEN/VentCritters/vent_harmless_critter.ogg"); } diff --git a/Content.Server/StationEvents/Events/VentCrittersRule.cs b/Content.Server/StationEvents/Events/VentCrittersRule.cs index c8113c3098d..7d186d89b5a 100644 --- a/Content.Server/StationEvents/Events/VentCrittersRule.cs +++ b/Content.Server/StationEvents/Events/VentCrittersRule.cs @@ -1,12 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Nemanja +// SPDX-FileCopyrightText: 2023 Nim +// SPDX-FileCopyrightText: 2023 Slava0135 +// SPDX-FileCopyrightText: 2023 metalgearsloth +// SPDX-FileCopyrightText: 2024 VMSolidus +// SPDX-FileCopyrightText: 2024 deltanedas +// SPDX-FileCopyrightText: 2025 Eightballll +// SPDX-FileCopyrightText: 2025 Jakumba +// SPDX-FileCopyrightText: 2025 empty0set +// SPDX-FileCopyrightText: 2025 portfiend +// SPDX-FileCopyrightText: 2025 sleepyyapril +// +// SPDX-License-Identifier: AGPL-3.0-or-later + using Content.Server.StationEvents.Components; +using Content.Server.Antag; +using Content.Shared.EntityTable; using Content.Shared.GameTicking.Components; using Content.Shared.Station.Components; -using Content.Shared.Storage; using Robust.Shared.Map; +using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Audio; +using Content.Server.Chat.Systems; +using Content.Shared.Pinpointer; +using System.Linq; namespace Content.Server.StationEvents.Events; +/// +/// DeltaV: Reworked vent critters to spawn a number of mobs at a single telegraphed location. +/// This gives players time to run away and let sec do their job. +/// +/// +/// This entire file is rewritten, ignore upstream changes. +/// +/// public sealed class VentCrittersRule : StationEventSystem { /* @@ -14,48 +42,130 @@ public sealed class VentCrittersRule : StationEventSystem _locations = new(); + private Entity _selectedBeacon = new(); + + protected override void Added(EntityUid uid, VentCrittersRuleComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args) { - base.Started(uid, component, gameRule, args); + PickSpawnLocations(); - if (!TryGetRandomStation(out var station)) + if (_locations.Count == 0) { + ForceEndSelf(uid, gameRule); return; } - var locations = EntityQueryEnumerator(); - var validLocations = new List(); - while (locations.MoveNext(out _, out _, out var transform)) + foreach (var location in _locations) + { + _chatSystem.TrySendInGameICMessage(location.LocationUid, "emits an ominous rumbling sound...", Shared.Chat.InGameICChatType.Emote, Shared.Chat.ChatTransmitRange.Normal, false, null, null, "nearby vent", false, true); + } + + var audio = AudioParams.Default; + audio.Volume = 200; + audio.RolloffFactor = 0.01f; + audio.ReferenceDistance = 400; + + Audio.PlayPvs(comp.Sound, _selectedBeacon.Comp.Coordinates, audio); + + base.Added(uid, comp, gameRule, args); + } + + protected override void Ended(EntityUid uid, VentCrittersRuleComponent comp, GameRuleComponent gameRule, GameRuleEndedEvent args) + { + if (_locations.Count == 0) + return; + + var players = _antag.GetTotalPlayerCount(_player.Sessions); + var min = comp.Min * players / comp.PlayerRatio; + var max = comp.Max * players / comp.PlayerRatio; + var maxSpawns = Math.Max(RobustRandom.Next(min, max), 1); + + var spawnsPerVent = Math.Max((maxSpawns / _locations.Count), 1); + + foreach (var location in _locations) { - if (!transform.Anchored) - continue; + _chatSystem.TrySendInGameICMessage(location.LocationUid, "screeches as something bursts free in a cloud of dust!", Shared.Chat.InGameICChatType.Emote, Shared.Chat.ChatTransmitRange.Normal, false, null, null, "nearby vent", false, true); + + Spawn("AdminInstantEffectSmoke10", location.Coordinates); // Dust effect - if (CompOrNull(transform.GridUid)?.Station == station) + SpawnCritters(comp, location, spawnsPerVent); + } + + base.Ended(uid, comp, gameRule, args); + } + + private void SpawnCritters(VentCrittersRuleComponent comp, VentCritterLocationData vent, int spawnCount) + { + for (int i = 0; i < spawnCount; i++) + { + foreach (var spawn in _entityTable.GetSpawns(comp.Table)) { - validLocations.Add(transform.Coordinates); - foreach (var spawn in EntitySpawnCollection.GetSpawns(component.Entries, RobustRandom)) - { - Spawn(spawn, transform.Coordinates); - } + Spawn(spawn, vent.Coordinates); } } - if (component.SpecialEntries.Count == 0 || validLocations.Count == 0) + // guaranteed spawn + if (comp.SpecialEntries.Count > 0) { - return; + var specialEntry = RobustRandom.Pick(comp.SpecialEntries); + Spawn(specialEntry.PrototypeId, vent.Coordinates); } + } - // guaranteed spawn - var specialEntry = RobustRandom.Pick(component.SpecialEntries); - var specialSpawn = RobustRandom.Pick(validLocations); - Spawn(specialEntry.PrototypeId, specialSpawn); + private void PickSpawnLocations() + { + if (!TryGetRandomStation(out var station)) + return; + + _locations.Clear(); - foreach (var location in validLocations) + // Get all beacons on station + var beacons = EntityQueryEnumerator(); + var beaconList = new List>(); + + while (beacons.MoveNext(out var beaconUid, out var navMapBeacon, out var beaconPosition)) { - foreach (var spawn in EntitySpawnCollection.GetSpawns(component.SpecialEntries, RobustRandom)) + // Check that the beacon is actually on the station, if so add to the list + if (CompOrNull(beaconPosition.GridUid)?.Station == station) { - Spawn(spawn, location); + beaconList.Add((beaconUid, beaconPosition)); } } + // Grab a random beacon from our list + if (!beaconList.Any()) + return; + + _selectedBeacon = RobustRandom.Pick(beaconList); + + // 10 tile range is purely arbitrary, it would be better to pick vents up to a maximum value instead but + var ventsInRange = _lookup.GetEntitiesInRange(_selectedBeacon.Comp.Coordinates, 10).Where(x => x.Comp.CanSpawn); + + foreach (var vent in ventsInRange) + { + _locations.Add(new VentCritterLocationData(vent.Owner, vent.Comp, Transform(vent.Owner).Coordinates)); + } + } +} + +/// +/// Contains location data for the vents that have been selected to spawn critters +/// +public sealed class VentCritterLocationData +{ + public EntityUid LocationUid; + public VentCritterSpawnLocationComponent SpawnLocationComponent; + public EntityCoordinates Coordinates; + + public VentCritterLocationData(EntityUid uid, VentCritterSpawnLocationComponent spawnComp, EntityCoordinates coords) + { + LocationUid = uid; + SpawnLocationComponent = spawnComp; + Coordinates = coords; } } diff --git a/Resources/Audio/_DEN/VentCritters/attributions.yml b/Resources/Audio/_DEN/VentCritters/attributions.yml new file mode 100644 index 00000000000..64b4dbab147 --- /dev/null +++ b/Resources/Audio/_DEN/VentCritters/attributions.yml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 Jakumba +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +- files: ["vent_xeno.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Edited and composited by MajorMoth, samples taken from freesound.org at ID's 800277, 803044, 795432, 806647, 806649" + source: "https://freesound.org" + +- files: ["vent_carp.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Edited and composited by MajorMoth, modified bite.ogg and metal_break4.ogg" + source: "https://github.com/TheDenSS14/TheDen" + +- files: ["vent_harmless_critter.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Edited and composited by MajorMoth, samples taken from freesound.org at ID's 244015, 528191" + source: "https://freesound.org" + +- files: ["vent_slime.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Edited and composited by MajorMoth, samples taken from https://www.youtube.com/watch?v=hX5S4KC0hx0, https://www.youtube.com/watch?v=REOeff9WJ2o, +https://www.youtube.com/watch?v=9XIDm_sZhx4, +https://www.youtube.com/watch?v=jiTV8MwgL3c" + source: "https://www.youtube.com" diff --git a/Resources/Audio/_DEN/VentCritters/vent_carp.ogg b/Resources/Audio/_DEN/VentCritters/vent_carp.ogg new file mode 100644 index 00000000000..4848580c64f Binary files /dev/null and b/Resources/Audio/_DEN/VentCritters/vent_carp.ogg differ diff --git a/Resources/Audio/_DEN/VentCritters/vent_harmless_critter.ogg b/Resources/Audio/_DEN/VentCritters/vent_harmless_critter.ogg new file mode 100644 index 00000000000..86d6a746f55 Binary files /dev/null and b/Resources/Audio/_DEN/VentCritters/vent_harmless_critter.ogg differ diff --git a/Resources/Audio/_DEN/VentCritters/vent_slime.ogg b/Resources/Audio/_DEN/VentCritters/vent_slime.ogg new file mode 100644 index 00000000000..95848a8fbbe Binary files /dev/null and b/Resources/Audio/_DEN/VentCritters/vent_slime.ogg differ diff --git a/Resources/Audio/_DEN/VentCritters/vent_xeno.ogg b/Resources/Audio/_DEN/VentCritters/vent_xeno.ogg new file mode 100644 index 00000000000..968b9ec7141 Binary files /dev/null and b/Resources/Audio/_DEN/VentCritters/vent_xeno.ogg differ