Merge remote-tracking branch 'space-wizards/master'

# Conflicts:
#	Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
#	Content.Server/Mindshield/MindShieldSystem.cs
#	Content.Shared/Gravity/SharedGravitySystem.cs
#	Content.Shared/Implants/SharedSubdermalImplantSystem.cs
#	Resources/Locale/en-US/_strings/traits/traits.ftl
#	Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
#	Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml
#	Resources/Prototypes/Entities/Objects/Misc/implanters.yml
#	Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
#	Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml
#	Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
This commit is contained in:
Vigers Ray 2025-08-23 23:10:05 +03:00
commit 252e81c728
344 changed files with 11262 additions and 1829 deletions

View file

@ -1,33 +0,0 @@
using Content.Shared.Administration.Logs;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Administration.UI.CustomControls;
public sealed class AdminLogLabel : RichTextLabel
{
public AdminLogLabel(ref SharedAdminLog log, HSeparator separator)
{
Log = log;
Separator = separator;
SetMessage($"{log.Date:HH:mm:ss}: {log.Message}");
OnVisibilityChanged += VisibilityChanged;
}
public SharedAdminLog Log { get; }
public HSeparator Separator { get; }
private void VisibilityChanged(Control control)
{
Separator.Visible = Visible;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
OnVisibilityChanged -= VisibilityChanged;
}
}

View file

@ -1,16 +1,15 @@
using System.Linq;
using System.Text.RegularExpressions;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
@ -96,13 +95,26 @@ public sealed partial class PlayerListControl : BoxContainer
private void FilterList()
{
_sortedPlayerList.Clear();
Regex filterRegex;
// There is no neat way to handle invalid regex being submitted other than
// catching and ignoring the exception which gets thrown when it's invalid.
try
{
filterRegex = new Regex(FilterLineEdit.Text, RegexOptions.IgnoreCase);
}
catch (ArgumentException)
{
return;
}
foreach (var info in _playerList)
{
var displayName = $"{info.CharacterName} ({info.Username})";
if (info.IdentityName != info.CharacterName)
displayName += $" [{info.IdentityName}]";
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
&& !filterRegex.IsMatch(displayName))
continue;
_sortedPlayerList.Add(info);
}

View file

@ -1,10 +1,8 @@
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;

View file

@ -1,5 +1,6 @@
<Control xmlns="https://spacestation14.io"
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ui="clr-namespace:Content.Client.Options.UI">
<PanelContainer StyleClasses="BackgroundDark">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical">
@ -52,6 +53,13 @@
<Button Name="ExportLogs" Access="Public" Text="{Loc admin-logs-export}"/>
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
</BoxContainer>
<BoxContainer HorizontalExpand="True">
<Button Name="RenderRichTextButton" Access="Public" Text="{Loc admin-logs-render-rich-text}"
StyleClasses="OpenRight" ToggleMode="True"/>
<Button Name="RemoveMarkupButton" Access="Public" Text="{Loc admin-logs-remove-markup}"
StyleClasses="OpenLeft" ToggleMode="True"/>
<Control HorizontalExpand="True"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<LineEdit Name="LogSearch" Access="Public" StyleClasses="actionSearchBox"
HorizontalExpand="true" PlaceHolder="{Loc admin-logs-search-logs-placeholder}"/>

View file

@ -1,7 +1,9 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Content.Client._Sunrise.Administration.UI.CustomControls;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Administration.UI.Logs.Entries;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
@ -39,6 +41,9 @@ public sealed partial class AdminLogsControl : Control
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
RenderRichTextButton.OnPressed += RenderRichTextChanged;
RemoveMarkupButton.OnPressed += RemoveMarkupChanged;
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
RoundSpinBox.InitDefaultButtons();
@ -51,13 +56,16 @@ public sealed partial class AdminLogsControl : Control
private int CurrentRound { get; set; }
private Regex LogSearchRegex { get; set; } = new("");
public int SelectedRoundId => RoundSpinBox.Value;
public string Search => LogSearch.Text;
private int ShownLogs { get; set; }
private int TotalLogs { get; set; }
private int RoundLogs { get; set; }
public bool IncludeNonPlayerLogs { get; set; }
private bool RenderRichText { get; set; }
private bool RemoveMarkup { get; set; }
public HashSet<LogType> SelectedTypes { get; } = new();
public HashSet<Guid> SelectedPlayers { get; } = new();
@ -104,6 +112,19 @@ public sealed partial class AdminLogsControl : Control
private void LogSearchChanged(LineEditEventArgs args)
{
// This exception is thrown if the regex is invalid, which happens often, so we ignore it.
try
{
LogSearchRegex = new Regex(
"(" + LogSearch.Text + ")",
RegexOptions.IgnoreCase,
TimeSpan.FromSeconds(1));
}
catch (ArgumentException)
{
return;
}
UpdateLogs();
}
@ -185,6 +206,26 @@ public sealed partial class AdminLogsControl : Control
UpdateLogs();
}
private void RenderRichTextChanged(ButtonEventArgs args)
{
RenderRichText = args.Button.Pressed;
RemoveMarkup = RemoveMarkup && !RenderRichText;
RemoveMarkupButton.Pressed = RemoveMarkup;
UpdateLogs();
}
private void RemoveMarkupChanged(ButtonEventArgs args)
{
RemoveMarkup = args.Button.Pressed;
RenderRichText = !RemoveMarkup && RenderRichText;
RenderRichTextButton.Pressed = RenderRichText;
UpdateLogs();
}
public void SetTypesSelection(HashSet<LogType> selectedTypes, bool invert = false)
{
SelectedTypes.Clear();
@ -243,16 +284,15 @@ public sealed partial class AdminLogsControl : Control
foreach (var child in LogsContainer.Children)
{
if (child is not SunriseAdminLogLabel log)
{
if (child is not AdminLogEntry log)
continue;
}
child.Visible = ShouldShowLog(log);
if (child.Visible)
{
ShownLogs++;
}
if (!child.Visible)
continue;
log.RenderResults(LogSearchRegex, RenderRichText, RemoveMarkup);
ShownLogs++;
}
UpdateCount();
@ -270,30 +310,30 @@ public sealed partial class AdminLogsControl : Control
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
}
private bool LogMatchesPlayerFilter(SunriseAdminLogLabel label)
private bool LogMatchesPlayerFilter(AdminLogEntry entry)
{
if (label.Log.Players.Length == 0)
if (entry.Log.Players.Length == 0)
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
return SelectedPlayers.Overlaps(label.Log.Players);
return SelectedPlayers.Overlaps(entry.Log.Players);
}
private bool ShouldShowLog(SunriseAdminLogLabel label)
private bool ShouldShowLog(AdminLogEntry entry)
{
// Check log type
if (!SelectedTypes.Contains(label.Log.Type))
if (!SelectedTypes.Contains(entry.Log.Type))
return false;
// Check players
if (!LogMatchesPlayerFilter(label))
if (!LogMatchesPlayerFilter(entry))
return false;
// Check impact
if (!SelectedImpacts.Contains(label.Log.Impact))
if (!SelectedImpacts.Contains(entry.Log.Impact))
return false;
// Check search
if (!label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase))
if (!LogSearchRegex.IsMatch(entry.Log.Message))
return false;
return true;
@ -469,21 +509,11 @@ public sealed partial class AdminLogsControl : Control
for (var i = 0; i < span.Length; i++)
{
ref var log = ref span[i];
var separator = new HSeparator();
var label = new SunriseAdminLogLabel(ref log, separator);
label.Visible = ShouldShowLog(label);
var entry = new AdminLogEntry(ref log);
TotalLogs++;
if (label.Visible)
{
ShownLogs++;
}
LogsContainer.AddChild(label);
LogsContainer.AddChild(separator);
LogsContainer.AddChild(entry);
}
UpdateCount();
UpdateLogs();
}
public void SetLogs(List<SharedAdminLog> logs)
@ -527,6 +557,7 @@ public sealed partial class AdminLogsControl : Control
SelectAllTypesButton.OnPressed -= SelectAllTypes;
SelectNoTypesButton.OnPressed -= SelectNoTypes;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;

View file

@ -1,6 +1,6 @@
using System.IO;
using System.Linq;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Administration.UI.Logs.Entries;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
@ -22,7 +22,7 @@ public sealed class AdminLogsEui : BaseEui
private const char CsvSeparator = ',';
private const string CsvQuote = "\"";
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message";
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message,CurTime";
private ISawmill _sawmill;
@ -109,10 +109,10 @@ public sealed class AdminLogsEui : BaseEui
await writer.WriteLineAsync(CsvHeader);
foreach (var child in LogsControl.LogsContainer.Children)
{
if (child is not AdminLogLabel logLabel || !child.Visible)
if (child is not AdminLogEntry entry || !child.Visible)
continue;
var log = logLabel.Log;
var log = entry.Log;
// Date
// I swear to god if someone adds ,s or "s to the other fields...
@ -138,6 +138,9 @@ public sealed class AdminLogsEui : BaseEui
await writer.WriteAsync(CsvQuote);
await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote));
await writer.WriteAsync(CsvQuote);
await writer.WriteAsync(CsvSeparator);
// CurTime
await writer.WriteAsync(log.CurTime.ToString());
await writer.WriteLineAsync();
}

View file

@ -0,0 +1,14 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Orientation="Vertical">
<BoxContainer Margin="2">
<Collapsible>
<CollapsibleHeading Name="DetailsHeading" Access="Public">
<RichTextLabel Margin="20 0 0 0" Name="Message" MinSize="50 10" VerticalExpand="True" Access="Public" />
</CollapsibleHeading>
<CollapsibleBody Name="DetailsBody" Access="Public" />
</Collapsible>
</BoxContainer>
<cc:HSeparator/>
</BoxContainer>

View file

@ -0,0 +1,79 @@
using System.Text.RegularExpressions;
using Content.Client.Message;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Logs.Entries;
[GenerateTypedNameReferences]
public sealed partial class AdminLogEntry : BoxContainer
{
private readonly IConfigurationManager _cfgManager;
public SharedAdminLog Log { get; }
private readonly string _rawMessage;
public AdminLogEntry(ref SharedAdminLog log)
{
_cfgManager = IoCManager.Resolve<IConfigurationManager>();
RobustXamlLoader.Load(this);
Log = log;
_rawMessage = $"{log.Date:HH:mm:ss}: {log.Message}";
Message.SetMessage(_rawMessage);
DetailsHeading.OnToggled += DetailsToggled;
}
/// <summary>
/// Sets text to be highlighted from a search result, and renders rich text, or removes all rich text markup.
/// </summary>
public void RenderResults(Regex highlightRegex, bool renderRichText, bool removeMarkup)
{
var color = _cfgManager.GetCVar(CCVars.AdminLogsHighlightColor);
var formattedMessage = renderRichText
? _rawMessage
: removeMarkup
? FormattedMessage.RemoveMarkupPermissive(_rawMessage)
: FormattedMessage.EscapeText(_rawMessage);
// Want to avoid highlighting smaller strings
if (highlightRegex.ToString().Length > 4)
{
try
{
formattedMessage = highlightRegex.Replace(formattedMessage, $"[color={color}]$1[/color]", 3);
}
catch (RegexMatchTimeoutException)
{
// if we time out then don't bother highlighting results
}
}
if (!FormattedMessage.TryFromMarkup(formattedMessage, out var outputMessage))
return;
Message.SetMessage(outputMessage);
}
/// <summary>
/// We perform some extra calculations in the dropdown, so we want to render that only when
/// the dropdown is actually opened.
/// This also removes itself from the event listener so it doesn't trigger again.
/// </summary>
private void DetailsToggled(BaseButton.ButtonToggledEventArgs args)
{
if (!args.Pressed || DetailsBody.ChildCount > 0)
return;
DetailsBody.AddChild(new AdminLogEntryDetails(Log));
DetailsHeading.OnToggled -= DetailsToggled;
}
}

View file

@ -0,0 +1,44 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
StyleClasses="BackgroundDark">
<BoxContainer Orientation="Vertical" Margin="4">
<BoxContainer Orientation="Vertical">
<Label Text="{Loc admin-logs-field-type}" Margin="0 0 4 0"/>
<Label Name="Type" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Vertical">
<Label Text="{Loc admin-logs-field-impact}" Margin="0 0 4 0"/>
<Label Name="Impact" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
</BoxContainer>
</BoxContainer>
<cc:VSeparator/>
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc admin-logs-field-time-header}"/>
<cc:HSeparator/>
<BoxContainer>
<Label Text="{Loc admin-logs-field-time-local}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="LocalTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-logs-field-time-utc}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="UTCTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-logs-field-time-round}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="CurTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
<cc:VSeparator/>
<BoxContainer Orientation="Vertical" Margin="4" HorizontalExpand="True">
<Label Text="{Loc admin-logs-field-players-header}"/>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal" Margin="4" VerticalExpand="True">
<controls:ListContainer Name="PlayerListContainer" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>

View file

@ -0,0 +1,98 @@
using System.Linq;
using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.Administration.Logs;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Network;
namespace Content.Client.Administration.UI.Logs.Entries;
[GenerateTypedNameReferences]
public sealed partial class AdminLogEntryDetails : BoxContainer
{
private readonly AdminSystem _adminSystem;
private readonly IUserInterfaceManager _uiManager;
private readonly IEntityManager _entManager;
public AdminLogEntryDetails(SharedAdminLog log)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
_adminSystem = _entManager.System<AdminSystem>();
Type.Text = log.Type.ToString();
Impact.Text = log.Impact.ToString();
LocalTime.Text = $"{log.Date.ToLocalTime():HH:mm:ss}";
UTCTime.Text = $"{log.Date:HH:mm:ss}";
// TimeSpan and DateTime use different formatting string conventions for some completely logical reason
// that mere mortals such as myself will never be able to understand.
CurTime.Text = new TimeSpan(log.CurTime).ToString(@"hh\:mm\:ss");
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PopulateList(log.Players);
}
private void PopulateList(Guid[] players)
{
if (players.Length == 0)
return;
if (_adminSystem.PlayerList is not { } allPlayers || allPlayers.Count == 0)
return;
var listData = new List<PlayerListData>();
foreach (var playerGuid in players)
{
var netUserId = new NetUserId(playerGuid);
// Linq here is fine since this runs in response to admin input in the UI and
// this loop only tends to go through 1-4 iterations.
if (allPlayers.FirstOrDefault(player => player.SessionId == netUserId) is not { } playerInfo)
continue;
listData.Add(new PlayerListData(playerInfo));
}
if (listData.Count == 0)
return;
PlayerListContainer.PopulateList(listData);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (!(args.Function == EngineKeyFunctions.UIRightClick
|| args.Function == EngineKeyFunctions.UIClick)
|| selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
var entryLabel = new Label();
entryLabel.Text = $"{info.CharacterName} ({info.Username})";
var entry = new BoxContainer();
entry.AddChild(entryLabel);
button.AddChild(entry);
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
}
}

View file

@ -57,12 +57,43 @@ public sealed partial class ObjectsTab : Control
private void TeleportTo(NetEntity nent)
{
_console.ExecuteCommand($"tpto {nent}");
var selection = _selections[ObjectTypeOptions.SelectedId];
switch (selection)
{
case ObjectsTabSelection.Grids:
{
// directly teleport to the entity
_console.ExecuteCommand($"tpto {nent}");
}
break;
case ObjectsTabSelection.Maps:
{
// teleport to the map, not to the map entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent<MapComponent>(map, out var mapComp))
break;
_console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
break;
}
case ObjectsTabSelection.Stations:
{
// teleport to the station's largest grid, not to the station entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var station))
break;
var largestGrid = _entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetLargestGrid(station.Value);
if (largestGrid == null)
break;
_console.ExecuteCommand($"tpto {largestGrid.Value}");
break;
}
default:
throw new NotImplementedException();
}
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
RefreshObjectList();
}
public void RefreshObjectList()
@ -79,25 +110,21 @@ public sealed partial class ObjectsTab : Control
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetStationNames());
break;
case ObjectsTabSelection.Grids:
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
}
break;
}
case ObjectsTabSelection.Maps:
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
}
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}

View file

@ -1,5 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
@ -20,7 +21,7 @@
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="DeleteButton"
<controls:ConfirmButton Name="DeleteButton"
Text="{Loc object-tab-entity-delete}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"

View file

@ -59,7 +59,6 @@ public sealed partial class PlayerTab : Control
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;

View file

@ -1,4 +1,4 @@
using Content.Shared.Changeling.Transform;
using Content.Shared.Changeling.Systems;
using JetBrains.Annotations;
using Robust.Client.UserInterface;

View file

@ -1,6 +1,6 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Transform;
using Content.Shared.Changeling.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

View file

@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem
{
if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
{
// TODO: Don't use protoId for deimplanting
// and especially not raw strings!
Dictionary<string, string> implants = new();
foreach (var implant in component.DeimplantWhitelist)
{

View file

@ -0,0 +1,5 @@
using Content.Shared.Implants;
namespace Content.Client.Implants;
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;

View file

@ -1,8 +0,0 @@
using Content.Shared.Kitchen;
namespace Content.Client.Kitchen;
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
{
}

View file

@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class CrematoriumSystem : SharedCrematoriumSystem;

View file

@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class MorgueSystem : SharedMorgueSystem;

View file

@ -9,6 +9,9 @@
<ui:OptionDropDown Name="DropDownPlayerTabSymbolSetting" Title="{Loc 'ui-options-admin-player-tab-symbol-setting'}" />
<ui:OptionDropDown Name="DropDownPlayerTabRoleSetting" Title="{Loc 'ui-options-admin-player-tab-role-setting'}" />
<ui:OptionDropDown Name="DropDownPlayerTabColorSetting" Title="{Loc 'ui-options-admin-player-tab-color-setting'}" />
<Label Text="{Loc 'ui-options-admin-logs-title'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="ColorSliderLogsHighlight" Title="{Loc 'ui-options-admin-logs-highlight-color'}" />
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
StyleClasses="LabelKeyText"/>
<ui:OptionDropDown Name="DropDownOverlayAntagFormat" Title="{Loc 'ui-options-admin-overlay-antag-format'}" />

View file

@ -51,6 +51,8 @@ public sealed partial class AdminOptionsTab : Control
playerTabSymbolSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}")));
}
Control.AddOptionColorSlider(CCVars.AdminLogsHighlightColor, ColorSliderLogsHighlight);
Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings);
Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings);
Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings);

View file

@ -1,10 +0,0 @@
using Content.Shared.Storage.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Storage.Components;
[RegisterComponent]
public sealed partial class EntityStorageComponent : SharedEntityStorageComponent
{
}

View file

@ -31,7 +31,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
SubscribeLocalEvent<EntityStorageComponent, ComponentHandleState>(OnHandleState);
}
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;

View file

@ -5,7 +5,6 @@ using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Body.Components;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Mobs.Components;
using Content.Shared.Prototypes;
@ -266,7 +265,6 @@ public sealed class CargoTest
{
foreach (var (proto, comp) in pair.GetPrototypesWithComponent<MobPriceComponent>())
{
Assert.That(proto.TryGetComponent<BodyComponent>(out _, componentFactory), $"Found MobPriceComponent on {proto.ID}, but no BodyComponent!");
Assert.That(proto.TryGetComponent<MobStateComponent>(out _, componentFactory), $"Found MobPriceComponent on {proto.ID}, but no MobStateComponent!");
}
});

View file

@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
components:
- type: Physics
bodyType: Dynamic
- type: GravityAffected
- type: Fixtures
fixtures:
fix1:

View file

@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
- type: Alerts
- type: Physics
bodyType: Dynamic
- type: GravityAffected
- type: entity
name: WeightlessGravityGeneratorDummy

View file

@ -76,8 +76,8 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.True);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).EnabledVV);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).Enabled);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled);
});
// Re-enable needs power so it turns off again.
@ -94,7 +94,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled, Is.False);
});
});

View file

@ -144,6 +144,7 @@ public abstract partial class InteractionTest
- type: Stripping
- type: Puller
- type: Physics
- type: GravityAffected
- type: Tag
tags:
- CanPilot

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class AdminLogsCurtime : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "cur_time",
table: "admin_log",
type: "bigint",
nullable: false,
defaultValue: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "cur_time",
table: "admin_log");
}
}
}

View file

@ -146,6 +146,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer")
.HasColumnName("admin_log_id");
b.Property<long>("CurTime")
.HasColumnType("bigint")
.HasColumnName("cur_time");
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone")
.HasColumnName("date");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class AdminLogsCurtime : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "cur_time",
table: "admin_log",
type: "INTEGER",
nullable: false,
defaultValue: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "cur_time",
table: "admin_log");
}
}
}

View file

@ -133,6 +133,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("admin_log_id");
b.Property<long>("CurTime")
.HasColumnType("INTEGER")
.HasColumnName("cur_time");
b.Property<DateTime>("Date")
.HasColumnType("TEXT")
.HasColumnName("date");

View file

@ -720,6 +720,11 @@ namespace Content.Server.Database
[Required] public DateTime Date { get; set; }
/// <summary>
/// The current time in the round in ticks since the start of the round.
/// </summary>
public long CurTime { get; set; }
[Required] public string Message { get; set; } = default!;
[Required, Column(TypeName = "jsonb")] public JsonDocument Json { get; set; } = default!;

View file

@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View file

@ -1,5 +1,6 @@
using Content.Server.Storage.Components;
using Content.Shared.Administration;
using Content.Shared.Storage.Components;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;

View file

@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View file

@ -51,7 +51,7 @@ public sealed partial class AdminLogManager
private void CacheLog(AdminLog log)
{
var players = log.Players.Select(player => player.PlayerUserId).ToArray();
var record = new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.Message, players);
var record = new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.CurTime, log.Message, players);
CacheLog(record);
}

View file

@ -87,6 +87,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
// Per round
private int _currentRoundId;
private int _currentLogId;
private TimeSpan _currentRoundStartTime;
private int NextLogId => Interlocked.Increment(ref _currentLogId);
private GameRunLevel _runLevel = GameRunLevel.PreRoundLobby;
@ -260,6 +261,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
public void RoundStarting(int id)
{
_currentRoundStartTime = _timing.CurTime;
_currentRoundId = id;
CacheNewRound();
}
@ -316,6 +318,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
Type = type,
Impact = impact,
Date = DateTime.UtcNow,
CurTime = (_timing.CurTime - _currentRoundStartTime).Ticks,
Message = message,
Json = json,
Players = new List<AdminLogPlayer>(players.Count)

View file

@ -16,7 +16,6 @@ using Content.Server.Pointing.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
using Content.Server.Speech.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Tabletop;
using Content.Server.Tabletop.Components;
@ -34,6 +33,7 @@ using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Electrocution;
using Content.Shared.Gravity;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
@ -46,6 +46,7 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Slippery;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tabletop.Components;
using Content.Shared.Tools.Systems;
@ -743,6 +744,11 @@ public sealed partial class AdminVerbSystem
grav.Weightless = true;
Dirty(args.Target, grav);
EnsureComp<GravityAffectedComponent>(args.Target, out var weightless);
weightless.Weightless = true;
Dirty(args.Target, weightless);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description"))

View file

@ -1,5 +1,4 @@
using Content.Server.Botany.Components;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Botany;
@ -16,6 +15,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Kitchen.Components;
namespace Content.Server.Botany.Systems;

View file

@ -1,7 +1,7 @@
using Content.Server.Botany.Components;
using Content.Server.Kitchen.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components;
using Content.Shared.Random;
using Robust.Shared.Containers;

View file

@ -1,7 +1,6 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Botany.Components;
using Content.Server.Hands.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Atmos;
@ -26,6 +25,7 @@ using Robust.Shared.Timing;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Kitchen.Components;
using Content.Shared.Labels.Components;
namespace Content.Server.Botany.Systems;

View file

@ -1,6 +1,6 @@
using Content.Server.Storage.Components;
using Content.Shared.Construction;
using Content.Shared.Examine;
using Content.Shared.Storage.Components;
using Content.Shared.Tools.Systems;
using JetBrains.Annotations;

View file

@ -1107,7 +1107,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
players[i] = log.Players[i].PlayerUserId;
}
yield return new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.Message, players);
yield return new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.CurTime, log.Message, players);
}
}

View file

@ -166,7 +166,7 @@ public sealed class SpraySystem : EntitySystem
if (TryComp<PhysicsComponent>(user, out var body))
{
if (_gravity.IsWeightless(user, body))
if (_gravity.IsWeightless(user))
{
// push back the player
_physics.ApplyLinearImpulse(user, -impulseDirection * entity.Comp.PushbackAmount, body: body);

View file

@ -18,7 +18,7 @@ namespace Content.Server.Gravity
/// </summary>
public void RefreshGravity(EntityUid uid, GravityComponent? gravity = null)
{
if (!Resolve(uid, ref gravity))
if (!GravityQuery.Resolve(uid, ref gravity))
return;
if (gravity.Inherent)
@ -61,7 +61,7 @@ namespace Content.Server.Gravity
/// </summary>
public void EnableGravity(EntityUid uid, GravityComponent? gravity = null)
{
if (!Resolve(uid, ref gravity))
if (!GravityQuery.Resolve(uid, ref gravity))
return;
if (gravity.Enabled || gravity.Inherent)

View file

@ -29,17 +29,17 @@ public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem
{
base.Initialize();
SubscribeLocalEvent<SubdermalImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
SubscribeLocalEvent<ChameleonControllerImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
SubscribeLocalEvent<ChameleonClothingComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected);
}
private void OnSelected(Entity<SubdermalImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
private void OnSelected(Entity<ChameleonControllerImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
{
if (!_delay.TryResetDelay(ent.Owner, true) || ent.Comp.ImplantedEntity == null || !HasComp<ChameleonControllerImplantComponent>(ent))
if (!TryComp<SubdermalImplantComponent>(ent, out var implantComp) || implantComp.ImplantedEntity == null || !_delay.TryResetDelay(ent.Owner, true))
return;
ChangeChameleonClothingToOutfit(ent.Comp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
ChangeChameleonClothingToOutfit(implantComp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
}
/// <summary>

View file

@ -27,6 +27,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
}
// TODO: This all needs to be moved to shared and predicted.
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach || args.Handled)

View file

@ -1,7 +1,6 @@
using Content.Server.Radio.Components;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Robust.Shared.Containers;
namespace Content.Server.Implants;
@ -12,7 +11,7 @@ public sealed class RadioImplantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
SubscribeLocalEvent<RadioImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
}
/// <summary>
@ -20,19 +19,16 @@ public sealed class RadioImplantSystem : EntitySystem
/// </summary>
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
{
if (args.Implanted == null)
return;
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted);
foreach (var channel in ent.Comp.RadioChannels)
{
if (activeRadio.Channels.Add(channel))
ent.Comp.ActiveAddedChannels.Add(channel);
}
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted);
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted);
foreach (var channel in ent.Comp.RadioChannels)
{
if (intrinsicRadioTransmitter.Channels.Add(channel))
@ -43,9 +39,9 @@ public sealed class RadioImplantSystem : EntitySystem
/// <summary>
/// Removes intrinsic radio components once the Radio Implant is removed
/// </summary>
private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
private void OnImplantRemoved(Entity<RadioImplantComponent> ent, ref ImplantRemovedEvent args)
{
if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
if (TryComp<ActiveRadioComponent>(args.Implanted, out var activeRadioComponent))
{
foreach (var channel in ent.Comp.ActiveAddedChannels)
{
@ -55,11 +51,11 @@ public sealed class RadioImplantSystem : EntitySystem
if (activeRadioComponent.Channels.Count == 0)
{
RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
RemCompDeferred<ActiveRadioComponent>(args.Implanted);
}
}
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Implanted, out var radioTransmitterComponent))
return;
foreach (var channel in ent.Comp.TransmitterAddedChannels)
@ -70,7 +66,7 @@ public sealed class RadioImplantSystem : EntitySystem
if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
{
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Implanted);
}
}
}

View file

@ -18,6 +18,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay);
}
// TODO: This shouldn't be in the SubdermalImplantSystem
private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
{
var args = implantRelay.Event;

View file

@ -1,15 +0,0 @@
namespace Content.Server.Kitchen.Components;
/// <summary>
/// Applies to items that are capable of butchering entities, or
/// are otherwise sharp for some purpose.
/// </summary>
[RegisterComponent]
public sealed partial class SharpComponent : Component
{
// TODO just make this a tool type.
public HashSet<EntityUid> Butchering = new();
[DataField("butcherDelayModifier")]
public float ButcherDelayModifier = 1.0f;
}

View file

@ -1,292 +0,0 @@
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Kitchen;
using Content.Shared.Kitchen.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using static Content.Shared.Kitchen.Components.KitchenSpikeComponent;
namespace Content.Server.Kitchen.EntitySystems
{
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IAdminLogManager _logger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedSuicideSystem _suicide = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<KitchenSpikeComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<KitchenSpikeComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
//DoAfter
SubscribeLocalEvent<KitchenSpikeComponent, SpikeDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<KitchenSpikeComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
SubscribeLocalEvent<ButcherableComponent, CanDropDraggedEvent>(OnButcherableCanDrop);
}
private void OnButcherableCanDrop(Entity<ButcherableComponent> entity, ref CanDropDraggedEvent args)
{
args.Handled = true;
args.CanDrop |= entity.Comp.Type != ButcheringType.Knife;
}
/// <summary>
/// TODO: Update this so it actually meatspikes the user instead of applying lethal damage to them.
/// </summary>
private void OnSuicideByEnvironment(Entity<KitchenSpikeComponent> entity, ref SuicideByEnvironmentEvent args)
{
if (args.Handled)
return;
if (!TryComp<DamageableComponent>(args.Victim, out var damageableComponent))
return;
_suicide.ApplyLethalDamage((args.Victim, damageableComponent), "Piercing");
var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other",
("victim", Identity.Entity(args.Victim, EntityManager)),
("this", entity));
_popupSystem.PopupEntity(othersMessage, args.Victim, Filter.PvsExcept(args.Victim), true);
var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self",
("this", entity));
_popupSystem.PopupEntity(selfMessage, args.Victim, args.Victim);
args.Handled = true;
}
private void OnDoAfter(Entity<KitchenSpikeComponent> entity, ref SpikeDoAfterEvent args)
{
if (args.Args.Target == null)
return;
if (TryComp<ButcherableComponent>(args.Args.Target.Value, out var butcherable))
butcherable.BeingButchered = false;
if (args.Cancelled)
{
entity.Comp.InUse = false;
return;
}
if (args.Handled)
return;
if (Spikeable(entity, args.Args.User, args.Args.Target.Value, entity.Comp, butcherable))
Spike(entity, args.Args.User, args.Args.Target.Value, entity.Comp);
entity.Comp.InUse = false;
args.Handled = true;
}
private void OnDragDrop(Entity<KitchenSpikeComponent> entity, ref DragDropTargetEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (Spikeable(entity, args.User, args.Dragged, entity.Comp))
TrySpike(entity, args.User, args.Dragged, entity.Comp);
}
private void OnInteractHand(Entity<KitchenSpikeComponent> entity, ref InteractHandEvent args)
{
if (args.Handled)
return;
if (entity.Comp.PrototypesToSpawn?.Count > 0)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-knife-needed"), entity, args.User);
args.Handled = true;
}
}
private void OnInteractUsing(Entity<KitchenSpikeComponent> entity, ref InteractUsingEvent args)
{
if (args.Handled)
return;
if (TryGetPiece(entity, args.User, args.Used))
args.Handled = true;
}
private void Spike(EntityUid uid, EntityUid userUid, EntityUid victimUid,
KitchenSpikeComponent? component = null, ButcherableComponent? butcherable = null)
{
if (!Resolve(uid, ref component) || !Resolve(victimUid, ref butcherable))
return;
var logImpact = LogImpact.Medium;
if (HasComp<HumanoidAppearanceComponent>(victimUid))
logImpact = LogImpact.Extreme;
_logger.Add(LogType.Gib, logImpact, $"{ToPrettyString(userUid):user} kitchen spiked {ToPrettyString(victimUid):target}");
// TODO VERY SUS
component.PrototypesToSpawn = EntitySpawnCollection.GetSpawns(butcherable.SpawnedEntities, _random);
// This feels not okay, but entity is getting deleted on "Spike", for now...
component.MeatSource1p = Loc.GetString("comp-kitchen-spike-remove-meat", ("victim", victimUid));
component.MeatSource0 = Loc.GetString("comp-kitchen-spike-remove-meat-last", ("victim", victimUid));
component.Victim = Name(victimUid);
UpdateAppearance(uid, null, component);
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-kill",
("user", Identity.Entity(userUid, EntityManager)),
("victim", Identity.Entity(victimUid, EntityManager)),
("this", uid)),
uid, PopupType.LargeCaution);
_transform.SetCoordinates(victimUid, Transform(uid).Coordinates);
// THE WHAT?
// TODO: Need to be able to leave them on the spike to do DoT, see ss13.
var gibs = _bodySystem.GibBody(victimUid);
foreach (var gib in gibs) {
QueueDel(gib);
}
_audio.PlayPvs(component.SpikeSound, uid);
}
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
KitchenSpikeComponent? component = null, SharpComponent? sharp = null)
{
if (!Resolve(uid, ref component) || component.PrototypesToSpawn == null || component.PrototypesToSpawn.Count == 0)
return false;
// Is using knife
if (!Resolve(used, ref sharp, false) )
{
return false;
}
var item = _random.PickAndTake(component.PrototypesToSpawn);
var ent = Spawn(item, Transform(uid).Coordinates);
_metaData.SetEntityName(ent,
Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim)));
if (component.PrototypesToSpawn.Count != 0)
_popupSystem.PopupEntity(component.MeatSource1p, uid, user, PopupType.MediumCaution);
else
{
UpdateAppearance(uid, null, component);
_popupSystem.PopupEntity(component.MeatSource0, uid, user, PopupType.MediumCaution);
}
return true;
}
private void UpdateAppearance(EntityUid uid, AppearanceComponent? appearance = null, KitchenSpikeComponent? component = null)
{
if (!Resolve(uid, ref component, ref appearance, false))
return;
_appearance.SetData(uid, KitchenSpikeVisuals.Status, component.PrototypesToSpawn?.Count > 0 ? KitchenSpikeStatus.Bloody : KitchenSpikeStatus.Empty, appearance);
}
private bool Spikeable(EntityUid uid, EntityUid userUid, EntityUid victimUid,
KitchenSpikeComponent? component = null, ButcherableComponent? butcherable = null)
{
if (!Resolve(uid, ref component))
return false;
if (component.PrototypesToSpawn?.Count > 0)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-collect", ("this", uid)), uid, userUid);
return false;
}
if (!Resolve(victimUid, ref butcherable, false))
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
}
switch (butcherable.Type)
{
case ButcheringType.Spike:
return true;
case ButcheringType.Knife:
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher-knife", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
default:
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
}
}
public bool TrySpike(EntityUid uid, EntityUid userUid, EntityUid victimUid, KitchenSpikeComponent? component = null,
ButcherableComponent? butcherable = null, MobStateComponent? mobState = null)
{
if (!Resolve(uid, ref component) || component.InUse ||
!Resolve(victimUid, ref butcherable) || butcherable.BeingButchered)
return false;
// THE WHAT? (again)
// Prevent dead from being spiked TODO: Maybe remove when rounds can be played and DOT is implemented
if (Resolve(victimUid, ref mobState, false) &&
_mobStateSystem.IsAlive(victimUid, mobState))
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-not-dead", ("victim", Identity.Entity(victimUid, EntityManager))),
victimUid, userUid);
return true;
}
if (userUid != victimUid)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-begin-hook-victim", ("user", Identity.Entity(userUid, EntityManager)), ("this", uid)), victimUid, victimUid, PopupType.LargeCaution);
}
// TODO: make it work when SuicideEvent is implemented
// else
// _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-begin-hook-self", ("this", uid)), victimUid, Filter.Pvs(uid)); // This is actually unreachable and should be in SuicideEvent
butcherable.BeingButchered = true;
component.InUse = true;
var doAfterArgs = new DoAfterArgs(EntityManager, userUid, component.SpikeDelay + butcherable.ButcherDelay, new SpikeDoAfterEvent(), uid, target: victimUid, used: uid)
{
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
BreakOnDropItem = false,
};
_doAfter.TryStartDoAfter(doAfterArgs);
return true;
}
}
}

View file

@ -1,5 +1,4 @@
using Content.Server.Body.Systems;
using Content.Server.Kitchen.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
using Content.Shared.Database;
@ -8,6 +7,7 @@ using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Kitchen;
using Content.Shared.Kitchen.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;

View file

@ -28,7 +28,7 @@ public sealed class MindShieldSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<MindShieldImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<MindShieldImplantComponent, EntGotRemovedFromContainerMessage>(OnImplantDraw);
SubscribeLocalEvent<MindShieldImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
SubscribeLocalEvent<SubdermalImplantComponent, ImplantEjectEvent>(ImplantCheck);
}
@ -37,8 +37,8 @@ public sealed class MindShieldSystem : EntitySystem
if (ev.Implanted == null)
return;
EnsureComp<MindShieldComponent>(ev.Implanted.Value);
MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant);
EnsureComp<MindShieldComponent>(ev.Implanted);
MindShieldRemovalCheck(ev.Implanted, ev.Implant);
}
// Sunrise-Start
@ -70,9 +70,9 @@ public sealed class MindShieldSystem : EntitySystem
}
}
private void OnImplantDraw(Entity<MindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
private void OnImplantRemoved(Entity<MindShieldImplantComponent> ent, ref ImplantRemovedEvent args)
{
RemComp<MindShieldComponent>(args.Container.Owner);
RemComp<MindShieldComponent>(args.Implanted);
}
}

View file

@ -1,11 +0,0 @@
namespace Content.Server.Morgue.Components;
/// <summary>
/// used to track actively cooking crematoriums
/// </summary>
[RegisterComponent]
public sealed partial class ActiveCrematoriumComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0;
}

View file

@ -1,22 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Server.Morgue.Components;
[RegisterComponent]
public sealed partial class CrematoriumComponent : Component
{
/// <summary>
/// The time it takes to cook in second
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int CookTime = 5;
[DataField("cremateStartSound")]
public SoundSpecifier CremateStartSound = new SoundPathSpecifier("/Audio/Items/Lighters/lighter1.ogg");
[DataField("crematingSound")]
public SoundSpecifier CrematingSound = new SoundPathSpecifier("/Audio/Effects/burning.ogg");
[DataField("cremateFinishSound")]
public SoundSpecifier CremateFinishSound = new SoundPathSpecifier("/Audio/Machines/ding.ogg");
}

View file

@ -1,195 +1,58 @@
using Content.Server.Ghost;
using Content.Server.Morgue.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind;
using Content.Shared.Morgue;
using Content.Shared.Morgue.Components;
using Content.Shared.Popups;
using Content.Shared.Standing;
using Content.Shared.Storage;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Morgue;
public sealed class CrematoriumSystem : EntitySystem
public sealed class CrematoriumSystem : SharedCrematoriumSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CrematoriumComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<CrematoriumComponent, GetVerbsEvent<AlternativeVerb>>(AddCremateVerb);
SubscribeLocalEvent<CrematoriumComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
SubscribeLocalEvent<ActiveCrematoriumComponent, StorageOpenAttemptEvent>(OnAttemptOpen);
}
private void OnExamine(EntityUid uid, CrematoriumComponent component, ExaminedEvent args)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
using (args.PushGroup(nameof(CrematoriumComponent)))
{
if (_appearance.TryGetData<bool>(uid, CrematoriumVisuals.Burning, out var isBurning, appearance) &&
isBurning)
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning",
("owner", uid)));
}
if (_appearance.TryGetData<bool>(uid, StorageVisuals.HasContents, out var hasContents, appearance) &&
hasContents)
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents"));
}
else
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty"));
}
}
}
private void OnAttemptOpen(EntityUid uid, ActiveCrematoriumComponent component, ref StorageOpenAttemptEvent args)
{
args.Cancelled = true;
}
private void AddCremateVerb(EntityUid uid, CrematoriumComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!TryComp<EntityStorageComponent>(uid, out var storage))
return;
if (!args.CanAccess || !args.CanInteract || args.Hands == null || storage.Open)
return;
if (HasComp<ActiveCrematoriumComponent>(uid))
return;
AlternativeVerb verb = new()
{
Text = Loc.GetString("cremate-verb-get-data-text"),
// TODO VERB ICON add flame/burn symbol?
Act = () => TryCremate(uid, component, storage),
Impact = LogImpact.High // could be a body? or evidence? I dunno.
};
args.Verbs.Add(verb);
}
public bool Cremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref component, ref storage))
return false;
if (HasComp<ActiveCrematoriumComponent>(uid))
return false;
_audio.PlayPvs(component.CremateStartSound, uid);
_appearance.SetData(uid, CrematoriumVisuals.Burning, true);
_audio.PlayPvs(component.CrematingSound, uid);
AddComp<ActiveCrematoriumComponent>(uid);
return true;
}
public bool TryCremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref component, ref storage))
return false;
if (storage.Open || storage.Contents.ContainedEntities.Count < 1)
return false;
return Cremate(uid, component, storage);
}
private void FinishCooking(EntityUid uid, CrematoriumComponent component, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref storage))
return;
_appearance.SetData(uid, CrematoriumVisuals.Burning, false);
RemComp<ActiveCrematoriumComponent>(uid);
if (storage.Contents.ContainedEntities.Count > 0)
{
for (var i = storage.Contents.ContainedEntities.Count - 1; i >= 0; i--)
{
var item = storage.Contents.ContainedEntities[i];
_containers.Remove(item, storage.Contents);
Del(item);
}
var ash = Spawn("Ash", Transform(uid).Coordinates);
_containers.Insert(ash, storage.Contents);
}
_entityStorage.OpenStorage(uid, storage);
_audio.PlayPvs(component.CremateFinishSound, uid);
}
private void OnSuicideByEnvironment(EntityUid uid, CrematoriumComponent component, SuicideByEnvironmentEvent args)
private void OnSuicideByEnvironment(Entity<CrematoriumComponent> ent, ref SuicideByEnvironmentEvent args)
{
if (args.Handled)
return;
var victim = args.Victim;
if (TryComp(victim, out ActorComponent? actor) && _minds.TryGetMind(victim, out var mindId, out var mind))
if (HasComp<ActorComponent>(victim) && Mind.TryGetMind(victim, out var mindId, out var mind))
{
_ghostSystem.OnGhostAttempt(mindId, false, mind: mind);
if (mind.OwnedEntity is { Valid: true } entity)
{
_popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
}
}
_popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
("victim", Identity.Entity(victim, EntityManager))),
victim, Filter.PvsExcept(victim), true, PopupType.LargeCaution);
victim,
Filter.PvsExcept(victim),
true,
PopupType.LargeCaution);
if (_entityStorage.CanInsert(victim, uid))
if (EntityStorage.CanInsert(victim, ent.Owner))
{
_entityStorage.CloseStorage(uid);
_standing.Down(victim, false);
_entityStorage.Insert(victim, uid);
EntityStorage.CloseStorage(ent.Owner);
Standing.Down(victim, false);
EntityStorage.Insert(victim, ent.Owner);
}
else
{
EntityStorage.CloseStorage(ent.Owner);
Del(victim);
}
_entityStorage.CloseStorage(uid);
Cremate(uid, component);
Cremate(ent.AsNullable());
args.Handled = true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ActiveCrematoriumComponent, CrematoriumComponent>();
while (query.MoveNext(out var uid, out var act, out var crem))
{
act.Accumulator += frameTime;
if (act.Accumulator >= crem.CookTime)
FinishCooking(uid, crem);
}
}
}

View file

@ -1,95 +1,46 @@
using Content.Server.Storage.Components;
using Content.Shared.Examine;
using Content.Shared.Mobs.Components;
using Content.Shared.Morgue;
using Content.Shared.Morgue.Components;
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.Morgue;
public sealed class MorgueSystem : EntitySystem
public sealed class MorgueSystem : SharedMorgueSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MorgueComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<MorgueComponent, MapInitEvent>(OnMapInit);
}
/// <summary>
/// Handles the examination text for looking at a morgue.
/// </summary>
private void OnExamine(Entity<MorgueComponent> ent, ref ExaminedEvent args)
private void OnMapInit(Entity<MorgueComponent> ent, ref MapInitEvent args)
{
if (!args.IsInDetailsRange)
return;
_appearance.TryGetData<MorgueContents>(ent.Owner, MorgueVisuals.Contents, out var contents);
var text = contents switch
{
MorgueContents.HasSoul => "morgue-entity-storage-component-on-examine-details-body-has-soul",
MorgueContents.HasContents => "morgue-entity-storage-component-on-examine-details-has-contents",
MorgueContents.HasMob => "morgue-entity-storage-component-on-examine-details-body-has-no-soul",
_ => "morgue-entity-storage-component-on-examine-details-empty"
};
args.PushMarkup(Loc.GetString(text));
ent.Comp.NextBeep = _timing.CurTime + ent.Comp.NextBeep;
}
/// <summary>
/// Updates data periodically in case something died/got deleted in the morgue.
/// </summary>
private void CheckContents(EntityUid uid, MorgueComponent? morgue = null, EntityStorageComponent? storage = null, AppearanceComponent? app = null)
{
if (!Resolve(uid, ref morgue, ref storage, ref app))
return;
if (storage.Contents.ContainedEntities.Count == 0)
{
_appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.Empty);
return;
}
var hasMob = false;
foreach (var ent in storage.Contents.ContainedEntities)
{
if (!hasMob && HasComp<MobStateComponent>(ent))
hasMob = true;
if (HasComp<ActorComponent>(ent))
{
_appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.HasSoul, app);
return;
}
}
_appearance.SetData(uid, MorgueVisuals.Contents, hasMob ? MorgueContents.HasMob : MorgueContents.HasContents, app);
}
/// <summary>
/// Handles the periodic beeping that morgues do when a live body is inside.
/// Handles the periodic beeping that morgues do when a live body is inside.
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<MorgueComponent, EntityStorageComponent, AppearanceComponent>();
while (query.MoveNext(out var uid, out var comp, out var storage, out var appearance))
{
comp.AccumulatedFrameTime += frameTime;
CheckContents(uid, comp, storage);
if (comp.AccumulatedFrameTime < comp.BeepTime)
if (curTime < comp.NextBeep)
continue;
comp.AccumulatedFrameTime -= comp.BeepTime;
comp.NextBeep += comp.BeepTime;
CheckContents(uid, comp, storage);
if (comp.DoSoulBeep && _appearance.TryGetData<MorgueContents>(uid, MorgueVisuals.Contents, out var contents, appearance) && contents == MorgueContents.HasSoul)
{

View file

@ -7,7 +7,6 @@ using Content.Server.NPC.Queries.Curves;
using Content.Server.NPC.Queries.Queries;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Storage.Components;
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
@ -20,6 +19,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tools.Systems;
using Content.Shared.Turrets;

View file

@ -42,7 +42,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (!args.CanReach
|| !_solutionContainerSystem.TryGetRefillableSolution(entity.Owner, out _, out var solution)
|| !HasComp<BloodstreamComponent>(args.Target)
|| _ingestion.HasMouthAvailable(args.Target.Value, args.User)
|| !_ingestion.HasMouthAvailable(args.Target.Value, args.User)
)
{
return;

View file

@ -18,7 +18,7 @@ public sealed partial class PolymorphedEntityComponent : Component
/// The original entity that the player will revert back into
/// </summary>
[DataField(required: true)]
public EntityUid Parent;
public EntityUid? Parent;
/// <summary>
/// The amount of time that has passed since the entity was created

View file

@ -59,6 +59,7 @@ public sealed partial class PolymorphSystem : EntitySystem
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<PolymorphedEntityComponent, EntityTerminatingEvent>(OnPolymorphedTerminating);
InitializeMap();
}
@ -151,6 +152,16 @@ public sealed partial class PolymorphSystem : EntitySystem
}
}
private void OnPolymorphedTerminating(Entity<PolymorphedEntityComponent> ent, ref EntityTerminatingEvent args)
{
if (ent.Comp.Configuration.RevertOnDelete)
Revert(ent.AsNullable());
// Remove our original entity too
// Note that Revert will set Parent to null, so reverted entities will not be deleted
QueueDel(ent.Comp.Parent);
}
/// <summary>
/// Polymorphs the target entity into the specific polymorph prototype
/// </summary>
@ -297,13 +308,21 @@ public sealed partial class PolymorphSystem : EntitySystem
if (Deleted(uid))
return null;
var parent = component.Parent;
if (component.Parent is not { } parent)
return null;
// Clear our reference to the original entity
component.Parent = null;
if (Deleted(parent))
return null;
var uidXform = Transform(uid);
var parentXform = Transform(parent);
// Don't swap back onto a terminating grid
if (TerminatingOrDeleted(uidXform.ParentUid))
return null;
if (component.Configuration.ExitPolymorphSound != null)
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);

View file

@ -1,11 +1,11 @@
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Resist;
using Content.Shared.Storage.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.ActionBlocker;

View file

@ -3,7 +3,7 @@ using Content.Shared.Damage;
using Content.Shared.Revenant;
using Robust.Shared.Random;
using Content.Shared.Tag;
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Light.Components;
using Content.Server.Ghost;
using Robust.Shared.Physics;

View file

@ -19,11 +19,13 @@ public sealed class ParadoxCloneRoleSystem : EntitySystem
private void OnRefreshNameModifiers(Entity<ParadoxCloneRoleComponent> ent, ref MindRelayedEvent<RefreshNameModifiersEvent> args)
{
if (!TryComp<MindRoleComponent>(ent.Owner, out var roleComp))
var mindId = Transform(ent).ParentUid; // the mind role entity is in a container in the mind entity
if (!TryComp<MindComponent>(mindId, out var mindComp))
return;
// only show for ghosts
if (!HasComp<GhostComponent>(roleComp.Mind.Comp.OwnedEntity))
if (!HasComp<GhostComponent>(mindComp.OwnedEntity))
return;
if (ent.Comp.NameModifier != null)

View file

@ -36,7 +36,7 @@ public sealed class RoleSystem : SharedRoleSystem
// Briefing is no longer raised on the mind entity itself
// because all the components that briefings subscribe to should be on Mind Role Entities
foreach(var role in mindComp.MindRoles)
foreach (var role in mindComp.MindRoleContainer.ContainedEntities)
{
RaiseLocalEvent(role, ref ev);
}

View file

@ -79,6 +79,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly DockingSystem _dock = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly IdCardSystem _idSystem = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
@ -196,7 +197,9 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateEmergencyConsole(frameTime);
// Don't handle any of this logic if in lobby
if (_ticker.RunLevel != GameRunLevel.PreRoundLobby)
UpdateEmergencyConsole(frameTime);
}
/// <summary>

View file

@ -303,15 +303,16 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
protected override void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
{
// TODO: Prediction dump this
if (!TryComp(args.Entity, out SiliconLawProviderComponent? provider))
if (!TryComp<SiliconLawProviderComponent>(args.Entity, out var provider))
return;
var lawset = GetLawset(provider.Laws).Laws;
var lawset = provider.Lawset ?? GetLawset(provider.Laws);
var query = EntityManager.CompRegistryQueryEnumerator(ent.Comp.Components);
while (query.MoveNext(out var update))
{
SetLaws(lawset, update, provider.LawUploadSound);
SetLaws(lawset.Laws, update, provider.LawUploadSound);
}
}
}

View file

@ -4,6 +4,7 @@ using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.Station.Components;
using Content.Shared.Storage.Components;
using Content.Shared.GameTicking.Components;
namespace Content.Server.StationEvents.Events;

View file

@ -1,8 +1,7 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.GameTicking.Components;
using Content.Shared.Storage.Components;
using Robust.Shared.Map;
using Robust.Shared.Random;

View file

@ -1,18 +0,0 @@
using Content.Server.Atmos;
using Content.Shared.Atmos;
using Content.Shared.Storage.Components;
using Robust.Shared.GameStates;
namespace Content.Server.Storage.Components;
[RegisterComponent]
public sealed partial class EntityStorageComponent : SharedEntityStorageComponent, IGasMixtureHolder
{
/// <summary>
/// Gas currently contained in this entity storage.
/// None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("air")]
public GasMixture Air { get; set; } = new (200);
}

View file

@ -68,7 +68,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
}
}
protected override void OnComponentInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
protected override void OnComponentInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
{
base.OnComponentInit(uid, component, args);
@ -76,7 +76,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
_construction.AddContainer(uid, ContainerName, construction);
}
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;
@ -107,7 +107,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
args.Contents.AddRange(ent.Comp.Contents.ContainedEntities);
}
protected override void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
protected override void TakeGas(EntityUid uid, EntityStorageComponent component)
{
if (!component.Airtight)
return;
@ -121,7 +121,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
}
}
public override void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
public override void ReleaseGas(EntityUid uid, EntityStorageComponent component)
{
var serverComp = (EntityStorageComponent) component;

View file

@ -22,7 +22,7 @@ internal sealed class StunOnCollideSystem : EntitySystem
private void TryDoCollideStun(Entity<StunOnCollideComponent> ent, EntityUid target)
{
_stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, ent.Comp.Refresh, ent.Comp.AutoStand, ent.Comp.Drop);
_stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, ent.Comp.Refresh, ent.Comp.AutoStand, ent.Comp.Drop, true);
if (ent.Comp.Refresh)
{

View file

@ -2,10 +2,10 @@ using Content.Server.Body.Systems;
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Server.Storage.Components;
using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Power;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Content.Shared.Xenoarchaeology.Equipment;

View file

@ -1,8 +0,0 @@
namespace Content.Server.Zombies
{
[RegisterComponent]
public sealed partial class ZombifyOnDeathComponent : Component
{
//this is not the component you are looking for
}
}

View file

@ -9,5 +9,6 @@ public readonly record struct SharedAdminLog(
LogType Type,
LogImpact Impact,
DateTime Date,
long CurTime,
string Message,
Guid[] Players);

View file

@ -0,0 +1,9 @@
namespace Content.Shared.Body.Events;
/// <summary>
/// Raised on an entity before they bleed to modify the amount.
/// </summary>
/// <param name="BleedAmount">The amount of blood the entity will lose.</param>
/// <param name="BleedReductionAmount">The amount of bleed reduction that will happen.</param>
[ByRefEvent]
public record struct BleedModifierEvent(float BleedAmount, float BleedReductionAmount);

View file

@ -81,10 +81,14 @@ public abstract class SharedBloodstreamSystem : EntitySystem
// as well as stop their bleeding to a certain extent.
if (bloodstream.BleedAmount > 0)
{
var ev = new BleedModifierEvent(bloodstream.BleedAmount, bloodstream.BleedReductionAmount);
RaiseLocalEvent(uid, ref ev);
// Blood is removed from the bloodstream at a 1-1 rate with the bleed amount
TryModifyBloodLevel((uid, bloodstream), -bloodstream.BleedAmount);
TryModifyBloodLevel((uid, bloodstream), -ev.BleedAmount);
// Bleed rate is reduced by the bleed reduction amount in the bloodstream component.
TryModifyBleedAmount((uid, bloodstream), -bloodstream.BleedReductionAmount);
TryModifyBleedAmount((uid, bloodstream), -ev.BleedReductionAmount);
}
// deal bloodloss damage if their blood level is below a threshold.

View file

@ -39,6 +39,12 @@ public sealed partial class CCVars
public static readonly CVarDef<bool> OutlineEnabled =
CVarDef.Create("outline.enabled", true, CVar.CLIENTONLY);
/// <summary>
/// Determines the color to use when highlighting search results in the admin log browser.
/// </summary>
public static readonly CVarDef<string> AdminLogsHighlightColor =
CVarDef.Create("ui.admin_logs_highlight_color", Color.Red.ToHex(), CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// Determines how antagonist status/roletype is displayed. Based on AdminOverlayAntagFormats enum
/// Binary: Roletypes of interest get an "ANTAG" label

View file

@ -2,7 +2,7 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Changeling.Devour;
namespace Content.Shared.Changeling;
/// <summary>
/// Action event for Devour, someone has initiated a devour on someone, begin to windup.

View file

@ -2,7 +2,7 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Changeling.Transform;
namespace Content.Shared.Changeling;
/// <summary>
/// Action event for opening the changeling transformation radial menu.

View file

@ -1,3 +1,4 @@
using Content.Shared.Changeling.Systems;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
@ -7,7 +8,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Changeling.Devour;
namespace Content.Shared.Changeling.Components;
/// <summary>
/// Component responsible for Changelings Devour attack. Including the amount of damage

View file

@ -2,7 +2,7 @@ using Content.Shared.Cloning;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling;
namespace Content.Shared.Changeling.Components;
/// <summary>
/// The storage component for Changelings, it handles the link between a changeling and its consumed identities
@ -29,6 +29,7 @@ public sealed partial class ChangelingIdentityComponent : Component
/// The cloning settings passed to the CloningSystem, contains a list of all components to copy or have handled by their
/// respective systems.
/// </summary>
[DataField]
public ProtoId<CloningSettingsPrototype> IdentityCloningSettings = "ChangelingCloningSettings";
public override bool SendOnlyToOwner => true;

View file

@ -1,7 +1,7 @@
using Robust.Shared.GameStates;
using Robust.Shared.Player;
namespace Content.Shared.Changeling;
namespace Content.Shared.Changeling.Components;
/// <summary>
/// Marker component for cloned identities devoured by a changeling.

View file

@ -1,9 +1,10 @@
using Content.Shared.Changeling.Systems;
using Content.Shared.Cloning;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling.Transform;
namespace Content.Shared.Changeling.Components;
/// <summary>
/// The component containing information about Changelings Transformation action

View file

@ -3,6 +3,7 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Armor;
using Content.Shared.Atmos.Rotting;
using Content.Shared.Body.Components;
using Content.Shared.Changeling.Components;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
@ -19,7 +20,7 @@ using Robust.Shared.Network;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared.Changeling.Devour;
namespace Content.Shared.Changeling.Systems;
public sealed class ChangelingDevourSystem : EntitySystem
{

View file

@ -1,4 +1,5 @@
using System.Numerics;
using Content.Shared.Changeling.Components;
using Content.Shared.Cloning;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
@ -9,7 +10,7 @@ using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling;
namespace Content.Shared.Changeling.Systems;
public sealed class ChangelingIdentitySystem : EntitySystem
{

View file

@ -1,6 +1,6 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Changeling.Transform;
namespace Content.Shared.Changeling.Systems;
/// <summary>
/// Send when a player selects an intentity to transform into in the radial menu.

View file

@ -1,5 +1,6 @@
using Content.Shared.Actions;
using Content.Shared.Administration.Logs;
using Content.Shared.Changeling.Components;
using Content.Shared.Cloning;
using Content.Shared.Database;
using Content.Shared.DoAfter;
@ -10,7 +11,7 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling.Transform;
namespace Content.Shared.Changeling.Systems;
public sealed partial class ChangelingTransformSystem : EntitySystem
{
@ -102,7 +103,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
if (_net.IsServer)
ent.Comp.CurrentTransformSound = _audio.PlayPvs(ent.Comp.TransformAttemptNoise, ent)?.Entity;
if(TryComp<ChangelingStoredIdentityComponent>(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null)
if (TryComp<ChangelingStoredIdentityComponent>(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null)
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\" ({storedIdentity.OriginalSession:player}) ");
else
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\"");
@ -162,8 +163,8 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
_humanoidAppearanceSystem.CloneAppearance(targetIdentity, args.User);
_cloningSystem.CloneComponents(targetIdentity, args.User, settings);
if(TryComp<ChangelingStoredIdentityComponent>(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null)
if (TryComp<ChangelingStoredIdentityComponent>(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null)
_adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent.Owner):player} successfully transformed into \"{Name(targetIdentity)}\" ({storedIdentity.OriginalSession:player})");
else
_adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent.Owner):player} successfully transformed into \"{Name(targetIdentity)}\"");

View file

@ -16,7 +16,7 @@ public sealed partial class LimitedChargesComponent : Component
/// <summary>
/// The max charges this action has.
/// </summary>
[DataField, AutoNetworkedField, Access(Other = AccessPermissions.Read)]
[DataField, AutoNetworkedField]
public int MaxCharges = 3;
/// <summary>

View file

@ -102,6 +102,12 @@ public abstract class SharedChargesSystem : EntitySystem
/// <summary>
/// Adds the specified charges. Does not reset the accumulator.
/// </summary>
/// <param name="action">
/// The action to add charges to. If it doesn't have <see cref="LimitedChargesComponent"/>, it will be added.
/// </param>
/// <param name="addCharges">
/// The number of charges to add. Can be negative. Resulting charge count is clamped to [0, MaxCharges].
/// </param>
public void AddCharges(Entity<LimitedChargesComponent?, AutoRechargeComponent?> action, int addCharges)
{
if (addCharges == 0)
@ -178,9 +184,21 @@ public abstract class SharedChargesSystem : EntitySystem
Dirty(action);
}
/// <summary>
/// Set the number of charges an action has.
/// </summary>
/// <param name="action">The action in question</param>
/// <param name="value">
/// The number of charges. Clamped to [0, MaxCharges].
/// </param>
/// <remarks>
/// This method doesn't implicitly add <see cref="LimitedChargesComponent"/>
/// unlike some other methods in this system.
/// </remarks>
public void SetCharges(Entity<LimitedChargesComponent?> action, int value)
{
action.Comp ??= EnsureComp<LimitedChargesComponent>(action.Owner);
if (!Resolve(action, ref action.Comp))
return;
var adjusted = Math.Clamp(value, 0, action.Comp.MaxCharges);
@ -194,6 +212,31 @@ public abstract class SharedChargesSystem : EntitySystem
Dirty(action);
}
/// <summary>
/// Sets the maximum charges of a given action.
/// </summary>
/// <param name="action">The action being modified.</param>
/// <param name="value">The new maximum charges of the action. Clamped to zero.</param>
/// <remarks>
/// Does not change the current charge count, or adjust the
/// accumulator for auto-recharge. It also doesn't implicitly add
/// <see cref="LimitedChargesComponent"/> unlike some other methods
/// in this system.
/// </remarks>
public void SetMaxCharges(Entity<LimitedChargesComponent?> action, int value)
{
if (!Resolve(action, ref action.Comp))
return;
// You can't have negative max charges (even zero is a bit goofy but eh)
var adjusted = Math.Max(0, value);
if (action.Comp.MaxCharges == adjusted)
return;
action.Comp.MaxCharges = adjusted;
Dirty(action);
}
/// <summary>
/// The next time a charge will be considered to be filled.
/// </summary>

View file

@ -63,6 +63,12 @@ namespace Content.Shared.Chemistry.Reagent
[DataField]
public bool Recognizable;
/// <summary>
/// Whether this reagent stands out (blood, slime).
/// </summary>
[DataField]
public bool Standsout;
[DataField]
public ProtoId<FlavorPrototype>? Flavor;

View file

@ -15,6 +15,13 @@ public sealed partial class ClothingSpeedModifierComponent : Component
[DataField]
public float SprintModifier = 1.0f;
/// <summary>
/// An optional required standing state.
/// Set to true if you need to be standing, false if you need to not be standing, null if you don't care.
/// </summary>
[DataField]
public bool? Standing;
}
[Serializable, NetSerializable]

View file

@ -3,6 +3,7 @@ using Content.Shared.Inventory;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@ -12,10 +13,11 @@ namespace Content.Shared.Clothing;
public sealed class ClothingSpeedModifierSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
public override void Initialize()
{
@ -54,8 +56,13 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
if (_toggle.IsActivated(uid))
args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
if (!_toggle.IsActivated(uid))
return;
if (component.Standing != null && !_standing.IsMatchingState(args.Owner, component.Standing.Value))
return;
args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent component, GetVerbsEvent<ExamineVerb> args)

View file

@ -1,23 +1,64 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Gravity;
using Content.Shared.Inventory;
using Content.Shared.Standing;
namespace Content.Shared.Clothing.EntitySystems;
/// <remarks>
/// We check standing state on all clothing because we don't want you to have anti-gravity unless you're standing.
/// This is for balance reasons as it prevents you from wearing anti-grav clothing to cheese being stun cuffed, as
/// well as other worse things.
/// </remarks>
public sealed class AntiGravityClothingSystem : EntitySystem
{
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
SubscribeLocalEvent<AntiGravityClothingComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<AntiGravityClothingComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<DownedEvent>>(OnDowned);
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<StoodEvent>>(OnStood);
}
private void OnIsWeightless(Entity<AntiGravityClothingComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
{
if (args.Args.Handled)
if (args.Args.Handled || _standing.IsDown(args.Owner))
return;
args.Args.Handled = true;
args.Args.IsWeightless = true;
}
private void OnEquipped(Entity<AntiGravityClothingComponent> entity, ref ClothingGotEquippedEvent args)
{
// This clothing item does nothing if we're not standing
if (_standing.IsDown(args.Wearer))
return;
_gravity.RefreshWeightless(args.Wearer, true);
}
private void OnUnequipped(Entity<AntiGravityClothingComponent> entity, ref ClothingGotUnequippedEvent args)
{
// This clothing item does nothing if we're not standing
if (_standing.IsDown(args.Wearer))
return;
_gravity.RefreshWeightless(args.Wearer, false);
}
private void OnDowned(Entity<AntiGravityClothingComponent> entity, ref InventoryRelayedEvent<DownedEvent> args)
{
_gravity.RefreshWeightless(args.Owner, false);
}
private void OnStood(Entity<AntiGravityClothingComponent> entity, ref InventoryRelayedEvent<StoodEvent> args)
{
_gravity.RefreshWeightless(args.Owner, true);
}
}

View file

@ -1,4 +1,5 @@
using Content.Shared.Alert;
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@ -16,10 +17,4 @@ public sealed partial class MagbootsComponent : Component
/// </summary>
[DataField]
public bool RequiresGrid = true;
/// <summary>
/// Slot the clothing has to be worn in to work.
/// </summary>
[DataField]
public string Slot = "shoes";
}

View file

@ -32,14 +32,8 @@ public sealed class SharedMagbootsSystem : EntitySystem
private void OnToggled(Entity<MagbootsComponent> ent, ref ItemToggledEvent args)
{
var (uid, comp) = ent;
// only stick to the floor if being worn in the correct slot
if (_container.TryGetContainingContainer((uid, null, null), out var container) &&
_inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
&& uid == worn)
{
if (_container.TryGetContainingContainer((ent.Owner, null, null), out var container))
UpdateMagbootEffects(container.Owner, ent, args.Activated);
}
}
private void OnGotUnequipped(Entity<MagbootsComponent> ent, ref ClothingGotUnequippedEvent args)
@ -58,6 +52,8 @@ public sealed class SharedMagbootsSystem : EntitySystem
if (TryComp<MovedByPressureComponent>(user, out var moved))
moved.Enabled = !state;
_gravity.RefreshWeightless(user, !state);
if (state)
_alerts.ShowAlert(user, ent.Comp.MagbootsAlert);
else

Some files were not shown because too many files have changed in this diff Show more