Merge remote-tracking branch 'upstream/master' into master-syndie

# Conflicts:
#	.github/PULL_REQUEST_TEMPLATE.md
#	Resources/Prototypes/Datasets/Names/last.yml
This commit is contained in:
Morbo 2022-12-15 18:30:38 +03:00
commit e32c928ff8
53 changed files with 717 additions and 270 deletions

View file

@ -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) ||

View file

@ -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
{

View file

@ -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<ExpendableLightComponent, ComponentShutdown>(OnLightShutdown);
}
private void OnLightShutdown(EntityUid uid, ExpendableLightComponent component, ComponentShutdown args)
{
component.PlayingStream?.Stop();
}
}

View file

@ -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<SharedAudioSystem>().PlayPvs(
expendableLight.LoopedSound,
expendableLight.Owner,
SharedExpendableLightComponent.LoopedSoundParams);
break;
}
case ExpendableLightState.Dead:
{
TryStopStream(expendableLight.PlayingStream);
expendableLight.PlayingStream?.Stop();
break;
}
}
}
}

View file

@ -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<SpriteComponent>();
foreach (var entity in pvsEntities)

View file

@ -82,7 +82,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
var bodyQuery = GetEntityQuery<PhysicsComponent>();
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<ClickableComponent>(ent, out var clickable) ||

View file

@ -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))

View file

@ -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<RadioChannelPrototype>("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<RadioChannelPrototype>("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);
}
}

View file

@ -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.

View file

@ -48,7 +48,7 @@ namespace Content.Server.Construction.Conditions
var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>());
var lookup = entityManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
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)
{

View file

@ -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;

View file

@ -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
/// </summary>
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<MetaDataComponent>(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)

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -1,38 +0,0 @@
using Content.Server.NPC.Systems;
namespace Content.Server.NPC.Components;
/// <summary>
/// Stores data for RVO collision avoidance
/// </summary>
[RegisterComponent]
public sealed class NPCRVOComponent : Component
{
/// <summary>
/// Maximum number of dynamic neighbors to consider for collision avoidance.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")]
public int MaxNeighbors = 5;
/// <summary>
/// Time horizon to consider for dynamic neighbor collision
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f;
/// <summary>
/// Time horizon to consider for static neighbor collision.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f;
/// <summary>
/// Range considered for neighbor agents
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")]
public float NeighborRange = 3f;
[ViewVariables]
public readonly HashSet<EntityUid> ObstacleNeighbors = new();
[ViewVariables]
public readonly HashSet<EntityUid> AgentNeighbors = new();
}

View file

@ -42,6 +42,11 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
/// </summary>
public bool ReadOnly = false;
public void Clear()
{
_blackboard.Clear();
}
public NPCBlackboard ShallowClone()
{
var dict = new NPCBlackboard();

View file

@ -8,7 +8,7 @@ using Robust.Shared.Utility;
namespace Content.Server.NPC;
public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, MappingDataNode>
public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, MappingDataNode>, ITypeCopier<NPCBlackboard>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
@ -79,4 +79,17 @@ public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, Mapping
return value;
}
public void CopyTo(ISerializationManager serializationManager, NPCBlackboard source, ref NPCBlackboard target, bool skipHook,
ISerializationContext? context = null)
{
target.Clear();
using var enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
target.SetValue(current.Key, current.Value);
}
}
}

View file

@ -45,18 +45,16 @@ public sealed partial class PathfindingSystem
{
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
SubscribeLocalEvent<GridPathfindingComponent, EntityPausedEvent>(OnGridPathPause);
SubscribeLocalEvent<GridPathfindingComponent, EntityUnpausedEvent>(OnGridPathPause);
SubscribeLocalEvent<GridPathfindingComponent, ComponentShutdown>(OnGridPathShutdown);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
SubscribeLocalEvent<PhysicsBodyTypeChangedEvent>(OnBodyTypeChange);
SubscribeLocalEvent<MoveEvent>(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)

View file

@ -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<IConfigurationManager>();
configManager.OnValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance);
}
private void ShutdownAvoidance()
{
var configManager = IoCManager.Resolve<IConfigurationManager>();
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;
}
}

View file

@ -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)

View file

@ -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<PhysicsComponent> 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<NPCMeleeCombatComponent>(component.Owner, out var melee) &&
TryComp<MeleeWeaponComponent>(melee.Weapon, out var meleeWeapon))
else if ((component.Flags & PathFlags.Smashing) != 0x0)
{
var destructibleQuery = GetEntityQuery<DestructibleComponent>();
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<CombatModeComponent>(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<DestructibleComponent>();
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;

View file

@ -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<ICommonSession> _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<NPCRVOComponent>(uid);
component.Coordinates = coordinates;
return component;
}
@ -185,7 +186,6 @@ namespace Content.Server.NPC.Systems
component.PathfindToken?.Cancel();
component.PathfindToken = null;
RemComp<NPCRVOComponent>(uid);
RemComp<NPCSteeringComponent>(uid);
}
@ -203,7 +203,7 @@ namespace Content.Server.NPC.Systems
var npcs = EntityQuery<NPCSteeringComponent, ActiveNPCComponent, InputMoverComponent, TransformComponent>()
.ToArray();
var options = new ParallelOptions()
var options = new ParallelOptions
{
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
};
@ -261,6 +261,8 @@ namespace Content.Server.NPC.Systems
EntityQuery<TransformComponent> xformQuery,
float frameTime)
{
IoCManager.InitThread(_dependencies, replaceExisting: true);
if (Deleted(steering.Coordinates.EntityId))
{
SetDirection(mover, steering, Vector2.Zero);

View file

@ -34,15 +34,22 @@ namespace Content.Server.Power.EntitySystems
SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentInit>(ApcPowerReceiverInit);
SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentShutdown>(ApcPowerReceiverShutdown);
SubscribeLocalEvent<ApcPowerReceiverComponent, EntityPausedEvent>(ApcPowerReceiverPaused);
SubscribeLocalEvent<ApcPowerReceiverComponent, EntityUnpausedEvent>(ApcPowerReceiverUnpaused);
SubscribeLocalEvent<PowerNetworkBatteryComponent, ComponentInit>(BatteryInit);
SubscribeLocalEvent<PowerNetworkBatteryComponent, ComponentShutdown>(BatteryShutdown);
SubscribeLocalEvent<PowerNetworkBatteryComponent, EntityPausedEvent>(BatteryPaused);
SubscribeLocalEvent<PowerNetworkBatteryComponent, EntityUnpausedEvent>(BatteryUnpaused);
SubscribeLocalEvent<PowerConsumerComponent, ComponentInit>(PowerConsumerInit);
SubscribeLocalEvent<PowerConsumerComponent, ComponentShutdown>(PowerConsumerShutdown);
SubscribeLocalEvent<PowerConsumerComponent, EntityPausedEvent>(PowerConsumerPaused);
SubscribeLocalEvent<PowerConsumerComponent, EntityUnpausedEvent>(PowerConsumerUnpaused);
SubscribeLocalEvent<PowerSupplierComponent, ComponentInit>(PowerSupplierInit);
SubscribeLocalEvent<PowerSupplierComponent, ComponentShutdown>(PowerSupplierShutdown);
SubscribeLocalEvent<PowerSupplierComponent, EntityPausedEvent>(PowerSupplierPaused);
SubscribeLocalEvent<PowerSupplierComponent, EntityUnpausedEvent>(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)

View file

@ -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<RadioChannelPrototype>))]
public string BroadcastChannel = "Common";
[ViewVariables, DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer<RadioChannelPrototype>))]
public List<string>? SupportedChannels;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("listenRange")]
public int ListenRange = 4;
[DataField("enabled")]
public bool Enabled = false;
[DataField("powerRequired")]
public bool PowerRequired = false;
/// <summary>
/// Whether or not the speaker must have an
/// unobstructed path to the radio to speak
/// </summary>
[DataField("unobstructedRequired")]
public bool UnobstructedRequired = false;
}

View file

@ -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<RadioMicrophoneComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<RadioMicrophoneComponent, ActivateInWorldEvent>(OnActivateMicrophone);
SubscribeLocalEvent<RadioMicrophoneComponent, ListenEvent>(OnListen);
SubscribeLocalEvent<RadioMicrophoneComponent, ListenAttemptEvent>(OnAttemptListen);
SubscribeLocalEvent<RadioMicrophoneComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
SubscribeLocalEvent<RadioMicrophoneComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<RadioSpeakerComponent, ComponentInit>(OnSpeakerInit);
SubscribeLocalEvent<RadioSpeakerComponent, ActivateInWorldEvent>(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<ActiveListenerComponent>(uid);
}
private void OnGetVerbs(EntityUid uid, RadioMicrophoneComponent component, GetVerbsEvent<Verb> 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<RadioChannelPrototype>(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<RadioChannelPrototype>(component.BroadcastChannel).Frequency;
args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", freq)));
var proto = _protoMan.Index<RadioChannelPrototype>(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<RadioChannelPrototype>(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);
}
}

View file

@ -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<TagComponent>();
var entityStorage = GetEntityQuery<EntityStorageComponent>();
var items = GetEntityQuery<ItemComponent>();

View file

@ -73,7 +73,7 @@ public sealed partial class ShuttleSystem
SubscribeLocalEvent<FTLDestinationComponent, EntityPausedEvent>(OnDestinationPause);
}
private void OnDestinationPause(EntityUid uid, FTLDestinationComponent component, EntityPausedEvent args)
private void OnDestinationPause(EntityUid uid, FTLDestinationComponent component, ref EntityPausedEvent args)
{
_console.RefreshShuttleConsoles();
}

View file

@ -542,8 +542,6 @@ namespace Content.Shared.CCVar
/// </summary>
public static readonly CVarDef<bool> NPCPathfinding = CVarDef.Create("npc.pathfinding", true);
public static readonly CVarDef<bool> NPCCollisionAvoidance = CVarDef.Create("npc.collision_avoidance", true);
/*
* Net
*/

View file

@ -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<IEntityManager>().HasComponent<SharedCanBuildWindowOnTopComponent>(entity))
result = true;

View file

@ -13,7 +13,7 @@ namespace Content.Shared.Construction.Conditions
{
var tagSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<TagSystem>();
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;

View file

@ -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;

View file

@ -175,7 +175,7 @@ namespace Content.Shared.Maps
/// Helper that returns all entities in a turf.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<EntityUid> GetEntitiesInTile(this TileRef turf, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null)
public static IEnumerable<EntityUid> GetEntitiesInTile(this TileRef turf, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null)
{
lookupSystem ??= EntitySystem.Get<EntityLookupSystem>();
@ -188,7 +188,7 @@ namespace Content.Shared.Maps
/// <summary>
/// Helper that returns all entities in a turf.
/// </summary>
public static IEnumerable<EntityUid> GetEntitiesInTile(this EntityCoordinates coordinates, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null)
public static IEnumerable<EntityUid> GetEntitiesInTile(this EntityCoordinates coordinates, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null)
{
var turf = coordinates.GetTileRef();
@ -201,7 +201,7 @@ namespace Content.Shared.Maps
/// <summary>
/// Helper that returns all entities in a turf.
/// </summary>
public static IEnumerable<EntityUid> GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null)
public static IEnumerable<EntityUid> GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null)
{
return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem);
}

View file

@ -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
}

View file

@ -18,6 +18,7 @@ public abstract class SharedStealthSystem : EntitySystem
SubscribeLocalEvent<StealthOnMoveComponent, MoveEvent>(OnMove);
SubscribeLocalEvent<StealthOnMoveComponent, GetVisibilityModifiersEvent>(OnGetVisibilityModifiers);
SubscribeLocalEvent<StealthComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<StealthComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<StealthComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<StealthComponent, ExamineAttemptEvent>(OnExamineAttempt);
SubscribeLocalEvent<StealthComponent, ExaminedEvent>(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);
}

View file

@ -20,23 +20,23 @@ public sealed class UseDelaySystem : EntitySystem
SubscribeLocalEvent<UseDelayComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<UseDelayComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<UseDelayComponent, EntityUnpausedEvent>(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.

View file

@ -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);
}
}

View file

@ -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'

View file

@ -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
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}.

View file

@ -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

View file

@ -292,6 +292,7 @@
requiredTechnologies:
- BasicResearch
unlockedRecipes:
- IntercomElectronics
- ConveyorBeltAssembly
- FlashlightLantern
- FireExtinguisher

View file

@ -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

View file

@ -205,6 +205,7 @@
- APCElectronics
- AirAlarmElectronics
- FireAlarmElectronics
- IntercomElectronics
- MailingUnitElectronics
- Bucket
- MopItem
@ -256,6 +257,7 @@
- AirAlarmElectronics
- FireAlarmElectronics
- MailingUnitElectronics
- IntercomElectronics
- SMESMachineCircuitboard
- SubstationMachineCircuitboard
- ThermomachineFreezerMachineCircuitBoard

View file

@ -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

View file

@ -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

View file

@ -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 {}

View file

@ -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 }

View file

@ -291,6 +291,9 @@
- type: Tag
id: Ingot
- type: Tag
id: IntercomElectronics
- type: Tag
id: JawsOfLife

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

@ -1 +1 @@
Subproject commit fef963e77a4168cbaaf5400c0233a6701cc56e9d
Subproject commit e64d499000f61792a092c21c8444c643055ac754