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:
commit
252e81c728
344 changed files with 11262 additions and 1829 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Content.Shared.Changeling.Transform;
|
||||
using Content.Shared.Changeling.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
5
Content.Client/Implants/SubdermalImplantSystem.cs
Normal file
5
Content.Client/Implants/SubdermalImplantSystem.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Implants;
|
||||
|
||||
namespace Content.Client.Implants;
|
||||
|
||||
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using Content.Shared.Kitchen;
|
||||
|
||||
namespace Content.Client.Kitchen;
|
||||
|
||||
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
|
||||
{
|
||||
|
||||
}
|
||||
5
Content.Client/Morgue/CrematoriumSystem.cs
Normal file
5
Content.Client/Morgue/CrematoriumSystem.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Morgue;
|
||||
|
||||
namespace Content.Client.Morgue;
|
||||
|
||||
public sealed class CrematoriumSystem : SharedCrematoriumSystem;
|
||||
5
Content.Client/Morgue/MorgueSystem.cs
Normal file
5
Content.Client/Morgue/MorgueSystem.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Morgue;
|
||||
|
||||
namespace Content.Client.Morgue;
|
||||
|
||||
public sealed class MorgueSystem : SharedMorgueSystem;
|
||||
|
|
@ -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'}" />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
|
|||
components:
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: GravityAffected
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||
- type: Alerts
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: GravityAffected
|
||||
|
||||
- type: entity
|
||||
name: WeightlessGravityGeneratorDummy
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ public abstract partial class InteractionTest
|
|||
- type: Stripping
|
||||
- type: Puller
|
||||
- type: Physics
|
||||
- type: GravityAffected
|
||||
- type: Tag
|
||||
tags:
|
||||
- CanPilot
|
||||
|
|
|
|||
2125
Content.Server.Database/Migrations/Postgres/20250723055137_AdminLogsCurtime.Designer.cs
generated
Normal file
2125
Content.Server.Database/Migrations/Postgres/20250723055137_AdminLogsCurtime.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
2048
Content.Server.Database/Migrations/Sqlite/20250723055127_AdminLogsCurtime.Designer.cs
generated
Normal file
2048
Content.Server.Database/Migrations/Sqlite/20250723055127_AdminLogsCurtime.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
namespace Content.Server.Zombies
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ZombifyOnDeathComponent : Component
|
||||
{
|
||||
//this is not the component you are looking for
|
||||
}
|
||||
}
|
||||
|
|
@ -9,5 +9,6 @@ public readonly record struct SharedAdminLog(
|
|||
LogType Type,
|
||||
LogImpact Impact,
|
||||
DateTime Date,
|
||||
long CurTime,
|
||||
string Message,
|
||||
Guid[] Players);
|
||||
|
|
|
|||
9
Content.Shared/Body/Events/BleedModifierEvent.cs
Normal file
9
Content.Shared/Body/Events/BleedModifierEvent.cs
Normal 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);
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)}\"");
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue