diff --git a/Content.Client/DragDrop/DragDropSystem.cs b/Content.Client/DragDrop/DragDropSystem.cs index 37bae796d7..488fc87eb6 100644 --- a/Content.Client/DragDrop/DragDropSystem.cs +++ b/Content.Client/DragDrop/DragDropSystem.cs @@ -379,7 +379,7 @@ namespace Content.Client.DragDrop // TODO: Duplicated in SpriteSystem and TargetOutlineSystem. Should probably be cached somewhere for a frame? var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition).Position; var bounds = new Box2(mousePos - 1.5f, mousePos + 1.5f); - var pvsEntities = _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, LookupFlags.Approximate | LookupFlags.Anchored); + var pvsEntities = _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, LookupFlags.Approximate | LookupFlags.Static); foreach (var pvsEntity in pvsEntities) { if (!EntityManager.TryGetComponent(pvsEntity, out SpriteComponent? inRangeSprite) || diff --git a/Content.Client/Light/Components/ExpendableLightComponent.cs b/Content.Client/Light/Components/ExpendableLightComponent.cs index 2dcf99158b..d2cc535cf4 100644 --- a/Content.Client/Light/Components/ExpendableLightComponent.cs +++ b/Content.Client/Light/Components/ExpendableLightComponent.cs @@ -1,6 +1,5 @@ -using Content.Shared.Light.Component; +using Content.Shared.Light.Component; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; namespace Content.Client.Light.Components { diff --git a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs new file mode 100644 index 0000000000..c982f5c463 --- /dev/null +++ b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs @@ -0,0 +1,18 @@ +using Content.Client.Light.Components; + +namespace Content.Client.Light.EntitySystems; + +public sealed class ExpendableLightSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnLightShutdown); + } + + private void OnLightShutdown(EntityUid uid, ExpendableLightComponent component, ComponentShutdown args) + { + component.PlayingStream?.Stop(); + } +} diff --git a/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs b/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs index a83239b6b5..813e623de0 100644 --- a/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs +++ b/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs @@ -2,10 +2,6 @@ using Content.Client.Light.Components; using Content.Shared.Light.Component; using JetBrains.Annotations; using Robust.Client.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Player; namespace Content.Client.Light.Visualizers { @@ -35,31 +31,21 @@ namespace Content.Client.Light.Visualizers } } - void TryStopStream(IPlayingAudioStream? stream) - { - stream?.Stop(); - } - if (component.TryGetData(ExpendableLightVisuals.State, out ExpendableLightState state) - && entities.TryGetComponent(component.Owner, out ExpendableLightComponent? expendableLight)) + && entities.TryGetComponent(component.Owner, out ExpendableLightComponent? expendableLight)) { switch (state) { case ExpendableLightState.Lit: - { - TryStopStream(expendableLight.PlayingStream); - if (expendableLight.LoopedSound != null) - { - expendableLight.PlayingStream = SoundSystem.Play(expendableLight.LoopedSound, Filter.Local(), - expendableLight.Owner, SharedExpendableLightComponent.LoopedSoundParams.WithLoop(true)); - } + expendableLight.PlayingStream?.Stop(); + expendableLight.PlayingStream = entities.EntitySysManager.GetEntitySystem().PlayPvs( + expendableLight.LoopedSound, + expendableLight.Owner, + SharedExpendableLightComponent.LoopedSoundParams); break; - } case ExpendableLightState.Dead: - { - TryStopStream(expendableLight.PlayingStream); + expendableLight.PlayingStream?.Stop(); break; - } } } } diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs index b7a338798f..18838d2572 100644 --- a/Content.Client/Outline/TargetOutlineSystem.cs +++ b/Content.Client/Outline/TargetOutlineSystem.cs @@ -117,7 +117,7 @@ public sealed class TargetOutlineSystem : EntitySystem // TODO: Duplicated in SpriteSystem and DragDropSystem. Should probably be cached somewhere for a frame? var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition).Position; var bounds = new Box2(mousePos - LookupSize / 2f, mousePos + LookupSize / 2f); - var pvsEntities = _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, LookupFlags.Approximate | LookupFlags.Anchored); + var pvsEntities = _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, LookupFlags.Approximate | LookupFlags.Static); var spriteQuery = GetEntityQuery(); foreach (var entity in pvsEntities) diff --git a/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs b/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs index c9cb6eb0fd..67e23930ca 100644 --- a/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs @@ -82,7 +82,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem var bodyQuery = GetEntityQuery(); var lowest = new List<(int DrawDepth, uint RenderOrder, EntityUid Entity)>(); - foreach (var ent in _lookup.GetEntitiesIntersecting(mousePos, LookupFlags.Approximate | LookupFlags.Anchored)) + foreach (var ent in _lookup.GetEntitiesIntersecting(mousePos, LookupFlags.Approximate | LookupFlags.Static)) { if (!bodyQuery.HasComponent(ent) || !TryComp(ent, out var clickable) || diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs index bf7eaf4511..106d6363fc 100644 --- a/Content.Server/Chat/SuicideSystem.cs +++ b/Content.Server/Chat/SuicideSystem.cs @@ -102,7 +102,7 @@ namespace Content.Server.Chat } // Suicide by nearby entity (ex: Microwave) - foreach (var entity in _entityLookupSystem.GetEntitiesInRange(victim, 1, LookupFlags.Approximate | LookupFlags.Anchored)) + foreach (var entity in _entityLookupSystem.GetEntitiesInRange(victim, 1, LookupFlags.Approximate | LookupFlags.Static)) { // Skip any nearby items that can be picked up, we already checked the active held item above if (itemQuery.HasComponent(entity)) diff --git a/Content.Server/Chat/Systems/ChatSystem.Radio.cs b/Content.Server/Chat/Systems/ChatSystem.Radio.cs index ec9246aadf..e6f56a3229 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Radio.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Radio.cs @@ -43,34 +43,37 @@ public sealed partial class ChatSystem private (string, RadioChannelPrototype?) GetRadioPrefix(EntityUid source, string message) { // TODO: Turn common into a true frequency and support multiple aliases. - var channelMessage = message.StartsWith(':') || message.StartsWith('.'); - var radioMessage = message.StartsWith(';') || channelMessage; - if (!radioMessage) return (message, null); + var isRadioMessage = false; + RadioChannelPrototype? channel = null; + // First check if this is a message to the base radio frequency + if (message.StartsWith(';')) + { + // First Remove semicolon + channel = _prototypeManager.Index("Common"); + message = message[1..].TrimStart(); + isRadioMessage = true; + } + + // Check now if the remaining message is a targeted radio message + if ((message.StartsWith(':') || message.StartsWith('.')) && message.Length >= 2) + { + // Strip remaining message prefix. + _keyCodes.TryGetValue(message[1], out channel); + message = message[2..].TrimStart(); + isRadioMessage = true; + } + + // If not a radio message at all + if (!isRadioMessage) return (message, null); // Special case for empty messages if (message.Length <= 1) return (string.Empty, null); - // Look for a prefix indicating a destination radio channel. - RadioChannelPrototype? chan; - if (channelMessage && message.Length >= 2) + if (channel == null) { - _keyCodes.TryGetValue(message[1], out chan); - - if (chan == null) - { - _popup.PopupEntity(Loc.GetString("chat-manager-no-such-channel"), source, Filter.Entities(source)); - chan = null; - } - - // Strip message prefix. - message = message[2..].TrimStart(); - } - else - { - // Remove semicolon - message = message[1..].TrimStart(); - chan = _prototypeManager.Index("Common"); + _popup.PopupEntity(Loc.GetString("chat-manager-no-such-channel"), source, Filter.Entities(source)); + channel = null; } // Re-capitalize message since we removed the prefix. @@ -83,6 +86,6 @@ public sealed partial class ChatSystem _popup.PopupEntity(Loc.GetString("chat-manager-no-headset-on-message"), source, Filter.Entities(source)); } - return (message, chan); + return (message, channel); } } diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 499784fe3b..5e6228819e 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -151,11 +151,22 @@ public sealed partial class ChatSystem : SharedChatSystem if (string.IsNullOrEmpty(message)) return; + // This message may have a radio prefix, and should then be whispered to the resolved radio channel + if (checkRadioPrefix) + { + var (radioMessage, channel) = GetRadioPrefix(source, message); + if (channel != null) + { + SendEntityWhisper(source, radioMessage, hideChat, hideGlobalGhostChat, channel, nameOverride); + return; + } + } + // Otherwise, send whatever type. switch (desiredType) { case InGameICChatType.Speak: - SendEntitySpeak(source, message, hideChat, hideGlobalGhostChat, nameOverride, checkRadioPrefix); + SendEntitySpeak(source, message, hideChat, hideGlobalGhostChat, nameOverride); break; case InGameICChatType.Whisper: SendEntityWhisper(source, message, hideChat, hideGlobalGhostChat, null, nameOverride); @@ -255,26 +266,12 @@ public sealed partial class ChatSystem : SharedChatSystem #region Private API - private void SendEntitySpeak(EntityUid source, string originalMessage, bool hideChat, bool hideGlobalGhostChat, string? nameOverride, bool checkRadioPrefix) + private void SendEntitySpeak(EntityUid source, string originalMessage, bool hideChat, bool hideGlobalGhostChat, string? nameOverride) { if (!_actionBlocker.CanSpeak(source)) return; - RadioChannelPrototype? channel = null; - string message; - - if (checkRadioPrefix) - (message, channel) = GetRadioPrefix(source, originalMessage); - else - message = originalMessage; - - if (channel != null) - { - SendEntityWhisper(source, message, hideChat, hideGlobalGhostChat, channel, nameOverride); - return; - } - - message = TransformSpeech(source, message); + var message = TransformSpeech(source, originalMessage); if (message.Length == 0) return; @@ -297,7 +294,7 @@ public sealed partial class ChatSystem : SharedChatSystem SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, hideChat, hideGlobalGhostChat); - var ev = new EntitySpokeEvent(source, message, channel, null); + var ev = new EntitySpokeEvent(source, message, null, null); RaiseLocalEvent(source, ev, true); // To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc. diff --git a/Content.Server/Construction/Conditions/ComponentInTile.cs b/Content.Server/Construction/Conditions/ComponentInTile.cs index d2f6209ca4..1ceb70c822 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -48,7 +48,7 @@ namespace Content.Server.Construction.Conditions var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve()); var lookup = entityManager.EntitySysManager.GetEntitySystem(); - var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Anchored, lookup); + var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup); foreach (var ent in entities) { diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 81aa7b46c4..b3c359561d 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -168,7 +168,7 @@ namespace Content.Server.Electrocution if (electrified.NoWindowInTile) { foreach (var entity in transform.Coordinates.GetEntitiesInTile( - LookupFlags.Approximate | LookupFlags.Anchored, _entityLookup)) + LookupFlags.Approximate | LookupFlags.Static, _entityLookup)) { if (_tagSystem.HasTag(entity, "Window")) return false; diff --git a/Content.Server/Fax/FaxSystem.cs b/Content.Server/Fax/FaxSystem.cs index 1b2275db84..c8832ab839 100644 --- a/Content.Server/Fax/FaxSystem.cs +++ b/Content.Server/Fax/FaxSystem.cs @@ -10,7 +10,9 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Tools; using Content.Server.UserInterface; +using Content.Shared.Administration.Logs; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Database; using Content.Shared.Emag.Systems; using Content.Shared.Fax; using Content.Shared.Interaction; @@ -33,6 +35,7 @@ public sealed class FaxSystem : EntitySystem [Dependency] private readonly ToolSystem _toolSystem = default!; [Dependency] private readonly QuickDialogSystem _quickDialog = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; public const string PaperSlotId = "Paper"; @@ -213,6 +216,8 @@ public sealed class FaxSystem : EntitySystem return; } + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid)} from \"{component.FaxName}\" to \"{newName}\""); component.FaxName = newName; _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-set"), uid, Filter.Pvs(uid)); UpdateUserInterface(uid, component); @@ -283,7 +288,7 @@ public sealed class FaxSystem : EntitySystem private void OnSendButtonPressed(EntityUid uid, FaxMachineComponent component, FaxSendMessage args) { - Send(uid, component); + Send(uid, component, args.Session.AttachedEntity); } private void OnRefreshButtonPressed(EntityUid uid, FaxMachineComponent component, FaxRefreshMessage args) @@ -363,7 +368,7 @@ public sealed class FaxSystem : EntitySystem /// Sends message to addressee if paper is set and a known fax is selected /// A timeout is set after sending /// - public void Send(EntityUid uid, FaxMachineComponent? component = null) + public void Send(EntityUid uid, FaxMachineComponent? component = null, EntityUid? sender = null) { if (!Resolve(uid, ref component)) return; @@ -390,6 +395,8 @@ public sealed class FaxSystem : EntitySystem }; _deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{(sender != null ? ToPrettyString(sender.Value) : "Unknown"):user} sent fax from \"{component.FaxName}\" {ToPrettyString(uid)} to {faxName} ({component.DestinationFaxAddress}): {paper.Content}"); + component.SendTimeoutRemaining += component.SendTimeout; _audioSystem.PlayPvs(component.SendSound, uid); @@ -432,6 +439,8 @@ public sealed class FaxSystem : EntitySystem if (TryComp(printed, out var metadata)) metadata.EntityName = printout.Name; + + _adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid)} printed {ToPrettyString(printed)}: {printout.Content}"); } private void NotifyAdmins(string faxName) diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 13b99b3e0f..43ab43585a 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -116,8 +116,12 @@ namespace Content.Server.Ghost eye.VisibilityMask |= (uint) VisibilityFlags.Ghost; } - component.TimeOfDeath = _gameTiming.RealTime; + var time = _gameTiming.CurTime; + component.TimeOfDeath = time; + // TODO ghost: remove once ghosts are persistent and aren't deleted when returning to body + if (component.Action.UseDelay != null) + component.Action.Cooldown = (time, time + component.Action.UseDelay.Value); _actions.AddAction(uid, component.Action, null); } diff --git a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs index 09a84de0d8..2604253447 100644 --- a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs @@ -19,6 +19,7 @@ namespace Content.Server.Light.EntitySystems [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly ClothingSystem _clothing = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { @@ -130,9 +131,7 @@ namespace Content.Server.Light.EntitySystems switch (component.CurrentState) { case ExpendableLightState.Lit: - { - SoundSystem.Play(component.LitSound.GetSound(), Filter.Pvs(component.Owner), component.Owner); - + _audio.PlayPvs(component.LitSound, component.Owner); if (component.IconStateLit != string.Empty) { sprite.LayerSetState(2, component.IconStateLit); @@ -141,22 +140,21 @@ namespace Content.Server.Light.EntitySystems sprite.LayerSetVisible(1, true); break; - } case ExpendableLightState.Fading: { break; } default: case ExpendableLightState.Dead: - { - if (component.DieSound != null) - SoundSystem.Play(component.DieSound.GetSound(), Filter.Pvs(component.Owner), component.Owner); + _audio.PlayPvs(component.DieSound, component.Owner); + if (!string.IsNullOrEmpty(component.IconStateSpent)) + { + sprite.LayerSetState(0, component.IconStateSpent); + sprite.LayerSetShader(0, "shaded"); + } - sprite.LayerSetState(0, component.IconStateSpent); - sprite.LayerSetShader(0, "shaded"); sprite.LayerSetVisible(1, false); break; - } } } diff --git a/Content.Server/NPC/Components/NPCRVOComponent.cs b/Content.Server/NPC/Components/NPCRVOComponent.cs deleted file mode 100644 index 3fe76fc24e..0000000000 --- a/Content.Server/NPC/Components/NPCRVOComponent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.NPC.Systems; - -namespace Content.Server.NPC.Components; - -/// -/// Stores data for RVO collision avoidance -/// -[RegisterComponent] -public sealed class NPCRVOComponent : Component -{ - /// - /// Maximum number of dynamic neighbors to consider for collision avoidance. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")] - public int MaxNeighbors = 5; - - /// - /// Time horizon to consider for dynamic neighbor collision - /// - [ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f; - - /// - /// Time horizon to consider for static neighbor collision. - /// - [ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f; - - /// - /// Range considered for neighbor agents - /// - [ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")] - public float NeighborRange = 3f; - - [ViewVariables] - public readonly HashSet ObstacleNeighbors = new(); - - [ViewVariables] - public readonly HashSet AgentNeighbors = new(); -} diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs index f0a87a2098..e60be3fb96 100644 --- a/Content.Server/NPC/NPCBlackboard.cs +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -42,6 +42,11 @@ public sealed class NPCBlackboard : IEnumerable> /// public bool ReadOnly = false; + public void Clear() + { + _blackboard.Clear(); + } + public NPCBlackboard ShallowClone() { var dict = new NPCBlackboard(); diff --git a/Content.Server/NPC/NPCBlackboardSerializer.cs b/Content.Server/NPC/NPCBlackboardSerializer.cs index e2335d5aad..ec7a0756c2 100644 --- a/Content.Server/NPC/NPCBlackboardSerializer.cs +++ b/Content.Server/NPC/NPCBlackboardSerializer.cs @@ -8,7 +8,7 @@ using Robust.Shared.Utility; namespace Content.Server.NPC; -public sealed class NPCBlackboardSerializer : ITypeReader +public sealed class NPCBlackboardSerializer : ITypeReader, ITypeCopier { public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) @@ -79,4 +79,17 @@ public sealed class NPCBlackboardSerializer : ITypeReader(OnGridInit); SubscribeLocalEvent(OnGridRemoved); - SubscribeLocalEvent(OnGridPathPause); + SubscribeLocalEvent(OnGridPathPause); SubscribeLocalEvent(OnGridPathShutdown); SubscribeLocalEvent(OnCollisionChange); SubscribeLocalEvent(OnBodyTypeChange); SubscribeLocalEvent(OnMoveEvent); } - private void OnGridPathPause(EntityUid uid, GridPathfindingComponent component, EntityPausedEvent args) + private void OnGridPathPause(EntityUid uid, GridPathfindingComponent component, ref EntityUnpausedEvent args) { - // TODO: Need the offsets + time serializer. Mainly just need this here to ensure it gets update after load - if (!args.Paused && component.NextUpdate < _timing.CurTime) - component.NextUpdate = _timing.CurTime; + component.NextUpdate += args.PausedTime; } private void OnGridPathShutdown(EntityUid uid, GridPathfindingComponent component, ComponentShutdown args) diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs deleted file mode 100644 index d6acb3e9b8..0000000000 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.CCVar; -using Robust.Shared.Configuration; - -namespace Content.Server.NPC.Systems; - -public sealed partial class NPCSteeringSystem -{ - // TODO - - // Derived from RVO2 library which uses ORCA (optimal reciprocal collision avoidance). - // Could also potentially use something force based or RVO or detour crowd. - - public bool CollisionAvoidanceEnabled { get; set; } = true; - - public bool ObstacleAvoidanceEnabled { get; set; } = true; - - private const float Radius = 0.35f; - private const float RVO_EPSILON = 0.00001f; - - private void InitializeAvoidance() - { - var configManager = IoCManager.Resolve(); - configManager.OnValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance); - } - - private void ShutdownAvoidance() - { - var configManager = IoCManager.Resolve(); - configManager.UnsubValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance); - } - - // I deleted all of my relevant code for now as I only had dynamic body avoidance working and not static - // but it will be added back real soon. - private void SetCollisionAvoidance(bool obj) - { - CollisionAvoidanceEnabled = obj; - } -} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs index 14a087b6f3..34c53b8e98 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs @@ -86,7 +86,7 @@ public sealed partial class NPCSteeringSystem } else { - arrivalDistance = SharedInteractionSystem.InteractionRange - 0.8f; + arrivalDistance = SharedInteractionSystem.InteractionRange - 0.65f; } // Check if mapids match. @@ -105,9 +105,15 @@ public sealed partial class NPCSteeringSystem if (direction.Length <= arrivalDistance) { // Node needs some kind of special handling like access or smashing. - if (steering.CurrentPath.TryPeek(out var node)) + if (steering.CurrentPath.TryPeek(out var node) && !node.Data.IsFreeSpace) { - var status = TryHandleFlags(steering, node, bodyQuery); + SteeringObstacleStatus status; + + // Breaking behaviours and the likes. + lock (_obstacles) + { + status = TryHandleFlags(steering, node, bodyQuery); + } // TODO: Need to handle re-pathing in case the target moves around. switch (status) diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs index 042e9df734..03f1fb06f9 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs @@ -1,11 +1,12 @@ +using Content.Server.CombatMode; using Content.Server.Destructible; using Content.Server.NPC.Components; using Content.Server.NPC.Pathfinding; using Content.Shared.Doors.Components; using Content.Shared.NPC; -using Content.Shared.Weapons.Melee; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; namespace Content.Server.NPC.Systems; @@ -33,9 +34,7 @@ public sealed partial class NPCSteeringSystem private SteeringObstacleStatus TryHandleFlags(NPCSteeringComponent component, PathPoly poly, EntityQuery bodyQuery) { - if (poly.Data.IsFreeSpace) - return SteeringObstacleStatus.Completed; - + DebugTools.Assert(!poly.Data.IsFreeSpace); // TODO: Store PathFlags on the steering comp // and be able to re-check it. @@ -111,26 +110,35 @@ public sealed partial class NPCSteeringSystem return SteeringObstacleStatus.Completed; } // Try smashing obstacles. - else if ((component.Flags & PathFlags.Smashing) != 0x0 && TryComp(component.Owner, out var melee) && - TryComp(melee.Weapon, out var meleeWeapon)) + else if ((component.Flags & PathFlags.Smashing) != 0x0) { - var destructibleQuery = GetEntityQuery(); + var meleeWeapon = _melee.GetWeapon(component.Owner); - // TODO: This is a hack around grilles and windows. - _random.Shuffle(obstacleEnts); - - foreach (var ent in obstacleEnts) + if (meleeWeapon != null && meleeWeapon.NextAttack <= _timing.CurTime && TryComp(component.Owner, out var combatMode)) { - // TODO: Validate we can damage it - if (destructibleQuery.HasComponent(ent)) - { - _melee.AttemptLightAttack(component.Owner, meleeWeapon, ent); - return SteeringObstacleStatus.Continuing; - } - } + combatMode.IsInCombatMode = true; + var destructibleQuery = GetEntityQuery(); - if (obstacleEnts.Count == 0) - return SteeringObstacleStatus.Completed; + // TODO: This is a hack around grilles and windows. + _random.Shuffle(obstacleEnts); + + foreach (var ent in obstacleEnts) + { + // TODO: Validate we can damage it + if (destructibleQuery.HasComponent(ent)) + { + _melee.AttemptLightAttack(component.Owner, meleeWeapon, ent); + break; + } + } + + combatMode.IsInCombatMode = false; + + if (obstacleEnts.Count == 0) + return SteeringObstacleStatus.Completed; + + return SteeringObstacleStatus.Continuing; + } } return SteeringObstacleStatus.Failed; diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs index 1f5a369188..6423d026f3 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -41,6 +41,7 @@ namespace Content.Server.NPC.Systems [Dependency] private readonly IAdminManager _admin = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IParallelManager _parallel = default!; @@ -66,6 +67,8 @@ namespace Content.Server.NPC.Systems private readonly HashSet _subscribedSessions = new(); + private object _obstacles = new(); + public override void Initialize() { base.Initialize(); @@ -76,7 +79,6 @@ namespace Content.Server.NPC.Systems } UpdatesBefore.Add(typeof(SharedPhysicsSystem)); - InitializeAvoidance(); _configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled); _configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding); @@ -114,7 +116,6 @@ namespace Content.Server.NPC.Systems public override void Shutdown() { base.Shutdown(); - ShutdownAvoidance(); _configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled); } @@ -131,6 +132,7 @@ namespace Content.Server.NPC.Systems private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args) { + // Cancel any active pathfinding jobs as they're irrelevant. component.PathfindToken?.Cancel(); } @@ -151,7 +153,6 @@ namespace Content.Server.NPC.Systems component.Flags = _pathfindingSystem.GetFlags(uid); } - EnsureComp(uid); component.Coordinates = coordinates; return component; } @@ -185,7 +186,6 @@ namespace Content.Server.NPC.Systems component.PathfindToken?.Cancel(); component.PathfindToken = null; - RemComp(uid); RemComp(uid); } @@ -203,7 +203,7 @@ namespace Content.Server.NPC.Systems var npcs = EntityQuery() .ToArray(); - var options = new ParallelOptions() + var options = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount, }; @@ -261,6 +261,8 @@ namespace Content.Server.NPC.Systems EntityQuery xformQuery, float frameTime) { + IoCManager.InitThread(_dependencies, replaceExisting: true); + if (Deleted(steering.Coordinates.EntityId)) { SetDirection(mover, steering, Vector2.Zero); diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 94f736df9d..a9898f8cce 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -34,15 +34,22 @@ namespace Content.Server.Power.EntitySystems SubscribeLocalEvent(ApcPowerReceiverInit); SubscribeLocalEvent(ApcPowerReceiverShutdown); SubscribeLocalEvent(ApcPowerReceiverPaused); + SubscribeLocalEvent(ApcPowerReceiverUnpaused); + SubscribeLocalEvent(BatteryInit); SubscribeLocalEvent(BatteryShutdown); SubscribeLocalEvent(BatteryPaused); + SubscribeLocalEvent(BatteryUnpaused); + SubscribeLocalEvent(PowerConsumerInit); SubscribeLocalEvent(PowerConsumerShutdown); SubscribeLocalEvent(PowerConsumerPaused); + SubscribeLocalEvent(PowerConsumerUnpaused); + SubscribeLocalEvent(PowerSupplierInit); SubscribeLocalEvent(PowerSupplierShutdown); SubscribeLocalEvent(PowerSupplierPaused); + SubscribeLocalEvent(PowerSupplierUnpaused); } private void ApcPowerReceiverInit(EntityUid uid, ApcPowerReceiverComponent component, ComponentInit args) @@ -59,9 +66,17 @@ namespace Content.Server.Power.EntitySystems private static void ApcPowerReceiverPaused( EntityUid uid, ApcPowerReceiverComponent component, - EntityPausedEvent args) + ref EntityPausedEvent args) { - component.NetworkLoad.Paused = args.Paused; + component.NetworkLoad.Paused = true; + } + + private static void ApcPowerReceiverUnpaused( + EntityUid uid, + ApcPowerReceiverComponent component, + ref EntityUnpausedEvent args) + { + component.NetworkLoad.Paused = false; } private void BatteryInit(EntityUid uid, PowerNetworkBatteryComponent component, ComponentInit args) @@ -74,9 +89,14 @@ namespace Content.Server.Power.EntitySystems _powerState.Batteries.Free(component.NetworkBattery.Id); } - private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, EntityPausedEvent args) + private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, ref EntityPausedEvent args) { - component.NetworkBattery.Paused = args.Paused; + component.NetworkBattery.Paused = true; + } + + private static void BatteryUnpaused(EntityUid uid, PowerNetworkBatteryComponent component, ref EntityUnpausedEvent args) + { + component.NetworkBattery.Paused = false; } private void PowerConsumerInit(EntityUid uid, PowerConsumerComponent component, ComponentInit args) @@ -89,9 +109,14 @@ namespace Content.Server.Power.EntitySystems _powerState.Loads.Free(component.NetworkLoad.Id); } - private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, EntityPausedEvent args) + private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, ref EntityPausedEvent args) { - component.NetworkLoad.Paused = args.Paused; + component.NetworkLoad.Paused = true; + } + + private static void PowerConsumerUnpaused(EntityUid uid, PowerConsumerComponent component, ref EntityUnpausedEvent args) + { + component.NetworkLoad.Paused = false; } private void PowerSupplierInit(EntityUid uid, PowerSupplierComponent component, ComponentInit args) @@ -104,9 +129,14 @@ namespace Content.Server.Power.EntitySystems _powerState.Supplies.Free(component.NetworkSupply.Id); } - private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, EntityPausedEvent args) + private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, ref EntityPausedEvent args) { - component.NetworkSupply.Paused = args.Paused; + component.NetworkSupply.Paused = true; + } + + private static void PowerSupplierUnpaused(EntityUid uid, PowerSupplierComponent component, ref EntityUnpausedEvent args) + { + component.NetworkSupply.Paused = false; } public void InitPowerNet(PowerNet powerNet) diff --git a/Content.Server/Radio/Components/RadioMicrophoneComponent.cs b/Content.Server/Radio/Components/RadioMicrophoneComponent.cs index 8b2b1c1a30..86c750762b 100644 --- a/Content.Server/Radio/Components/RadioMicrophoneComponent.cs +++ b/Content.Server/Radio/Components/RadioMicrophoneComponent.cs @@ -1,6 +1,7 @@ using Content.Server.Radio.EntitySystems; using Content.Shared.Radio; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Server.Radio.Components; @@ -15,10 +16,23 @@ public sealed class RadioMicrophoneComponent : Component [DataField("broadcastChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] public string BroadcastChannel = "Common"; + [ViewVariables, DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List? SupportedChannels; + [ViewVariables(VVAccess.ReadWrite)] [DataField("listenRange")] public int ListenRange = 4; [DataField("enabled")] public bool Enabled = false; + + [DataField("powerRequired")] + public bool PowerRequired = false; + + /// + /// Whether or not the speaker must have an + /// unobstructed path to the radio to speak + /// + [DataField("unobstructedRequired")] + public bool UnobstructedRequired = false; } diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 8d192c4de0..9b40e5b67f 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -1,11 +1,16 @@ using Content.Server.Chat.Systems; +using Content.Server.Interaction; using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; using Content.Server.Radio.Components; using Content.Server.Speech; using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Radio; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -20,6 +25,8 @@ public sealed class RadioDeviceSystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); @@ -31,6 +38,9 @@ public sealed class RadioDeviceSystem : EntitySystem SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnActivateMicrophone); SubscribeLocalEvent(OnListen); + SubscribeLocalEvent(OnAttemptListen); + SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSpeakerInit); SubscribeLocalEvent(OnActivateSpeaker); @@ -80,7 +90,10 @@ public sealed class RadioDeviceSystem : EntitySystem if (!Resolve(uid, ref component)) return; - component.Enabled = !component.Enabled; + if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) + return; + + SetMicrophoneEnabled(uid, !component.Enabled, component); if (!quiet) { @@ -95,6 +108,55 @@ public sealed class RadioDeviceSystem : EntitySystem RemCompDeferred(uid); } + private void OnGetVerbs(EntityUid uid, RadioMicrophoneComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + if (component.SupportedChannels == null || component.SupportedChannels.Count <= 1) + return; + + if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) + return; + + foreach (var channel in component.SupportedChannels) + { + var proto = _protoMan.Index(channel); + + var v = new Verb + { + Text = proto.LocalizedName, + Priority = 1, + Category = VerbCategory.ChannelSelect, + Disabled = component.BroadcastChannel == channel, + DoContactInteraction = true, + Act = () => + { + component.BroadcastChannel = channel; + _popup.PopupEntity(Loc.GetString("handheld-radio-component-channel-set", + ("channel", channel)), uid, Filter.Entities(args.User)); + } + }; + args.Verbs.Add(v); + } + } + + private void OnPowerChanged(EntityUid uid, RadioMicrophoneComponent component, ref PowerChangedEvent args) + { + if (args.Powered) + return; + SetMicrophoneEnabled(uid, false, component); + } + + public void SetMicrophoneEnabled(EntityUid uid, bool enabled, RadioMicrophoneComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return; + + component.Enabled = enabled; + _appearance.SetData(uid, RadioDeviceVisuals.Broadcasting, component.Enabled); + } + public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) @@ -121,8 +183,9 @@ public sealed class RadioDeviceSystem : EntitySystem if (!args.IsInDetailsRange) return; - var freq = _protoMan.Index(component.BroadcastChannel).Frequency; - args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", freq))); + var proto = _protoMan.Index(component.BroadcastChannel); + args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency))); + args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine", ("channel", proto.LocalizedName))); } private void OnListen(EntityUid uid, RadioMicrophoneComponent component, ListenEvent args) @@ -134,6 +197,15 @@ public sealed class RadioDeviceSystem : EntitySystem _radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index(component.BroadcastChannel)); } + private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component, ListenAttemptEvent args) + { + if (component.PowerRequired && !this.IsPowered(uid, EntityManager) + || component.UnobstructedRequired && !_interaction.InRangeUnobstructed(args.Source, uid, 0)) + { + args.Cancel(); + } + } + private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, RadioReceiveEvent args) { var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source)); @@ -143,6 +215,6 @@ public sealed class RadioDeviceSystem : EntitySystem ("originalName", nameEv.Name)); var hideGlobalGhostChat = true; // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios - _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, false, nameOverride: name, hideGlobalGhostChat:hideGlobalGhostChat, checkRadioPrefix: false); + _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, false, nameOverride: name, hideGlobalGhostChat:hideGlobalGhostChat, checkRadioPrefix: false); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 92b2e17a57..c82bbea077 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -235,7 +235,7 @@ public sealed partial class RevenantSystem value.PryTile(); } - var lookup = _lookup.GetEntitiesInRange(uid, component.DefileRadius, LookupFlags.Approximate | LookupFlags.Anchored); + var lookup = _lookup.GetEntitiesInRange(uid, component.DefileRadius, LookupFlags.Approximate | LookupFlags.Static); var tags = GetEntityQuery(); var entityStorage = GetEntityQuery(); var items = GetEntityQuery(); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index ca293d7b98..7e47f9a06b 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -73,7 +73,7 @@ public sealed partial class ShuttleSystem SubscribeLocalEvent(OnDestinationPause); } - private void OnDestinationPause(EntityUid uid, FTLDestinationComponent component, EntityPausedEvent args) + private void OnDestinationPause(EntityUid uid, FTLDestinationComponent component, ref EntityPausedEvent args) { _console.RefreshShuttleConsoles(); } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 4f138b2e3b..0b746a258d 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -542,8 +542,6 @@ namespace Content.Shared.CCVar /// public static readonly CVarDef NPCPathfinding = CVarDef.Create("npc.pathfinding", true); - public static readonly CVarDef NPCCollisionAvoidance = CVarDef.Create("npc.collision_avoidance", true); - /* * Net */ diff --git a/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs b/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs index 0f0852204e..b5348654e1 100644 --- a/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs +++ b/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs @@ -15,7 +15,7 @@ namespace Content.Shared.Construction.Conditions { var result = false; - foreach (var entity in location.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Anchored)) + foreach (var entity in location.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static)) { if (IoCManager.Resolve().HasComponent(entity)) result = true; diff --git a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs index 8ee95809a0..4a409a998e 100644 --- a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs +++ b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs @@ -13,7 +13,7 @@ namespace Content.Shared.Construction.Conditions { var tagSystem = IoCManager.Resolve().GetEntitySystem(); - foreach (var entity in location.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Anchored)) + foreach (var entity in location.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static)) { if (tagSystem.HasTag(entity, "Window")) return false; diff --git a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs index 6f4708c934..3eb4008b0f 100644 --- a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs +++ b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs @@ -52,11 +52,11 @@ namespace Content.Shared.Light.Component [DataField("iconStateOn")] public string IconStateLit { get; set; } = string.Empty; - [DataField("litSound", required: true)] - public SoundSpecifier LitSound { get; set; } = default!; + [DataField("litSound")] + public SoundSpecifier? LitSound { get; set; } [DataField("loopedSound")] - public string? LoopedSound { get; set; } = null; + public SoundSpecifier? LoopedSound { get; set; } [DataField("dieSound")] public SoundSpecifier? DieSound { get; set; } = null; diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 4d6eef488f..b5b705de4c 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -175,7 +175,7 @@ namespace Content.Shared.Maps /// Helper that returns all entities in a turf. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable GetEntitiesInTile(this TileRef turf, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) + public static IEnumerable GetEntitiesInTile(this TileRef turf, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) { lookupSystem ??= EntitySystem.Get(); @@ -188,7 +188,7 @@ namespace Content.Shared.Maps /// /// Helper that returns all entities in a turf. /// - public static IEnumerable GetEntitiesInTile(this EntityCoordinates coordinates, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) + public static IEnumerable GetEntitiesInTile(this EntityCoordinates coordinates, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) { var turf = coordinates.GetTileRef(); @@ -201,7 +201,7 @@ namespace Content.Shared.Maps /// /// Helper that returns all entities in a turf. /// - public static IEnumerable GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) + public static IEnumerable GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) { return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem); } diff --git a/Content.Shared/Radio/RadioVisuals.cs b/Content.Shared/Radio/RadioVisuals.cs new file mode 100644 index 0000000000..fc2993dec9 --- /dev/null +++ b/Content.Shared/Radio/RadioVisuals.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Radio; + +[Serializable, NetSerializable] +public enum RadioDeviceVisuals : byte +{ + Broadcasting +} + +[Serializable, NetSerializable] +public enum RadioDeviceVisualLayers : byte +{ + Broadcasting +} diff --git a/Content.Shared/Stealth/SharedStealthSystem.cs b/Content.Shared/Stealth/SharedStealthSystem.cs index 7d5840c9a2..89f848f3c0 100644 --- a/Content.Shared/Stealth/SharedStealthSystem.cs +++ b/Content.Shared/Stealth/SharedStealthSystem.cs @@ -18,6 +18,7 @@ public abstract class SharedStealthSystem : EntitySystem SubscribeLocalEvent(OnMove); SubscribeLocalEvent(OnGetVisibilityModifiers); SubscribeLocalEvent(OnPaused); + SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnExamineAttempt); SubscribeLocalEvent(OnExamined); @@ -57,18 +58,16 @@ public abstract class SharedStealthSystem : EntitySystem Dirty(component); } - private void OnPaused(EntityUid uid, StealthComponent component, EntityPausedEvent args) + private void OnPaused(EntityUid uid, StealthComponent component, ref EntityPausedEvent args) { - if (args.Paused) - { - component.LastVisibility = GetVisibility(uid, component); - component.LastUpdated = null; - } - else - { - component.LastUpdated = _timing.CurTime; - } + component.LastVisibility = GetVisibility(uid, component); + component.LastUpdated = null; + Dirty(component); + } + private void OnUnpaused(EntityUid uid, StealthComponent component, ref EntityUnpausedEvent args) + { + component.LastUpdated = _timing.CurTime; Dirty(component); } diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs index 974c245a0c..3626370d89 100644 --- a/Content.Shared/Timing/UseDelaySystem.cs +++ b/Content.Shared/Timing/UseDelaySystem.cs @@ -20,23 +20,23 @@ public sealed class UseDelaySystem : EntitySystem SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnPaused); + SubscribeLocalEvent(OnUnpaused); } - private void OnPaused(EntityUid uid, UseDelayComponent component, EntityPausedEvent args) + private void OnPaused(EntityUid uid, UseDelayComponent component, ref EntityPausedEvent args) { - if (args.Paused) - { - // This entity just got paused, but wasn't before - if (component.DelayEndTime != null) - component.RemainingDelay = _gameTiming.CurTime - component.DelayEndTime; + // This entity just got paused, but wasn't before + if (component.DelayEndTime != null) + component.RemainingDelay = _gameTiming.CurTime - component.DelayEndTime; - _activeDelays.Remove(component); - } - else if (component.RemainingDelay == null) - { - // Got unpaused, but had no active delay + _activeDelays.Remove(component); + Dirty(component); + } + + private void OnUnpaused(EntityUid uid, UseDelayComponent component, ref EntityUnpausedEvent args) + { + if (component.RemainingDelay == null) return; - } // We got unpaused, resume the delay/cooldown. Currently this takes for granted that ItemCooldownComponent // handles the pausing on its own. I'm not even gonna check, because I CBF fixing it if it doesn't. diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs index 51c26c91cf..1f63f3d106 100644 --- a/Content.Shared/Verbs/VerbCategory.cs +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -76,8 +76,10 @@ namespace Content.Shared.Verbs public static readonly VerbCategory InstrumentStyle = new("verb-categories-instrument-style", null); + public static readonly VerbCategory ChannelSelect = new("verb-categories-channel-select", null); + public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null); - + public static readonly VerbCategory Lever = new("verb-categories-lever", null); } } diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f61c2935a0..1082e5c180 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,32 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - {message: Zombie move speed should no longer mispredict., type: Fix} - id: 2255 - time: '2022-09-07T01:59:27.0000000+00:00' -- author: Rane - changes: - - {message: Sleeping should behave more as expected with critical and dead mobs., - type: Fix} - id: 2256 - time: '2022-09-07T04:37:33.0000000+00:00' -- author: rolfero - changes: - - {message: Armors shows their armor values when examined!, type: Add} - id: 2257 - time: '2022-09-08T04:11:22.0000000+00:00' -- author: rolfero - changes: - - {message: 'You can examine hardsuits, magboots, etc to find out how much your - speed is decreased from them.', type: Add} - id: 2258 - time: '2022-09-08T11:09:34.0000000+00:00' -- author: ashtronaut - changes: - - {message: Nanotrasen has unlocked the gas analyzer's full potential and it can - now be used to analyze atmospheric equipment, type: Tweak} - id: 2259 - time: '2022-09-08T14:22:14.0000000+00:00' - author: metalgearsloth changes: - {message: Default camera rotations have been inverted., type: Tweak} @@ -2932,3 +2904,31 @@ Entries: type: Remove} id: 2754 time: '2022-12-12T10:19:38.0000000+00:00' +- author: tgrkzus + changes: + - {message: Radio chat now supports department specific radio prefixes, type: Add} + id: 2755 + time: '2022-12-14T01:24:49.0000000+00:00' +- author: EmoGarbage404 + changes: + - {message: 'Added wallmounted intercoms, useful for radioing a department.', type: Add} + id: 2756 + time: '2022-12-14T01:39:55.0000000+00:00' +- author: EmoGarbage404 + changes: + - {message: Intercoms now have a light indicating they are broadcasting., type: Add} + - {message: Intercoms no longer stay on when the power shuts off., type: Fix} + - {message: Intercoms can now be rotated properly., type: Fix} + id: 2757 + time: '2022-12-15T04:13:02.0000000+00:00' +- author: ElectroJr + changes: + - {message: Fixed electrified grilles electrocuting players despite being blocked + by windows., type: Fix} + id: 2758 + time: '2022-12-15T04:29:47.0000000+00:00' +- author: metalgearsloth + changes: + - {message: NPCs will now smash and pry correctly., type: Fix} + id: 2759 + time: '2022-12-15T04:30:28.0000000+00:00' diff --git a/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl b/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl index ba7506ca64..39bbf8f968 100644 --- a/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl +++ b/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl @@ -1,4 +1,6 @@ handheld-radio-component-on-use = The radio is now {$radioState}. handheld-radio-component-on-examine = It's set to broadcast over the {$frequency} frequency. handheld-radio-component-on-state = on -handheld-radio-component-off-state = off \ No newline at end of file +handheld-radio-component-off-state = off +handheld-radio-component-channel-set = Channel set to {$channel} +handheld-radio-component-chennel-examine = The current channel is {$channel}. \ No newline at end of file diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl index 6ed0b94858..32a3f5d521 100644 --- a/Resources/Locale/en-US/verbs/verb-system.ftl +++ b/Resources/Locale/en-US/verbs/verb-system.ftl @@ -22,6 +22,7 @@ verb-categories-tricks = Tricks verb-categories-transfer = Set Transfer Amount verb-categories-split = Split verb-categories-instrument-style = Instrument Style +verb-categories-channel-select = Channels verb-categories-set-sensor = Sensor verb-categories-timer = Set Delay verb-categories-lever = Lever diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index 08ea11d974..f8849b7c88 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -292,6 +292,7 @@ requiredTechnologies: - BasicResearch unlockedRecipes: + - IntercomElectronics - ConveyorBeltAssembly - FlashlightLantern - FireExtinguisher diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml new file mode 100644 index 0000000000..3bd3be9bbe --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml @@ -0,0 +1,14 @@ +- type: entity + id: IntercomElectronics + parent: BaseElectronics + name: intercom electronics + description: An electronics board used in intercoms + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: id_mod + - type: Tag + tags: + - DroneUsable + - IntercomElectronics + diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 5f3fb9c8c7..c8346c80a5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -205,6 +205,7 @@ - APCElectronics - AirAlarmElectronics - FireAlarmElectronics + - IntercomElectronics - MailingUnitElectronics - Bucket - MopItem @@ -256,6 +257,7 @@ - AirAlarmElectronics - FireAlarmElectronics - MailingUnitElectronics + - IntercomElectronics - SMESMachineCircuitboard - SubstationMachineCircuitboard - ThermomachineFreezerMachineCircuitBoard diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml new file mode 100644 index 0000000000..f8d1a8cfcc --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -0,0 +1,197 @@ +- type: entity + id: Intercom + name: intercom + description: An intercom. For when the station just needs to know something. + components: + - type: WallMount + - type: ApcPowerReceiver + - type: Electrified + enabled: false + usesApcPower: true + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + - type: ExtensionCableReceiver + - type: Clickable + - type: InteractionOutline + - type: Appearance + - type: Sprite + noRot: false + sprite: Structures/Wallmounts/intercom.rsi + layers: + - state: base + - state: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + - state: broadcasting + map: ["enum.RadioDeviceVisualLayers.Broadcasting"] + shader: unshaded + visible: false + - type: Transform + noRot: false + anchored: true + - type: Wires + BoardName: "Intercom" + LayoutId: Intercom + - type: Construction + graph: Intercom + node: intercom + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/metalbreak.ogg + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + enum.RadioDeviceVisuals.Broadcasting: + enum.RadioDeviceVisualLayers.Broadcasting: + True: { visible: true } + False: { visible: false } + placement: + mode: SnapgridCenter + snap: + - Wallmount + +- type: entity + id: IntercomAssesmbly + name: intercom assembly + description: An intercom. It doesn't seem very helpful right now. + components: + - type: WallMount + - type: Clickable + - type: InteractionOutline + - type: Sprite + sprite: Structures/Wallmounts/intercom.rsi + state: build + - type: Construction + graph: Intercom + node: assembly + - type: Transform + anchored: true + placement: + mode: SnapgridCenter + snap: + - Wallmount + +- type: entity + id: IntercomCommand + parent: Intercom + suffix: Command + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Command + +- type: entity + id: IntercomEngineering + parent: Intercom + suffix: Engineering + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Engineering + +- type: entity + id: IntercomMedical + parent: Intercom + suffix: Medical + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Medical + +- type: entity + id: IntercomScience + parent: Intercom + suffix: Science + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Science + +- type: entity + id: IntercomSecurity + parent: Intercom + suffix: Security + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Security + +- type: entity + id: IntercomService + parent: Intercom + suffix: Service + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Service + +- type: entity + id: IntercomSupply + parent: Intercom + suffix: Supply + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Supply + +- type: entity + id: IntercomAll + parent: Intercom + suffix: All + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Command + - Engineering + - Medical + - Science + - Security + - Service + - Supply \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml new file mode 100644 index 0000000000..a1e8e679ce --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml @@ -0,0 +1,72 @@ +- type: constructionGraph + id: Intercom + start: start + graph: + - node: start + edges: + - to: assembly + steps: + - material: Steel + amount: 2 + doAfter: 2.0 + + - node: assembly + entity: IntercomAssesmbly + edges: + - to: wired + steps: + - material: Cable + amount: 2 + doAfter: 1 + - to: start + completed: + - !type:GivePrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity {} + steps: + - tool: Welding + doAfter: 2 + + - node: wired + entity: IntercomAssesmbly + edges: + - to: electronics + steps: + - tag: IntercomElectronics + store: board + name: "intercom electronics" + icon: + sprite: "Objects/Misc/module.rsi" + state: "id_mod" + doAfter: 1 + - to: assembly + completed: + - !type:GivePrototype + prototype: CableApcStack1 + amount: 2 + steps: + - tool: Cutting + doAfter: 1 + + - node: electronics + edges: + - to: intercom + steps: + - tool: Screwing + doAfter: 2 + + - node: intercom + entity: Intercom #TODO: make this work with encryption keys + edges: + - to: wired + conditions: + - !type:AllWiresCut {} + - !type:WirePanel {} + - !type:ContainerNotEmpty + container: board + completed: + - !type:EmptyAllContainers {} + steps: + - tool: Prying + doAfter: 1 diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index af80f520e4..7ecbcaaed1 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -580,3 +580,22 @@ state: off conditions: - !type:TileNotBlocked {} + +# INTERCOM +- type: construction + name: intercom + id: IntercomAssesmbly + graph: Intercom + startNode: start + targetNode: intercom + category: construction-category-structures + description: An intercom. For when the station just needs to know something. + icon: + sprite: Structures/Wallmounts/intercom.rsi + state: base + placementMode: SnapgridCenter + objectType: Structure + canRotate: true + canBuildInImpassable: true + conditions: + - !type:WallmountCondition {} \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 2cef6729fe..d02671274b 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -34,6 +34,15 @@ Steel: 50 Plastic: 50 +- type: latheRecipe + id: IntercomElectronics + icon: { sprite: Objects/Misc/module.rsi, state: id_mod } + result: IntercomElectronics + completetime: 2 + materials: + Steel: 50 + Plastic: 50 + - type: latheRecipe id: FireAlarmElectronics icon: { sprite: Objects/Misc/module.rsi, state: door_electronics } diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 5fda8238c0..663f7855d9 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -291,6 +291,9 @@ - type: Tag id: Ingot +- type: Tag + id: IntercomElectronics + - type: Tag id: JawsOfLife diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png new file mode 100644 index 0000000000..787af3f538 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png differ diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/broadcasting.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/broadcasting.png new file mode 100644 index 0000000000..0566c70e35 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/broadcasting.png differ diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png new file mode 100644 index 0000000000..cfd5d5fffa Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png differ diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json new file mode 100644 index 0000000000..5df8b265bf --- /dev/null +++ b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station at https://github.com/tgstation/tgstation/commit/6bfe3b2e4fcbcdac9159dc4f0327a82ddf05ba7bi", + "states": [ + { + "name": "base", + "directions": 4 + }, + { + "name": "broadcasting", + "directions": 4 + }, + { + "name": "build", + "directions": 4 + }, + { + "name": "unshaded", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png new file mode 100644 index 0000000000..b6731da141 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png differ diff --git a/RobustToolbox b/RobustToolbox index fef963e77a..e64d499000 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit fef963e77a4168cbaaf5400c0233a6701cc56e9d +Subproject commit e64d499000f61792a092c21c8444c643055ac754