Merge pull request #1613 from crystallpunk-14/ed-04-08-2025-upstream-sync
Crawling upstream sync
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Abilities.Mime
|
||||
{
|
||||
/// <summary>
|
||||
/// Lets its owner entity use mime powers, like placing invisible walls.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MimePowersComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this component is active or not.
|
||||
/// </summarY>
|
||||
[DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// The wall prototype to use.
|
||||
/// </summary>
|
||||
[DataField("wallPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string WallPrototype = "WallInvisible";
|
||||
|
||||
[DataField("invisibleWallAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? InvisibleWallAction = "ActionMimeInvisibleWall";
|
||||
|
||||
[DataField("invisibleWallActionEntity")] public EntityUid? InvisibleWallActionEntity;
|
||||
|
||||
// The vow zone lies below
|
||||
public bool VowBroken = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mime is ready to take the vow again.
|
||||
/// Note that if they already have the vow, this is also false.
|
||||
/// </summary>
|
||||
public bool ReadyToRepent = false;
|
||||
|
||||
/// <summary>
|
||||
/// Time when the mime can repent their vow
|
||||
/// </summary>
|
||||
[DataField("vowRepentTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan VowRepentTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes the mime to get their powers back
|
||||
/// </summary>
|
||||
[DataField("vowCooldown")]
|
||||
public TimeSpan VowCooldown = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowAlert = "VowOfSilence";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowBrokenAlert = "VowBroken";
|
||||
|
||||
/// <summary>
|
||||
/// Does this component prevent the mime from writing on paper while their vow is active?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PreventWriting = false;
|
||||
|
||||
/// <summary>
|
||||
/// What message is displayed when the mime fails to write?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId FailWriteMessage = "paper-component-illiterate-mime";
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Abilities.Mime;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Speech.Muting;
|
||||
|
||||
namespace Content.Server.Abilities.Mime
|
||||
{
|
||||
public sealed class MimePowersSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MimePowersComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<MimePowersComponent, InvisibleWallActionEvent>(OnInvisibleWall);
|
||||
|
||||
SubscribeLocalEvent<MimePowersComponent, BreakVowAlertEvent>(OnBreakVowAlert);
|
||||
SubscribeLocalEvent<MimePowersComponent, RetakeVowAlertEvent>(OnRetakeVowAlert);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
// Queue to track whether mimes can retake vows yet
|
||||
|
||||
var query = EntityQueryEnumerator<MimePowersComponent>();
|
||||
while (query.MoveNext(out var uid, out var mime))
|
||||
{
|
||||
if (!mime.VowBroken || mime.ReadyToRepent)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < mime.VowRepentTime)
|
||||
continue;
|
||||
|
||||
mime.ReadyToRepent = true;
|
||||
_popupSystem.PopupEntity(Loc.GetString("mime-ready-to-repent"), uid, uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args)
|
||||
{
|
||||
EnsureComp<MutedComponent>(uid);
|
||||
if (component.PreventWriting)
|
||||
{
|
||||
EnsureComp<BlockWritingComponent>(uid, out var illiterateComponent);
|
||||
illiterateComponent.FailWriteMessage = component.FailWriteMessage;
|
||||
Dirty(uid, illiterateComponent);
|
||||
}
|
||||
|
||||
_alertsSystem.ShowAlert(uid, component.VowAlert);
|
||||
_actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an invisible wall in a free space after some checks.
|
||||
/// </summary>
|
||||
private void OnInvisibleWall(EntityUid uid, MimePowersComponent component, InvisibleWallActionEvent args)
|
||||
{
|
||||
if (!component.Enabled)
|
||||
return;
|
||||
|
||||
if (_container.IsEntityOrParentInContainer(uid))
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
// Get the tile in front of the mime
|
||||
var offsetValue = xform.LocalRotation.ToWorldVec();
|
||||
var coords = xform.Coordinates.Offset(offsetValue).SnapToGrid(EntityManager, _mapMan);
|
||||
var tile = _turf.GetTileRef(coords);
|
||||
if (tile == null)
|
||||
return;
|
||||
|
||||
// Check if the tile is blocked by a wall or mob, and don't create the wall if so
|
||||
if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable | CollisionGroup.Opaque))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", uid)), uid);
|
||||
// Make sure we set the invisible wall to despawn properly
|
||||
Spawn(component.WallPrototype, _turf.GetTileCenter(tile.Value));
|
||||
// Handle args so cooldown works
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBreakVowAlert(Entity<MimePowersComponent> ent, ref BreakVowAlertEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
BreakVow(ent, ent);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRetakeVowAlert(Entity<MimePowersComponent> ent, ref RetakeVowAlertEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
RetakeVow(ent, ent);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Break this mime's vow to not speak.
|
||||
/// </summary>
|
||||
public void BreakVow(EntityUid uid, MimePowersComponent? mimePowers = null)
|
||||
{
|
||||
if (!Resolve(uid, ref mimePowers))
|
||||
return;
|
||||
|
||||
if (mimePowers.VowBroken)
|
||||
return;
|
||||
|
||||
mimePowers.Enabled = false;
|
||||
mimePowers.VowBroken = true;
|
||||
mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown;
|
||||
RemComp<MutedComponent>(uid);
|
||||
if (mimePowers.PreventWriting)
|
||||
RemComp<BlockWritingComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retake this mime's vow to not speak.
|
||||
/// </summary>
|
||||
public void RetakeVow(EntityUid uid, MimePowersComponent? mimePowers = null)
|
||||
{
|
||||
if (!Resolve(uid, ref mimePowers))
|
||||
return;
|
||||
|
||||
if (!mimePowers.ReadyToRepent)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("mime-not-ready-repent"), uid, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
mimePowers.Enabled = true;
|
||||
mimePowers.ReadyToRepent = false;
|
||||
mimePowers.VowBroken = false;
|
||||
AddComp<MutedComponent>(uid);
|
||||
if (mimePowers.PreventWriting)
|
||||
{
|
||||
EnsureComp<BlockWritingComponent>(uid, out var illiterateComponent);
|
||||
illiterateComponent.FailWriteMessage = mimePowers.FailWriteMessage;
|
||||
Dirty(uid, illiterateComponent);
|
||||
}
|
||||
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowAlert);
|
||||
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Content.Server/Administration/Commands/CameraCommand.cs
Normal file
58
Content.Server/Administration/Commands/CameraCommand.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Content.Server.Administration.UI;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class CameraCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override string Command => "camera";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } user)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var targetNetId) || !_entManager.TryGetEntity(targetNetId, out var targetUid))
|
||||
{
|
||||
if (!_playerManager.TryGetSessionByUsername(args[0], out var player)
|
||||
|| player.AttachedEntity == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-camera-wrong-argument"));
|
||||
return;
|
||||
}
|
||||
targetUid = player.AttachedEntity.Value;
|
||||
}
|
||||
|
||||
var ui = new AdminCameraEui(targetUid.Value);
|
||||
_eui.OpenEui(ui, user);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.SessionNames(players: _playerManager),
|
||||
Loc.GetString("cmd-camera-hint"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -48,6 +49,7 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
@@ -877,7 +879,7 @@ public sealed partial class AdminVerbSystem
|
||||
if (!hadSlipComponent)
|
||||
{
|
||||
slipComponent.SlipData.SuperSlippery = true;
|
||||
slipComponent.SlipData.ParalyzeTime = TimeSpan.FromSeconds(5);
|
||||
slipComponent.SlipData.StunTime = TimeSpan.FromSeconds(5);
|
||||
slipComponent.SlipData.LaunchForwardsMultiplier = 20;
|
||||
}
|
||||
|
||||
@@ -922,5 +924,20 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", omniaccentName, Loc.GetString("admin-smite-omni-accent-description"))
|
||||
};
|
||||
args.Verbs.Add(omniaccent);
|
||||
|
||||
var crawlerName = Loc.GetString("admin-smite-crawler-name").ToLowerInvariant();
|
||||
Verb crawler = new()
|
||||
{
|
||||
Text = crawlerName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new("Mobs/Animals/snake.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<WormComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = string.Join(": ", crawlerName, Loc.GetString("admin-smite-crawler-description"))
|
||||
};
|
||||
args.Verbs.Add(crawler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Administration.UI;
|
||||
using Content.Server.Disposal.Tube;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Server.Silicons.Laws;
|
||||
@@ -16,7 +15,6 @@ using Content.Shared.Configurable;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
@@ -390,6 +388,22 @@ namespace Content.Server.Administration.Systems
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"),
|
||||
});
|
||||
}
|
||||
|
||||
// open camera
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Priority = 10,
|
||||
Text = Loc.GetString("admin-verbs-camera"),
|
||||
Message = Loc.GetString("admin-verbs-camera-description"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
var ui = new AdminCameraEui(args.Target);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
},
|
||||
Impact = LogImpact.Low
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +472,7 @@ namespace Content.Server.Administration.Systems
|
||||
Text = Loc.GetString("make-sentient-verb-get-data-text"),
|
||||
Category = VerbCategory.Debug,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/sentient.svg.192dpi.png")),
|
||||
Act = () => MakeSentientCommand.MakeSentient(args.Target, EntityManager),
|
||||
Act = () => _mindSystem.MakeSentient(args.Target),
|
||||
Impact = LogImpact.Medium
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
|
||||
97
Content.Server/Administration/UI/AdminCameraEui.cs
Normal file
97
Content.Server/Administration/UI/AdminCameraEui.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Follower;
|
||||
using Content.Shared.Coordinates;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Administration.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Admin Eui for opening a viewport window to observe entities.
|
||||
/// Use the "Open Camera" admin verb or the "camera" command to open.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class AdminCameraEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly FollowerSystem _follower = default!;
|
||||
private readonly PvsOverrideSystem _pvs = default!;
|
||||
private readonly SharedViewSubscriberSystem _viewSubscriber = default!;
|
||||
|
||||
private static readonly EntProtoId CameraProtoId = "AdminCamera";
|
||||
|
||||
private readonly EntityUid _target;
|
||||
private EntityUid? _camera;
|
||||
|
||||
|
||||
public AdminCameraEui(EntityUid target)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_follower = _entityManager.System<FollowerSystem>();
|
||||
_pvs = _entityManager.System<PvsOverrideSystem>();
|
||||
_viewSubscriber = _entityManager.System<SharedViewSubscriberSystem>();
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
_camera = CreateCamera(_target, Player);
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_entityManager.DeleteEntity(_camera);
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case AdminCameraFollowMessage:
|
||||
if (!_admin.HasAdminFlag(Player, AdminFlags.Admin) || Player.AttachedEntity == null)
|
||||
return;
|
||||
_follower.StartFollowingEntity(Player.AttachedEntity.Value, _target);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
var name = _entityManager.GetComponent<MetaDataComponent>(_target).EntityName;
|
||||
var netEnt = _entityManager.GetNetEntity(_camera);
|
||||
return new AdminCameraEuiState(netEnt, name, _timing.CurTick);
|
||||
}
|
||||
|
||||
private EntityUid CreateCamera(EntityUid target, ICommonSession observer)
|
||||
{
|
||||
// Spawn a camera entity attached to the target.
|
||||
var coords = target.ToCoordinates();
|
||||
var camera = _entityManager.SpawnAttachedTo(CameraProtoId, coords);
|
||||
|
||||
// Allow the user to see the entities near the camera.
|
||||
// This also force sends the camera entity to the user, overriding the visibility flags.
|
||||
// (The camera entity has its visibility flags set to VisibilityFlags.Admin so that cheat clients can't see it)
|
||||
_viewSubscriber.AddViewSubscriber(camera, observer);
|
||||
|
||||
return camera;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
EntityManager.AddComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.StartSound is not null)
|
||||
@@ -125,7 +125,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
|
||||
{
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity));
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
EntityManager.RemoveComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
|
||||
|
||||
if (ent.Comp.EndMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
@@ -53,6 +54,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
|
||||
|
||||
// Grid events
|
||||
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
|
||||
SubscribeLocalEvent<PipeNodeGroupRemovedEvent>(OnPipeNodeGroupRemoved);
|
||||
}
|
||||
|
||||
#region Event handling
|
||||
@@ -295,6 +297,25 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
|
||||
|
||||
#region Pipe net functions
|
||||
|
||||
private void OnPipeNodeGroupRemoved(ref PipeNodeGroupRemovedEvent args)
|
||||
{
|
||||
// When a pipe node group is removed, we need to iterate over all of
|
||||
// our pipe chunks and remove any entries with a matching net id.
|
||||
// (We only need to check the chunks for the affected grid, though.)
|
||||
|
||||
if (!_gridAtmosPipeChunks.TryGetValue(args.Grid, out var chunkData))
|
||||
return;
|
||||
|
||||
foreach (var chunk in chunkData.Values)
|
||||
{
|
||||
foreach (var key in chunk.AtmosPipeData.Keys)
|
||||
{
|
||||
if (key.NetId == args.NetId)
|
||||
chunk.AtmosPipeData.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildAtmosPipeGrid(EntityUid gridUid, MapGridComponent grid)
|
||||
{
|
||||
var allChunks = new Dictionary<Vector2i, AtmosPipeChunk>();
|
||||
@@ -411,7 +432,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
|
||||
continue;
|
||||
|
||||
var netId = GetPipeNodeNetId(pipeNode);
|
||||
var subnet = new AtmosMonitoringConsoleSubnet(netId, pipeNode.CurrentPipeLayer, pipeColor.Color.ToHex());
|
||||
var subnet = new AtmosMonitoringConsoleSubnet(netId, pipeNode.CurrentPipeLayer, pipeColor.Color);
|
||||
var pipeDirection = pipeNode.CurrentPipeDirection;
|
||||
|
||||
chunk.AtmosPipeData.TryGetValue(subnet, out var atmosPipeData);
|
||||
|
||||
@@ -279,6 +279,14 @@ public partial class AtmosphereSystem
|
||||
|
||||
public bool RemovePipeNet(Entity<GridAtmosphereComponent?> grid, PipeNet pipeNet)
|
||||
{
|
||||
// Technically this event can be fired even on grids that don't
|
||||
// actually have grid atmospheres.
|
||||
if (pipeNet.Grid is not null)
|
||||
{
|
||||
var ev = new PipeNodeGroupRemovedEvent(grid, pipeNet.NetId);
|
||||
RaiseLocalEvent(ref ev);
|
||||
}
|
||||
|
||||
return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Remove(pipeNet);
|
||||
}
|
||||
|
||||
@@ -329,3 +337,12 @@ public partial class AtmosphereSystem
|
||||
[ByRefEvent] private record struct IsHotspotActiveMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Raised broadcasted when a pipe node group within a grid has been removed.
|
||||
/// </summary>
|
||||
/// <param name="Grid">The grid with the removed node group.</param>
|
||||
/// <param name="NetId">The net id of the removed node group.</param>
|
||||
[ByRefEvent]
|
||||
public record struct PipeNodeGroupRemovedEvent(EntityUid Grid, int NetId);
|
||||
|
||||
@@ -409,7 +409,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
flammable.Resisting = true;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("flammable-component-resist-message"), uid, uid);
|
||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
|
||||
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(2f));
|
||||
|
||||
// TODO FLAMMABLE: Make this not use TimerComponent...
|
||||
uid.SpawnTimer(2000, () =>
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed partial class AtmosAlarmableComponent : Component
|
||||
public HashSet<string> SyncWithTags { get; private set; } = new();
|
||||
|
||||
[DataField("monitorAlertTypes")]
|
||||
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; private set; }
|
||||
public AtmosMonitorThresholdTypeFlags MonitorAlertTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this device should receive only. If it can only
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed partial class AtmosMonitorComponent : Component
|
||||
public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal;
|
||||
|
||||
[DataField("trippedThresholds")]
|
||||
public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
||||
public AtmosMonitorThresholdTypeFlags TrippedThresholds;
|
||||
|
||||
/// <summary>
|
||||
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
||||
|
||||
@@ -108,9 +108,9 @@ public sealed class AtmosAlarmableSystem : EntitySystem
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
|
||||
if (args.Data.TryGetValue(AlertTypes, out AtmosMonitorThresholdTypeFlags types) && component.MonitorAlertTypes != AtmosMonitorThresholdTypeFlags.None)
|
||||
{
|
||||
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
|
||||
isValid = (types & component.MonitorAlertTypes) != 0;
|
||||
}
|
||||
|
||||
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
|
||||
|
||||
@@ -207,7 +207,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (component.MonitorFire
|
||||
&& component.LastAlarmState != AtmosAlarmType.Danger)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature;
|
||||
Alert(uid, AtmosAlarmType.Danger, null, component); // technically???
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
||||
&& temperatureState > component.LastAlarmState)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature;
|
||||
Alert(uid, AtmosAlarmType.Danger, null, component);
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
var state = AtmosAlarmType.Normal;
|
||||
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
||||
var alarmTypes = monitor.TrippedThresholds;
|
||||
|
||||
if (monitor.TemperatureThreshold != null
|
||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
||||
@@ -267,11 +267,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (temperatureState > state)
|
||||
{
|
||||
state = temperatureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
||||
alarmTypes |= AtmosMonitorThresholdTypeFlags.Temperature;
|
||||
}
|
||||
else if (temperatureState == AtmosAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
||||
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Temperature;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,11 +282,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (pressureState > state)
|
||||
{
|
||||
state = pressureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
||||
alarmTypes |= AtmosMonitorThresholdTypeFlags.Pressure;
|
||||
}
|
||||
else if (pressureState == AtmosAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
|
||||
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Pressure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,17 +306,17 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
|
||||
if (tripped)
|
||||
{
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
|
||||
alarmTypes |= AtmosMonitorThresholdTypeFlags.Gas;
|
||||
}
|
||||
else
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
|
||||
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Gas;
|
||||
}
|
||||
}
|
||||
|
||||
// if the state of the current air doesn't match the last alarm state,
|
||||
// we update the state
|
||||
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
||||
if (state != monitor.LastAlarmState || alarmTypes != monitor.TrippedThresholds)
|
||||
{
|
||||
Alert(uid, state, alarmTypes, monitor);
|
||||
}
|
||||
@@ -327,7 +327,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="state">The alarm state to set this monitor to.</param>
|
||||
/// <param name="alarms">The alarms that caused this alarm state.</param>
|
||||
public void Alert(EntityUid uid, AtmosAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
public void Alert(EntityUid uid, AtmosAlarmType state, AtmosMonitorThresholdTypeFlags? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor))
|
||||
return;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AtmosPipeColorComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Color Color { get; set; } = Color.White;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), UsedImplicitly]
|
||||
public Color ColorVV
|
||||
{
|
||||
get => Color;
|
||||
set => IoCManager.Resolve<IEntityManager>().System<AtmosPipeColorSystem>().SetColor(Owner, this, value);
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct AtmosPipeColorChangedEvent(Color color)
|
||||
{
|
||||
public Color Color = color;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
{
|
||||
public sealed class AtmosPipeColorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AtmosPipeColorComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<AtmosPipeColorComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, AtmosPipeColorComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PipeColorVisuals.Color, component.Color, appearance);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, AtmosPipeColorComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PipeColorVisuals.Color, Color.White, appearance);
|
||||
}
|
||||
|
||||
public void SetColor(EntityUid uid, AtmosPipeColorComponent component, Color color)
|
||||
{
|
||||
component.Color = color;
|
||||
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PipeColorVisuals.Color, color, appearance);
|
||||
|
||||
var ev = new AtmosPipeColorChangedEvent(color);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,110 +12,109 @@ namespace Content.Server.Chat.Managers;
|
||||
/// </summary>
|
||||
public sealed class ChatSanitizationManager : IChatSanitizationManager
|
||||
{
|
||||
private static readonly Dictionary<string, string> ShorthandToEmote = new()
|
||||
{
|
||||
private static readonly (Regex regex, string emoteKey)[] ShorthandToEmote =
|
||||
[
|
||||
// CP14-RU-Localization-Start
|
||||
{ "лол", "chatsan-laughs" },
|
||||
{ "хд", "chatsan-laughs" },
|
||||
{ "о-о", "chatsan-wide-eyed" }, // cyrillic о
|
||||
{ "о.о", "chatsan-wide-eyed" }, // cyrillic о
|
||||
{ "0_о", "chatsan-wide-eyed" }, // cyrillic о
|
||||
{ "о/", "chatsan-waves" }, // cyrillic о
|
||||
{ "о7", "chatsan-salutes" }, // cyrillic о
|
||||
{ "0_o", "chatsan-wide-eyed" },
|
||||
{ "лмао", "chatsan-laughs" },
|
||||
{ "рофл", "chatsan-laughs" },
|
||||
{ "яхз", "chatsan-shrugs" },
|
||||
{ ":0", "chatsan-surprised" },
|
||||
{ ":р", "chatsan-stick-out-tongue" }, // cyrillic р
|
||||
{ "кек", "chatsan-laughs" },
|
||||
{ "T_T", "chatsan-cries" },
|
||||
{ "Т_Т", "chatsan-cries" }, // cyrillic T
|
||||
{ "=_(", "chatsan-cries" },
|
||||
{ "))", "chatsan-smiles-widely" },
|
||||
{ ")", "chatsan-smiles" },
|
||||
{ "((", "chatsan-frowns-deeply" },
|
||||
{ "(", "chatsan-frowns" },
|
||||
Entry( "лол", "chatsan-laughs" ),
|
||||
Entry( "хд", "chatsan-laughs" ),
|
||||
Entry( "о-о", "chatsan-wide-eyed" ), // cyrillic о
|
||||
Entry( "о.о", "chatsan-wide-eyed" ), // cyrillic о
|
||||
Entry( "0_о", "chatsan-wide-eyed" ), // cyrillic о
|
||||
Entry( "о/", "chatsan-waves" ), // cyrillic о
|
||||
Entry( "о7", "chatsan-salutes" ), // cyrillic о
|
||||
Entry( "0_o", "chatsan-wide-eyed" ),
|
||||
Entry( "лмао", "chatsan-laughs" ),
|
||||
Entry( "рофл", "chatsan-laughs" ),
|
||||
Entry( "яхз", "chatsan-shrugs" ),
|
||||
Entry( ":0", "chatsan-surprised" ),
|
||||
Entry( ":р", "chatsan-stick-out-tongue" ), // cyrillic р
|
||||
Entry( "кек", "chatsan-laughs" ),
|
||||
Entry( "T_T", "chatsan-cries" ),
|
||||
Entry( "Т_Т", "chatsan-cries" ), // cyrillic T
|
||||
Entry( "=_(", "chatsan-cries" ),
|
||||
Entry( "))", "chatsan-smiles-widely" ),
|
||||
Entry( ")", "chatsan-smiles" ),
|
||||
Entry( "((", "chatsan-frowns-deeply" ),
|
||||
Entry( "(", "chatsan-frowns" ),
|
||||
// CP14-RU-Localization-End
|
||||
// I could've done this with regex, but felt it wasn't the right idea.
|
||||
{ ":)", "chatsan-smiles" },
|
||||
{ ":]", "chatsan-smiles" },
|
||||
{ "=)", "chatsan-smiles" },
|
||||
{ "=]", "chatsan-smiles" },
|
||||
{ "(:", "chatsan-smiles" },
|
||||
{ "[:", "chatsan-smiles" },
|
||||
{ "(=", "chatsan-smiles" },
|
||||
{ "[=", "chatsan-smiles" },
|
||||
{ "^^", "chatsan-smiles" },
|
||||
{ "^-^", "chatsan-smiles" },
|
||||
{ ":(", "chatsan-frowns" },
|
||||
{ ":[", "chatsan-frowns" },
|
||||
{ "=(", "chatsan-frowns" },
|
||||
{ "=[", "chatsan-frowns" },
|
||||
{ "):", "chatsan-frowns" },
|
||||
{ ")=", "chatsan-frowns" },
|
||||
{ "]:", "chatsan-frowns" },
|
||||
{ "]=", "chatsan-frowns" },
|
||||
{ ":D", "chatsan-smiles-widely" },
|
||||
{ "D:", "chatsan-frowns-deeply" },
|
||||
{ ":O", "chatsan-surprised" },
|
||||
{ ":3", "chatsan-smiles" },
|
||||
{ ":S", "chatsan-uncertain" },
|
||||
{ ":>", "chatsan-grins" },
|
||||
{ ":<", "chatsan-pouts" },
|
||||
{ "xD", "chatsan-laughs" },
|
||||
{ ":'(", "chatsan-cries" },
|
||||
{ ":'[", "chatsan-cries" },
|
||||
{ "='(", "chatsan-cries" },
|
||||
{ "='[", "chatsan-cries" },
|
||||
{ ")':", "chatsan-cries" },
|
||||
{ "]':", "chatsan-cries" },
|
||||
{ ")'=", "chatsan-cries" },
|
||||
{ "]'=", "chatsan-cries" },
|
||||
{ ";-;", "chatsan-cries" },
|
||||
{ ";_;", "chatsan-cries" },
|
||||
{ "qwq", "chatsan-cries" },
|
||||
{ ":u", "chatsan-smiles-smugly" },
|
||||
{ ":v", "chatsan-smiles-smugly" },
|
||||
{ ">:i", "chatsan-annoyed" },
|
||||
{ ":i", "chatsan-sighs" },
|
||||
{ ":|", "chatsan-sighs" },
|
||||
{ ":p", "chatsan-stick-out-tongue" },
|
||||
{ ";p", "chatsan-stick-out-tongue" },
|
||||
{ ":b", "chatsan-stick-out-tongue" },
|
||||
{ "0-0", "chatsan-wide-eyed" },
|
||||
{ "o-o", "chatsan-wide-eyed" },
|
||||
{ "o.o", "chatsan-wide-eyed" },
|
||||
{ "._.", "chatsan-surprised" },
|
||||
{ ".-.", "chatsan-confused" },
|
||||
{ "-_-", "chatsan-unimpressed" },
|
||||
{ "smh", "chatsan-unimpressed" },
|
||||
{ "o/", "chatsan-waves" },
|
||||
{ "^^/", "chatsan-waves" },
|
||||
{ ":/", "chatsan-uncertain" },
|
||||
{ ":\\", "chatsan-uncertain" },
|
||||
{ "lmao", "chatsan-laughs" },
|
||||
{ "lmfao", "chatsan-laughs" },
|
||||
{ "lol", "chatsan-laughs" },
|
||||
{ "lel", "chatsan-laughs" },
|
||||
{ "kek", "chatsan-laughs" },
|
||||
{ "rofl", "chatsan-laughs" },
|
||||
{ "o7", "chatsan-salutes" },
|
||||
{ ";_;7", "chatsan-tearfully-salutes" },
|
||||
{ "idk", "chatsan-shrugs" },
|
||||
{ ";)", "chatsan-winks" },
|
||||
{ ";]", "chatsan-winks" },
|
||||
{ "(;", "chatsan-winks" },
|
||||
{ "[;", "chatsan-winks" },
|
||||
{ ":')", "chatsan-tearfully-smiles" },
|
||||
{ ":']", "chatsan-tearfully-smiles" },
|
||||
{ "=')", "chatsan-tearfully-smiles" },
|
||||
{ "=']", "chatsan-tearfully-smiles" },
|
||||
{ "(':", "chatsan-tearfully-smiles" },
|
||||
{ "[':", "chatsan-tearfully-smiles" },
|
||||
{ "('=", "chatsan-tearfully-smiles" },
|
||||
{ "['=", "chatsan-tearfully-smiles" }
|
||||
};
|
||||
Entry(":)", "chatsan-smiles"),
|
||||
Entry(":]", "chatsan-smiles"),
|
||||
Entry("=)", "chatsan-smiles"),
|
||||
Entry("=]", "chatsan-smiles"),
|
||||
Entry("(:", "chatsan-smiles"),
|
||||
Entry("[:", "chatsan-smiles"),
|
||||
Entry("(=", "chatsan-smiles"),
|
||||
Entry("[=", "chatsan-smiles"),
|
||||
Entry("^^", "chatsan-smiles"),
|
||||
Entry("^-^", "chatsan-smiles"),
|
||||
Entry(":(", "chatsan-frowns"),
|
||||
Entry(":[", "chatsan-frowns"),
|
||||
Entry("=(", "chatsan-frowns"),
|
||||
Entry("=[", "chatsan-frowns"),
|
||||
Entry("):", "chatsan-frowns"),
|
||||
Entry(")=", "chatsan-frowns"),
|
||||
Entry("]:", "chatsan-frowns"),
|
||||
Entry("]=", "chatsan-frowns"),
|
||||
Entry(":D", "chatsan-smiles-widely"),
|
||||
Entry("D:", "chatsan-frowns-deeply"),
|
||||
Entry(":O", "chatsan-surprised"),
|
||||
Entry(":3", "chatsan-smiles"),
|
||||
Entry(":S", "chatsan-uncertain"),
|
||||
Entry(":>", "chatsan-grins"),
|
||||
Entry(":<", "chatsan-pouts"),
|
||||
Entry("xD", "chatsan-laughs"),
|
||||
Entry(":'(", "chatsan-cries"),
|
||||
Entry(":'[", "chatsan-cries"),
|
||||
Entry("='(", "chatsan-cries"),
|
||||
Entry("='[", "chatsan-cries"),
|
||||
Entry(")':", "chatsan-cries"),
|
||||
Entry("]':", "chatsan-cries"),
|
||||
Entry(")'=", "chatsan-cries"),
|
||||
Entry("]'=", "chatsan-cries"),
|
||||
Entry(";-;", "chatsan-cries"),
|
||||
Entry(";_;", "chatsan-cries"),
|
||||
Entry("qwq", "chatsan-cries"),
|
||||
Entry(":u", "chatsan-smiles-smugly"),
|
||||
Entry(":v", "chatsan-smiles-smugly"),
|
||||
Entry(">:i", "chatsan-annoyed"),
|
||||
Entry(":i", "chatsan-sighs"),
|
||||
Entry(":|", "chatsan-sighs"),
|
||||
Entry(":p", "chatsan-stick-out-tongue"),
|
||||
Entry(";p", "chatsan-stick-out-tongue"),
|
||||
Entry(":b", "chatsan-stick-out-tongue"),
|
||||
Entry("0-0", "chatsan-wide-eyed"),
|
||||
Entry("o-o", "chatsan-wide-eyed"),
|
||||
Entry("o.o", "chatsan-wide-eyed"),
|
||||
Entry("._.", "chatsan-surprised"),
|
||||
Entry(".-.", "chatsan-confused"),
|
||||
Entry("-_-", "chatsan-unimpressed"),
|
||||
Entry("smh", "chatsan-unimpressed"),
|
||||
Entry("o/", "chatsan-waves"),
|
||||
Entry("^^/", "chatsan-waves"),
|
||||
Entry(":/", "chatsan-uncertain"),
|
||||
Entry(":\\", "chatsan-uncertain"),
|
||||
Entry("lmao", "chatsan-laughs"),
|
||||
Entry("lmfao", "chatsan-laughs"),
|
||||
Entry("lol", "chatsan-laughs"),
|
||||
Entry("lel", "chatsan-laughs"),
|
||||
Entry("kek", "chatsan-laughs"),
|
||||
Entry("rofl", "chatsan-laughs"),
|
||||
Entry("o7", "chatsan-salutes"),
|
||||
Entry(";_;7", "chatsan-tearfully-salutes"),
|
||||
Entry("idk", "chatsan-shrugs"),
|
||||
Entry(";)", "chatsan-winks"),
|
||||
Entry(";]", "chatsan-winks"),
|
||||
Entry("(;", "chatsan-winks"),
|
||||
Entry("[;", "chatsan-winks"),
|
||||
Entry(":')", "chatsan-tearfully-smiles"),
|
||||
Entry(":']", "chatsan-tearfully-smiles"),
|
||||
Entry("=')", "chatsan-tearfully-smiles"),
|
||||
Entry("=']", "chatsan-tearfully-smiles"),
|
||||
Entry("(':", "chatsan-tearfully-smiles"),
|
||||
Entry("[':", "chatsan-tearfully-smiles"),
|
||||
Entry("('=", "chatsan-tearfully-smiles"),
|
||||
Entry("['=", "chatsan-tearfully-smiles"),
|
||||
];
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
@@ -149,21 +148,8 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
||||
// -1 is just a canary for nothing found yet
|
||||
var lastEmoteIndex = -1;
|
||||
|
||||
foreach (var (shorthand, emoteKey) in ShorthandToEmote)
|
||||
foreach (var (r, emoteKey) in ShorthandToEmote)
|
||||
{
|
||||
// We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise.
|
||||
var escaped = Regex.Escape(shorthand);
|
||||
|
||||
// So there are 2 cases:
|
||||
// - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line
|
||||
// Delete the word and the whitespace before
|
||||
// - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line
|
||||
// Delete the word and the punctuation if it exists.
|
||||
var pattern =
|
||||
$@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))";
|
||||
|
||||
var r = new Regex(pattern, RegexOptions.RightToLeft | RegexOptions.IgnoreCase);
|
||||
|
||||
// We're using sanitized as the original message until the end so that we can make sure the indices of
|
||||
// the emotes are accurate.
|
||||
var lastMatch = r.Match(sanitized);
|
||||
@@ -183,4 +169,21 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
||||
sanitized = message.Trim();
|
||||
return emote is not null;
|
||||
}
|
||||
|
||||
private static (Regex regex, string emoteKey) Entry(string shorthand, string emoteKey)
|
||||
{
|
||||
// We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise.
|
||||
var escaped = Regex.Escape(shorthand);
|
||||
|
||||
// So there are 2 cases:
|
||||
// - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line
|
||||
// Delete the word and the whitespace before
|
||||
// - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line
|
||||
// Delete the word and the punctuation if it exists.
|
||||
var pattern = new Regex(
|
||||
$@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))",
|
||||
RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
return (pattern, emoteKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
else if (_robustRandom.Prob(component.KnockChance))
|
||||
{
|
||||
_audio.PlayPvs(component.KnockSound, uid);
|
||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(component.ParalyzeTime), true);
|
||||
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(component.ParalyzeTime));
|
||||
_chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace Content.Server.Communications
|
||||
{
|
||||
// All events that refresh the BUI
|
||||
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
|
||||
SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((uid, comp, _) => UpdateCommsConsoleInterface(uid, comp));
|
||||
SubscribeLocalEvent<RoundEndSystemChangedEvent>(_ => OnGenericBroadcastEvent());
|
||||
SubscribeLocalEvent<AlertLevelDelayFinishedEvent>(_ => OnGenericBroadcastEvent());
|
||||
|
||||
@@ -85,6 +84,7 @@ namespace Content.Server.Communications
|
||||
public void OnCommunicationsConsoleMapInit(EntityUid uid, CommunicationsConsoleComponent comp, MapInitEvent args)
|
||||
{
|
||||
comp.AnnouncementCooldownRemaining = comp.InitialDelay;
|
||||
UpdateCommsConsoleInterface(uid, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Drowsiness;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
|
||||
@@ -397,7 +397,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
var shouldStun = siemensCoefficient > 0.5f;
|
||||
|
||||
if (shouldStun)
|
||||
_stun.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects);
|
||||
{
|
||||
_ = refresh
|
||||
? _stun.TryUpdateParalyzeDuration(uid, time * ParalyzeTimeMultiplier)
|
||||
: _stun.TryAddParalyzeDuration(uid, time * ParalyzeTimeMultiplier);
|
||||
}
|
||||
|
||||
|
||||
// TODO: Sparks here.
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ public sealed class EntityEffectSystem : EntitySystem
|
||||
args.Result = false;
|
||||
if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
|
||||
{
|
||||
if (temp.CurrentTemperature > args.Condition.Min && temp.CurrentTemperature < args.Condition.Max)
|
||||
if (temp.CurrentTemperature >= args.Condition.Min && temp.CurrentTemperature <= args.Condition.Max)
|
||||
args.Result = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
// Ensure we actually have the component
|
||||
EnsureComp<TileFrictionModifierComponent>(entity);
|
||||
|
||||
EnsureComp<SlipperyComponent>(entity, out var slipComp);
|
||||
|
||||
// This is the base amount of reagent needed before a puddle can be considered slippery. Is defined based on
|
||||
// the sprite threshold for a puddle larger than 5 pixels.
|
||||
var smallPuddleThreshold = FixedPoint2.New(entity.Comp.OverflowVolume.Float() * LowThreshold);
|
||||
@@ -357,17 +359,21 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
var launchMult = FixedPoint2.Zero;
|
||||
// A cumulative weighted amount of stun times from slippery reagents
|
||||
var stunTimer = TimeSpan.Zero;
|
||||
// A cumulative weighted amount of knockdown times from slippery reagents
|
||||
var knockdownTimer = TimeSpan.Zero;
|
||||
|
||||
// Check if the puddle is big enough to slip in to avoid doing unnecessary logic
|
||||
if (solution.Volume <= smallPuddleThreshold)
|
||||
{
|
||||
_stepTrigger.SetActive(entity, false, comp);
|
||||
_tile.SetModifier(entity, 1f);
|
||||
slipComp.SlipData.SlipFriction = 1f;
|
||||
slipComp.AffectsSliding = false;
|
||||
Dirty(entity, slipComp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<SlipperyComponent>(entity, out var slipComp))
|
||||
return;
|
||||
slipComp.AffectsSliding = true;
|
||||
|
||||
foreach (var (reagent, quantity) in solution.Contents)
|
||||
{
|
||||
@@ -387,7 +393,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
// Aggregate launch speed based on quantity
|
||||
launchMult += reagentProto.SlipData.LaunchForwardsMultiplier * quantity;
|
||||
// Aggregate stun times based on quantity
|
||||
stunTimer += reagentProto.SlipData.ParalyzeTime * (float)quantity;
|
||||
stunTimer += reagentProto.SlipData.StunTime * (float)quantity;
|
||||
knockdownTimer += reagentProto.SlipData.KnockdownTime * (float)quantity;
|
||||
|
||||
if (reagentProto.SlipData.SuperSlippery)
|
||||
superSlipperyUnits += quantity;
|
||||
@@ -405,8 +412,9 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
||||
// A puddle with 10 units of lube vs a puddle with 10 of lube and 20 catchup should stun and launch forward the same amount.
|
||||
if (slipperyUnits > 0)
|
||||
{
|
||||
slipComp.SlipData.LaunchForwardsMultiplier = (float)(launchMult / slipperyUnits);
|
||||
slipComp.SlipData.ParalyzeTime = stunTimer / (float)slipperyUnits;
|
||||
slipComp.SlipData.LaunchForwardsMultiplier = (float)(launchMult/slipperyUnits);
|
||||
slipComp.SlipData.StunTime = (stunTimer/(float)slipperyUnits);
|
||||
slipComp.SlipData.KnockdownTime = (knockdownTimer/(float)slipperyUnits);
|
||||
}
|
||||
|
||||
// Only make it super slippery if there is enough super slippery units for its own puddle
|
||||
|
||||
@@ -228,7 +228,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
continue;
|
||||
|
||||
_npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction);
|
||||
_stun.TryParalyze(uid, stunTime, true);
|
||||
_stun.TryUpdateParalyzeDuration(uid, stunTime);
|
||||
RemCompDeferred<RevolutionaryComponent>(uid);
|
||||
_popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid);
|
||||
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying.");
|
||||
|
||||
@@ -125,8 +125,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
if (traitorRole is not null)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Add traitor briefing components");
|
||||
AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
|
||||
Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
|
||||
EnsureComp<RoleBriefingComponent>(traitorRole.Value.Owner, out var briefingComp);
|
||||
briefingComp.Briefing = briefing;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Shared.Ghost.Roles.Raffles;
|
||||
using Content.Server.Ghost.Roles.UI;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
@@ -698,7 +697,7 @@ public sealed class GhostRoleSystem : EntitySystem
|
||||
RaiseLocalEvent(mob, spawnedEvent);
|
||||
|
||||
if (ghostRole.MakeSentient)
|
||||
MakeSentientCommand.MakeSentient(mob, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
_mindSystem.MakeSentient(mob, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
|
||||
EnsureComp<MindContainerComponent>(mob);
|
||||
|
||||
@@ -745,7 +744,7 @@ public sealed class GhostRoleSystem : EntitySystem
|
||||
}
|
||||
|
||||
if (ghostRole.MakeSentient)
|
||||
MakeSentientCommand.MakeSentient(uid, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
_mindSystem.MakeSentient(uid, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
|
||||
GhostRoleInternalCreateMindAndTransfer(args.Player, uid, uid, ghostRole);
|
||||
UnregisterGhostRole((uid, ghostRole));
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Glue;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Glue;
|
||||
|
||||
public sealed class GlueSystem : SharedGlueSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GlueComponent, AfterInteractEvent>(OnInteract, after: new[] { typeof(OpenableSystem) });
|
||||
SubscribeLocalEvent<GluedComponent, ComponentInit>(OnGluedInit);
|
||||
SubscribeLocalEvent<GlueComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
SubscribeLocalEvent<GluedComponent, GotEquippedHandEvent>(OnHandPickUp);
|
||||
SubscribeLocalEvent<GluedComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
// When glue bottle is used on item it will apply the glued and unremoveable components.
|
||||
private void OnInteract(Entity<GlueComponent> entity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!args.CanReach || args.Target is not { Valid: true } target)
|
||||
return;
|
||||
|
||||
if (TryGlue(entity, target, args.User))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(Entity<GlueComponent> entity, ref GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess || args.Target is not { Valid: true } target ||
|
||||
_openable.IsClosed(entity))
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () => TryGlue(entity, target, user),
|
||||
IconEntity = GetNetEntity(entity),
|
||||
Text = Loc.GetString("glue-verb-text"),
|
||||
Message = Loc.GetString("glue-verb-message")
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private bool TryGlue(Entity<GlueComponent> entity, EntityUid target, EntityUid actor)
|
||||
{
|
||||
// if item is glued then don't apply glue again so it can be removed for reasonable time
|
||||
// If glue is applied to an unremoveable item, the component will disappear after the duration.
|
||||
// This effecitvely means any unremoveable item could be removed with a bottle of glue.
|
||||
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target) || HasComp<UnremoveableComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<ItemComponent>(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
|
||||
{
|
||||
var quantity = solution.RemoveReagent(entity.Comp.Reagent, entity.Comp.ConsumptionUnit);
|
||||
if (quantity > 0)
|
||||
{
|
||||
EnsureComp<GluedComponent>(target).Duration = quantity.Double() * entity.Comp.DurationPerUnit;
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(actor):actor} glued {ToPrettyString(target):subject} with {ToPrettyString(entity.Owner):tool}");
|
||||
_audio.PlayPvs(entity.Comp.Squeeze, entity.Owner);
|
||||
_popup.PopupEntity(Loc.GetString("glue-success", ("target", target)), actor, actor, PopupType.Medium);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<GluedComponent, UnremoveableComponent>();
|
||||
while (query.MoveNext(out var uid, out var glue, out var _))
|
||||
{
|
||||
if (_timing.CurTime < glue.Until)
|
||||
continue;
|
||||
|
||||
RemComp<UnremoveableComponent>(uid);
|
||||
RemComp<GluedComponent>(uid);
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGluedInit(Entity<GluedComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
private void OnHandPickUp(Entity<GluedComponent> entity, ref GotEquippedHandEvent args)
|
||||
{
|
||||
var comp = EnsureComp<UnremoveableComponent>(entity);
|
||||
comp.DeleteOnDrop = false;
|
||||
entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<GluedComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("glued-name-prefix");
|
||||
}
|
||||
}
|
||||
@@ -461,7 +461,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
if (instrument.InstrumentPlayer is {Valid: true} mob)
|
||||
{
|
||||
_stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
|
||||
_stuns.TryUpdateParalyzeDuration(mob, TimeSpan.FromSeconds(1));
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
|
||||
uid, mob, PopupType.LargeCaution);
|
||||
|
||||
@@ -2,16 +2,15 @@ using Content.Server.Body.Systems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -27,7 +26,7 @@ namespace Content.Server.Medical
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
[Dependency] private readonly ThirstSystem _thirst = default!;
|
||||
[Dependency] private readonly ForensicsSystem _forensics = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||
@@ -57,8 +56,7 @@ namespace Content.Server.Medical
|
||||
// It fully empties the stomach, this amount from the chem stream is relatively small
|
||||
var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
|
||||
// Apply a bit of slowdown
|
||||
if (TryComp<StatusEffectsComponent>(uid, out var status))
|
||||
_stun.TrySlowdown(uid, TimeSpan.FromSeconds(solutionSize), true, 0.5f, 0.5f, status);
|
||||
_movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f);
|
||||
|
||||
// TODO: Need decals
|
||||
var solution = new Solution();
|
||||
|
||||
@@ -1,63 +1,30 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Emoting;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Mind.Commands
|
||||
namespace Content.Server.Mind.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class MakeSentientCommand : LocalizedEntityCommands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class MakeSentientCommand : IConsoleCommand
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
public override string Command => "makesentient";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "makesentient";
|
||||
public string Description => "Makes an entity sentient (able to be controlled by a player)";
|
||||
public string Help => "makesentient <entity id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
if (args.Length != 1)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Wrong number of arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var entNet) || !_entManager.TryGetEntity(entNet, out var entId))
|
||||
{
|
||||
shell.WriteLine("Invalid argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.EntityExists(entId))
|
||||
{
|
||||
shell.WriteLine("Invalid entity specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
MakeSentient(entId.Value, _entManager, true, true);
|
||||
shell.WriteLine(Loc.GetString("shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
public static void MakeSentient(EntityUid uid, IEntityManager entityManager, bool allowMovement = true, bool allowSpeech = true)
|
||||
if (!NetEntity.TryParse(args[0], out var entNet) || !EntityManager.TryGetEntity(entNet, out var entId) || !EntityManager.EntityExists(entId))
|
||||
{
|
||||
entityManager.EnsureComponent<MindContainerComponent>(uid);
|
||||
if (allowMovement)
|
||||
{
|
||||
entityManager.EnsureComponent<InputMoverComponent>(uid);
|
||||
entityManager.EnsureComponent<MobMoverComponent>(uid);
|
||||
entityManager.EnsureComponent<MovementSpeedModifierComponent>(uid);
|
||||
}
|
||||
|
||||
if (allowSpeech)
|
||||
{
|
||||
entityManager.EnsureComponent<SpeechComponent>(uid);
|
||||
entityManager.EnsureComponent<EmotingComponent>(uid);
|
||||
}
|
||||
|
||||
entityManager.EnsureComponent<ExaminerComponent>(uid);
|
||||
shell.WriteLine(Loc.GetString("shell-could-not-find-entity-with-uid", ("uid", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
_mindSystem.MakeSentient(entId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
@@ -349,7 +348,7 @@ public sealed class MindSystem : SharedMindSystem
|
||||
return;
|
||||
}
|
||||
|
||||
MakeSentientCommand.MakeSentient(target, EntityManager);
|
||||
MakeSentient(target);
|
||||
TransferTo(mindId, target, ghostCheckOverride: true, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ public sealed class CritMobActionsSystem : EntitySystem
|
||||
_quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString("action-name-crit-last-words"), "",
|
||||
(string lastWords) =>
|
||||
{
|
||||
// if a person is gibbed/deleted, they can't say last words
|
||||
if (Deleted(uid))
|
||||
return;
|
||||
|
||||
// Intentionally does not check for muteness
|
||||
if (actor.PlayerSession.AttachedEntity != uid
|
||||
|| !_mobState.IsCritical(uid))
|
||||
|
||||
@@ -205,7 +205,7 @@ public sealed partial class NPCCombatSystem
|
||||
return;
|
||||
}
|
||||
|
||||
_gun.AttemptShoot(uid, gunUid, gun, targetCordinates);
|
||||
_gun.AttemptShoot(uid, gunUid, gun, targetCordinates, comp.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,16 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Prying.Systems;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Prometheus;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
||||
{
|
||||
private static readonly Gauge ActiveSteeringGauge = Metrics.CreateGauge(
|
||||
"npc_steering_active_count",
|
||||
"Amount of NPCs trying to actively do steering");
|
||||
|
||||
/*
|
||||
* We use context steering to determine which way to move.
|
||||
* This involves creating an array of possible directions and assigning a value for the desireability of each direction.
|
||||
@@ -87,6 +92,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
||||
|
||||
private object _obstacles = new();
|
||||
|
||||
private int _activeSteeringCount;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -244,12 +251,15 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
||||
};
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
_activeSteeringCount = 0;
|
||||
|
||||
Parallel.For(0, index, options, i =>
|
||||
{
|
||||
var (uid, steering, mover, xform) = npcs[i];
|
||||
Steer(uid, steering, mover, xform, frameTime, curTime);
|
||||
});
|
||||
|
||||
ActiveSteeringGauge.Set(_activeSteeringCount);
|
||||
|
||||
if (_subscribedSessions.Count > 0)
|
||||
{
|
||||
@@ -324,6 +334,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _activeSteeringCount);
|
||||
|
||||
var agentRadius = steering.Radius;
|
||||
var worldPos = _transform.GetWorldPosition(xform);
|
||||
var (layer, mask) = _physics.GetHardCollision(uid);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Prometheus;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
@@ -19,6 +20,10 @@ namespace Content.Server.NPC.Systems
|
||||
/// </summary>
|
||||
public sealed partial class NPCSystem : EntitySystem
|
||||
{
|
||||
private static readonly Gauge ActiveGauge = Metrics.CreateGauge(
|
||||
"npc_active_count",
|
||||
"Amount of NPCs that are actively processing");
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly HTNSystem _htn = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
@@ -138,6 +143,8 @@ namespace Content.Server.NPC.Systems
|
||||
|
||||
// Add your system here.
|
||||
_htn.UpdateNPC(ref _count, _maxUpdates, frameTime);
|
||||
|
||||
ActiveGauge.Set(Count<ActiveNPCComponent>());
|
||||
}
|
||||
|
||||
public void OnMobStateChange(EntityUid uid, HTNComponent component, MobStateChangedEvent args)
|
||||
|
||||
@@ -82,8 +82,8 @@ public sealed class NameIdentifierSystem : EntitySystem
|
||||
randomVal = set[^1];
|
||||
set.RemoveAt(set.Count - 1);
|
||||
|
||||
return proto.Prefix is not null
|
||||
? $"{proto.Prefix}-{randomVal}"
|
||||
return proto.Format is not null
|
||||
? Loc.GetString(proto.Format, ("number", randomVal))
|
||||
: $"{randomVal}";
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ public sealed class NameIdentifierSystem : EntitySystem
|
||||
ids.Remove(ent.Comp.Identifier))
|
||||
{
|
||||
id = ent.Comp.Identifier;
|
||||
uniqueName = group.Prefix is not null
|
||||
? $"{group.Prefix}-{id}"
|
||||
uniqueName = group.Format is not null
|
||||
? Loc.GetString(group.Format, ("number", id))
|
||||
: $"{id}";
|
||||
}
|
||||
else
|
||||
|
||||
@@ -63,7 +63,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
_audio.PlayPvs(comp.Sound, target);
|
||||
|
||||
_damageable.TryChangeDamage(target, comp.StunDamage, false, true, null, origin: uid);
|
||||
_stun.TryParalyze(target, comp.StunTime, refresh: false);
|
||||
_stun.TryAddParalyzeDuration(target, comp.StunTime);
|
||||
|
||||
// short cooldown to prevent instant stunlocking
|
||||
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using DroneConsoleComponent = Content.Server.Shuttles.DroneConsoleComponent;
|
||||
@@ -17,6 +18,10 @@ namespace Content.Server.Physics.Controllers;
|
||||
|
||||
public sealed class MoverController : SharedMoverController
|
||||
{
|
||||
private static readonly Gauge ActiveMoverGauge = Metrics.CreateGauge(
|
||||
"physics_active_mover_count",
|
||||
"Active amount of InputMovers being processed by MoverController");
|
||||
|
||||
[Dependency] private readonly ThrusterSystem _thruster = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
@@ -97,6 +102,8 @@ public sealed class MoverController : SharedMoverController
|
||||
HandleMobMovement(mover, frameTime);
|
||||
}
|
||||
|
||||
ActiveMoverGauge.Set(_movers.Count);
|
||||
|
||||
HandleShuttleMovement(frameTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Server.Stunnable;
|
||||
@@ -7,7 +6,6 @@ using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.PneumaticCannon;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
@@ -80,10 +78,9 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem
|
||||
if (gas == null && component.GasUsage > 0f)
|
||||
return;
|
||||
|
||||
if (TryComp<StatusEffectsComponent>(args.User, out var status)
|
||||
&& component.Power == PneumaticCannonPower.High)
|
||||
if (component.Power == PneumaticCannonPower.High
|
||||
&& _stun.TryUpdateParalyzeDuration(args.User, TimeSpan.FromSeconds(component.HighPowerStunTime)))
|
||||
{
|
||||
_stun.TryParalyze(args.User, TimeSpan.FromSeconds(component.HighPowerStunTime), true, status);
|
||||
Popup.PopupEntity(Loc.GetString("pneumatic-cannon-component-power-stun",
|
||||
("cannon", uid)), cannon, args.User);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Damage;
|
||||
@@ -210,7 +207,7 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
("child", Identity.Entity(child, EntityManager))),
|
||||
child);
|
||||
|
||||
MakeSentientCommand.MakeSentient(child, EntityManager);
|
||||
_mindSystem.MakeSentient(child);
|
||||
|
||||
var polymorphedComp = Factory.GetComponent<PolymorphedEntityComponent>();
|
||||
polymorphedComp.Parent = uid;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -19,6 +21,7 @@ namespace Content.Server.Power.EntitySystems;
|
||||
/// </remarks>
|
||||
public sealed class BatteryInterfaceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -43,12 +46,16 @@ public sealed class BatteryInterfaceSystem : EntitySystem
|
||||
{
|
||||
var netBattery = Comp<PowerNetworkBatteryComponent>(ent);
|
||||
netBattery.CanCharge = args.On;
|
||||
|
||||
_adminLog.Add(LogType.Action,$"{ToPrettyString(args.Actor):actor} set input breaker to {args.On} on {ToPrettyString(ent):target}");
|
||||
}
|
||||
|
||||
private void HandleSetOutputBreaker(Entity<BatteryInterfaceComponent> ent, ref BatterySetOutputBreakerMessage args)
|
||||
{
|
||||
var netBattery = Comp<PowerNetworkBatteryComponent>(ent);
|
||||
netBattery.CanDischarge = args.On;
|
||||
|
||||
_adminLog.Add(LogType.Action,$"{ToPrettyString(args.Actor):actor} set output breaker to {args.On} on {ToPrettyString(ent):target}");
|
||||
}
|
||||
|
||||
private void HandleSetChargeRate(Entity<BatteryInterfaceComponent> ent, ref BatterySetChargeRateMessage args)
|
||||
|
||||
@@ -177,7 +177,7 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
ChangeEssenceAmount(uid, -abilityCost, component, false);
|
||||
|
||||
_statusEffects.TryAddStatusEffect<CorporealComponent>(uid, "Corporeal", TimeSpan.FromSeconds(debuffs.Y), false);
|
||||
_stun.TryStun(uid, TimeSpan.FromSeconds(debuffs.X), false);
|
||||
_stun.TryAddStunDuration(uid, TimeSpan.FromSeconds(debuffs.X));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
@@ -78,7 +78,7 @@ namespace Content.Server.Sandbox.Commands
|
||||
if (!EntityManager.TryGetComponent(x.Owner, out AtmosPipeColorComponent? atmosPipeColorComponent))
|
||||
continue;
|
||||
|
||||
_pipeColorSystem.SetColor(x.Owner, atmosPipeColorComponent, color);
|
||||
_pipeColorSystem.SetColor((x.Owner, atmosPipeColorComponent), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,10 +635,7 @@ public sealed partial class ShuttleSystem
|
||||
{
|
||||
foreach (var child in toKnock)
|
||||
{
|
||||
if (!_statusQuery.TryGetComponent(child, out var status))
|
||||
continue;
|
||||
|
||||
_stuns.TryParalyze(child, _hyperspaceKnockdownTime, true, status);
|
||||
_stuns.TryUpdateParalyzeDuration(child, _hyperspaceKnockdownTime);
|
||||
|
||||
// If the guy we knocked down is on a spaced tile, throw them too
|
||||
if (grid != null)
|
||||
|
||||
@@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
if (direction.LengthSquared() > minsq)
|
||||
{
|
||||
_stuns.TryKnockdown(uid, knockdownTime, true);
|
||||
_stuns.TryUpdateKnockdownDuration(uid, knockdownTime);
|
||||
_throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -19,9 +19,21 @@ namespace Content.Server.Speech.EntitySystems
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private readonly Dictionary<ProtoId<ReplacementAccentPrototype>, (Regex regex, string replacement)[]>
|
||||
_cachedReplacements = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ReplacementAccentComponent, AccentGetEvent>(OnAccent);
|
||||
|
||||
_proto.PrototypesReloaded += OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_proto.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args)
|
||||
@@ -48,27 +60,22 @@ namespace Content.Server.Speech.EntitySystems
|
||||
return prototype.FullReplacements.Length != 0 ? Loc.GetString(_random.Pick(prototype.FullReplacements)) : "";
|
||||
}
|
||||
|
||||
if (prototype.WordReplacements == null)
|
||||
return message;
|
||||
|
||||
// Prohibition of repeated word replacements.
|
||||
// All replaced words placed in the final message are placed here as dashes (___) with the same length.
|
||||
// The regex search goes through this buffer message, from which the already replaced words are crossed out,
|
||||
// ensuring that the replaced words cannot be replaced again.
|
||||
var maskMessage = message;
|
||||
|
||||
foreach (var (first, replace) in prototype.WordReplacements)
|
||||
foreach (var (regex, replace) in GetCachedReplacements(prototype))
|
||||
{
|
||||
var f = _loc.GetString(first);
|
||||
var r = _loc.GetString(replace);
|
||||
// this is kind of slow but its not that bad
|
||||
// essentially: go over all matches, try to match capitalization where possible, then replace
|
||||
// rather than using regex.replace
|
||||
for (int i = Regex.Count(maskMessage, $@"(?<!\w){f}(?!\w)", RegexOptions.IgnoreCase); i > 0; i--)
|
||||
for (int i = regex.Count(maskMessage); i > 0; i--)
|
||||
{
|
||||
// fetch the match again as the character indices may have changed
|
||||
Match match = Regex.Match(maskMessage, $@"(?<!\w){f}(?!\w)", RegexOptions.IgnoreCase);
|
||||
var replacement = r;
|
||||
Match match = regex.Match(maskMessage);
|
||||
var replacement = replace;
|
||||
|
||||
// Intelligently replace capitalization
|
||||
// two cases where we will do so:
|
||||
@@ -98,5 +105,40 @@ namespace Content.Server.Speech.EntitySystems
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private (Regex regex, string replacement)[] GetCachedReplacements(ReplacementAccentPrototype prototype)
|
||||
{
|
||||
if (!_cachedReplacements.TryGetValue(prototype.ID, out var replacements))
|
||||
{
|
||||
replacements = GenerateCachedReplacements(prototype);
|
||||
_cachedReplacements.Add(prototype.ID, replacements);
|
||||
}
|
||||
|
||||
return replacements;
|
||||
}
|
||||
|
||||
private (Regex regex, string replacement)[] GenerateCachedReplacements(ReplacementAccentPrototype prototype)
|
||||
{
|
||||
if (prototype.WordReplacements is not { } replacements)
|
||||
return [];
|
||||
|
||||
return replacements.Select(kv =>
|
||||
{
|
||||
var (first, replace) = kv;
|
||||
var firstLoc = _loc.GetString(first);
|
||||
var replaceLoc = _loc.GetString(replace);
|
||||
|
||||
var regex = new Regex($@"(?<!\w){firstLoc}(?!\w)", RegexOptions.IgnoreCase);
|
||||
|
||||
return (regex, replaceLoc);
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
_cachedReplacements.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Abilities.Mime;
|
||||
using Content.Shared.Abilities.Mime;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Components;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Server.Charges;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Destructible;
|
||||
@@ -147,7 +147,7 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
|
||||
return;
|
||||
|
||||
Audio.PlayPvs(ent.Comp.SpraySound, ent);
|
||||
_pipeColor.SetColor(target, color, args.Color);
|
||||
_pipeColor.SetColor((target, color), args.Color);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Access.Components;
|
||||
@@ -41,6 +41,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to spawn a player character onto the given station.
|
||||
@@ -110,7 +111,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
{
|
||||
DebugTools.Assert(entity is null);
|
||||
var jobEntity = Spawn(prototype.JobEntity, coordinates);
|
||||
MakeSentientCommand.MakeSentient(jobEntity, EntityManager);
|
||||
_mindSystem.MakeSentient(jobEntity);
|
||||
|
||||
// Make sure custom names get handled, what is gameticker control flow whoopy.
|
||||
if (loadout != null)
|
||||
|
||||
@@ -8,21 +8,47 @@ namespace Content.Server.Stunnable.Components
|
||||
{
|
||||
// TODO: Can probably predict this.
|
||||
|
||||
// See stunsystem for what these do
|
||||
[DataField("stunAmount")]
|
||||
public int StunAmount;
|
||||
/// <summary>
|
||||
/// How long we are stunned for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan StunAmount;
|
||||
|
||||
[DataField("knockdownAmount")]
|
||||
public int KnockdownAmount;
|
||||
/// <summary>
|
||||
/// How long we are knocked down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan KnockdownAmount;
|
||||
|
||||
[DataField("slowdownAmount")]
|
||||
public int SlowdownAmount;
|
||||
/// <summary>
|
||||
/// How long we are slowed down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SlowdownAmount;
|
||||
|
||||
[DataField("walkSpeedMultiplier")]
|
||||
public float WalkSpeedMultiplier = 1f;
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's walking speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WalkSpeedModifier = 1f;
|
||||
|
||||
[DataField("runSpeedMultiplier")]
|
||||
public float RunSpeedMultiplier = 1f;
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's sprinting speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SprintSpeedModifier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh Stun or Slowdown on hit
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Refresh = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the entity try and stand automatically after being knocked down?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AutoStand = true;
|
||||
|
||||
/// <summary>
|
||||
/// Fixture we track for the collision.
|
||||
|
||||
6
Content.Server/Stunnable/StunSystem.cs
Normal file
6
Content.Server/Stunnable/StunSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Stunnable;
|
||||
|
||||
namespace Content.Server.Stunnable;
|
||||
|
||||
public sealed class StunSystem : SharedStunSystem;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
@@ -12,6 +10,7 @@ namespace Content.Server.Stunnable
|
||||
internal sealed class StunOnCollideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -22,18 +21,19 @@ namespace Content.Server.Stunnable
|
||||
|
||||
private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target)
|
||||
{
|
||||
_stunSystem.TryUpdateStunDuration(target, component.StunAmount);
|
||||
|
||||
if (TryComp<StatusEffectsComponent>(target, out var status))
|
||||
{
|
||||
_stunSystem.TryStun(target, TimeSpan.FromSeconds(component.StunAmount), true, status);
|
||||
_stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true);
|
||||
|
||||
_stunSystem.TryKnockdown(target, TimeSpan.FromSeconds(component.KnockdownAmount), true,
|
||||
status);
|
||||
|
||||
_stunSystem.TrySlowdown(target, TimeSpan.FromSeconds(component.SlowdownAmount), true,
|
||||
component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status);
|
||||
}
|
||||
_movementMod.TryUpdateMovementSpeedModDuration(
|
||||
target,
|
||||
MovementModStatusSystem.TaserSlowdown,
|
||||
component.SlowdownAmount,
|
||||
component.WalkSpeedModifier,
|
||||
component.SprintSpeedModifier
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != component.FixtureID)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Stunnable;
|
||||
|
||||
namespace Content.Server.Stunnable
|
||||
{
|
||||
public sealed class StunSystem : SharedStunSystem
|
||||
{}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Power;
|
||||
@@ -21,6 +23,8 @@ public sealed class SurveillanceCameraSystem : EntitySystem
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
|
||||
// Pings a surveillance camera subnet. All cameras will always respond
|
||||
// with a data message if they are on the same subnet.
|
||||
@@ -170,6 +174,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem
|
||||
component.CameraId = args.Name;
|
||||
component.NameSet = true;
|
||||
UpdateSetupInterface(uid, component);
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\"");
|
||||
}
|
||||
|
||||
private void OnSetNetwork(EntityUid uid, SurveillanceCameraComponent component,
|
||||
|
||||
@@ -68,9 +68,13 @@ public sealed class TipsSystem : EntitySystem
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-tippy-auto-1")),
|
||||
1 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.SessionNames(players: _playerManager),
|
||||
Loc.GetString("cmd-tippy-auto-1")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")),
|
||||
3 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<EntityPrototype>(), Loc.GetString("cmd-tippy-auto-3")),
|
||||
3 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIdsLimited<EntityPrototype>(args[2], _prototype),
|
||||
Loc.GetString("cmd-tippy-auto-3")),
|
||||
4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")),
|
||||
5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")),
|
||||
6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")),
|
||||
|
||||
@@ -1,47 +1,5 @@
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Tools.Components;
|
||||
|
||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
||||
using Content.Shared.Tools.Systems;
|
||||
|
||||
namespace Content.Server.Tools;
|
||||
|
||||
public sealed class ToolSystem : SharedToolSystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateWelders(frameTime);
|
||||
}
|
||||
|
||||
//todo move to shared once you can remove reagents from shared without it freaking out.
|
||||
private void UpdateWelders(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<WelderComponent, SolutionContainerManagerComponent>();
|
||||
while (query.MoveNext(out var uid, out var welder, out var solutionContainer))
|
||||
{
|
||||
if (!welder.Enabled)
|
||||
continue;
|
||||
|
||||
welder.WelderTimer += frameTime;
|
||||
|
||||
if (welder.WelderTimer < welder.WelderUpdateTimer)
|
||||
continue;
|
||||
|
||||
if (!SolutionContainerSystem.TryGetSolution((uid, solutionContainer), welder.FuelSolutionName, out var solutionComp, out var solution))
|
||||
continue;
|
||||
|
||||
SolutionContainerSystem.RemoveReagent(solutionComp.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderTimer);
|
||||
|
||||
if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero)
|
||||
{
|
||||
ItemToggle.Toggle(uid, predicted: false);
|
||||
}
|
||||
|
||||
Dirty(uid, welder);
|
||||
welder.WelderTimer -= welder.WelderUpdateTimer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ToolSystem : SharedToolSystem;
|
||||
|
||||
@@ -114,12 +114,12 @@ namespace Content.Server.Voting
|
||||
{
|
||||
var ties = string.Join(", ", eventArgs.Winners.Select(c => args[(int) c]));
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Custom vote {options.Title} finished as tie: {ties}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-tie", ("ties", ties)));
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-tie", ("title", options.Title), ("ties", ties)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Custom vote {options.Title} finished: {args[(int) eventArgs.Winner]}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win", ("winner", args[(int) eventArgs.Winner])));
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win", ("title", options.Title), ("winner", args[(int) eventArgs.Winner])));
|
||||
}
|
||||
|
||||
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||
|
||||
@@ -7,7 +7,6 @@ using Content.Server.Humanoid;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.NPC;
|
||||
using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.Systems;
|
||||
@@ -215,7 +214,7 @@ public sealed partial class ZombieSystem
|
||||
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
|
||||
|
||||
//Make it sentient if it's an animal or something
|
||||
MakeSentientCommand.MakeSentient(target, EntityManager);
|
||||
_mind.MakeSentient(target);
|
||||
|
||||
//Make the zombie not die in the cold. Good for space zombies
|
||||
if (TryComp<TemperatureComponent>(target, out var tempComp))
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed class CP14BloodMoonCurseRule : GameRuleSystem<CP14BloodMoonCurseRu
|
||||
if (!Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
_stun.TryParalyze(ent, ent.Comp.EndStunDuration, true);
|
||||
_stun.TryUpdateParalyzeDuration(ent, ent.Comp.EndStunDuration);
|
||||
_popup.PopupEntity(Loc.GetString("cp14-bloodmoon-curse-removed"), ent, PopupType.SmallCaution);
|
||||
if (TryComp<CP14BloodMoonCurseComponent>(ent, out var curseComp))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user