Merge branch 'master' into DONOTMAPTEST
This commit is contained in:
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog" };
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog", "Convex"};
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -39,6 +39,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
||||
if (message is not CargoBountyConsoleState state)
|
||||
return;
|
||||
|
||||
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
|
||||
_menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
|
||||
}
|
||||
}
|
||||
|
||||
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal file
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="10 10 10 0"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<RichTextLabel Name="RewardLabel"/>
|
||||
<RichTextLabel Name="ManifestLabel"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" MinWidth="120" Margin="0 0 10 0">
|
||||
<RichTextLabel Name="TimestampLabel" HorizontalAlignment="Right" />
|
||||
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator Margin="5 10 5 10"/>
|
||||
<RichTextLabel Name="NoticeLabel" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal file
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BountyHistoryEntry : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public BountyHistoryEntry(CargoBountyHistoryData bounty)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
var items = new List<string>();
|
||||
foreach (var entry in bountyPrototype.Entries)
|
||||
{
|
||||
items.Add(Loc.GetString("bounty-console-manifest-entry",
|
||||
("amount", entry.Amount),
|
||||
("item", Loc.GetString(entry.Name))));
|
||||
}
|
||||
|
||||
ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
|
||||
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
|
||||
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
|
||||
|
||||
TimestampLabel.SetMarkup(bounty.Timestamp.ToString(@"hh\:mm\:ss"));
|
||||
|
||||
if (bounty.Result == CargoBountyHistoryData.BountyResult.Completed)
|
||||
{
|
||||
NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label"));
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
|
||||
("id", bounty.ActorName ?? "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,28 @@
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Name="BountyEntriesContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</BoxContainer>
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<Label Name="NoHistoryLabel"
|
||||
Text="{Loc 'bounty-console-history-empty-label'}"
|
||||
Visible="False"
|
||||
Align="Center" />
|
||||
<BoxContainer Name="BountyHistoryContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
</PanelContainer>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
|
||||
public CargoBountyMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
|
||||
MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
|
||||
}
|
||||
|
||||
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||
public void UpdateEntries(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
|
||||
{
|
||||
BountyEntriesContainer.Children.Clear();
|
||||
foreach (var b in bounties)
|
||||
@@ -32,5 +35,21 @@ public sealed partial class CargoBountyMenu : FancyWindow
|
||||
{
|
||||
MinHeight = 10
|
||||
});
|
||||
|
||||
BountyHistoryContainer.Children.Clear();
|
||||
if (history.Count == 0)
|
||||
{
|
||||
NoHistoryLabel.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NoHistoryLabel.Visible = false;
|
||||
|
||||
// Show the history in reverse, so last entry is first in the list
|
||||
for (var i = history.Count - 1; i >= 0; i--)
|
||||
{
|
||||
BountyHistoryContainer.AddChild(new BountyHistoryEntry(history[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterfac
|
||||
SendMessage(new CriminalRecordChangeStatus(status, null));
|
||||
_window.OnDialogConfirmed += (status, reason) =>
|
||||
SendMessage(new CriminalRecordChangeStatus(status, reason));
|
||||
_window.OnStatusFilterPressed += (statusFilter) =>
|
||||
SendMessage(new CriminalRecordSetStatusFilter(statusFilter));
|
||||
_window.OnHistoryUpdated += UpdateHistory;
|
||||
_window.OnHistoryClosed += () => _historyWindow?.Close();
|
||||
_window.OnClose += Close;
|
||||
|
||||
@@ -1,36 +1,142 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'criminal-records-console-window-title'}"
|
||||
MinSize="660 400">
|
||||
MinSize="695 440">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<!-- Record search bar
|
||||
TODO: make this into a control shared with general records -->
|
||||
<BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center">
|
||||
<OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor -->
|
||||
<LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/>
|
||||
<BoxContainer Name="AllList"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="8">
|
||||
<!-- Record search bar -->
|
||||
<BoxContainer Margin="5 5 5 10"
|
||||
HorizontalExpand="true"
|
||||
VerticalAlignment="Center">
|
||||
<OptionButton Name="FilterType"
|
||||
MinWidth="250"
|
||||
Margin="0 0 10 0" />
|
||||
<!-- Populated in constructor -->
|
||||
<LineEdit Name="FilterText"
|
||||
PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
VerticalExpand="True">
|
||||
<!-- Record listing -->
|
||||
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250">
|
||||
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/>
|
||||
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Margin="10 10"
|
||||
MinWidth="250"
|
||||
MaxWidth="250">
|
||||
<Label Name="RecordListingTitle"
|
||||
Text="{Loc 'criminal-records-console-records-list-title'}"
|
||||
HorizontalExpand="True"
|
||||
Align="Center" />
|
||||
<Label Name="NoRecords"
|
||||
Text="{Loc 'criminal-records-console-no-records'}"
|
||||
HorizontalExpand="True"
|
||||
Align="Center"
|
||||
FontColorOverride="DarkGray" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<ItemList Name="RecordListing"/> <!-- Populated when loading state -->
|
||||
<ItemList Name="RecordListing" />
|
||||
<!-- Populated when loading state -->
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
|
||||
<Label Name="RecordUnselected"
|
||||
Text="{Loc 'criminal-records-console-select-record-info'}"
|
||||
HorizontalExpand="True"
|
||||
Align="Center"
|
||||
FontColorOverride="DarkGray" />
|
||||
<!-- Selected record info -->
|
||||
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False">
|
||||
<Label Name="PersonName" StyleClasses="LabelBig"/>
|
||||
<Label Name="PersonPrints"/>
|
||||
<Label Name="PersonDna"/>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
|
||||
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/>
|
||||
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor -->
|
||||
<BoxContainer Name="PersonContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="5"
|
||||
Visible="False">
|
||||
<Label Name="PersonName"
|
||||
Margin="0 0 0 5"
|
||||
StyleClasses="LabelBig" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}:"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
FontColorOverride="DarkGray" />
|
||||
<TextureRect Name="PersonJobIcon"
|
||||
TextureScale="2 2"
|
||||
Margin="6 0"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Name="PersonJob" />
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="WantedReason" Visible="False"/>
|
||||
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<Label Text="{Loc 'general-station-record-prints-filter'}"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
Margin="0 0 6 0"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Name="PersonPrints" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<Label Text="{Loc 'general-station-record-dna-filter'}"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
Margin="0 0 6 0"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Name="PersonDna" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider"
|
||||
Margin="0 5 0 5" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 5 0 5">
|
||||
<Label Name="StatusLabel"
|
||||
Text="{Loc 'criminal-records-console-status'}"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Name="PersonStatus"
|
||||
FontColorOverride="DarkGray" />
|
||||
<AnimatedTextureRect Name="PersonStatusTX"
|
||||
Margin="8 0" />
|
||||
<OptionButton Name="StatusOptionButton"
|
||||
MinWidth="130" />
|
||||
<!-- Populated in constructor -->
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="WantedReason"
|
||||
Visible="False"
|
||||
MaxWidth="425" />
|
||||
<Button Name="HistoryButton"
|
||||
Text="{Loc 'criminal-records-console-crime-history'}"
|
||||
Margin="0 5" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<OptionButton
|
||||
Name="CrewListFilter"
|
||||
MinWidth="250"
|
||||
Margin="10 0 10 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="10 2 5 0"
|
||||
VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'criminal-records-console-flavor-left'}"
|
||||
StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'criminal-records-console-flavor-right'}"
|
||||
StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark"
|
||||
Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
SetSize="19 19" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -13,6 +13,9 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.CriminalRecords;
|
||||
|
||||
@@ -24,6 +27,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
private readonly IPrototypeManager _proto;
|
||||
private readonly IRobustRandom _random;
|
||||
private readonly AccessReaderSystem _accessReader;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public readonly EntityUid Console;
|
||||
|
||||
@@ -33,10 +38,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
public Action<uint?>? OnKeySelected;
|
||||
public Action<StationRecordFilterType, string>? OnFiltersChanged;
|
||||
public Action<SecurityStatus>? OnStatusSelected;
|
||||
public Action<uint>? OnCheckStatus;
|
||||
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
|
||||
public Action? OnHistoryClosed;
|
||||
public Action<SecurityStatus, string>? OnDialogConfirmed;
|
||||
|
||||
public Action<SecurityStatus>? OnStatusFilterPressed;
|
||||
private uint _maxLength;
|
||||
private bool _access;
|
||||
private uint? _selectedKey;
|
||||
@@ -46,6 +53,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
private StationRecordFilterType _currentFilterType;
|
||||
|
||||
private SecurityStatus _currentCrewListFilter;
|
||||
|
||||
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -55,10 +64,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
_proto = prototypeManager;
|
||||
_random = robustRandom;
|
||||
_accessReader = accessReader;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
_maxLength = maxLength;
|
||||
_currentFilterType = StationRecordFilterType.Name;
|
||||
|
||||
_currentCrewListFilter = SecurityStatus.None;
|
||||
|
||||
OpenCentered();
|
||||
|
||||
foreach (var item in Enum.GetValues<StationRecordFilterType>())
|
||||
@@ -71,6 +84,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
AddStatusSelect(status);
|
||||
}
|
||||
|
||||
//Populate status to filter crew list
|
||||
foreach (var item in Enum.GetValues<SecurityStatus>())
|
||||
{
|
||||
CrewListFilter.AddItem(GetCrewListFilterLocals(item), (int)item);
|
||||
}
|
||||
|
||||
OnClose += () => _reasonDialog?.Close();
|
||||
|
||||
RecordListing.OnItemSelected += args =>
|
||||
@@ -97,6 +116,20 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
}
|
||||
};
|
||||
|
||||
//Select Status to filter crew
|
||||
CrewListFilter.OnItemSelected += eventArgs =>
|
||||
{
|
||||
var type = (SecurityStatus)eventArgs.Id;
|
||||
|
||||
if (_currentCrewListFilter != type)
|
||||
{
|
||||
_currentCrewListFilter = type;
|
||||
|
||||
StatusFilterPressed(type);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
FilterText.OnTextEntered += args =>
|
||||
{
|
||||
FilterListingOfRecords(args.Text);
|
||||
@@ -104,16 +137,21 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
StatusOptionButton.OnItemSelected += args =>
|
||||
{
|
||||
SetStatus((SecurityStatus) args.Id);
|
||||
SetStatus((SecurityStatus)args.Id);
|
||||
};
|
||||
|
||||
HistoryButton.OnPressed += _ =>
|
||||
{
|
||||
if (_selectedRecord is {} record)
|
||||
if (_selectedRecord is { } record)
|
||||
OnHistoryUpdated?.Invoke(record, _access, true);
|
||||
};
|
||||
}
|
||||
|
||||
public void StatusFilterPressed(SecurityStatus statusSelected)
|
||||
{
|
||||
OnStatusFilterPressed?.Invoke(statusSelected);
|
||||
}
|
||||
|
||||
public void UpdateState(CriminalRecordsConsoleState state)
|
||||
{
|
||||
if (state.Filter != null)
|
||||
@@ -129,10 +167,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
if (state.FilterStatus != _currentCrewListFilter)
|
||||
{
|
||||
_currentCrewListFilter = state.FilterStatus;
|
||||
}
|
||||
|
||||
_selectedKey = state.SelectedKey;
|
||||
|
||||
FilterType.SelectId((int)_currentFilterType);
|
||||
|
||||
CrewListFilter.SelectId((int)_currentCrewListFilter);
|
||||
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
|
||||
PopulateRecordListing(state.RecordListing);
|
||||
|
||||
@@ -179,7 +221,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
// in parallel to synchronize the items in RecordListing with `entries`.
|
||||
int i = RecordListing.Count - 1;
|
||||
int j = entries.Count - 1;
|
||||
while(i >= 0 && j >= 0)
|
||||
while (i >= 0 && j >= 0)
|
||||
{
|
||||
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
|
||||
if (strcmp == 0)
|
||||
@@ -212,23 +254,44 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
|
||||
RecordListing.Insert(0, new ItemList.Item(RecordListing){ Text = entries[j].Value, Metadata = entries[j].Key });
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");
|
||||
var na = Loc.GetString("generic-not-available-shorthand");
|
||||
PersonName.Text = stationRecord.Name;
|
||||
PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na));
|
||||
PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
|
||||
PersonJob.Text = stationRecord.JobTitle ?? na;
|
||||
|
||||
StatusOptionButton.SelectId((int) criminalRecord.Status);
|
||||
if (criminalRecord.Reason is {} reason)
|
||||
// Job icon
|
||||
if (_proto.TryIndex<JobIconPrototype>(stationRecord.JobIcon, out var proto))
|
||||
{
|
||||
PersonJobIcon.Texture = _spriteSystem.Frame0(proto.Icon);
|
||||
}
|
||||
|
||||
PersonPrints.Text = stationRecord.Fingerprint ?? Loc.GetString("generic-not-available-shorthand");
|
||||
PersonDna.Text = stationRecord.DNA ?? Loc.GetString("generic-not-available-shorthand");
|
||||
|
||||
if (criminalRecord.Status != SecurityStatus.None)
|
||||
{
|
||||
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/security_icons.rsi"), GetStatusIcon(criminalRecord.Status));
|
||||
}
|
||||
PersonStatusTX.SetFromSpriteSpecifier(specifier);
|
||||
PersonStatusTX.DisplayRect.TextureScale = new Vector2(3f, 3f);
|
||||
|
||||
StatusOptionButton.SelectId((int)criminalRecord.Status);
|
||||
if (criminalRecord.Reason is { } reason)
|
||||
{
|
||||
var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason"));
|
||||
|
||||
if (criminalRecord.Status == SecurityStatus.Suspected)
|
||||
{
|
||||
message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-suspected-reason"));
|
||||
}
|
||||
message.AddText($": {reason}");
|
||||
|
||||
WantedReason.SetMessage(message);
|
||||
WantedReason.Visible = true;
|
||||
}
|
||||
@@ -288,9 +351,37 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
_reasonDialog.OnClose += () => { _reasonDialog = null; };
|
||||
}
|
||||
|
||||
private string GetStatusIcon(SecurityStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
SecurityStatus.Paroled => "hud_paroled",
|
||||
SecurityStatus.Wanted => "hud_wanted",
|
||||
SecurityStatus.Detained => "hud_incarcerated",
|
||||
SecurityStatus.Discharged => "hud_discharged",
|
||||
SecurityStatus.Suspected => "hud_suspected",
|
||||
_ => "SecurityIconNone"
|
||||
};
|
||||
}
|
||||
private string GetTypeFilterLocals(StationRecordFilterType type)
|
||||
{
|
||||
return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter");
|
||||
}
|
||||
|
||||
private string GetCrewListFilterLocals(SecurityStatus type)
|
||||
{
|
||||
string result;
|
||||
|
||||
// If "NONE" override to "show all"
|
||||
if (type == SecurityStatus.None)
|
||||
{
|
||||
result = Loc.GetString("criminal-records-console-show-all");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Loc.GetString($"criminal-records-status-{type.ToString().ToLower()}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,112 +21,131 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
comp.OpenSpriteStates = new(2);
|
||||
comp.ClosedSpriteStates = new(2);
|
||||
comp.OpenSpriteStates = new List<(DoorVisualLayers, string)>(2);
|
||||
comp.ClosedSpriteStates = new List<(DoorVisualLayers, string)>(2);
|
||||
|
||||
comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
|
||||
comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));
|
||||
|
||||
comp.OpeningAnimation = new Animation()
|
||||
comp.OpeningAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DoorVisualLayers.Base,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f) }
|
||||
}
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
comp.ClosingAnimation = new Animation()
|
||||
comp.ClosingAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DoorVisualLayers.Base,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f) }
|
||||
}
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
comp.EmaggingAnimation = new Animation ()
|
||||
comp.EmaggingAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DoorVisualLayers.BaseUnlit,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.EmaggingSpriteState, 0f) }
|
||||
}
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(comp.EmaggingSpriteState, 0f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, DoorComponent comp, ref AppearanceChangeEvent args)
|
||||
private void OnAppearanceChange(Entity<DoorComponent> entity, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if(!AppearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
|
||||
if (!AppearanceSystem.TryGetData<DoorState>(entity, DoorVisuals.State, out var state, args.Component))
|
||||
state = DoorState.Closed;
|
||||
|
||||
if (AppearanceSystem.TryGetData<string>(uid, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
|
||||
if (AppearanceSystem.TryGetData<string>(entity, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
|
||||
UpdateSpriteLayers(args.Sprite, baseRsi);
|
||||
|
||||
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
|
||||
_animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
|
||||
|
||||
UpdateAppearanceForDoorState(entity, args.Sprite, state);
|
||||
}
|
||||
|
||||
private void UpdateAppearanceForDoorState(Entity<DoorComponent> entity, SpriteComponent sprite, DoorState state)
|
||||
{
|
||||
sprite.DrawDepth = state is DoorState.Open ? entity.Comp.OpenDrawDepth : entity.Comp.ClosedDrawDepth;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case DoorState.Open:
|
||||
foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
|
||||
{
|
||||
sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
|
||||
return;
|
||||
case DoorState.Closed:
|
||||
foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
|
||||
{
|
||||
sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
|
||||
return;
|
||||
case DoorState.Opening:
|
||||
if (entity.Comp.OpeningAnimationTime == 0.0)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Closing:
|
||||
if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Denying:
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Emagging:
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpriteLayers(SpriteComponent sprite, string baseRsi)
|
||||
{
|
||||
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
|
||||
{
|
||||
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
|
||||
}
|
||||
foreach (var layer in args.Sprite.AllLayers)
|
||||
{
|
||||
layer.Rsi = res?.RSI;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TryComp<AnimationPlayerComponent>(uid, out var animPlayer);
|
||||
if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey))
|
||||
_animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
|
||||
|
||||
args.Sprite.DrawDepth = comp.ClosedDrawDepth;
|
||||
switch(state)
|
||||
{
|
||||
case DoorState.Open:
|
||||
args.Sprite.DrawDepth = comp.OpenDrawDepth;
|
||||
foreach(var (layer, layerState) in comp.OpenSpriteStates)
|
||||
{
|
||||
args.Sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
break;
|
||||
case DoorState.Closed:
|
||||
foreach(var (layer, layerState) in comp.ClosedSpriteStates)
|
||||
{
|
||||
args.Sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
break;
|
||||
case DoorState.Opening:
|
||||
if (animPlayer != null && comp.OpeningAnimationTime != 0.0)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Closing:
|
||||
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Denying:
|
||||
if (animPlayer != null)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Welded:
|
||||
break;
|
||||
case DoorState.Emagging:
|
||||
if (animPlayer != null)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
|
||||
}
|
||||
sprite.BaseRSI = res.RSI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic.Events;
|
||||
|
||||
namespace Content.Client.Magic;
|
||||
|
||||
public sealed class MagicSystem : SharedMagicSystem;
|
||||
public sealed class MagicSystem : SharedMagicSystem
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Orbit;
|
||||
|
||||
@@ -11,8 +12,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animations = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly string _orbitAnimationKey = "orbiting";
|
||||
private readonly string _orbitStopKey = "orbiting_stop";
|
||||
|
||||
public override void Initialize()
|
||||
@@ -21,11 +22,11 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
|
||||
{
|
||||
_robustRandom.SetSeed((int)_timing.CurTime.TotalMilliseconds);
|
||||
component.OrbitDistance =
|
||||
_robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * component.OrbitDistance);
|
||||
|
||||
@@ -38,15 +39,10 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
}
|
||||
|
||||
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
|
||||
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
|
||||
return;
|
||||
|
||||
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
|
||||
{
|
||||
_animations.Stop(uid, animationPlayer, _orbitStopKey);
|
||||
_animations.Stop((uid, animationPlayer), _orbitStopKey);
|
||||
}
|
||||
|
||||
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
|
||||
}
|
||||
|
||||
private void OnComponentRemove(EntityUid uid, OrbitVisualsComponent component, ComponentRemove args)
|
||||
@@ -57,14 +53,9 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
sprite.EnableDirectionOverride = false;
|
||||
|
||||
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
|
||||
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
|
||||
{
|
||||
_animations.Stop(uid, animationPlayer, _orbitAnimationKey);
|
||||
}
|
||||
|
||||
if (!_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
|
||||
{
|
||||
_animations.Play(uid, animationPlayer, GetStopAnimation(component, sprite), _orbitStopKey);
|
||||
_animations.Play((uid, animationPlayer), GetStopAnimation(component, sprite), _orbitStopKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +65,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
|
||||
foreach (var (orbit, sprite) in EntityManager.EntityQuery<OrbitVisualsComponent, SpriteComponent>())
|
||||
{
|
||||
var angle = new Angle(Math.PI * 2 * orbit.Orbit);
|
||||
var progress = (float)(_timing.CurTime.TotalSeconds / orbit.OrbitLength) % 1;
|
||||
var angle = new Angle(Math.PI * 2 * progress);
|
||||
var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0));
|
||||
|
||||
sprite.Rotation = angle;
|
||||
@@ -82,38 +74,6 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, OrbitVisualsComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key == _orbitAnimationKey && TryComp(uid, out AnimationPlayerComponent? animationPlayer))
|
||||
{
|
||||
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
|
||||
}
|
||||
}
|
||||
|
||||
private Animation GetOrbitAnimation(OrbitVisualsComponent component)
|
||||
{
|
||||
var length = component.OrbitLength;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(length),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(OrbitVisualsComponent),
|
||||
Property = nameof(OrbitVisualsComponent.Orbit),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(0.0f, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(1.0f, length),
|
||||
},
|
||||
InterpolationMode = AnimationInterpolationMode.Linear
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Animation GetStopAnimation(OrbitVisualsComponent component, SpriteComponent sprite)
|
||||
{
|
||||
var length = component.OrbitStopLength;
|
||||
|
||||
@@ -56,35 +56,35 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
||||
|
||||
protected virtual void DeactivateInternal() { }
|
||||
|
||||
private void OnStartup(EntityUid uid, T component, ComponentStartup args)
|
||||
private void OnStartup(Entity<T> ent, ref ComponentStartup args)
|
||||
{
|
||||
RefreshOverlay(uid);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, T component, ComponentRemove args)
|
||||
private void OnRemove(Entity<T> ent, ref ComponentRemove args)
|
||||
{
|
||||
RefreshOverlay(uid);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(LocalPlayerAttachedEvent args)
|
||||
{
|
||||
RefreshOverlay(args.Entity);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(LocalPlayerDetachedEvent args)
|
||||
{
|
||||
if (_player.LocalSession?.AttachedEntity == null)
|
||||
if (_player.LocalSession?.AttachedEntity is null)
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
private void OnCompEquip(EntityUid uid, T component, GotEquippedEvent args)
|
||||
private void OnCompEquip(Entity<T> ent, ref GotEquippedEvent args)
|
||||
{
|
||||
RefreshOverlay(args.Equipee);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnCompUnequip(EntityUid uid, T component, GotUnequippedEvent args)
|
||||
private void OnCompUnequip(Entity<T> ent, ref GotUnequippedEvent args)
|
||||
{
|
||||
RefreshOverlay(args.Equipee);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent args)
|
||||
@@ -92,24 +92,24 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
protected virtual void OnRefreshEquipmentHud(EntityUid uid, T component, InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
|
||||
protected virtual void OnRefreshEquipmentHud(Entity<T> ent, ref InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
|
||||
{
|
||||
OnRefreshComponentHud(uid, component, args.Args);
|
||||
OnRefreshComponentHud(ent, ref args.Args);
|
||||
}
|
||||
|
||||
protected virtual void OnRefreshComponentHud(EntityUid uid, T component, RefreshEquipmentHudEvent<T> args)
|
||||
protected virtual void OnRefreshComponentHud(Entity<T> ent, ref RefreshEquipmentHudEvent<T> args)
|
||||
{
|
||||
args.Active = true;
|
||||
args.Components.Add(component);
|
||||
args.Components.Add(ent.Comp);
|
||||
}
|
||||
|
||||
protected void RefreshOverlay(EntityUid uid)
|
||||
protected void RefreshOverlay()
|
||||
{
|
||||
if (uid != _player.LocalSession?.AttachedEntity)
|
||||
if (_player.LocalSession?.AttachedEntity is not { } entity)
|
||||
return;
|
||||
|
||||
var ev = new RefreshEquipmentHudEvent<T>(TargetSlots);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
|
||||
if (ev.Active)
|
||||
Update(ev);
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
|
||||
|
||||
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
RefreshOverlay(ent);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
|
||||
|
||||
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
RefreshOverlay(ent);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
|
||||
|
||||
@@ -2,49 +2,128 @@
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.ParticleAccelerator.UI"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'particle-accelerator-control-menu-device-version-label'}"
|
||||
MinSize="420 320"
|
||||
SetSize="420 320">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0">
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 5">
|
||||
MinSize="320 120">
|
||||
|
||||
<!-- Main Container -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
|
||||
<!-- Sub-Main container -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
<!-- Info part -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
Margin="8">
|
||||
|
||||
<!-- Info -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
SeparationOverride="4">
|
||||
|
||||
<!-- Status -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="StatusLabel" HorizontalExpand="True"/>
|
||||
<Control MinWidth="8"/>
|
||||
<RichTextLabel Name="StatusStateLabel"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Power -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="PowerLabel" Margin="0 0 20 0" HorizontalExpand="True" VerticalAlignment="Center"/>
|
||||
<Button Name="OffButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-off-button'}" StyleClasses="OpenRight"/>
|
||||
<Button Name="OnButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-on-button'}" StyleClasses="OpenLeft"/>
|
||||
<RichTextLabel Name="PowerLabel"
|
||||
HorizontalExpand="True"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Control MinWidth="8"/>
|
||||
|
||||
<Button Name="OffButton"
|
||||
ToggleMode="False"
|
||||
Text="{Loc 'particle-accelerator-control-menu-off-button'}"
|
||||
StyleClasses="OpenRight"/>
|
||||
|
||||
<Button Name="OnButton"
|
||||
ToggleMode="False"
|
||||
Text="{Loc 'particle-accelerator-control-menu-on-button'}"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Strenght -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="StrengthLabel" Margin="0 0 20 0" HorizontalExpand="True" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<RichTextLabel Name="StrengthLabel"
|
||||
HorizontalExpand="True"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Control MinWidth="8"/>
|
||||
|
||||
<SpinBox Name="StateSpinBox" Value="0"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Power -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
|
||||
<Control MinWidth="8"/>
|
||||
<RichTextLabel Name="DrawValueLabel"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10" VerticalExpand="True"/>
|
||||
<BoxContainer Name="AlarmControl" Orientation="Vertical" VerticalAlignment="Center" Visible="False">
|
||||
<RichTextLabel Name="BigAlarmLabel" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Name="BigAlarmLabelTwo" HorizontalAlignment="Center"/>
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}" HorizontalAlignment="Center" StyleClasses="LabelSubText"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10" VerticalExpand="True"/>
|
||||
|
||||
<Control MinHeight="8" VerticalExpand="True"/> <!-- Filler -->
|
||||
|
||||
<!-- Alarm -->
|
||||
<BoxContainer Name="AlarmControl"
|
||||
Orientation="Vertical"
|
||||
VerticalAlignment="Center"
|
||||
Visible="False">
|
||||
|
||||
<controls:StripeBack Margin="-8 0">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="BigAlarmLabel"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
<RichTextLabel Name="BigAlarmLabelTwo"
|
||||
HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<customControls:VSeparator Margin="0 0 0 10"/>
|
||||
<BoxContainer Orientation="Vertical" Margin="20 0 20 0" VerticalAlignment="Center">
|
||||
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
|
||||
</controls:StripeBack>
|
||||
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}"
|
||||
HorizontalAlignment="Center"
|
||||
StyleClasses="LabelSubText"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 -8" HorizontalAlignment="Right"/>
|
||||
|
||||
<!-- PA Visual part -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8">
|
||||
|
||||
<PanelContainer Name="BackPanel"
|
||||
HorizontalAlignment="Center">
|
||||
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxTexture Modulate="#202023" PatchMarginBottom="10" PatchMarginLeft="10" PatchMarginRight="10" PatchMarginTop="10"/>
|
||||
<gfx:StyleBoxTexture Modulate="#202023"
|
||||
PatchMarginBottom="8"
|
||||
PatchMarginLeft="8"
|
||||
PatchMarginRight="8"
|
||||
PatchMarginTop="8"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" HorizontalAlignment="Center" VerticalExpand="True">
|
||||
<GridContainer Columns="3" VSeparationOverride="0" HSeparationOverride="0" HorizontalAlignment="Center">
|
||||
|
||||
<BoxContainer Orientation="Vertical"
|
||||
SeparationOverride="6"
|
||||
VerticalExpand="True"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
HorizontalAlignment="Center">
|
||||
|
||||
<!-- PA Visualisation -->
|
||||
<GridContainer Columns="3"
|
||||
VSeparationOverride="0"
|
||||
HSeparationOverride="0"
|
||||
HorizontalAlignment="Center">
|
||||
|
||||
<Control/>
|
||||
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
|
||||
<Control/>
|
||||
@@ -58,17 +137,47 @@
|
||||
<ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/>
|
||||
<ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/>
|
||||
</GridContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<Button Name="ScanButton" Text="{Loc 'particle-accelerator-control-menu-scan-parts-button'}" HorizontalAlignment="Center"/>
|
||||
|
||||
<Button Name="ScanButton"
|
||||
Text="{Loc 'particle-accelerator-control-menu-scan-parts-button'}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalAlignment="Bottom">
|
||||
|
||||
<controls:StripeBack>
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/>
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}"
|
||||
HorizontalAlignment="Center"
|
||||
StyleClasses="LabelSubText"
|
||||
Margin="0 4"/>
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Orientation="Horizontal" Margin="12 0 0 0">
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-foo-bar-baz'}" StyleClasses="LabelSubText"/>
|
||||
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="12 0 6 2"
|
||||
VerticalAlignment="Bottom">
|
||||
|
||||
<!-- Footer title -->
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-left'}"
|
||||
StyleClasses="WindowFooterText" />
|
||||
|
||||
<!-- Version -->
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-right'}"
|
||||
StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 0 4 0" />
|
||||
|
||||
<TextureRect StyleClasses="NTLogoDark"
|
||||
Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -81,13 +81,19 @@ public sealed partial class NavScreen : BoxContainer
|
||||
// Get the positive reduced angle.
|
||||
var displayRot = -worldRot.Reduced();
|
||||
|
||||
GridPosition.Text = $"{worldPos.X:0.0}, {worldPos.Y:0.0}";
|
||||
GridOrientation.Text = $"{displayRot.Degrees:0.0}";
|
||||
GridPosition.Text = Loc.GetString("shuttle-console-position-value",
|
||||
("X", $"{worldPos.X:0.0}"),
|
||||
("Y", $"{worldPos.Y:0.0}"));
|
||||
GridOrientation.Text = Loc.GetString("shuttle-console-orientation-value",
|
||||
("angle", $"{displayRot.Degrees:0.0}"));
|
||||
|
||||
var gridVelocity = gridBody.LinearVelocity;
|
||||
gridVelocity = displayRot.RotateVec(gridVelocity);
|
||||
// Get linear velocity relative to the console entity
|
||||
GridLinearVelocity.Text = $"{gridVelocity.X + 10f * float.Epsilon:0.0}, {gridVelocity.Y + 10f * float.Epsilon:0.0}";
|
||||
GridAngularVelocity.Text = $"{-gridBody.AngularVelocity + 10f * float.Epsilon:0.0}";
|
||||
GridLinearVelocity.Text = Loc.GetString("shuttle-console-linear-velocity-value",
|
||||
("X", $"{gridVelocity.X + 10f * float.Epsilon:0.0}"),
|
||||
("Y", $"{gridVelocity.Y + 10f * float.Epsilon:0.0}"));
|
||||
GridAngularVelocity.Text = Loc.GetString("shuttle-console-angular-velocity-value",
|
||||
("angularVelocity", $"{-MathHelper.RadiansToDegrees(gridBody.AngularVelocity) + 10f * float.Epsilon:0.0}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
|
||||
_inputManager.ViewportKeyEvent(this, args);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
protected override void Draw(IRenderHandle handle)
|
||||
{
|
||||
EnsureViewportCreated();
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
|
||||
var drawBox = GetDrawBox();
|
||||
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
|
||||
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
|
||||
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ public sealed partial class MeleeWeaponSystem
|
||||
const float length = 0.15f;
|
||||
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
|
||||
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
|
||||
sprite.Rotation += spriteRotation;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
|
||||
@@ -70,7 +70,9 @@ namespace Content.IntegrationTests.Tests
|
||||
"Amber",
|
||||
"Loop",
|
||||
"Plasma",
|
||||
"Elkridge"
|
||||
"Elkridge",
|
||||
"Convex"
|
||||
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Store.Systems;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.StoreDiscount.Components;
|
||||
@@ -64,6 +65,7 @@ public sealed class StoreTests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var invSystem = entManager.System<InventorySystem>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
|
||||
human = entManager.SpawnEntity("HumanUniformDummy", coordinates);
|
||||
uniform = entManager.SpawnEntity("UniformDummy", coordinates);
|
||||
@@ -72,6 +74,9 @@ public sealed class StoreTests
|
||||
Assert.That(invSystem.TryEquip(human, uniform, "jumpsuit"));
|
||||
Assert.That(invSystem.TryEquip(human, pda, "id"));
|
||||
|
||||
var mind = mindSystem.CreateMind(null);
|
||||
mindSystem.TransferTo(mind, human, mind: mind);
|
||||
|
||||
FixedPoint2 originalBalance = 20;
|
||||
uplinkSystem.AddUplink(human, originalBalance, null, true);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
@@ -30,12 +29,10 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
if (!EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
{
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,7 +55,7 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
{
|
||||
if (!wire.IsCut)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access) && !EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access))
|
||||
{
|
||||
access.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, access);
|
||||
|
||||
@@ -245,6 +245,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||
|
||||
var ev = new OnAccessOverriderAccessUpdatedEvent(player);
|
||||
RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
|
||||
|
||||
Dirty(accessReaderEnt.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
null);
|
||||
|
||||
await _db.AddServerBanAsync(banDef);
|
||||
if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
|
||||
await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
|
||||
var adminName = banningAdmin == null
|
||||
? Loc.GetString("system-user")
|
||||
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||
|
||||
@@ -892,5 +892,36 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description"))
|
||||
};
|
||||
args.Verbs.Add(superslip);
|
||||
|
||||
var omniaccentName = Loc.GetString("admin-smite-omni-accent-name").ToLowerInvariant();
|
||||
Verb omniaccent = new()
|
||||
{
|
||||
Text = omniaccentName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new("Interface/Actions/voice-mask.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<BarkAccentComponent>(args.Target);
|
||||
EnsureComp<BleatingAccentComponent>(args.Target);
|
||||
EnsureComp<FrenchAccentComponent>(args.Target);
|
||||
EnsureComp<GermanAccentComponent>(args.Target);
|
||||
EnsureComp<LizardAccentComponent>(args.Target);
|
||||
EnsureComp<MobsterAccentComponent>(args.Target);
|
||||
EnsureComp<MothAccentComponent>(args.Target);
|
||||
EnsureComp<OwOAccentComponent>(args.Target);
|
||||
EnsureComp<SkeletonAccentComponent>(args.Target);
|
||||
EnsureComp<SouthernAccentComponent>(args.Target);
|
||||
EnsureComp<SpanishAccentComponent>(args.Target);
|
||||
EnsureComp<StutteringAccentComponent>(args.Target);
|
||||
|
||||
if (_random.Next(0, 8) == 0)
|
||||
{
|
||||
EnsureComp<BackwardsAccentComponent>(args.Target); // was asked to make this at a low chance idk
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = string.Join(": ", omniaccentName, Loc.GetString("admin-smite-omni-accent-description"))
|
||||
};
|
||||
args.Verbs.Add(omniaccent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +116,11 @@ public sealed class TechAnomalySystem : EntitySystem
|
||||
|
||||
if (_random.Prob(tech.Comp.EmagSupercritProbability))
|
||||
{
|
||||
_emag.DoEmagEffect(tech, source);
|
||||
_emag.DoEmagEffect(tech, sink);
|
||||
var sourceEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(source, ref sourceEv);
|
||||
|
||||
var sinkEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(sink, ref sinkEv);
|
||||
}
|
||||
|
||||
CreateNewLink(tech, source, sink);
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
@@ -77,11 +78,18 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
return;
|
||||
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Bed
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
@@ -114,7 +115,12 @@ namespace Content.Server.Bed
|
||||
|
||||
private void OnEmagged(EntityUid uid, StasisBedComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
args.Repeatable = true;
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
// Reset any metabolisms first so they receive the multiplier correctly
|
||||
UpdateMetabolisms(uid, component, false);
|
||||
component.Multiplier = 1 / component.Multiplier;
|
||||
|
||||
@@ -45,5 +45,6 @@ public sealed class LogSystem : EntitySystem
|
||||
}
|
||||
|
||||
QueueDel(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
/// <summary>
|
||||
/// Maximum amount of bounties a station can have.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int MaxBounties = 6;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties currently active for a station.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public List<CargoBountyData> Bounties = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties that have been completed or
|
||||
/// skipped for a station.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<CargoBountyHistoryData> History = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine unique order IDs
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -16,6 +17,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -54,7 +57,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
|
||||
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
@@ -95,13 +98,13 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryRemoveBounty(station, bounty.Value))
|
||||
if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
|
||||
return;
|
||||
|
||||
FillBountyDatabase(station);
|
||||
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
_audio.PlayPvs(component.SkipSound, uid);
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
TryRemoveBounty(station, bounty.Value);
|
||||
TryRemoveBounty(station, bounty.Value, false);
|
||||
FillBountyDatabase(station);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
|
||||
}
|
||||
@@ -434,24 +437,44 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
string dataId,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!TryGetBountyFromId(uid, dataId, out var data, component))
|
||||
if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
|
||||
return false;
|
||||
|
||||
return TryRemoveBounty(uid, data.Value, component);
|
||||
return TryRemoveBounty(ent, data.Value, skipped, actor);
|
||||
}
|
||||
|
||||
public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
CargoBountyData data,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < component.Bounties.Count; i++)
|
||||
for (var i = 0; i < ent.Comp.Bounties.Count; i++)
|
||||
{
|
||||
if (component.Bounties[i].Id == data.Id)
|
||||
if (ent.Comp.Bounties[i].Id == data.Id)
|
||||
{
|
||||
component.Bounties.RemoveAt(i);
|
||||
string? actorName = null;
|
||||
if (actor != null)
|
||||
{
|
||||
var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
|
||||
RaiseLocalEvent(getIdentityEvent);
|
||||
actorName = getIdentityEvent.Title;
|
||||
}
|
||||
|
||||
ent.Comp.History.Add(new CargoBountyHistoryData(data,
|
||||
skipped
|
||||
? CargoBountyHistoryData.BountyResult.Skipped
|
||||
: CargoBountyHistoryData.BountyResult.Completed,
|
||||
_gameTiming.CurTime,
|
||||
actorName));
|
||||
ent.Comp.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -492,7 +515,7 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Paper;
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Cargo.Systems
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How much time to wait (in seconds) before increasing bank accounts balance.
|
||||
@@ -41,6 +42,7 @@ namespace Content.Server.Cargo.Systems
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ namespace Content.Server.Cargo.Systems
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
UpdateBankAccount(stationUid.Value, bank, (int) price);
|
||||
QueueDel(args.Used);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args)
|
||||
@@ -75,6 +78,17 @@ namespace Content.Server.Cargo.Systems
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
@@ -85,9 +99,11 @@ namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
_timer -= Delay;
|
||||
|
||||
foreach (var account in EntityQuery<StationBankAccountComponent>())
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
account.Balance += account.IncreasePerSecond * Delay;
|
||||
var balanceToAdd = bank.IncreasePerSecond * Delay;
|
||||
UpdateBankAccount(uid, bank, balanceToAdd);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
|
||||
@@ -192,7 +208,7 @@ namespace Content.Server.Cargo.Systems
|
||||
order.Approved = true;
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, player);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
@@ -213,7 +229,7 @@ namespace Content.Server.Cargo.Systems
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
|
||||
|
||||
orderDatabase.Orders.Remove(order);
|
||||
DeductFunds(bank, cost);
|
||||
UpdateBankAccount(station.Value, bank, -cost);
|
||||
UpdateOrders(station.Value);
|
||||
}
|
||||
|
||||
@@ -536,11 +552,6 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
}
|
||||
|
||||
private void DeductFunds(StationBankAccountComponent component, int amount)
|
||||
{
|
||||
component.Balance = Math.Max(0, component.Balance - amount);
|
||||
}
|
||||
|
||||
#region Station
|
||||
|
||||
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Content.Server.Cloning
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new();
|
||||
public const float EasyModeCloningCost = 0.7f;
|
||||
@@ -276,10 +277,15 @@ namespace Content.Server.Cloning
|
||||
/// </summary>
|
||||
private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(clonePod.SparkSound, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -309,7 +315,7 @@ namespace Content.Server.Cloning
|
||||
var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform));
|
||||
var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_audio.PlayPvs(clonePod.ScreamSound, uid);
|
||||
Spawn(clonePod.MobSpawnId, transform.Coordinates);
|
||||
@@ -327,7 +333,7 @@ namespace Content.Server.Cloning
|
||||
}
|
||||
_puddleSystem.TrySpillAt(uid, bloodSolution, out _);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ using Content.Shared.Chat;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -177,7 +176,7 @@ namespace Content.Server.Communications
|
||||
|
||||
private bool CanUse(EntityUid user, EntityUid console)
|
||||
{
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console))
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent))
|
||||
{
|
||||
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ using Robust.Server.GameObjects;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Security.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
@@ -42,6 +44,7 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
|
||||
subs.Event<CriminalRecordAddHistory>(OnAddHistory);
|
||||
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
|
||||
subs.Event<CriminalRecordSetStatusFilter>(OnStatusFilterPressed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +60,11 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
ent.Comp.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
private void OnStatusFilterPressed(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordSetStatusFilter msg)
|
||||
{
|
||||
ent.Comp.FilterStatus = msg.FilterStatus;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnFiltersChanged(Entity<CriminalRecordsConsoleComponent> ent, ref SetStationRecordFilter msg)
|
||||
{
|
||||
@@ -112,13 +120,26 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
}
|
||||
|
||||
// will probably never fail given the checks above
|
||||
name = _records.RecordName(key.Value);
|
||||
officer = Loc.GetString("criminal-records-console-unknown-officer");
|
||||
var jobName = "Unknown";
|
||||
|
||||
_records.TryGetRecord<GeneralStationRecord>(key.Value, out var entry);
|
||||
if (entry != null)
|
||||
jobName = entry.JobTitle;
|
||||
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
if (tryGetIdentityShortInfoEvent.Title != null)
|
||||
officer = tryGetIdentityShortInfoEvent.Title;
|
||||
|
||||
_criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer);
|
||||
|
||||
(string, object)[] args;
|
||||
if (reason != null)
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason), ("job", jobName) };
|
||||
else
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("job", jobName) };
|
||||
|
||||
// figure out which radio message to send depending on transition
|
||||
var statusString = (oldStatus, msg.Status) switch
|
||||
@@ -193,8 +214,18 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
return;
|
||||
}
|
||||
|
||||
// get the listing of records to display
|
||||
var listing = _records.BuildListing((owningStation.Value, stationRecords), console.Filter);
|
||||
|
||||
// filter the listing by the selected criminal record status
|
||||
//if NONE, dont filter by status, just show all crew
|
||||
if (console.FilterStatus != SecurityStatus.None)
|
||||
{
|
||||
listing = listing
|
||||
.Where(x => _records.TryGetRecord<CriminalRecord>(new StationRecordKey(x.Key, owningStation.Value), out var record) && record.Status == console.FilterStatus)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
var state = new CriminalRecordsConsoleState(listing, console.Filter);
|
||||
if (console.ActiveKey is { } id)
|
||||
{
|
||||
@@ -205,6 +236,9 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
state.SelectedKey = id;
|
||||
}
|
||||
|
||||
// Set the Current Tab aka the filter status type for the records list
|
||||
state.FilterStatus = console.FilterStatus;
|
||||
|
||||
_ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -1112,7 +1112,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
.SingleOrDefaultAsync());
|
||||
}
|
||||
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset date)
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset? date)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
@@ -1122,7 +1122,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
return;
|
||||
}
|
||||
|
||||
dbPlayer.LastReadRules = date.UtcDateTime;
|
||||
dbPlayer.LastReadRules = date?.UtcDateTime;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace Content.Server.Database
|
||||
#region Rules
|
||||
|
||||
Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset time);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset? time);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -830,7 +830,7 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.GetLastReadRules(player));
|
||||
}
|
||||
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset time)
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset? time)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetLastReadRules(player, time));
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// The type of explosion. Determines damage types and tile break chance scaling.
|
||||
/// </summary>
|
||||
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
|
||||
[JsonIgnore]
|
||||
public string ExplosionType = default!;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,14 +22,12 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// chance.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxIntensity = 5;
|
||||
|
||||
/// <summary>
|
||||
/// How quickly intensity drops off as you move away from the epicenter
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensitySlope = 1;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,16 +38,21 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxTotalIntensity = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The intensity of the explosion per unit reaction.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensityPerUnit = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger
|
||||
/// explosives that don't space tiles, without having to create a new explosion-type prototype.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TileBreakScale = 1f;
|
||||
|
||||
public override bool ShouldLog => true;
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
@@ -72,6 +74,7 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
ExplosionType,
|
||||
intensity,
|
||||
IntensitySlope,
|
||||
MaxIntensity);
|
||||
MaxIntensity,
|
||||
TileBreakScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private const string PaperSlotId = "Paper";
|
||||
|
||||
@@ -227,7 +228,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !HasComp<EmaggedComponent>(uid)) // Allow existing names if emagged for fun
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !_emag.CheckFlag(uid, EmagType.Interaction)) // Allow existing names if emagged for fun
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
|
||||
return;
|
||||
@@ -246,7 +247,12 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.EmagSound, uid);
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -260,7 +266,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
switch (command)
|
||||
{
|
||||
case FaxConstants.FaxPingCommand:
|
||||
var isForSyndie = HasComp<EmaggedComponent>(uid) &&
|
||||
var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
|
||||
args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
|
||||
if (!isForSyndie && !component.ResponsePings)
|
||||
return;
|
||||
@@ -405,7 +411,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
|
||||
};
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
payload.Add(FaxConstants.FaxSyndicateData, true);
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(uid, null, payload);
|
||||
|
||||
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed class RadioImplantSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
|
||||
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If implanted with a radio implant, installs the necessary intrinsic radio components
|
||||
/// </summary>
|
||||
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
|
||||
{
|
||||
if (args.Implanted == null)
|
||||
return;
|
||||
|
||||
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (activeRadio.Channels.Add(channel))
|
||||
ent.Comp.ActiveAddedChannels.Add(channel);
|
||||
}
|
||||
|
||||
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
|
||||
|
||||
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (intrinsicRadioTransmitter.Channels.Add(channel))
|
||||
ent.Comp.TransmitterAddedChannels.Add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes intrinsic radio components once the Radio Implant is removed
|
||||
/// </summary>
|
||||
private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
|
||||
{
|
||||
foreach (var channel in ent.Comp.ActiveAddedChannels)
|
||||
{
|
||||
activeRadioComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.ActiveAddedChannels.Clear();
|
||||
|
||||
if (activeRadioComponent.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
|
||||
return;
|
||||
|
||||
foreach (var channel in ent.Comp.TransmitterAddedChannels)
|
||||
{
|
||||
radioTransmitterComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.TransmitterAddedChannels.Clear();
|
||||
|
||||
if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public sealed class RulesManager
|
||||
{
|
||||
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
|
||||
CoreRules = _cfg.GetCVar(CCVars.RulesFile),
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown,
|
||||
};
|
||||
_netManager.ServerSendMessage(showRulesMessage, e.Channel);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Materials;
|
||||
@@ -42,6 +43,7 @@ namespace Content.Server.Lathe
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSys = default!;
|
||||
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
@@ -292,7 +294,7 @@ namespace Content.Server.Lathe
|
||||
{
|
||||
if (uid != args.Lathe || !TryComp<TechnologyDatabaseComponent>(uid, out var technologyDatabase))
|
||||
return;
|
||||
if (!args.getUnavailable && !HasComp<EmaggedComponent>(uid))
|
||||
if (!args.getUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
foreach (var recipe in component.EmagDynamicRecipes)
|
||||
{
|
||||
|
||||
@@ -19,4 +19,17 @@ public sealed class MagicSystem : SharedMagicSystem
|
||||
{
|
||||
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false);
|
||||
}
|
||||
|
||||
public override void OnVoidApplause(VoidApplauseSpellEvent ev)
|
||||
{
|
||||
base.OnVoidApplause(ev);
|
||||
|
||||
_chat.TryEmoteWithChat(ev.Performer, ev.Emote);
|
||||
|
||||
var perfXForm = Transform(ev.Performer);
|
||||
var targetXForm = Transform(ev.Target);
|
||||
|
||||
Spawn(ev.Effect, perfXForm.Coordinates);
|
||||
Spawn(ev.Effect, targetXForm.Coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,10 @@ public sealed class HealingSystem : EntitySystem
|
||||
_bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
|
||||
if (isBleeding != bloodstream.BleedAmount > 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-stop-bleeding"), entity, args.User);
|
||||
var popup = (args.User == entity.Owner)
|
||||
? Loc.GetString("medical-item-stop-bleeding-self")
|
||||
: Loc.GetString("medical-item-stop-bleeding", ("target", Identity.Entity(entity.Owner, EntityManager)));
|
||||
_popupSystem.PopupEntity(popup, entity, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class MiningSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield);
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield+1);
|
||||
for (var i = 0; i < toSpawn; i++)
|
||||
{
|
||||
Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f)));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Components;
|
||||
@@ -55,34 +55,11 @@ public sealed partial class MedibotInjectOperator : HTNOperator
|
||||
if (!_entMan.TryGetComponent<MedibotComponent>(owner, out var botComp))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
|
||||
if (!_entMan.TryGetComponent<DamageableComponent>(target, out var damage))
|
||||
if (!_medibot.CheckInjectable((owner, botComp), target) || !_medibot.TryInject((owner, botComp), target))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_solutionContainer.TryGetInjectableSolution(target, out var injectable, out _))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(owner, target))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
var total = damage.TotalDamage;
|
||||
|
||||
// always inject healthy patients when emagged
|
||||
if (total == 0 && !_entMan.HasComponent<EmaggedComponent>(owner))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_entMan.TryGetComponent<MobStateComponent>(target, out var mobState))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
var state = mobState.CurrentState;
|
||||
if (!_medibot.TryGetTreatment(botComp, mobState.CurrentState, out var treatment) || !treatment.IsValid(total))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
_entMan.EnsureComponent<NPCRecentlyInjectedComponent>(target);
|
||||
_solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _);
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target);
|
||||
_audio.PlayPvs(botComp.InjectSound, target);
|
||||
_chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, hideChat: true, hideLog: true);
|
||||
|
||||
return HTNOperatorStatus.Finished;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace Content.Server.Nuke
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Timer = 300;
|
||||
|
||||
/// <summary>
|
||||
/// If the nuke is disarmed, this sets the minimum amount of time the timer can have.
|
||||
/// The remaining time will reset to this value if it is below it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MinimumTime = 180;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the bomb can arm again after deactivation.
|
||||
/// Used to prevent announcements spam.
|
||||
|
||||
@@ -522,6 +522,9 @@ public sealed class NukeSystem : EntitySystem
|
||||
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
||||
|
||||
// reset nuke remaining time to either itself or the minimum time, whichever is higher
|
||||
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
|
||||
|
||||
// disable sound and reset it
|
||||
component.PlayedAlertSound = false;
|
||||
component.AlertAudioStream = _audio.Stop(component.AlertAudioStream);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Nutrition.EntitySystems;
|
||||
public sealed class FatExtractorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly HungerSystem _hunger = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -36,8 +37,13 @@ public sealed class FatExtractorSystem : EntitySystem
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
args.Repeatable = false;
|
||||
}
|
||||
|
||||
private void OnClosed(EntityUid uid, FatExtractorComponent component, ref StorageAfterCloseEvent args)
|
||||
@@ -103,7 +109,7 @@ public sealed class FatExtractorSystem : EntitySystem
|
||||
if (_hunger.GetHunger(hunger) < component.NutritionPerSecond)
|
||||
return false;
|
||||
|
||||
if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp<EmaggedComponent>(uid))
|
||||
if (hunger.CurrentThreshold < component.MinHungerThreshold && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly FoodSystem _foodSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
@@ -63,7 +64,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
forced = false;
|
||||
}
|
||||
|
||||
if (entity.Comp.ExplodeOnUse || HasComp<EmaggedComponent>(entity.Owner))
|
||||
if (entity.Comp.ExplodeOnUse || _emag.CheckFlag(entity, EmagType.Interaction))
|
||||
{
|
||||
_explosionSystem.QueueExplosion(entity.Owner, "Default", entity.Comp.ExplosionIntensity, 0.5f, 3, canCreateVacuum: false);
|
||||
EntityManager.DeleteEntity(entity);
|
||||
@@ -161,8 +162,15 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
args.Args.Target.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<VapeComponent> entity, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(entity, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.APC;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Rounding;
|
||||
@@ -19,6 +18,7 @@ public sealed class ApcSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -111,7 +111,12 @@ public sealed class ApcSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, ApcComponent comp, ref GotEmaggedEvent args)
|
||||
{
|
||||
// no fancy conditions
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -170,7 +175,7 @@ public sealed class ApcSystem : EntitySystem
|
||||
|
||||
private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery)
|
||||
{
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return ApcChargeState.Emag;
|
||||
|
||||
if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold)
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Research.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
@@ -11,6 +12,8 @@ namespace Content.Server.Research.Systems;
|
||||
|
||||
public sealed partial class ResearchSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ConsoleUnlockTechnologyMessage>(OnConsoleUnlock);
|
||||
@@ -18,6 +21,7 @@ public sealed partial class ResearchSystem
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ResearchServerPointsChangedEvent>(OnPointsChanged);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ResearchRegistrationChangedEvent>(OnConsoleRegistrationChanged);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, TechnologyDatabaseModifiedEvent>(OnConsoleDatabaseModified);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args)
|
||||
@@ -39,7 +43,7 @@ public sealed partial class ResearchSystem
|
||||
if (!UnlockTechnology(uid, args.Id, act))
|
||||
return;
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, act);
|
||||
RaiseLocalEvent(getIdentityEvent);
|
||||
@@ -100,4 +104,15 @@ public sealed partial class ResearchSystem
|
||||
UpdateConsoleInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<ResearchConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ public sealed partial class RevenantSystem
|
||||
{
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
@@ -343,7 +342,8 @@ public sealed partial class RevenantSystem
|
||||
_whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent))
|
||||
continue;
|
||||
|
||||
_emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable.
|
||||
var ev = new GotEmaggedEvent(uid, EmagType.Interaction | EmagType.Access);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
@@ -15,6 +16,8 @@ namespace Content.Server.Silicons.Borgs;
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private void InitializeTransponder()
|
||||
{
|
||||
SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
@@ -127,7 +130,7 @@ public sealed partial class BorgSystem
|
||||
|
||||
private bool CheckEmagged(EntityUid uid, string name)
|
||||
{
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution);
|
||||
return true;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
@@ -14,7 +13,6 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -22,7 +20,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Silicons.Laws;
|
||||
|
||||
@@ -34,8 +31,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -52,7 +49,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, SiliconEmaggedEvent>(OnEmagLawsAdded);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
|
||||
@@ -135,7 +132,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
private void OnIonStormLaws(EntityUid uid, SiliconLawProviderComponent component, ref IonStormLawsEvent args)
|
||||
{
|
||||
// Emagged borgs are immune to ion storm
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
component.Lawset = args.Lawset;
|
||||
|
||||
@@ -152,9 +149,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent component, ref GotEmaggedEvent args)
|
||||
private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent component, ref SiliconEmaggedEvent args)
|
||||
{
|
||||
|
||||
if (component.Lawset == null)
|
||||
component.Lawset = GetLawset(component.Laws);
|
||||
|
||||
@@ -164,7 +160,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
// Add the first emag law before the others
|
||||
component.Lawset?.Laws.Insert(0, new SiliconLaw
|
||||
{
|
||||
LawString = Loc.GetString("law-emag-custom", ("name", Name(args.UserUid)), ("title", Loc.GetString(component.Lawset.ObeysTo))),
|
||||
LawString = Loc.GetString("law-emag-custom", ("name", Name(args.user)), ("title", Loc.GetString(component.Lawset.ObeysTo))),
|
||||
Order = 0
|
||||
});
|
||||
|
||||
@@ -176,20 +172,6 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (component.RequireOpenPanel && TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
|
||||
return;
|
||||
|
||||
base.OnGotEmagged(uid, component, ref args);
|
||||
NotifyLawsChanged(uid, component.EmaggedSound);
|
||||
if(_mind.TryGetMind(uid, out var mindId, out _))
|
||||
EnsureSubvertedSiliconRole(mindId);
|
||||
|
||||
_stunSystem.TryParalyze(uid, component.StunTime, true);
|
||||
|
||||
}
|
||||
|
||||
private void EnsureSubvertedSiliconRole(EntityUid mindId)
|
||||
{
|
||||
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Events;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Content.Server.VendingMachines
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private const float WallVendEjectDistanceFromWall = 1f;
|
||||
|
||||
@@ -48,7 +49,6 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
|
||||
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
@@ -123,12 +123,6 @@ namespace Content.Server.VendingMachines
|
||||
TryUpdateVisualState(uid, vendComponent);
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
// only emag if there are emag-only items
|
||||
args.Handled = component.EmaggedInventory.Count > 0;
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
@@ -232,7 +226,7 @@ namespace Content.Server.VendingMachines
|
||||
if (!TryComp<AccessReaderComponent>(uid, out var accessReader))
|
||||
return true;
|
||||
|
||||
if (_accessReader.IsAllowed(sender, uid, accessReader) || HasComp<EmaggedComponent>(uid))
|
||||
if (_accessReader.IsAllowed(sender, uid, accessReader))
|
||||
return true;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), uid);
|
||||
@@ -422,7 +416,7 @@ namespace Content.Server.VendingMachines
|
||||
if (!Resolve(uid, ref component))
|
||||
return null;
|
||||
|
||||
if (type == InventoryType.Emagged && HasComp<EmaggedComponent>(uid))
|
||||
if (type == InventoryType.Emagged && _emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return component.EmaggedInventory.GetValueOrDefault(entryId);
|
||||
|
||||
if (type == InventoryType.Contraband && component.Contraband)
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed partial class AccessReaderComponent : Component
|
||||
/// Whether or not emag interactions have an effect on this.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool BreakOnEmag = true;
|
||||
public bool BreakOnAccessBreaker = true;
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -24,6 +23,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -71,17 +71,28 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
{
|
||||
if (args.User == null) // AutoLink (and presumably future external linkers) have no user.
|
||||
return;
|
||||
if (!HasComp<EmaggedComponent>(uid) && !IsAllowed(args.User.Value, uid, component))
|
||||
if (!IsAllowed(args.User.Value, uid, component))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, AccessReaderComponent reader, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!reader.BreakOnEmag)
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!reader.BreakOnAccessBreaker)
|
||||
return;
|
||||
|
||||
if (!GetMainAccessReader(uid, out var accessReader))
|
||||
return;
|
||||
|
||||
if (accessReader.Value.Comp.AccessLists.Count < 1)
|
||||
return;
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
reader.Enabled = false;
|
||||
reader.AccessLog.Clear();
|
||||
accessReader.Value.Comp.AccessLists.Clear();
|
||||
accessReader.Value.Comp.AccessLog.Clear();
|
||||
Dirty(uid, reader);
|
||||
}
|
||||
|
||||
@@ -135,6 +146,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,3 +45,6 @@ namespace Content.Shared.Access.Systems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct OnAccessOverriderAccessUpdatedEvent(EntityUid UserUid, bool Handled = false);
|
||||
|
||||
@@ -89,6 +89,12 @@ public sealed partial class CCVars
|
||||
public static readonly CVarDef<bool> ServerBanErasePlayer =
|
||||
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// If true, will reset the last time the player has read the rules. This will mean on their next login they will be shown the rules again.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ServerBanResetLastReadRules =
|
||||
CVarDef.Create("admin.server_ban_reset_last_read_rules", true, CVar.ARCHIVE | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
|
||||
/// </summary>
|
||||
|
||||
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Cargo;
|
||||
|
||||
/// <summary>
|
||||
/// A data structure for storing historical information about bounties.
|
||||
/// </summary>
|
||||
[DataDefinition, NetSerializable, Serializable]
|
||||
public readonly partial record struct CargoBountyHistoryData
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique id used to identify the bounty
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this bounty was completed or skipped.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public BountyResult Result { get; init; } = BountyResult.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// Optional name of the actor that completed/skipped the bounty.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? ActorName { get; init; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Time when this bounty was completed or skipped
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan Timestamp { get; init; } = TimeSpan.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype containing information about the bounty.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<CargoBountyPrototype> Bounty { get; init; } = string.Empty;
|
||||
|
||||
public CargoBountyHistoryData(CargoBountyData bounty, BountyResult result, TimeSpan timestamp, string? actorName)
|
||||
{
|
||||
Bounty = bounty.Bounty;
|
||||
Result = result;
|
||||
Id = bounty.Id;
|
||||
ActorName = actorName;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Covers how a bounty was actually finished.
|
||||
/// </summary>
|
||||
public enum BountyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Bounty was actually fulfilled and the goods sold
|
||||
/// </summary>
|
||||
Completed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Bounty was explicitly skipped by some actor
|
||||
/// </summary>
|
||||
Skipped = 1,
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component
|
||||
public sealed class CargoBountyConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
public List<CargoBountyData> Bounties;
|
||||
public List<CargoBountyHistoryData> History;
|
||||
public TimeSpan UntilNextSkip;
|
||||
|
||||
public CargoBountyConsoleState(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||
public CargoBountyConsoleState(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
|
||||
{
|
||||
Bounties = bounties;
|
||||
History = history;
|
||||
UntilNextSkip = untilNextSkip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
|
||||
private void OnInteractUsing(Entity<RefillableSolutionComponent> entity, ref InteractUsingEvent args)
|
||||
{
|
||||
TrySpike(args.Used, args.Target, args.User, entity.Comp);
|
||||
if (TrySpike(args.Used, args.Target, args.User, entity.Comp))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +37,7 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
/// <param name="source">Source of the solution.</param>
|
||||
/// <param name="target">Target to spike with the solution from source.</param>
|
||||
/// <param name="user">User spiking the target solution.</param>
|
||||
private void TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
|
||||
private bool TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
|
||||
SolutionSpikerComponent? spikableSource = null,
|
||||
SolutionContainerManagerComponent? managerSource = null,
|
||||
SolutionContainerManagerComponent? managerTarget = null)
|
||||
@@ -46,21 +47,23 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
|| !_solution.TryGetRefillableSolution((target, spikableTarget, managerTarget), out var targetSoln, out var targetSolution)
|
||||
|| !_solution.TryGetSolution((source, managerSource), spikableSource.SourceSolution, out _, out var sourceSolution))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_solution.ForceAddSolution(targetSoln.Value, sourceSolution))
|
||||
return;
|
||||
return false;
|
||||
|
||||
_popup.PopupClient(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user);
|
||||
sourceSolution.RemoveAllSolution();
|
||||
if (spikableSource.Delete)
|
||||
QueueDel(source);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,15 +46,6 @@ public sealed partial class CloningPodComponent : Component
|
||||
[DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntProtoId MobSpawnId = "MobAbomination";
|
||||
|
||||
/// <summary>
|
||||
/// Emag sound effects.
|
||||
/// </summary>
|
||||
[DataField("sparkSound")]
|
||||
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(8),
|
||||
};
|
||||
|
||||
// TODO: Remove this from here when cloning and/or zombies are refactored
|
||||
[DataField("screamSound")]
|
||||
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams")
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.CriminalRecords.Components;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Security;
|
||||
|
||||
namespace Content.Shared.CriminalRecords.Components;
|
||||
|
||||
@@ -31,6 +34,12 @@ public sealed partial class CriminalRecordsConsoleComponent : Component
|
||||
[DataField]
|
||||
public StationRecordsFilter? Filter;
|
||||
|
||||
/// <summary>
|
||||
/// Current seleced security status for the filter by criminal status dropdown.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SecurityStatus FilterStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Channel to send messages to when someone's status gets changed.
|
||||
/// </summary>
|
||||
|
||||
@@ -35,9 +35,9 @@ public sealed class CriminalRecordsConsoleState : BoundUserInterfaceState
|
||||
/// Currently selected crewmember record key.
|
||||
/// </summary>
|
||||
public uint? SelectedKey = null;
|
||||
|
||||
public CriminalRecord? CriminalRecord = null;
|
||||
public GeneralStationRecord? StationRecord = null;
|
||||
public SecurityStatus FilterStatus = SecurityStatus.None;
|
||||
public readonly Dictionary<uint, string>? RecordListing;
|
||||
public readonly StationRecordsFilter? Filter;
|
||||
|
||||
@@ -100,3 +100,20 @@ public sealed class CriminalRecordDeleteHistory : BoundUserInterfaceMessage
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to set what status to filter by index.
|
||||
///
|
||||
/// </summary>
|
||||
///
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
public sealed class CriminalRecordSetStatusFilter : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly SecurityStatus FilterStatus;
|
||||
public CriminalRecordSetStatusFilter(SecurityStatus newFilterStatus)
|
||||
{
|
||||
FilterStatus = newFilterStatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed partial class DisposalDoAfterEvent : SimpleDoAfterEvent
|
||||
public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] protected readonly EmagSystem _emag = default!;
|
||||
[Dependency] protected readonly MetaDataSystem Metadata = default!;
|
||||
[Dependency] protected readonly SharedJointSystem Joints = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
@@ -102,6 +103,12 @@ public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
|
||||
protected void OnEmagged(EntityUid uid, SharedDisposalUnitComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (component.DisablePressure == true)
|
||||
return;
|
||||
|
||||
component.DisablePressure = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] protected readonly TagSystem Tags = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
@@ -77,8 +78,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
|
||||
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
|
||||
SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier);
|
||||
|
||||
SubscribeLocalEvent<DoorComponent, OnAttemptEmagEvent>(OnAttemptEmag);
|
||||
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
@@ -118,31 +117,24 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
_activeDoors.Remove(door);
|
||||
}
|
||||
|
||||
private void OnAttemptEmag(EntityUid uid, DoorComponent door, ref OnAttemptEmagEvent args)
|
||||
{
|
||||
if (!TryComp<AirlockComponent>(uid, out var airlock))
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsBolted(uid) || !airlock.Powered)
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (door.State != DoorState.Closed)
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!TryComp<AirlockComponent>(uid, out var airlock))
|
||||
return;
|
||||
|
||||
if (IsBolted(uid) || !airlock.Powered)
|
||||
return;
|
||||
|
||||
if (door.State != DoorState.Closed)
|
||||
return;
|
||||
|
||||
if (!SetState(uid, DoorState.Emagging, door))
|
||||
return;
|
||||
Audio.PlayPredicted(door.SparkSound, uid, args.UserUid, AudioParams.Default.WithVolume(8));
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -14,7 +16,21 @@ public sealed partial class EmagComponent : Component
|
||||
/// <summary>
|
||||
/// The tag that marks an entity as immune to emags
|
||||
/// </summary>
|
||||
[DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public string EmagImmuneTag = "EmagImmune";
|
||||
public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune";
|
||||
|
||||
/// <summary>
|
||||
/// What type of emag effect this device will do
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public EmagType EmagType = EmagType.Interaction;
|
||||
|
||||
/// <summary>
|
||||
/// What sound should the emag play when used
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Emag.Components;
|
||||
@@ -5,7 +6,12 @@ namespace Content.Shared.Emag.Components;
|
||||
/// <summary>
|
||||
/// Marker component for emagged entities
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class EmaggedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The EmagType flags that were used to emag this device
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EmagType EmagType = EmagType.None;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ using Content.Shared.Emag.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Emag.Systems;
|
||||
|
||||
@@ -23,88 +24,124 @@ public sealed class EmagSystem : EntitySystem
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<EmaggedComponent, OnAccessOverriderAccessUpdatedEvent>(OnAccessOverriderAccessUpdated);
|
||||
}
|
||||
|
||||
private void OnAccessOverriderAccessUpdated(Entity<EmaggedComponent> entity, ref OnAccessOverriderAccessUpdatedEvent args)
|
||||
{
|
||||
if (!CompareFlag(entity.Comp.EmagType, EmagType.Access))
|
||||
return;
|
||||
|
||||
entity.Comp.EmagType &= ~EmagType.Access;
|
||||
Dirty(entity);
|
||||
}
|
||||
private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target is not { } target)
|
||||
return;
|
||||
|
||||
args.Handled = TryUseEmag(uid, args.User, target, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to use the emag on a target entity
|
||||
/// </summary>
|
||||
public bool TryUseEmag(EntityUid uid, EntityUid user, EntityUid target, EmagComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return false;
|
||||
|
||||
if (_tag.HasTag(target, comp.EmagImmuneTag))
|
||||
return false;
|
||||
|
||||
TryComp<LimitedChargesComponent>(uid, out var charges);
|
||||
if (_charges.IsEmpty(uid, charges))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("emag-no-charges"), user, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
var handled = DoEmagEffect(user, target);
|
||||
if (!handled)
|
||||
return false;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user,
|
||||
user, PopupType.Medium);
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target}");
|
||||
|
||||
if (charges != null)
|
||||
_charges.UseCharge(uid, charges);
|
||||
return true;
|
||||
args.Handled = TryEmagEffect((uid, comp), args.User, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the emag effect on a specified entity
|
||||
/// </summary>
|
||||
public bool DoEmagEffect(EntityUid user, EntityUid target)
|
||||
public bool TryEmagEffect(Entity<EmagComponent?> ent, EntityUid user, EntityUid target)
|
||||
{
|
||||
// prevent emagging twice
|
||||
if (HasComp<EmaggedComponent>(target))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
var onAttemptEmagEvent = new OnAttemptEmagEvent(user);
|
||||
RaiseLocalEvent(target, ref onAttemptEmagEvent);
|
||||
|
||||
// prevent emagging if attempt fails
|
||||
if (onAttemptEmagEvent.Handled)
|
||||
if (_tag.HasTag(target, ent.Comp.EmagImmuneTag))
|
||||
return false;
|
||||
|
||||
var emaggedEvent = new GotEmaggedEvent(user);
|
||||
TryComp<LimitedChargesComponent>(ent, out var charges);
|
||||
if (_charges.IsEmpty(ent, charges))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("emag-no-charges"), user, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
var emaggedEvent = new GotEmaggedEvent(user, ent.Comp.EmagType);
|
||||
RaiseLocalEvent(target, ref emaggedEvent);
|
||||
|
||||
if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
|
||||
EnsureComp<EmaggedComponent>(target);
|
||||
if (!emaggedEvent.Handled)
|
||||
return false;
|
||||
|
||||
_popup.PopupPredicted(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.Medium);
|
||||
|
||||
_audio.PlayPredicted(ent.Comp.EmagSound, ent, ent);
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target} with flag(s): {ent.Comp.EmagType}");
|
||||
|
||||
if (charges != null && emaggedEvent.Handled)
|
||||
_charges.UseCharge(ent, charges);
|
||||
|
||||
if (!emaggedEvent.Repeatable)
|
||||
{
|
||||
EnsureComp<EmaggedComponent>(target, out var emaggedComp);
|
||||
|
||||
emaggedComp.EmagType |= ent.Comp.EmagType;
|
||||
Dirty(target, emaggedComp);
|
||||
}
|
||||
|
||||
return emaggedEvent.Handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity has the EmaggedComponent with a set flag.
|
||||
/// </summary>
|
||||
/// <param name="target">The target entity to check for the flag.</param>
|
||||
/// <param name="flag">The EmagType flag to check for.</param>
|
||||
/// <returns>True if entity has EmaggedComponent and the provided flag. False if the entity lacks EmaggedComponent or provided flag.</returns>
|
||||
public bool CheckFlag(EntityUid target, EmagType flag)
|
||||
{
|
||||
if (!TryComp<EmaggedComponent>(target, out var comp))
|
||||
return false;
|
||||
|
||||
if ((comp.EmagType & flag) == flag)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a flag to the target.
|
||||
/// </summary>
|
||||
/// <param name="target">The target flag to check.</param>
|
||||
/// <param name="flag">The flag to check for within the target.</param>
|
||||
/// <returns>True if target contains flag. Otherwise false.</returns>
|
||||
public bool CompareFlag(EmagType target, EmagType flag)
|
||||
{
|
||||
if ((target & flag) == flag)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum EmagType : byte
|
||||
{
|
||||
None = 0,
|
||||
Interaction = 1 << 1,
|
||||
Access = 1 << 2
|
||||
}
|
||||
/// <summary>
|
||||
/// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled
|
||||
/// </summary>
|
||||
/// <param name="UserUid">Emag user</param>
|
||||
/// <param name="Type">The emag type to use</param>
|
||||
/// <param name="Handled">Did the emagging succeed? Causes a user-only popup to show on client side</param>
|
||||
/// <param name="Repeatable">Can the entity be emagged more than once? Prevents adding of <see cref="EmaggedComponent"/></param>
|
||||
/// <remarks>Needs to be handled in shared/client, not just the server, to actually show the emagging popup</remarks>
|
||||
[ByRefEvent]
|
||||
public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct OnAttemptEmagEvent(EntityUid UserUid, bool Handled = false);
|
||||
public record struct GotEmaggedEvent(EntityUid UserUid, EmagType Type, bool Handled = false, bool Repeatable = false);
|
||||
|
||||
@@ -59,12 +59,6 @@ public sealed partial class FaxMachineComponent : Component
|
||||
[DataField]
|
||||
public bool ReceiveNukeCodes { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax has been emagged
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax printing new message
|
||||
/// </summary>
|
||||
|
||||
@@ -21,10 +21,4 @@ public sealed partial class OrbitVisualsComponent : Component
|
||||
/// How long should the orbit stop animation last in seconds?
|
||||
/// </summary>
|
||||
public float OrbitStopLength = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How far along in the orbit, from 0 to 1, is this entity?
|
||||
/// </summary>
|
||||
[Animatable]
|
||||
public float Orbit { get; set; } = 0.0f;
|
||||
}
|
||||
|
||||
37
Content.Shared/Implants/Components/RadioImplantComponent.cs
Normal file
37
Content.Shared/Implants/Components/RadioImplantComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Implants.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Gives the user access to a given channel without the need for a headset.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RadioImplantComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radio channel(s) to grant access to.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> RadioChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// The radio channels that have been added by the implant to a user's ActiveRadioComponent.
|
||||
/// Used to track which channels were successfully added (not already in user)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should not be modified outside RadioImplantSystem.cs
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> ActiveAddedChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// The radio channels that have been added by the implant to a user's IntrinsicRadioTransmitterComponent.
|
||||
/// Used to track which channels were successfully added (not already in user)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should not be modified outside RadioImplantSystem.cs
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> TransmitterAddedChannels = new();
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
namespace Content.Shared.Inventory.Events;
|
||||
|
||||
public sealed class RefreshEquipmentHudEvent<T> : EntityEventArgs, IInventoryRelayEvent where T : IComponent
|
||||
[ByRefEvent]
|
||||
public record struct RefreshEquipmentHudEvent<T>(SlotFlags TargetSlots) : IInventoryRelayEvent
|
||||
where T : IComponent
|
||||
{
|
||||
public SlotFlags TargetSlots { get; init; }
|
||||
public SlotFlags TargetSlots { get; } = TargetSlots;
|
||||
public bool Active = false;
|
||||
public List<T> Components = new();
|
||||
|
||||
public RefreshEquipmentHudEvent(SlotFlags targetSlots)
|
||||
{
|
||||
TargetSlots = targetSlots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,14 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, SolutionScanEvent>(RelayInventoryEvent);
|
||||
|
||||
// ComponentActivatedClientSystems
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public abstract class SharedLatheSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public readonly Dictionary<string, List<LatheRecipePrototype>> InverseRecipes = new();
|
||||
|
||||
@@ -66,6 +67,12 @@ public abstract class SharedLatheSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, EmagLatheRecipesComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -22,6 +23,7 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -78,6 +80,9 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!_light.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ public sealed partial class LockComponent : Component
|
||||
/// <summary>
|
||||
/// Whether or not an emag disables it.
|
||||
/// </summary>
|
||||
[DataField("breakOnEmag")]
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public bool BreakOnEmag = true;
|
||||
public bool BreakOnAccessBreaker = true;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of do-after time needed to lock the entity.
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed class LockSystem : EntitySystem
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
|
||||
@@ -295,7 +296,10 @@ public sealed class LockSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, LockComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!component.Locked || !component.BreakOnEmag)
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!component.Locked || !component.BreakOnAccessBreaker)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(component.UnlockSound, uid, args.UserUid);
|
||||
@@ -307,7 +311,7 @@ public sealed class LockSystem : EntitySystem
|
||||
var ev = new LockToggledEvent(false);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
RemComp<LockComponent>(uid); //Literally destroys the lock as a tell it was emagged
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
24
Content.Shared/Magic/Events/VoidApplauseSpellEvent.cs
Normal file
24
Content.Shared/Magic/Events/VoidApplauseSpellEvent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Magic.Events;
|
||||
|
||||
public sealed partial class VoidApplauseSpellEvent : EntityTargetActionEvent, ISpeakSpell
|
||||
{
|
||||
[DataField]
|
||||
public string? Speech { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Emote to use.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<EmotePrototype> Emote = "ClapSingle";
|
||||
|
||||
/// <summary>
|
||||
/// Visual effect entity that is spawned at both the user's and the target's location.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId Effect = "EffectVoidBlink";
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Lock;
|
||||
@@ -15,9 +14,6 @@ using Content.Shared.Magic.Components;
|
||||
using Content.Shared.Magic.Events;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Speech.Muting;
|
||||
@@ -37,6 +33,10 @@ using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Shared.Magic;
|
||||
|
||||
// TODO: Move BeforeCast & Prerequirements (like Wizard clothes) to action comp
|
||||
// Alt idea - make it its own comp and split, like the Charge PR
|
||||
// TODO: Move speech to actionComp or again, its own ECS
|
||||
// TODO: Use the MagicComp just for pure backend things like spawning patterns?
|
||||
/// <summary>
|
||||
/// Handles learning and using spells (actions)
|
||||
/// </summary>
|
||||
@@ -60,7 +60,6 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
[Dependency] private readonly LockSystem _lock = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
@@ -80,79 +79,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
SubscribeLocalEvent<ChargeSpellEvent>(OnChargeSpell);
|
||||
SubscribeLocalEvent<RandomGlobalSpawnSpellEvent>(OnRandomGlobalSpawnSpell);
|
||||
SubscribeLocalEvent<MindSwapSpellEvent>(OnMindSwapSpell);
|
||||
|
||||
// Spell wishlist
|
||||
// A wishlish of spells that I'd like to implement or planning on implementing in a future PR
|
||||
|
||||
// TODO: InstantDoAfterSpell and WorldDoafterSpell
|
||||
// Both would be an action that take in an event, that passes an event to trigger once the doafter is done
|
||||
// This would be three events:
|
||||
// 1 - Event that triggers from the action that starts the doafter
|
||||
// 2 - The doafter event itself, which passes the event with it
|
||||
// 3 - The event to trigger once the do-after finishes
|
||||
|
||||
// TODO: Inanimate objects to life ECS
|
||||
// AI sentience
|
||||
|
||||
// TODO: Flesh2Stone
|
||||
// Entity Target spell
|
||||
// Synergy with Inanimate object to life (detects player and allows player to move around)
|
||||
|
||||
// TODO: Lightning Spell
|
||||
// Should just fire lightning, try to prevent arc back to caster
|
||||
|
||||
// TODO: Magic Missile (homing projectile ecs)
|
||||
// Instant action, target any player (except self) on screen
|
||||
|
||||
// TODO: Random projectile ECS for magic-carp, wand of magic
|
||||
|
||||
// TODO: Recall Spell
|
||||
// mark any item in hand to recall
|
||||
// ItemRecallComponent
|
||||
// Event adds the component if it doesn't exist and the performer isn't stored in the comp
|
||||
// 2nd firing of the event checks to see if the recall comp has this uid, and if it does it calls it
|
||||
// if no free hands, summon at feet
|
||||
// if item deleted, clear stored item
|
||||
|
||||
// TODO: Jaunt (should be its own ECS)
|
||||
// Instant action
|
||||
// When clicked, disappear/reappear (goes to paused map)
|
||||
// option to restrict to tiles
|
||||
// option for requiring entry/exit (blood jaunt)
|
||||
// speed option
|
||||
|
||||
// TODO: Summon Events
|
||||
// List of wizard events to add into the event pool that frequently activate
|
||||
// floor is lava
|
||||
// change places
|
||||
// ECS that when triggered, will periodically trigger a random GameRule
|
||||
// Would need a controller/controller entity?
|
||||
|
||||
// TODO: Summon Guns
|
||||
// Summon a random gun at peoples feet
|
||||
// Get every alive player (not in cryo, not a simplemob)
|
||||
// TODO: After Antag Rework - Rare chance of giving gun collector status to people
|
||||
|
||||
// TODO: Summon Magic
|
||||
// Summon a random magic wand at peoples feet
|
||||
// Get every alive player (not in cryo, not a simplemob)
|
||||
// TODO: After Antag Rework - Rare chance of giving magic collector status to people
|
||||
|
||||
// TODO: Bottle of Blood
|
||||
// Summons Slaughter Demon
|
||||
// TODO: Slaughter Demon
|
||||
// Also see Jaunt
|
||||
|
||||
// TODO: Field Spells
|
||||
// Should be able to specify a grid of tiles (3x3 for example) that it effects
|
||||
// Timed despawn - so it doesn't last forever
|
||||
// Ignore caster - for spells that shouldn't effect the caster (ie if timestop should effect the caster)
|
||||
|
||||
// TODO: Touch toggle spell
|
||||
// 1 - When toggled on, show in hand
|
||||
// 2 - Block hand when toggled on
|
||||
// - Require free hand
|
||||
// 3 - use spell event when toggled & click
|
||||
SubscribeLocalEvent<VoidApplauseSpellEvent>(OnVoidApplause);
|
||||
}
|
||||
|
||||
private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args)
|
||||
@@ -402,8 +329,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var transform = Transform(args.Performer);
|
||||
|
||||
if (transform.MapID != args.Target.GetMapId(EntityManager) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
|
||||
if (transform.MapID != _transform.GetMapId(args.Target) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
|
||||
return;
|
||||
|
||||
_transform.SetCoordinates(args.Performer, args.Target);
|
||||
@@ -411,6 +337,17 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
Speak(args);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public virtual void OnVoidApplause(VoidApplauseSpellEvent ev)
|
||||
{
|
||||
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer))
|
||||
return;
|
||||
|
||||
ev.Handled = true;
|
||||
Speak(ev);
|
||||
|
||||
_transform.SwapPositions(ev.Performer, ev.Target);
|
||||
}
|
||||
// End Teleport Spells
|
||||
#endregion
|
||||
#region Spell Helpers
|
||||
@@ -435,7 +372,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
}
|
||||
// End Spell Helpers
|
||||
#endregion
|
||||
#region Smite Spells
|
||||
#region Touch Spells
|
||||
private void OnSmiteSpell(SmiteSpellEvent ev)
|
||||
{
|
||||
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer))
|
||||
@@ -454,7 +391,8 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
|
||||
_body.GibBody(ev.Target, true, body);
|
||||
}
|
||||
// End Smite Spells
|
||||
|
||||
// End Touch Spells
|
||||
#endregion
|
||||
#region Knock Spells
|
||||
/// <summary>
|
||||
|
||||
@@ -29,6 +29,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public const string ActiveReclaimerContainerId = "active-material-reclaimer-container";
|
||||
|
||||
@@ -60,6 +61,12 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, MaterialReclaimerComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -207,7 +214,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
component.Enabled &&
|
||||
!component.Broken &&
|
||||
HasComp<BodyComponent>(victim) &&
|
||||
HasComp<EmaggedComponent>(uid);
|
||||
_emag.CheckFlag(uid, EmagType.Interaction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standingStateSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -156,9 +157,13 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
|
||||
protected void OnEmagged(EntityUid uid, CryoPodComponent? cryoPodComponent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref cryoPodComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (cryoPodComponent.PermaLocked && cryoPodComponent.Locked)
|
||||
return;
|
||||
|
||||
cryoPodComponent.PermaLocked = true;
|
||||
cryoPodComponent.Locked = true;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Mindshield.FakeMindShield;
|
||||
|
||||
@@ -13,7 +14,9 @@ public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, FakeMindShieldToggleEvent>(OnFakeMindShieldToggle);
|
||||
SubscribeLocalEvent<FakeMindShieldImplantComponent, ImplantImplantedEvent>(ImplantCheck);
|
||||
SubscribeLocalEvent<FakeMindShieldImplantComponent, EntGotRemovedFromContainerMessage>(ImplantDraw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity
|
||||
/// </summary>
|
||||
@@ -33,4 +36,9 @@ public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
|
||||
if (ev.Implanted != null)
|
||||
EnsureComp<FakeMindShieldComponent>(ev.Implanted.Value);
|
||||
}
|
||||
|
||||
private void ImplantDraw(Entity<FakeMindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage ev)
|
||||
{
|
||||
RemComp<FakeMindShieldComponent>(ev.Container.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
namespace Content.Server.NPC.Components
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.NPC.Components
|
||||
{
|
||||
/// Added when a medibot injects someone
|
||||
/// So they don't get injected again for at least a minute.
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NPCRecentlyInjectedComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -17,11 +19,23 @@ public sealed partial class EmagProviderComponent : Component
|
||||
/// The tag that marks an entity as immune to emagging.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune";
|
||||
public ProtoId<TagPrototype> AccessBreakerImmuneTag = "AccessBreakerImmune";
|
||||
|
||||
/// <summary>
|
||||
/// Whitelist that entities must be on to work.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// What type of emag this will provide.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EmagType EmagType = EmagType.Access;
|
||||
|
||||
/// <summary>
|
||||
/// What sound should the emag play when used
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Shared.Ninja.Systems;
|
||||
|
||||
@@ -13,6 +14,7 @@ namespace Content.Shared.Ninja.Systems;
|
||||
/// </summary>
|
||||
public sealed class EmagProviderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
@@ -42,14 +44,18 @@ public sealed class EmagProviderSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// only allowed to emag non-immune entities
|
||||
if (_tag.HasTag(target, comp.EmagImmuneTag))
|
||||
if (_tag.HasTag(target, comp.AccessBreakerImmuneTag))
|
||||
return;
|
||||
|
||||
var handled = _emag.DoEmagEffect(uid, target);
|
||||
if (!handled)
|
||||
var emagEv = new GotEmaggedEvent(uid, EmagType.Access);
|
||||
RaiseLocalEvent(args.Target, ref emagEv);
|
||||
|
||||
if (!emagEv.Handled)
|
||||
return;
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(uid):player} emagged {ToPrettyString(target):target}");
|
||||
_audio.PlayPredicted(comp.EmagSound, uid, uid);
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(uid):player} emagged {ToPrettyString(target):target} with flag(s): {ent.Comp.EmagType}");
|
||||
var ev = new EmaggedSomethingEvent(target);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
args.Handled = true;
|
||||
@@ -57,7 +63,7 @@ public sealed class EmagProviderSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the player when emagging something.
|
||||
/// Raised on the player when access breaking something.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct EmaggedSomethingEvent(EntityUid Target);
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Content.Shared.Pinpointer;
|
||||
public abstract class SharedPinpointerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -137,6 +138,15 @@ public abstract class SharedPinpointerSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, PinpointerComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (component.CanRetarget)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
component.CanRetarget = true;
|
||||
}
|
||||
|
||||
@@ -40,13 +40,16 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
var formattedRoleDiff = ContentLocalizationManager.FormatPlaytime(roleDiffSpan);
|
||||
var departmentColor = Color.Yellow;
|
||||
|
||||
if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
|
||||
{
|
||||
if (!entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
|
||||
return false;
|
||||
|
||||
var jobProto = jobSystem.GetJobPrototype(proto);
|
||||
|
||||
if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
|
||||
departmentColor = departmentProto.Color;
|
||||
}
|
||||
|
||||
if (!protoManager.TryIndex<JobPrototype>(jobProto, out var indexedJob))
|
||||
return false;
|
||||
|
||||
if (!Inverted)
|
||||
{
|
||||
@@ -56,7 +59,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-role-insufficient",
|
||||
("time", formattedRoleDiff),
|
||||
("job", Loc.GetString(proto)),
|
||||
("job", indexedJob.LocalizedName),
|
||||
("departmentColor", departmentColor.ToHex())));
|
||||
return false;
|
||||
}
|
||||
@@ -66,7 +69,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-role-too-high",
|
||||
("time", formattedRoleDiff),
|
||||
("job", Loc.GetString(proto)),
|
||||
("job", indexedJob.LocalizedName),
|
||||
("departmentColor", departmentColor.ToHex())));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,4 @@ public sealed partial class EmaggableMedibotComponent : Component
|
||||
/// </summary>
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<MobState, MedibotTreatment> Replacements = new();
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the bot has been emagged
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(8f)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Silicons.Bots;
|
||||
@@ -11,20 +20,31 @@ namespace Content.Shared.Silicons.Bots;
|
||||
public sealed class MedibotSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfter = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EmaggableMedibotComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<MedibotComponent, UserActivateInWorldEvent>(OnInteract);
|
||||
SubscribeLocalEvent<MedibotComponent, MedibotInjectDoAfterEvent>(OnInject);
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, EmaggableMedibotComponent comp, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!TryComp<MedibotComponent>(uid, out var medibot))
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(comp.SparkSound, uid, args.UserUid);
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!TryComp<MedibotComponent>(uid, out var medibot))
|
||||
return;
|
||||
|
||||
foreach (var (state, treatment) in comp.Replacements)
|
||||
{
|
||||
@@ -34,6 +54,25 @@ public sealed class MedibotSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnInteract(Entity<MedibotComponent> medibot, ref UserActivateInWorldEvent args)
|
||||
{
|
||||
if (!CheckInjectable(medibot!, args.Target, true)) return;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, 2f, new MedibotInjectDoAfterEvent(), args.User, args.Target)
|
||||
{
|
||||
BlockDuplicate = true,
|
||||
BreakOnMove = true,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInject(EntityUid uid, MedibotComponent comp, ref MedibotInjectDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled) return;
|
||||
|
||||
if (args.Target is { } target)
|
||||
TryInject(uid, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a treatment for a given mob state.
|
||||
/// </summary>
|
||||
@@ -44,4 +83,66 @@ public sealed class MedibotSystem : EntitySystem
|
||||
{
|
||||
return comp.Treatments.TryGetValue(state, out treatment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the target can be injected.
|
||||
/// </summary>
|
||||
public bool CheckInjectable(Entity<MedibotComponent?> medibot, EntityUid target, bool manual = false)
|
||||
{
|
||||
if (!Resolve(medibot, ref medibot.Comp, false)) return false;
|
||||
|
||||
if (HasComp<NPCRecentlyInjectedComponent>(target))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("medibot-recently-injected"), medibot, medibot);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryComp<MobStateComponent>(target, out var mobState)) return false;
|
||||
if (!TryComp<DamageableComponent>(target, out var damageable)) return false;
|
||||
if (!_solutionContainer.TryGetInjectableSolution(target, out _, out _)) return false;
|
||||
|
||||
if (mobState.CurrentState != MobState.Alive && mobState.CurrentState != MobState.Critical)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("medibot-target-dead"), medibot, medibot);
|
||||
return false;
|
||||
}
|
||||
|
||||
var total = damageable.TotalDamage;
|
||||
if (total == 0 && !HasComp<EmaggedComponent>(medibot))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("medibot-target-healthy"), medibot, medibot);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetTreatment(medibot.Comp, mobState.CurrentState, out var treatment) || !treatment.IsValid(total) && !manual) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to inject the target.
|
||||
/// </summary>
|
||||
public bool TryInject(Entity<MedibotComponent?> medibot, EntityUid target)
|
||||
{
|
||||
if (!Resolve(medibot, ref medibot.Comp, false)) return false;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(medibot.Owner, target)) return false;
|
||||
|
||||
if (!TryComp<MobStateComponent>(target, out var mobState)) return false;
|
||||
if (!TryGetTreatment(medibot.Comp, mobState.CurrentState, out var treatment)) return false;
|
||||
if (!_solutionContainer.TryGetInjectableSolution(target, out var injectable, out _)) return false;
|
||||
|
||||
EnsureComp<NPCRecentlyInjectedComponent>(target);
|
||||
_solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target);
|
||||
_popup.PopupClient(Loc.GetString("medibot-target-injected"), medibot, medibot);
|
||||
|
||||
_audio.PlayPredicted(medibot.Comp.InjectSound, medibot, medibot);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class MedibotInjectDoAfterEvent : SimpleDoAfterEvent { }
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws;
|
||||
|
||||
@@ -11,22 +14,29 @@ namespace Content.Shared.Silicons.Laws;
|
||||
public abstract partial class SharedSiliconLawSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
InitializeUpdater();
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged);
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, OnAttemptEmagEvent>(OnAttemptEmag);
|
||||
}
|
||||
|
||||
protected virtual void OnAttemptEmag(EntityUid uid, EmagSiliconLawComponent component, ref OnAttemptEmagEvent args)
|
||||
private void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
//prevent self emagging
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
// prevent self-emagging
|
||||
if (uid == args.UserUid)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("law-emag-cannot-emag-self"), uid, args.UserUid);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -35,14 +45,33 @@ public abstract partial class SharedSiliconLawSystem : EntitySystem
|
||||
!panel.Open)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("law-emag-require-panel"), uid, args.UserUid);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
var ev = new SiliconEmaggedEvent(args.UserUid);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
protected virtual void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
component.OwnerName = Name(args.UserUid);
|
||||
|
||||
NotifyLawsChanged(uid, component.EmaggedSound);
|
||||
if(_mind.TryGetMind(uid, out var mindId, out _))
|
||||
EnsureSubvertedSiliconRole(mindId);
|
||||
|
||||
_stunSystem.TryParalyze(uid, component.StunTime, true);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected virtual void NotifyLawsChanged(EntityUid uid, SoundSpecifier? cue = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void EnsureSubvertedSiliconRole(EntityUid mindId)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct SiliconEmaggedEvent(EntityUid user);
|
||||
|
||||
@@ -11,6 +11,7 @@ public abstract class SharedSingularityGeneratorSystem : EntitySystem
|
||||
{
|
||||
#region Dependencies
|
||||
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
#endregion Dependencies
|
||||
|
||||
public override void Initialize()
|
||||
@@ -22,6 +23,15 @@ public abstract class SharedSingularityGeneratorSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (component.FailsafeDisabled)
|
||||
return;
|
||||
|
||||
component.FailsafeDisabled = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Shared.Stunnable;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Stunnable.Components;
|
||||
namespace Content.Shared.Stunnable;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Emag.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -19,11 +20,14 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] protected readonly IRobustRandom Randomizer = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
@@ -49,9 +53,21 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
// only emag if there are emag-only items
|
||||
args.Handled = component.EmaggedInventory.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all of the vending machine's inventory. Only includes emagged and contraband inventories if
|
||||
/// <see cref="EmaggedComponent"/> exists and <see cref="VendingMachineComponent.Contraband"/> is true
|
||||
/// <see cref="EmaggedComponent"/> with the EmagType.Interaction flag exists and <see cref="VendingMachineComponent.Contraband"/> is true
|
||||
/// are <c>true</c> respectively.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
@@ -64,7 +80,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
||||
|
||||
var inventory = new List<VendingMachineInventoryEntry>(component.Inventory.Values);
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
inventory.AddRange(component.EmaggedInventory.Values);
|
||||
|
||||
if (component.Contraband)
|
||||
|
||||
@@ -508,7 +508,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle);
|
||||
CreateEffect(gun, ev, gun);
|
||||
CreateEffect(gun, ev, user);
|
||||
}
|
||||
|
||||
public void CauseImpulse(EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid user, PhysicsComponent userPhysics)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user