Merge remote-tracking branch 'upstream/stable' into ed-04-08-2025-upstream-sync
# Conflicts: # Content.Server/Chat/Managers/ChatSanitizationManager.cs # Content.Shared/Damage/Systems/SharedStaminaSystem.cs # Content.Shared/Eye/VisibilityFlags.cs # Content.Shared/Lock/LockSystem.cs # Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs # Resources/Prototypes/Recipes/Reactions/chemicals.yml # Tools/actions_changelogs_since_last_run.py
This commit is contained in:
@@ -334,7 +334,12 @@ namespace Content.Client.Actions
|
||||
|
||||
private void OnEntityTargetAttempt(Entity<EntityTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
|
||||
{
|
||||
if (args.Handled || args.Input.EntityUid is not { Valid: true } entity)
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
if (args.Input.EntityUid is not { Valid: true } entity)
|
||||
return;
|
||||
|
||||
// let world target component handle it
|
||||
@@ -345,8 +350,6 @@ namespace Content.Client.Actions
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var action = args.Action;
|
||||
var user = args.User;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
||||
MouseFilter="Stop">
|
||||
<PanelContainer StyleClasses="BackgroundDark" Name="AdminCameraWindowRoot" Access="Public">
|
||||
<BoxContainer Orientation="Vertical" Access="Public">
|
||||
<!-- Camera -->
|
||||
<Control VerticalExpand="True" Name="CameraViewBox">
|
||||
<viewport:ScalingViewport Name="CameraView"
|
||||
MinSize="100 100"
|
||||
MouseFilter="Ignore" />
|
||||
</Control>
|
||||
<!-- Controller buttons -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
|
||||
<Button StyleClasses="OpenRight" Name="FollowButton" HorizontalExpand="True" Access="Public" Text="{Loc 'admin-camera-window-follow'}" />
|
||||
<Button StyleClasses="OpenLeft" Name="PopControl" HorizontalExpand="True" Access="Public" Text="{Loc 'admin-camera-window-pop-out'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Eye;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminCameraControl : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
|
||||
public event Action? OnFollow;
|
||||
public event Action? OnPopoutControl;
|
||||
|
||||
private readonly EyeLerpingSystem _eyeLerpingSystem;
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
private AdminCameraEuiState? _nextState;
|
||||
|
||||
private const float MinimumZoom = 0.1f;
|
||||
private const float MaximumZoom = 2.0f;
|
||||
|
||||
public EntityUid? CurrentCamera;
|
||||
public float Zoom = 1.0f;
|
||||
|
||||
public bool IsPoppedOut;
|
||||
|
||||
public AdminCameraControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_eyeLerpingSystem = _entManager.System<EyeLerpingSystem>();
|
||||
|
||||
CameraView.Eye = _defaultEye;
|
||||
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
PopControl.OnPressed += _ => OnPopoutControl?.Invoke();
|
||||
CameraView.OnResized += OnResized;
|
||||
}
|
||||
|
||||
private new void OnResized()
|
||||
{
|
||||
var width = Math.Max(CameraView.PixelWidth, (int)Math.Floor(CameraView.MinWidth));
|
||||
var height = Math.Max(CameraView.PixelHeight, (int)Math.Floor(CameraView.MinHeight));
|
||||
|
||||
CameraView.ViewportSize = new Vector2i(width, height);
|
||||
}
|
||||
|
||||
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (CameraView.Eye == null)
|
||||
return;
|
||||
|
||||
Zoom = Math.Clamp(Zoom - args.Delta.Y * 0.15f * Zoom, MinimumZoom, MaximumZoom);
|
||||
CameraView.Eye.Zoom = new Vector2(Zoom, Zoom);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
public void SetState(AdminCameraEuiState state)
|
||||
{
|
||||
_nextState = state;
|
||||
}
|
||||
|
||||
// I know that this is awful, but I copied this from the solution editor anyways.
|
||||
// This is needed because EUIs update before the gamestate is applied, which means it will fail to get the uid from the net entity.
|
||||
// The suggestion from the comment in the solution editor saying to use a BUI is not ideal either:
|
||||
// - We would need to bind the UI to an entity, but with how BUIs currently work we cannot open it in the same tick as we spawn that entity on the server.
|
||||
// - We want the UI opened by the user session, not by their currently attached entity. Otherwise it would close in cases where admins move from one entity to another, for example when ghosting.
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (_nextState == null || _timing.LastRealTick < _nextState.Tick) // make sure the last gamestate has been applied
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetEntity(_nextState.Camera, out var cameraUid))
|
||||
return;
|
||||
|
||||
if (CurrentCamera == null)
|
||||
{
|
||||
_eyeLerpingSystem.AddEye(cameraUid.Value);
|
||||
CurrentCamera = cameraUid;
|
||||
}
|
||||
else if (CurrentCamera != cameraUid)
|
||||
{
|
||||
_eyeLerpingSystem.RemoveEye(CurrentCamera.Value);
|
||||
_eyeLerpingSystem.AddEye(cameraUid.Value);
|
||||
CurrentCamera = cameraUid;
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<EyeComponent>(CurrentCamera, out var eye))
|
||||
CameraView.Eye = eye.Eye ?? _defaultEye;
|
||||
}
|
||||
}
|
||||
117
Content.Client/Administration/UI/AdminCamera/AdminCameraEui.cs
Normal file
117
Content.Client/Administration/UI/AdminCamera/AdminCameraEui.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
/// <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
|
||||
{
|
||||
private readonly AdminCameraWindow _window;
|
||||
private readonly AdminCameraControl _control;
|
||||
|
||||
// If not null the camera is in "popped out" mode and is in an external window.
|
||||
private OSWindow? _OSWindow;
|
||||
|
||||
// The last location the window was located at in game.
|
||||
// Is used for getting knowing where to "pop in" external windows.
|
||||
private Vector2 _lastLocation;
|
||||
|
||||
public AdminCameraEui()
|
||||
{
|
||||
_window = new AdminCameraWindow();
|
||||
_control = new AdminCameraControl();
|
||||
|
||||
_window.Contents.AddChild(_control);
|
||||
|
||||
_control.OnFollow += () => SendMessage(new AdminCameraFollowMessage());
|
||||
_window.OnClose += () =>
|
||||
{
|
||||
if (!_control.IsPoppedOut)
|
||||
SendMessage(new CloseEuiMessage());
|
||||
};
|
||||
|
||||
_control.OnPopoutControl += () =>
|
||||
{
|
||||
if (_control.IsPoppedOut)
|
||||
PopIn();
|
||||
else
|
||||
PopOut();
|
||||
};
|
||||
}
|
||||
|
||||
// Pop the window out into an external OS window
|
||||
private void PopOut()
|
||||
{
|
||||
_lastLocation = _window.Position;
|
||||
|
||||
// TODO: When there is a way to have a minimum window size, enforce something!
|
||||
_OSWindow = new OSWindow
|
||||
{
|
||||
SetSize = _window.Size,
|
||||
Title = _window.Title ?? Loc.GetString("admin-camera-window-title-placeholder"),
|
||||
};
|
||||
|
||||
_OSWindow.Show();
|
||||
|
||||
if (_OSWindow.Root == null)
|
||||
return;
|
||||
|
||||
_control.Orphan();
|
||||
_OSWindow.Root.AddChild(_control);
|
||||
|
||||
_OSWindow.Closed += () =>
|
||||
{
|
||||
if (_control.IsPoppedOut)
|
||||
SendMessage(new CloseEuiMessage());
|
||||
};
|
||||
|
||||
_control.IsPoppedOut = true;
|
||||
_control.PopControl.Text = Loc.GetString("admin-camera-window-pop-in");
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
// Pop the window back into the in game window.
|
||||
private void PopIn()
|
||||
{
|
||||
_control.Orphan();
|
||||
_window.Contents.AddChild(_control);
|
||||
|
||||
_window.Open(_lastLocation);
|
||||
|
||||
_control.IsPoppedOut = false;
|
||||
_control.PopControl.Text = Loc.GetString("admin-camera-window-pop-out");
|
||||
|
||||
_OSWindow?.Close();
|
||||
_OSWindow = null;
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase baseState)
|
||||
{
|
||||
if (baseState is not AdminCameraEuiState state)
|
||||
return;
|
||||
|
||||
_window.SetState(state);
|
||||
_control.SetState(state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc admin-camera-window-title-placeholder}"
|
||||
SetSize="425 550"
|
||||
MinSize="200 225"
|
||||
Name="Window">
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminCameraWindow : DefaultWindow
|
||||
{
|
||||
public AdminCameraWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
ContentsContainer.Margin = new Thickness(5, 0, 5, 0);
|
||||
}
|
||||
|
||||
public void SetState(AdminCameraEuiState state)
|
||||
{
|
||||
Title = Loc.GetString("admin-camera-window-title", ("name", state.Name));
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
|
||||
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
|
||||
<Button Name="CameraButton" Text="{Loc player-panel-camera}" Disabled="True"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
public event Action<NetUserId?>? OnOpenBans;
|
||||
public event Action<NetUserId?>? OnAhelp;
|
||||
public event Action<string?>? OnKick;
|
||||
public event Action<string?>? OnCamera;
|
||||
public event Action<NetUserId?>? OnOpenBanPanel;
|
||||
public event Action<NetUserId?, bool>? OnWhitelistToggle;
|
||||
public event Action? OnFollow;
|
||||
@@ -33,26 +34,27 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
|
||||
public PlayerPanel(IClientAdminManager adminManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
|
||||
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
|
||||
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
|
||||
WhitelistToggle.OnPressed += _ =>
|
||||
{
|
||||
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
|
||||
SetWhitelisted(!_isWhitelisted);
|
||||
};
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
|
||||
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
CameraButton.OnPressed += _ => OnCamera?.Invoke(TargetUsername);
|
||||
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
|
||||
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
|
||||
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
|
||||
WhitelistToggle.OnPressed += _ =>
|
||||
{
|
||||
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
|
||||
SetWhitelisted(!_isWhitelisted);
|
||||
};
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
|
||||
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
|
||||
}
|
||||
|
||||
public void SetUsername(string player)
|
||||
@@ -122,6 +124,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
{
|
||||
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
|
||||
KickButton.Disabled = !_adminManager.CanCommand("kick");
|
||||
CameraButton.Disabled = !_adminManager.CanCommand("camera");
|
||||
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
|
||||
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
|
||||
WhitelistToggle.Disabled =
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
|
||||
private PlayerPanel PlayerPanel { get; }
|
||||
private PlayerPanel PlayerPanel { get; }
|
||||
|
||||
public PlayerPanelEui()
|
||||
{
|
||||
@@ -25,6 +25,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
|
||||
// Kick command does not support GUIDs
|
||||
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
|
||||
PlayerPanel.OnCamera += username => _console.ExecuteCommand($"camera \"{username}\"");
|
||||
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
|
||||
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
|
||||
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
|
||||
@@ -37,7 +38,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
|
||||
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
|
||||
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
|
||||
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
PlayerPanel.OnDelete += () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
PlayerPanel.OnFollow += () => SendMessage(new PlayerPanelFollowMessage());
|
||||
|
||||
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
|
||||
@@ -162,10 +162,10 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
|
||||
{
|
||||
var list = new List<AtmosMonitoringConsoleLine>();
|
||||
|
||||
foreach (var ((netId, layer, hexColor), atmosPipeData) in chunk.AtmosPipeData)
|
||||
foreach (var ((netId, layer, pipeColor), atmosPipeData) in chunk.AtmosPipeData)
|
||||
{
|
||||
// Determine the correct coloration for the pipe
|
||||
var color = Color.FromHex(hexColor) * _basePipeNetColor;
|
||||
var color = pipeColor * _basePipeNetColor;
|
||||
|
||||
if (FocusNetId != null && FocusNetId != netId)
|
||||
color *= _unfocusedPipeNetColor;
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
using Content.Client.Atmos.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Item;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
public sealed class PipeColorVisualizerSystem : VisualizerSystem<PipeColorVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PipeColorVisualsComponent, GetInhandVisualsEvent>(OnGetVisuals);
|
||||
SubscribeLocalEvent<PipeColorVisualsComponent, BeforeRenderInGridEvent>(OnDrawInGrid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is used to display the color changes of the pipe on the screen..
|
||||
/// </summary>
|
||||
private void OnGetVisuals(Entity<PipeColorVisualsComponent> item, ref GetInhandVisualsEvent args)
|
||||
{
|
||||
foreach (var (_, layerData) in args.Layers)
|
||||
{
|
||||
if (TryComp(item.Owner, out AtmosPipeColorComponent? pipeColor))
|
||||
layerData.Color = pipeColor.Color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is used to change the pipe's color in a container grid.
|
||||
/// </summary>
|
||||
private void OnDrawInGrid(Entity<PipeColorVisualsComponent> item, ref BeforeRenderInGridEvent args)
|
||||
{
|
||||
if (TryComp(item.Owner, out AtmosPipeColorComponent? pipeColor))
|
||||
args.Color = pipeColor.Color;
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, PipeColorVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite)
|
||||
@@ -15,6 +50,8 @@ public sealed class PipeColorVisualizerSystem : VisualizerSystem<PipeColorVisual
|
||||
var layer = sprite[PipeVisualLayers.Pipe];
|
||||
layer.Color = color.WithAlpha(layer.Color.A);
|
||||
}
|
||||
|
||||
_itemSystem.VisualsChanged(uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2007/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
MinSize="500 500" Resizable="True" Title="Air Alarm">
|
||||
MinSize="500 500" Resizable="True" Title="{Loc air-alarm-ui-title}">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
|
||||
<!-- Status (pressure, temperature, alarm state, device total, address, etc) -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 2">
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
|
||||
SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
|
||||
SubscribeLocalEvent<InventoryComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
@@ -87,20 +87,19 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInventoryTemplateUpdated(Entity<ClothingComponent> ent, ref InventoryTemplateUpdated args)
|
||||
private void OnInventoryTemplateUpdated(Entity<InventoryComponent> ent, ref InventoryTemplateUpdated args)
|
||||
{
|
||||
UpdateAllSlots(ent.Owner, clothing: ent.Comp);
|
||||
UpdateAllSlots(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
private void UpdateAllSlots(
|
||||
EntityUid uid,
|
||||
InventoryComponent? inventoryComponent = null,
|
||||
ClothingComponent? clothing = null)
|
||||
InventoryComponent? inventoryComponent = null)
|
||||
{
|
||||
var enumerator = _inventorySystem.GetSlotEnumerator((uid, inventoryComponent));
|
||||
while (enumerator.NextItem(out var item, out var slot))
|
||||
{
|
||||
RenderEquipment(uid, item, slot.Name, inventoryComponent, clothingComponent: clothing);
|
||||
RenderEquipment(uid, item, slot.Name, inventoryComponent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
using Content.Client.Markers;
|
||||
using Content.Client.Popups;
|
||||
using Content.Client.SubFloor;
|
||||
using Content.Shared.SubFloor;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
internal sealed class ShowMarkersCommand : LocalizedCommands
|
||||
internal sealed class ShowMarkersCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly MarkerSystem _markerSystem = default!;
|
||||
|
||||
public override string Command => "showmarkers";
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible ^= true;
|
||||
_markerSystem.MarkersVisible ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowSubFloor : LocalizedCommands
|
||||
internal sealed class ShowSubFloor : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly SubFloorHideSystem _subfloorSystem = default!;
|
||||
|
||||
public override string Command => "showsubfloor";
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<SubFloorHideSystem>().ShowAll ^= true;
|
||||
_subfloorSystem.ShowAll ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotifyCommand : LocalizedCommands
|
||||
internal sealed class NotifyCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
public override string Command => "notify";
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var message = args[0];
|
||||
|
||||
_entitySystemManager.GetEntitySystem<PopupSystem>().PopupCursor(message);
|
||||
_popupSystem.PopupCursor(args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Markers;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Client.SubFloor;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
internal sealed class MappingClientSideSetupCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionSystem = default!;
|
||||
[Dependency] private readonly MarkerSystem _markerSystem = default!;
|
||||
[Dependency] private readonly SubFloorHideSystem _subfloorSystem = default!;
|
||||
|
||||
public override string Command => "mappingclientsidesetup";
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (!_lightManager.LockConsoleAccess)
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand("showsubfloor");
|
||||
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
|
||||
}
|
||||
if (_lightManager.LockConsoleAccess)
|
||||
return;
|
||||
|
||||
_markerSystem.MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
_subfloorSystem.ShowAll = true;
|
||||
_actionSystem.LoadActionAssignments("/mapping_actions.yml", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -36,7 +37,7 @@ namespace Content.Client.Cooldown
|
||||
if (Progress >= 0f)
|
||||
{
|
||||
var hue = (5f / 18f) * lerp;
|
||||
color = Color.FromHsv((hue, 0.75f, 0.75f, 0.50f));
|
||||
color = Color.FromHsv(new Vector4(hue, 0.75f, 0.75f, 0.50f));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Damage.Systems;
|
||||
|
||||
@@ -104,6 +105,8 @@ public sealed partial class StaminaSystem : SharedStaminaSystem
|
||||
|
||||
private void PlayAnimation(Entity<StaminaComponent, SpriteComponent> entity)
|
||||
{
|
||||
DebugTools.Assert(entity.Comp1.CritThreshold > entity.Comp1.AnimationThreshold, $"Animation threshold on {ToPrettyString(entity)} was not less than the crit threshold. This will cause errors, animation has been cancelled.");
|
||||
|
||||
var step = Math.Clamp((entity.Comp1.StaminaDamage - entity.Comp1.AnimationThreshold) /
|
||||
(entity.Comp1.CritThreshold - entity.Comp1.AnimationThreshold),
|
||||
0f,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Disposal;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Holopad;
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Content.Client.Input
|
||||
human.AddFunction(EngineKeyFunctions.MoveLeft);
|
||||
human.AddFunction(EngineKeyFunctions.MoveRight);
|
||||
human.AddFunction(EngineKeyFunctions.Walk);
|
||||
human.AddFunction(ContentKeyFunctions.ToggleKnockdown);
|
||||
human.AddFunction(ContentKeyFunctions.SwapHands);
|
||||
human.AddFunction(ContentKeyFunctions.SwapHandsReverse);
|
||||
human.AddFunction(ContentKeyFunctions.Drop);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Clothing;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Verbs.UI;
|
||||
@@ -11,6 +12,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Inventory
|
||||
{
|
||||
@@ -19,7 +21,7 @@ namespace Content.Client.Inventory
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
|
||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||
|
||||
@@ -91,6 +93,14 @@ namespace Content.Client.Inventory
|
||||
|
||||
private void OnShutdown(EntityUid uid, InventoryComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp(uid, out InventorySlotsComponent? inventorySlots))
|
||||
{
|
||||
foreach (var slot in component.Slots)
|
||||
{
|
||||
TryRemoveSlotData((uid, inventorySlots), (SlotData)slot);
|
||||
}
|
||||
}
|
||||
|
||||
if (uid == _playerManager.LocalEntity)
|
||||
OnUnlinkInventory?.Invoke();
|
||||
}
|
||||
@@ -102,23 +112,6 @@ namespace Content.Client.Inventory
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, InventorySlotsComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (TryGetSlots(uid, out var definitions))
|
||||
{
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
if (!TryGetSlotContainer(uid, definition.Name, out var container, out _))
|
||||
continue;
|
||||
|
||||
if (!component.SlotData.TryGetValue(definition.Name, out var data))
|
||||
{
|
||||
data = new SlotData(definition);
|
||||
component.SlotData[definition.Name] = data;
|
||||
}
|
||||
|
||||
data.Container = container;
|
||||
}
|
||||
}
|
||||
|
||||
OnLinkInventorySlots?.Invoke(uid, component);
|
||||
}
|
||||
|
||||
@@ -128,20 +121,6 @@ namespace Content.Client.Inventory
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
protected override void OnInit(EntityUid uid, InventoryComponent component, ComponentInit args)
|
||||
{
|
||||
base.OnInit(uid, component, args);
|
||||
_clothingVisualsSystem.InitClothing(uid, component);
|
||||
|
||||
if (!TryComp(uid, out InventorySlotsComponent? inventorySlots))
|
||||
return;
|
||||
|
||||
foreach (var slot in component.Slots)
|
||||
{
|
||||
TryAddSlotDef(uid, inventorySlots, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadInventory(InventorySlotsComponent? component = null)
|
||||
{
|
||||
var player = _playerManager.LocalEntity;
|
||||
@@ -165,7 +144,10 @@ namespace Content.Client.Inventory
|
||||
public void UpdateSlot(EntityUid owner, InventorySlotsComponent component, string slotName,
|
||||
bool? blocked = null, bool? highlight = null)
|
||||
{
|
||||
var oldData = component.SlotData[slotName];
|
||||
// The slot might have been removed when changing templates, which can cause items to be dropped.
|
||||
if (!component.SlotData.TryGetValue(slotName, out var oldData))
|
||||
return;
|
||||
|
||||
var newHighlight = oldData.Highlighted;
|
||||
var newBlocked = oldData.Blocked;
|
||||
|
||||
@@ -181,14 +163,28 @@ namespace Content.Client.Inventory
|
||||
EntitySlotUpdate?.Invoke(newData);
|
||||
}
|
||||
|
||||
public bool TryAddSlotDef(EntityUid owner, InventorySlotsComponent component, SlotDefinition newSlotDef)
|
||||
public bool TryAddSlotData(Entity<InventorySlotsComponent> ent, SlotData newSlotData)
|
||||
{
|
||||
SlotData newSlotData = newSlotDef; //convert to slotData
|
||||
if (!component.SlotData.TryAdd(newSlotDef.Name, newSlotData))
|
||||
if (!ent.Comp.SlotData.TryAdd(newSlotData.SlotName, newSlotData))
|
||||
return false;
|
||||
|
||||
if (owner == _playerManager.LocalEntity)
|
||||
if (TryGetSlotContainer(ent.Owner, newSlotData.SlotName, out var newContainer, out _))
|
||||
ent.Comp.SlotData[newSlotData.SlotName].Container = newContainer;
|
||||
|
||||
if (ent.Owner == _playerManager.LocalEntity)
|
||||
OnSlotAdded?.Invoke(newSlotData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemoveSlotData(Entity<InventorySlotsComponent> ent, SlotData removedSlotData)
|
||||
{
|
||||
if (!ent.Comp.SlotData.Remove(removedSlotData.SlotName))
|
||||
return false;
|
||||
|
||||
if (ent.Owner == _playerManager.LocalEntity)
|
||||
OnSlotRemoved?.Invoke(removedSlotData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -239,33 +235,52 @@ namespace Content.Client.Inventory
|
||||
{
|
||||
base.UpdateInventoryTemplate(ent);
|
||||
|
||||
if (TryComp(ent, out InventorySlotsComponent? inventorySlots))
|
||||
if (!TryComp<InventorySlotsComponent>(ent, out var inventorySlots))
|
||||
return;
|
||||
|
||||
List<SlotData> slotDataToRemove = new(); // don't modify dict while iterating
|
||||
|
||||
foreach (var slotData in inventorySlots.SlotData.Values)
|
||||
{
|
||||
foreach (var slot in ent.Comp.Slots)
|
||||
{
|
||||
if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
|
||||
slotData.SlotDef = slot;
|
||||
}
|
||||
if (!ent.Comp.Slots.Any(s => s.Name == slotData.SlotName))
|
||||
slotDataToRemove.Add(slotData);
|
||||
}
|
||||
|
||||
// remove slots that are no longer in the new template
|
||||
foreach (var slotData in slotDataToRemove)
|
||||
{
|
||||
TryRemoveSlotData((ent.Owner, inventorySlots), slotData);
|
||||
}
|
||||
|
||||
// update existing slots or add them if they don't exist yet
|
||||
foreach (var slot in ent.Comp.Slots)
|
||||
{
|
||||
if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
|
||||
slotData.SlotDef = slot;
|
||||
else
|
||||
TryAddSlotData((ent.Owner, inventorySlots), (SlotData)slot);
|
||||
}
|
||||
|
||||
_clothingVisualsSystem.InitClothing(ent, ent.Comp);
|
||||
if (ent.Owner == _playerManager.LocalEntity)
|
||||
ReloadInventory(inventorySlots);
|
||||
}
|
||||
|
||||
public sealed class SlotData
|
||||
{
|
||||
public SlotDefinition SlotDef;
|
||||
public EntityUid? HeldEntity => Container?.ContainedEntity;
|
||||
public bool Blocked;
|
||||
public bool Highlighted;
|
||||
|
||||
[ViewVariables]
|
||||
public ContainerSlot? Container;
|
||||
public bool HasSlotGroup => SlotDef.SlotGroup != "Default";
|
||||
public Vector2i ButtonOffset => SlotDef.UIWindowPosition;
|
||||
public string SlotName => SlotDef.Name;
|
||||
public bool ShowInWindow => SlotDef.ShowInWindow;
|
||||
public string SlotGroup => SlotDef.SlotGroup;
|
||||
public string SlotDisplayName => SlotDef.DisplayName;
|
||||
public string TextureName => "Slots/" + SlotDef.TextureName;
|
||||
public string FullTextureName => SlotDef.FullTextureName;
|
||||
[ViewVariables] public SlotDefinition SlotDef;
|
||||
[ViewVariables] public EntityUid? HeldEntity => Container?.ContainedEntity;
|
||||
[ViewVariables] public bool Blocked;
|
||||
[ViewVariables] public bool Highlighted;
|
||||
[ViewVariables] public ContainerSlot? Container;
|
||||
[ViewVariables] public bool HasSlotGroup => SlotDef.SlotGroup != "Default";
|
||||
[ViewVariables] public Vector2i ButtonOffset => SlotDef.UIWindowPosition;
|
||||
[ViewVariables] public string SlotName => SlotDef.Name;
|
||||
[ViewVariables] public bool ShowInWindow => SlotDef.ShowInWindow;
|
||||
[ViewVariables] public string SlotGroup => SlotDef.SlotGroup;
|
||||
[ViewVariables] public string SlotDisplayName => SlotDef.DisplayName;
|
||||
[ViewVariables] public string TextureName => "Slots/" + SlotDef.TextureName;
|
||||
[ViewVariables] public string FullTextureName => SlotDef.FullTextureName;
|
||||
|
||||
public SlotData(SlotDefinition slotDef, ContainerSlot? container = null, bool highlighted = false,
|
||||
bool blocked = false)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands;
|
||||
|
||||
@@ -57,7 +57,8 @@ public sealed class MouseRotatorSystem : SharedMouseRotatorSystem
|
||||
rotation += 2 * Math.PI;
|
||||
RaisePredictiveEvent(new RequestMouseRotatorRotationEvent
|
||||
{
|
||||
Rotation = rotation
|
||||
Rotation = rotation,
|
||||
User = GetNetEntity(player)
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -77,7 +78,8 @@ public sealed class MouseRotatorSystem : SharedMouseRotatorSystem
|
||||
|
||||
RaisePredictiveEvent(new RequestMouseRotatorRotationEvent
|
||||
{
|
||||
Rotation = angle
|
||||
Rotation = angle,
|
||||
User = GetNetEntity(player)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<CheckBox Name="IntegerScalingCheckBox"
|
||||
Text="{Loc 'ui-options-vp-integer-scaling'}"
|
||||
ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
|
||||
<ui:OptionDropDown Name="DropDownFilterMode" Title="{Loc 'ui-options-filter-label'}" />
|
||||
<CheckBox Name="ViewportVerticalFitCheckBox"
|
||||
Text="{Loc 'ui-options-vp-vertical-fit'}"
|
||||
ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
|
||||
|
||||
@@ -39,6 +39,14 @@ public sealed partial class GraphicsTab : Control
|
||||
new OptionDropDownCVar<float>.ValueOption(2.00f, Loc.GetString("ui-options-scale-200")),
|
||||
]);
|
||||
|
||||
Control.AddOptionDropDown(
|
||||
CCVars.ViewportScalingFilterMode,
|
||||
DropDownFilterMode,
|
||||
[
|
||||
new OptionDropDownCVar<string>.ValueOption("nearest", Loc.GetString("ui-options-filter-nearest")),
|
||||
new OptionDropDownCVar<string>.ValueOption("bilinear", Loc.GetString("ui-options-filter-bilinear")),
|
||||
]);
|
||||
|
||||
var vpStretch = Control.AddOptionCheckBox(CCVars.ViewportStretch, ViewportStretchCheckBox);
|
||||
var vpVertFit = Control.AddOptionCheckBox(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox);
|
||||
Control.AddOptionSlider(
|
||||
@@ -50,6 +58,7 @@ public sealed partial class GraphicsTab : Control
|
||||
|
||||
vpStretch.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
|
||||
vpVertFit.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
|
||||
IntegerScalingCheckBox.OnToggled += _ => UpdateViewportSettingsVisibility();
|
||||
|
||||
Control.AddOptionSlider(
|
||||
CCVars.ViewportWidth,
|
||||
@@ -77,6 +86,7 @@ public sealed partial class GraphicsTab : Control
|
||||
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportWidthSlider.Visible = !ViewportStretchCheckBox.Pressed || !ViewportVerticalFitCheckBox.Pressed;
|
||||
DropDownFilterMode.Visible = !IntegerScalingCheckBox.Pressed && ViewportStretchCheckBox.Pressed;
|
||||
}
|
||||
|
||||
private void UpdateViewportWidthRange()
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(EngineKeyFunctions.Walk);
|
||||
AddCheckBox("ui-options-hotkey-toggle-walk", _cfg.GetCVar(CCVars.ToggleWalk), HandleToggleWalk);
|
||||
InitToggleWalk();
|
||||
AddButton(ContentKeyFunctions.ToggleKnockdown);
|
||||
|
||||
AddHeader("ui-options-header-camera");
|
||||
AddButton(EngineKeyFunctions.CameraRotateLeft);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.APC;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
|
||||
|
||||
@@ -220,9 +220,9 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, curGridToView);
|
||||
|
||||
var distance = gridCentre.Length();
|
||||
var gridDistance = (gridBody.LocalCenter - xform.LocalPosition).Length();
|
||||
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
|
||||
("distance", $"{distance:0.0}"));
|
||||
("distance", $"{gridDistance:0.0}"));
|
||||
|
||||
var mapCoords = _transform.GetWorldPosition(gUid);
|
||||
var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})";
|
||||
|
||||
43
Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs
Normal file
43
Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.SmartFridge;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.SmartFridge;
|
||||
|
||||
public sealed class SmartFridgeBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private SmartFridgeMenu? _menu;
|
||||
|
||||
public SmartFridgeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<SmartFridgeMenu>();
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
if (_menu is not {} menu || !EntMan.TryGetComponent(Owner, out SmartFridgeComponent? fridge))
|
||||
return;
|
||||
|
||||
menu.SetFlavorText(Loc.GetString(fridge.FlavorText));
|
||||
menu.Populate((Owner, fridge));
|
||||
}
|
||||
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
if (data is not SmartFridgeListData entry)
|
||||
return;
|
||||
SendPredictedMessage(new SmartFridgeDispenseItemMessage(entry.Entry));
|
||||
}
|
||||
}
|
||||
16
Content.Client/SmartFridge/SmartFridgeItem.xaml
Normal file
16
Content.Client/SmartFridge/SmartFridgeItem.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<SpriteView
|
||||
Name="EntityView"
|
||||
Margin="4 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MinSize="32 32"
|
||||
/>
|
||||
<Label Name="NameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
18
Content.Client/SmartFridge/SmartFridgeItem.xaml.cs
Normal file
18
Content.Client/SmartFridge/SmartFridgeItem.xaml.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.SmartFridge;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SmartFridgeItem : BoxContainer
|
||||
{
|
||||
public SmartFridgeItem(EntityUid uid, string text)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
EntityView.SetEntity(uid);
|
||||
NameLabel.Text = text;
|
||||
}
|
||||
}
|
||||
24
Content.Client/SmartFridge/SmartFridgeMenu.xaml
Normal file
24
Content.Client/SmartFridge/SmartFridgeMenu.xaml
Normal file
@@ -0,0 +1,24 @@
|
||||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinHeight="450"
|
||||
MinWidth="350"
|
||||
Title="{Loc 'smart-fridge-component-title'}">
|
||||
<BoxContainer Name="MainContainer" Orientation="Vertical">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'smart-fridge-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/>
|
||||
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Name="LeftFlavorLabel" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'vending-machine-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
81
Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs
Normal file
81
Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.SmartFridge;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.SmartFridge;
|
||||
|
||||
public record SmartFridgeListData(EntityUid Representative, SmartFridgeEntry Entry, int Amount) : ListData;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SmartFridgeMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
|
||||
|
||||
private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
|
||||
|
||||
public SmartFridgeMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
VendingContents.SearchBar = SearchBar;
|
||||
VendingContents.DataFilterCondition += DataFilterCondition;
|
||||
VendingContents.GenerateItem += GenerateButton;
|
||||
VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data);
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData data)
|
||||
{
|
||||
if (data is not SmartFridgeListData entry)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return entry.Entry.Name.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not SmartFridgeListData entry)
|
||||
return;
|
||||
|
||||
var label = Loc.GetString("smart-fridge-list-item", ("item", entry.Entry.Name), ("amount", entry.Amount));
|
||||
button.AddChild(new SmartFridgeItem(entry.Representative, label));
|
||||
|
||||
button.ToolTip = label;
|
||||
button.StyleBoxOverride = _styleBox;
|
||||
}
|
||||
|
||||
public void Populate(Entity<SmartFridgeComponent> ent)
|
||||
{
|
||||
var listData = new List<ListData>();
|
||||
|
||||
foreach (var item in ent.Comp.Entries)
|
||||
{
|
||||
if (!ent.Comp.ContainedEntries.TryGetValue(item, out var items) || items.Count == 0)
|
||||
{
|
||||
listData.Add(new SmartFridgeListData(EntityUid.Invalid, item, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
var representative = _entityManager.GetEntity(items.First());
|
||||
listData.Add(new SmartFridgeListData(representative, item, items.Count));
|
||||
}
|
||||
}
|
||||
|
||||
VendingContents.PopulateList(listData);
|
||||
}
|
||||
|
||||
public void SetFlavorText(string flavor)
|
||||
{
|
||||
LeftFlavorLabel.Text = flavor;
|
||||
}
|
||||
}
|
||||
24
Content.Client/SmartFridge/SmartFridgeUISystem.cs
Normal file
24
Content.Client/SmartFridge/SmartFridgeUISystem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.SmartFridge;
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
namespace Content.Client.SmartFridge;
|
||||
|
||||
public sealed class SmartFridgeUISystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SmartFridgeComponent, AfterAutoHandleStateEvent>(OnSmartFridgeAfterState);
|
||||
}
|
||||
|
||||
private void OnSmartFridgeAfterState(Entity<SmartFridgeComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!_uiSystem.TryGetOpenUi<SmartFridgeBoundUserInterface>(ent.Owner, SmartFridgeUiKey.Key, out var bui))
|
||||
return;
|
||||
|
||||
bui.Refresh();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Mobs;
|
||||
using System.Numerics;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Stunnable;
|
||||
|
||||
public sealed class StunSystem : SharedStunSystem
|
||||
{
|
||||
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
|
||||
|
||||
@@ -23,6 +25,22 @@ public sealed class StunSystem : SharedStunSystem
|
||||
|
||||
SubscribeLocalEvent<StunVisualsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<StunVisualsComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
|
||||
CommandBinds.Builder
|
||||
.BindAfter(EngineKeyFunctions.UseSecondary, new PointerInputCmdHandler(OnUseSecondary, true, true), typeof(SharedInteractionSystem))
|
||||
.Register<StunSystem>();
|
||||
}
|
||||
|
||||
private bool OnUseSecondary(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.Session?.AttachedEntity is not {Valid: true} uid)
|
||||
return false;
|
||||
|
||||
if (args.EntityUid != uid || !HasComp<KnockedDownComponent>(uid) || !_combat.IsInCombatMode(uid))
|
||||
return false;
|
||||
|
||||
RaisePredictiveEvent(new ForceStandUpEvent());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
};
|
||||
|
||||
AddChild(Viewport);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ViewportScalingFilterMode, _ => UpdateCfg(), true);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
@@ -52,6 +54,7 @@ namespace Content.Client.UserInterface.Controls
|
||||
var renderScaleUp = _cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
var fixedFactor = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
var verticalFit = _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
var filterMode = _cfg.GetCVar(CCVars.ViewportScalingFilterMode);
|
||||
|
||||
if (stretch)
|
||||
{
|
||||
@@ -60,7 +63,11 @@ namespace Content.Client.UserInterface.Controls
|
||||
{
|
||||
// Did not find a snap, enable stretching.
|
||||
Viewport.FixedStretchSize = null;
|
||||
Viewport.StretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
Viewport.StretchMode = filterMode switch
|
||||
{
|
||||
"nearest" => ScalingViewportStretchMode.Nearest,
|
||||
"bilinear" => ScalingViewportStretchMode.Bilinear
|
||||
};
|
||||
Viewport.IgnoreDimension = verticalFit ? ScalingViewportIgnoreDimension.Horizontal : ScalingViewportIgnoreDimension.None;
|
||||
|
||||
if (renderScaleUp)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed class ProgressColorSystem : EntitySystem
|
||||
|
||||
// lerp
|
||||
var hue = 5f / 18f * progress;
|
||||
return Color.FromHsv((hue, 1f, 0.75f, 1f));
|
||||
return Color.FromHsv(new Vector4(hue, 1f, 0.75f, 1f));
|
||||
}
|
||||
|
||||
return InterpolateColorGaussian(Plasma, progress);
|
||||
|
||||
@@ -185,7 +185,12 @@ public sealed class ItemGridPiece : Control, IEntityControl
|
||||
|
||||
handle.SetTransform(pos, iconRotation);
|
||||
var box = new UIBox2(root, root + sprite.Size * scale);
|
||||
handle.DrawTextureRect(sprite, box);
|
||||
|
||||
var ev = new BeforeRenderInGridEvent(new Color(255, 255, 255));
|
||||
_entityManager.EventBus.RaiseLocalEvent(Entity, ev);
|
||||
|
||||
handle.DrawTextureRect(sprite, box, ev.Color);
|
||||
|
||||
handle.SetTransform(GlobalPixelPosition, Angle.Zero);
|
||||
}
|
||||
else
|
||||
@@ -298,6 +303,19 @@ public sealed class ItemGridPiece : Control, IEntityControl
|
||||
public EntityUid? UiEntity => Entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event gets raised before a sprite gets drawn in a grid and lets to change the sprite color for several gameobjects that have special sprites to render in containers.
|
||||
/// </summary>
|
||||
public sealed class BeforeRenderInGridEvent : EntityEventArgs
|
||||
{
|
||||
public Color Color { get; set; }
|
||||
|
||||
public BeforeRenderInGridEvent(Color color)
|
||||
{
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ItemGridPieceMarks
|
||||
{
|
||||
First,
|
||||
|
||||
@@ -621,7 +621,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
{
|
||||
marked.Add(cell);
|
||||
cell.ModulateSelfOverride = spotFree
|
||||
? Color.FromHsv((0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
|
||||
? Color.FromHsv(new Vector4(0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
|
||||
: Color.FromHex("#2222CC");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace Content.IntegrationTests.Tests
|
||||
entityMan.SpawnEntity(protoId, map.GridCoords);
|
||||
}
|
||||
});
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
|
||||
@@ -274,7 +274,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(entMan.HasComponent<AudioComponent>);
|
||||
|
||||
await Assert.MultipleAsync(async () =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -17,9 +17,7 @@ namespace Content.IntegrationTests.Tests
|
||||
components:
|
||||
- type: Inventory
|
||||
- type: ContainerContainer
|
||||
- type: StatusEffects
|
||||
allowed:
|
||||
- Stun
|
||||
- type: MobState
|
||||
|
||||
- type: entity
|
||||
name: InventoryJumpsuitJanitorDummy
|
||||
@@ -70,7 +68,7 @@ namespace Content.IntegrationTests.Tests
|
||||
});
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
systemMan.GetEntitySystem<StunSystem>().TryStun(human, TimeSpan.FromSeconds(1f), true);
|
||||
systemMan.GetEntitySystem<StunSystem>().TryUpdateStunDuration(human, TimeSpan.FromSeconds(1f));
|
||||
|
||||
#pragma warning disable NUnit2045
|
||||
// Since the mob is stunned, they can't equip this.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
@@ -339,7 +339,7 @@ public sealed partial class MindTests
|
||||
var entMan = server.ResolveDependency<IServerEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
|
||||
var mindSystem = entMan.EntitySysManager.GetEntitySystem<SharedMindSystem>();
|
||||
var mindSystem = entMan.EntitySysManager.GetEntitySystem<MindSystem>();
|
||||
|
||||
EntityUid entity = default!;
|
||||
EntityUid mindId = default!;
|
||||
@@ -379,7 +379,7 @@ public sealed partial class MindTests
|
||||
|
||||
mob = entMan.SpawnEntity(null, new MapCoordinates());
|
||||
|
||||
MakeSentientCommand.MakeSentient(mob, entMan);
|
||||
mindSystem.MakeSentient(mob);
|
||||
mobMindId = mindSystem.CreateMind(player.UserId, "Mindy McThinker the Second");
|
||||
mobMind = entMan.GetComponent<MindComponent>(mobMindId);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Threading;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -22,7 +21,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
private void OnRoundEnd(RoundEndSystemChangedEvent ev)
|
||||
{
|
||||
Interlocked.Increment(ref RoundCount);
|
||||
RoundCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,13 +126,17 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
async Task WaitForEvent()
|
||||
{
|
||||
var timeout = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
var currentCount = Thread.VolatileRead(ref sys.RoundCount);
|
||||
while (currentCount == Thread.VolatileRead(ref sys.RoundCount) && !timeout.IsCompleted)
|
||||
const int maxTicks = 60;
|
||||
var currentCount = sys.RoundCount;
|
||||
for (var i = 0; i < maxTicks; i++)
|
||||
{
|
||||
await pair.RunTicksSync(5);
|
||||
if (currentCount != sys.RoundCount)
|
||||
return;
|
||||
|
||||
await pair.RunTicksSync(1);
|
||||
}
|
||||
if (timeout.IsCompleted) throw new TimeoutException("Event took too long to trigger");
|
||||
|
||||
throw new TimeoutException("Event took too long to trigger");
|
||||
}
|
||||
|
||||
// Need to clean self up
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.SmartFridge;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.SmartFridge;
|
||||
|
||||
public sealed class SmartFridgeInteractionTest : InteractionTest
|
||||
{
|
||||
private const string SmartFridgeProtoId = "SmartFridge";
|
||||
private const string SampleItemProtoId = "FoodAmbrosiaVulgaris";
|
||||
private const string SampleDumpableAndInsertableId = "PillCanisterSomething";
|
||||
private const int SampleDumpableCount = 5;
|
||||
private const string SampleDumpableId = "ChemBagSomething";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string TestPrototypes = $@"
|
||||
- type: entity
|
||||
parent: PillCanister
|
||||
id: {SampleDumpableAndInsertableId}
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: PillCopper
|
||||
amount: 5
|
||||
|
||||
- type: entity
|
||||
parent: ChemBag
|
||||
id: {SampleDumpableId}
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: PillCopper
|
||||
amount: 5
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task InsertAndDispenseItemTest()
|
||||
{
|
||||
await PlaceInHands(SampleItemProtoId);
|
||||
|
||||
await SpawnTarget(SmartFridgeProtoId);
|
||||
var fridge = SEntMan.GetEntity(Target.Value);
|
||||
var component = SEntMan.GetComponent<SmartFridgeComponent>(fridge);
|
||||
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
// smartfridge spawns with nothing
|
||||
Assert.That(component.Entries, Is.Empty);
|
||||
await InteractUsing(SampleItemProtoId);
|
||||
|
||||
// smartfridge now has items
|
||||
Assert.That(component.Entries, Is.Not.Empty);
|
||||
Assert.That(component.ContainedEntries[component.Entries[0]], Is.Not.Empty);
|
||||
|
||||
// open the UI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(SmartFridgeUiKey.Key));
|
||||
|
||||
// dispense an item
|
||||
await SendBui(SmartFridgeUiKey.Key, new SmartFridgeDispenseItemMessage(component.Entries[0]));
|
||||
|
||||
// assert that the listing is still there
|
||||
Assert.That(component.Entries, Is.Not.Empty);
|
||||
// but empty
|
||||
Assert.That(component.ContainedEntries[component.Entries[0]], Is.Empty);
|
||||
|
||||
// and that the thing we dispensed is actually around
|
||||
await AssertEntityLookup(
|
||||
("APCBasic", 1),
|
||||
(SampleItemProtoId, 1)
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InsertDumpableInsertableItemTest()
|
||||
{
|
||||
await PlaceInHands(SampleItemProtoId);
|
||||
|
||||
await SpawnTarget(SmartFridgeProtoId);
|
||||
var fridge = SEntMan.GetEntity(Target.Value);
|
||||
var component = SEntMan.GetComponent<SmartFridgeComponent>(fridge);
|
||||
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
await InteractUsing(SampleDumpableAndInsertableId);
|
||||
|
||||
// smartfridge now has one item only
|
||||
Assert.That(component.Entries, Is.Not.Empty);
|
||||
Assert.That(component.ContainedEntries[component.Entries[0]].Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InsertDumpableItemTest()
|
||||
{
|
||||
await PlaceInHands(SampleItemProtoId);
|
||||
|
||||
await SpawnTarget(SmartFridgeProtoId);
|
||||
var fridge = SEntMan.GetEntity(Target.Value);
|
||||
var component = SEntMan.GetComponent<SmartFridgeComponent>(fridge);
|
||||
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
await InteractUsing(SampleDumpableId);
|
||||
|
||||
// smartfridge now has N items
|
||||
Assert.That(component.Entries, Is.Not.Empty);
|
||||
Assert.That(component.ContainedEntries[component.Entries[0]].Count, Is.EqualTo(SampleDumpableCount));
|
||||
}
|
||||
}
|
||||
@@ -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,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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,10 +618,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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user