Merge branch 'master' into DONOTMAPTEST

This commit is contained in:
Killerqu00
2025-02-04 12:56:47 +01:00
763 changed files with 264223 additions and 8905 deletions

View File

@@ -46,7 +46,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown(); 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))] [ParamsSource(nameof(MapsSource))]
public string Map; public string Map;

View File

@@ -39,6 +39,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
if (message is not CargoBountyConsoleState state) if (message is not CargoBountyConsoleState state)
return; return;
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip); _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
} }
} }

View 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>

View 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 ?? "")));
}
}
}

View File

@@ -11,15 +11,28 @@
<PanelContainer.PanelOverride> <PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" /> <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride> </PanelContainer.PanelOverride>
<ScrollContainer HScrollEnabled="False" <TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
HorizontalExpand="True" <ScrollContainer HScrollEnabled="False"
VerticalExpand="True"> HorizontalExpand="True"
<BoxContainer Name="BountyEntriesContainer" VerticalExpand="True">
Orientation="Vertical" <BoxContainer Name="BountyEntriesContainer"
VerticalExpand="True" Orientation="Vertical"
HorizontalExpand="True"> VerticalExpand="True"
</BoxContainer> HorizontalExpand="True" />
</ScrollContainer> </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> </PanelContainer>
<!-- Footer --> <!-- Footer -->
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">

View File

@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
public CargoBountyMenu() public CargoBountyMenu()
{ {
RobustXamlLoader.Load(this); 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(); BountyEntriesContainer.Children.Clear();
foreach (var b in bounties) foreach (var b in bounties)
@@ -32,5 +35,21 @@ public sealed partial class CargoBountyMenu : FancyWindow
{ {
MinHeight = 10 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]));
}
}
} }
} }

View File

@@ -39,6 +39,8 @@ public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterfac
SendMessage(new CriminalRecordChangeStatus(status, null)); SendMessage(new CriminalRecordChangeStatus(status, null));
_window.OnDialogConfirmed += (status, reason) => _window.OnDialogConfirmed += (status, reason) =>
SendMessage(new CriminalRecordChangeStatus(status, reason)); SendMessage(new CriminalRecordChangeStatus(status, reason));
_window.OnStatusFilterPressed += (statusFilter) =>
SendMessage(new CriminalRecordSetStatusFilter(statusFilter));
_window.OnHistoryUpdated += UpdateHistory; _window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close(); _window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close; _window.OnClose += Close;

View File

@@ -1,36 +1,142 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'criminal-records-console-window-title'}" Title="{Loc 'criminal-records-console-window-title'}"
MinSize="660 400"> MinSize="695 440">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<!-- Record search bar <BoxContainer Name="AllList"
TODO: make this into a control shared with general records --> Orientation="Vertical"
<BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center"> VerticalExpand="True"
<OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor --> HorizontalExpand="True"
<LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/> Margin="8">
</BoxContainer> <!-- Record search bar -->
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> <BoxContainer Margin="5 5 5 10"
<!-- Record listing --> HorizontalExpand="true"
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250"> VerticalAlignment="Center">
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/> <OptionButton Name="FilterType"
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> MinWidth="250"
<ScrollContainer VerticalExpand="True"> Margin="0 0 10 0" />
<ItemList Name="RecordListing"/> <!-- Populated when loading state --> <!-- Populated in constructor -->
</ScrollContainer> <LineEdit Name="FilterText"
PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> <BoxContainer Orientation="Horizontal"
<!-- Selected record info --> VerticalExpand="True">
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False"> <!-- Record listing -->
<Label Name="PersonName" StyleClasses="LabelBig"/> <BoxContainer Orientation="Vertical"
<Label Name="PersonPrints"/> Margin="10 10"
<Label Name="PersonDna"/> MinWidth="250"
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" /> MaxWidth="250">
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5"> <Label Name="RecordListingTitle"
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/> Text="{Loc 'criminal-records-console-records-list-title'}"
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor --> 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 -->
</ScrollContainer>
</BoxContainer> </BoxContainer>
<RichTextLabel Name="WantedReason" Visible="False"/> <Label Name="RecordUnselected"
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/> Text="{Loc 'criminal-records-console-select-record-info'}"
HorizontalExpand="True"
Align="Center"
FontColorOverride="DarkGray" />
<!-- Selected record info -->
<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>
<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> </BoxContainer>
</BoxContainer> </BoxContainer>

View File

@@ -13,6 +13,9 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using System.Numerics;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
namespace Content.Client.CriminalRecords; namespace Content.Client.CriminalRecords;
@@ -24,6 +27,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private readonly IPrototypeManager _proto; private readonly IPrototypeManager _proto;
private readonly IRobustRandom _random; private readonly IRobustRandom _random;
private readonly AccessReaderSystem _accessReader; private readonly AccessReaderSystem _accessReader;
[Dependency] private readonly IEntityManager _entManager = default!;
private readonly SpriteSystem _spriteSystem;
public readonly EntityUid Console; public readonly EntityUid Console;
@@ -33,10 +38,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
public Action<uint?>? OnKeySelected; public Action<uint?>? OnKeySelected;
public Action<StationRecordFilterType, string>? OnFiltersChanged; public Action<StationRecordFilterType, string>? OnFiltersChanged;
public Action<SecurityStatus>? OnStatusSelected; public Action<SecurityStatus>? OnStatusSelected;
public Action<uint>? OnCheckStatus;
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated; public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
public Action? OnHistoryClosed; public Action? OnHistoryClosed;
public Action<SecurityStatus, string>? OnDialogConfirmed; public Action<SecurityStatus, string>? OnDialogConfirmed;
public Action<SecurityStatus>? OnStatusFilterPressed;
private uint _maxLength; private uint _maxLength;
private bool _access; private bool _access;
private uint? _selectedKey; private uint? _selectedKey;
@@ -46,6 +53,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private StationRecordFilterType _currentFilterType; private StationRecordFilterType _currentFilterType;
private SecurityStatus _currentCrewListFilter;
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader) public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -55,10 +64,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
_proto = prototypeManager; _proto = prototypeManager;
_random = robustRandom; _random = robustRandom;
_accessReader = accessReader; _accessReader = accessReader;
IoCManager.InjectDependencies(this);
_spriteSystem = _entManager.System<SpriteSystem>();
_maxLength = maxLength; _maxLength = maxLength;
_currentFilterType = StationRecordFilterType.Name; _currentFilterType = StationRecordFilterType.Name;
_currentCrewListFilter = SecurityStatus.None;
OpenCentered(); OpenCentered();
foreach (var item in Enum.GetValues<StationRecordFilterType>()) foreach (var item in Enum.GetValues<StationRecordFilterType>())
@@ -71,6 +84,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
AddStatusSelect(status); AddStatusSelect(status);
} }
//Populate status to filter crew list
foreach (var item in Enum.GetValues<SecurityStatus>())
{
CrewListFilter.AddItem(GetCrewListFilterLocals(item), (int)item);
}
OnClose += () => _reasonDialog?.Close(); OnClose += () => _reasonDialog?.Close();
RecordListing.OnItemSelected += args => 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 => FilterText.OnTextEntered += args =>
{ {
FilterListingOfRecords(args.Text); FilterListingOfRecords(args.Text);
@@ -104,16 +137,21 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
StatusOptionButton.OnItemSelected += args => StatusOptionButton.OnItemSelected += args =>
{ {
SetStatus((SecurityStatus) args.Id); SetStatus((SecurityStatus)args.Id);
}; };
HistoryButton.OnPressed += _ => HistoryButton.OnPressed += _ =>
{ {
if (_selectedRecord is {} record) if (_selectedRecord is { } record)
OnHistoryUpdated?.Invoke(record, _access, true); OnHistoryUpdated?.Invoke(record, _access, true);
}; };
} }
public void StatusFilterPressed(SecurityStatus statusSelected)
{
OnStatusFilterPressed?.Invoke(statusSelected);
}
public void UpdateState(CriminalRecordsConsoleState state) public void UpdateState(CriminalRecordsConsoleState state)
{ {
if (state.Filter != null) if (state.Filter != null)
@@ -129,10 +167,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
} }
} }
if (state.FilterStatus != _currentCrewListFilter)
{
_currentCrewListFilter = state.FilterStatus;
}
_selectedKey = state.SelectedKey; _selectedKey = state.SelectedKey;
FilterType.SelectId((int)_currentFilterType); FilterType.SelectId((int)_currentFilterType);
CrewListFilter.SelectId((int)_currentCrewListFilter);
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0; NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
PopulateRecordListing(state.RecordListing); PopulateRecordListing(state.RecordListing);
@@ -179,7 +221,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
// in parallel to synchronize the items in RecordListing with `entries`. // in parallel to synchronize the items in RecordListing with `entries`.
int i = RecordListing.Count - 1; int i = RecordListing.Count - 1;
int j = entries.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); var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
if (strcmp == 0) 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. // And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
while (j >= 0) 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--; j--;
} }
} }
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord) 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"); var na = Loc.GetString("generic-not-available-shorthand");
PersonName.Text = stationRecord.Name; PersonName.Text = stationRecord.Name;
PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na)); PersonJob.Text = stationRecord.JobTitle ?? na;
PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
StatusOptionButton.SelectId((int) criminalRecord.Status); // Job icon
if (criminalRecord.Reason is {} reason) 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")); 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}"); message.AddText($": {reason}");
WantedReason.SetMessage(message); WantedReason.SetMessage(message);
WantedReason.Visible = true; WantedReason.Visible = true;
} }
@@ -288,9 +351,37 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
_reasonDialog.OnClose += () => { _reasonDialog = null; }; _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) private string GetTypeFilterLocals(StationRecordFilterType type)
{ {
return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter"); 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;
}
} }

View File

@@ -21,112 +21,131 @@ public sealed class DoorSystem : SharedDoorSystem
protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args) protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args)
{ {
var comp = ent.Comp; var comp = ent.Comp;
comp.OpenSpriteStates = new(2); comp.OpenSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.ClosedSpriteStates = new(2); comp.ClosedSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState)); comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState)); comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));
comp.OpeningAnimation = new Animation() comp.OpeningAnimation = new Animation
{ {
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime), Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.Base, 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), Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.Base, 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), Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.BaseUnlit, 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) if (args.Sprite == null)
return; 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; 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 (!_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;
}
}
TryComp<AnimationPlayerComponent>(uid, out var animPlayer); if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey)) _animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
_animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
args.Sprite.DrawDepth = comp.ClosedDrawDepth; UpdateAppearanceForDoorState(entity, args.Sprite, state);
switch(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: case DoorState.Open:
args.Sprite.DrawDepth = comp.OpenDrawDepth; foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
foreach(var (layer, layerState) in comp.OpenSpriteStates)
{ {
args.Sprite.LayerSetState(layer, layerState); sprite.LayerSetState(layer, layerState);
} }
break;
return;
case DoorState.Closed: case DoorState.Closed:
foreach(var (layer, layerState) in comp.ClosedSpriteStates) foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
{ {
args.Sprite.LayerSetState(layer, layerState); sprite.LayerSetState(layer, layerState);
} }
break;
return;
case DoorState.Opening: case DoorState.Opening:
if (animPlayer != null && comp.OpeningAnimationTime != 0.0) if (entity.Comp.OpeningAnimationTime == 0.0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey); return;
break;
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Closing: case DoorState.Closing:
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0) if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey); return;
break;
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Denying: case DoorState.Denying:
if (animPlayer != null) _animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
break; return;
case DoorState.Welded:
break;
case DoorState.Emagging: case DoorState.Emagging:
if (animPlayer != null) _animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
break; return;
default:
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
} }
} }
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);
return;
}
sprite.BaseRSI = res.RSI;
}
} }

View File

@@ -1,5 +1,8 @@
using Content.Shared.Magic; using Content.Shared.Magic;
using Content.Shared.Magic.Events;
namespace Content.Client.Magic; namespace Content.Client.Magic;
public sealed class MagicSystem : SharedMagicSystem; public sealed class MagicSystem : SharedMagicSystem
{
}

View File

@@ -4,6 +4,7 @@ using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Animations; using Robust.Shared.Animations;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Client.Orbit; namespace Content.Client.Orbit;
@@ -11,8 +12,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
{ {
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly AnimationPlayerSystem _animations = default!; [Dependency] private readonly AnimationPlayerSystem _animations = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly string _orbitAnimationKey = "orbiting";
private readonly string _orbitStopKey = "orbiting_stop"; private readonly string _orbitStopKey = "orbiting_stop";
public override void Initialize() public override void Initialize()
@@ -21,11 +22,11 @@ public sealed class OrbitVisualsSystem : EntitySystem
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove); SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
} }
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args) private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
{ {
_robustRandom.SetSeed((int)_timing.CurTime.TotalMilliseconds);
component.OrbitDistance = component.OrbitDistance =
_robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * 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); var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
return;
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey)) 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) private void OnComponentRemove(EntityUid uid, OrbitVisualsComponent component, ComponentRemove args)
@@ -57,14 +53,9 @@ public sealed class OrbitVisualsSystem : EntitySystem
sprite.EnableDirectionOverride = false; sprite.EnableDirectionOverride = false;
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid); var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
{
_animations.Stop(uid, animationPlayer, _orbitAnimationKey);
}
if (!_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey)) 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>()) 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)); var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0));
sprite.Rotation = angle; 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) private Animation GetStopAnimation(OrbitVisualsComponent component, SpriteComponent sprite)
{ {
var length = component.OrbitStopLength; var length = component.OrbitStopLength;

View File

@@ -56,35 +56,35 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
protected virtual void DeactivateInternal() { } 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) private void OnPlayerAttached(LocalPlayerAttachedEvent args)
{ {
RefreshOverlay(args.Entity); RefreshOverlay();
} }
private void OnPlayerDetached(LocalPlayerDetachedEvent args) private void OnPlayerDetached(LocalPlayerDetachedEvent args)
{ {
if (_player.LocalSession?.AttachedEntity == null) if (_player.LocalSession?.AttachedEntity is null)
Deactivate(); 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) private void OnRoundRestart(RoundRestartCleanupEvent args)
@@ -92,24 +92,24 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
Deactivate(); 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.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; return;
var ev = new RefreshEquipmentHudEvent<T>(TargetSlots); var ev = new RefreshEquipmentHudEvent<T>(TargetSlots);
RaiseLocalEvent(uid, ev); RaiseLocalEvent(entity, ref ev);
if (ev.Active) if (ev.Active)
Update(ev); Update(ev);

View File

@@ -28,7 +28,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
{ {
RefreshOverlay(ent); RefreshOverlay();
} }
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)

View File

@@ -47,7 +47,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
{ {
RefreshOverlay(ent); RefreshOverlay();
} }
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args) private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)

View File

@@ -1,50 +1,129 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.ParticleAccelerator.UI" 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'}"
Title="{Loc 'particle-accelerator-control-menu-device-version-label'}" MinSize="320 120">
MinSize="420 320"
SetSize="420 320"> <!-- Main Container -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0"> <BoxContainer Orientation="Vertical"
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 5">
<BoxContainer Orientation="Horizontal"> <!-- Sub-Main container -->
<RichTextLabel Name="StatusLabel" HorizontalExpand="True"/> <BoxContainer Orientation="Horizontal"
<RichTextLabel Name="StatusStateLabel"/> 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>
<!-- Power -->
<BoxContainer Orientation="Horizontal">
<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>
<!-- Strenght -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StrengthLabel"
HorizontalExpand="True"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<Control MinWidth="8"/>
<SpinBox Name="StateSpinBox" Value="0"/>
</BoxContainer>
<!-- Power -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
<Control MinWidth="8"/>
<RichTextLabel Name="DrawValueLabel"/>
</BoxContainer>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal"> <Control MinHeight="8" VerticalExpand="True"/> <!-- Filler -->
<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"/> <!-- Alarm -->
<Button Name="OnButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-on-button'}" StyleClasses="OpenLeft"/> <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>
</controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}"
HorizontalAlignment="Center"
StyleClasses="LabelSubText"/>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StrengthLabel" Margin="0 0 20 0" HorizontalExpand="True" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<SpinBox Name="StateSpinBox" Value="0"/>
</BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
<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"/>
</BoxContainer> </BoxContainer>
<customControls:VSeparator Margin="0 0 0 10"/>
<BoxContainer Orientation="Vertical" Margin="20 0 20 0" VerticalAlignment="Center"> <PanelContainer StyleClasses="LowDivider" Margin="0 -8" HorizontalAlignment="Right"/>
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
<!-- PA Visual part -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Center"
Margin="8">
<PanelContainer Name="BackPanel"
HorizontalAlignment="Center">
<PanelContainer.PanelOverride> <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> </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/> <Control/>
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/> <ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
<Control/> <Control/>
@@ -58,17 +137,47 @@
<ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/> <ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/>
<ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/> <ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/>
</GridContainer> </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> </BoxContainer>
</PanelContainer> </PanelContainer>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
<controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/> <!-- Footer -->
</controls:StripeBack> <BoxContainer Orientation="Vertical"
<BoxContainer Orientation="Horizontal" Margin="12 0 0 0"> VerticalAlignment="Bottom">
<Label Text="{Loc 'particle-accelerator-control-menu-foo-bar-baz'}" StyleClasses="LabelSubText"/>
<controls:StripeBack>
<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 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>
</BoxContainer> </BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -81,13 +81,19 @@ public sealed partial class NavScreen : BoxContainer
// Get the positive reduced angle. // Get the positive reduced angle.
var displayRot = -worldRot.Reduced(); var displayRot = -worldRot.Reduced();
GridPosition.Text = $"{worldPos.X:0.0}, {worldPos.Y:0.0}"; GridPosition.Text = Loc.GetString("shuttle-console-position-value",
GridOrientation.Text = $"{displayRot.Degrees:0.0}"; ("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; var gridVelocity = gridBody.LinearVelocity;
gridVelocity = displayRot.RotateVec(gridVelocity); gridVelocity = displayRot.RotateVec(gridVelocity);
// Get linear velocity relative to the console entity // Get linear velocity relative to the console entity
GridLinearVelocity.Text = $"{gridVelocity.X + 10f * float.Epsilon:0.0}, {gridVelocity.Y + 10f * float.Epsilon:0.0}"; GridLinearVelocity.Text = Loc.GetString("shuttle-console-linear-velocity-value",
GridAngularVelocity.Text = $"{-gridBody.AngularVelocity + 10f * float.Epsilon:0.0}"; ("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}"));
} }
} }

View File

@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
_inputManager.ViewportKeyEvent(this, args); _inputManager.ViewportKeyEvent(this, args);
} }
protected override void Draw(DrawingHandleScreen handle) protected override void Draw(IRenderHandle handle)
{ {
EnsureViewportCreated(); EnsureViewportCreated();
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
var drawBox = GetDrawBox(); var drawBox = GetDrawBox();
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition); var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal); _viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal); _viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
} }

View File

@@ -138,6 +138,7 @@ public sealed partial class MeleeWeaponSystem
const float length = 0.15f; const float length = 0.15f;
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f)); var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance)); var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
sprite.Rotation += spriteRotation;
return new Animation() return new Animation()
{ {

View File

@@ -70,7 +70,9 @@ namespace Content.IntegrationTests.Tests
"Amber", "Amber",
"Loop", "Loop",
"Plasma", "Plasma",
"Elkridge" "Elkridge",
"Convex"
}; };
/// <summary> /// <summary>

View File

@@ -5,6 +5,7 @@ using Content.Server.Store.Systems;
using Content.Server.Traitor.Uplink; using Content.Server.Traitor.Uplink;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components; using Content.Shared.Store.Components;
using Content.Shared.StoreDiscount.Components; using Content.Shared.StoreDiscount.Components;
@@ -64,6 +65,7 @@ public sealed class StoreTests
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var invSystem = entManager.System<InventorySystem>(); var invSystem = entManager.System<InventorySystem>();
var mindSystem = entManager.System<SharedMindSystem>();
human = entManager.SpawnEntity("HumanUniformDummy", coordinates); human = entManager.SpawnEntity("HumanUniformDummy", coordinates);
uniform = entManager.SpawnEntity("UniformDummy", 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, uniform, "jumpsuit"));
Assert.That(invSystem.TryEquip(human, pda, "id")); Assert.That(invSystem.TryEquip(human, pda, "id"));
var mind = mindSystem.CreateMind(null);
mindSystem.TransferTo(mind, human, mind: mind);
FixedPoint2 originalBalance = 20; FixedPoint2 originalBalance = 20;
uplinkSystem.AddUplink(human, originalBalance, null, true); uplinkSystem.AddUplink(human, originalBalance, null, true);

View File

@@ -1,7 +1,6 @@
using Content.Server.Wires; using Content.Server.Wires;
using Content.Shared.Access; using Content.Shared.Access;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Emag.Components;
using Content.Shared.Wires; using Content.Shared.Wires;
namespace Content.Server.Access; namespace Content.Server.Access;
@@ -31,11 +30,9 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp) public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
{ {
if (!EntityManager.HasComponent<EmaggedComponent>(wire.Owner)) comp.Enabled = true;
{ EntityManager.Dirty(wire.Owner, comp);
comp.Enabled = true;
EntityManager.Dirty(wire.Owner, comp);
}
return true; return true;
} }
@@ -58,7 +55,7 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
{ {
if (!wire.IsCut) 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; access.Enabled = true;
EntityManager.Dirty(wire.Owner, access); EntityManager.Dirty(wire.Owner, access);

View File

@@ -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)}]"); $"{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); accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
var ev = new OnAccessOverriderAccessUpdatedEvent(player);
RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
Dirty(accessReaderEnt.Value); Dirty(accessReaderEnt.Value);
} }

View File

@@ -165,6 +165,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
null); null);
await _db.AddServerBanAsync(banDef); 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 var adminName = banningAdmin == null
? Loc.GetString("system-user") ? Loc.GetString("system-user")
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user"); : (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");

View File

@@ -892,5 +892,36 @@ public sealed partial class AdminVerbSystem
Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description")) Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description"))
}; };
args.Verbs.Add(superslip); 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);
} }
} }

View File

@@ -116,8 +116,11 @@ public sealed class TechAnomalySystem : EntitySystem
if (_random.Prob(tech.Comp.EmagSupercritProbability)) if (_random.Prob(tech.Comp.EmagSupercritProbability))
{ {
_emag.DoEmagEffect(tech, source); var sourceEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
_emag.DoEmagEffect(tech, sink); RaiseLocalEvent(source, ref sourceEv);
var sinkEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
RaiseLocalEvent(sink, ref sinkEv);
} }
CreateNewLink(tech, source, sink); CreateNewLink(tech, source, sink);

View File

@@ -21,6 +21,7 @@ public sealed class FireAlarmSystem : EntitySystem
{ {
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly AccessReaderSystem _access = default!; [Dependency] private readonly AccessReaderSystem _access = default!;
[Dependency] private readonly IConfigurationManager _configManager = 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) 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;
// Remove the atmos alarmable component permanently from this device.
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable); if (_emag.CheckFlag(uid, EmagType.Interaction))
RemCompDeferred<AtmosAlarmableComponent>(uid); 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;
} }
} }

View File

@@ -20,6 +20,7 @@ namespace Content.Server.Bed
{ {
[Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly ActionsSystem _actionsSystem = default!; [Dependency] private readonly ActionsSystem _actionsSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SleepingSystem _sleepingSystem = default!; [Dependency] private readonly SleepingSystem _sleepingSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = 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) 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 // Reset any metabolisms first so they receive the multiplier correctly
UpdateMetabolisms(uid, component, false); UpdateMetabolisms(uid, component, false);
component.Multiplier = 1 / component.Multiplier; component.Multiplier = 1 / component.Multiplier;

View File

@@ -45,5 +45,6 @@ public sealed class LogSystem : EntitySystem
} }
QueueDel(uid); QueueDel(uid);
args.Handled = true;
} }
} }

View File

@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
/// <summary> /// <summary>
/// Maximum amount of bounties a station can have. /// Maximum amount of bounties a station can have.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public int MaxBounties = 6; public int MaxBounties = 6;
/// <summary> /// <summary>
/// A list of all the bounties currently active for a station. /// A list of all the bounties currently active for a station.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public List<CargoBountyData> Bounties = new(); 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> /// <summary>
/// Used to determine unique order IDs /// Used to determine unique order IDs
/// </summary> /// </summary>

View File

@@ -8,6 +8,7 @@ using Content.Shared.Cargo;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.NameIdentifier; using Content.Shared.NameIdentifier;
using Content.Shared.Paper; using Content.Shared.Paper;
using Content.Shared.Stacks; using Content.Shared.Stacks;
@@ -16,6 +17,7 @@ using JetBrains.Annotations;
using Robust.Server.Containers; using Robust.Server.Containers;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Cargo.Systems; namespace Content.Server.Cargo.Systems;
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
[Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!; [Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[ValidatePrototypeId<NameIdentifierGroupPrototype>] [ValidatePrototypeId<NameIdentifierGroupPrototype>]
private const string BountyNameIdentifierGroup = "Bounty"; private const string BountyNameIdentifierGroup = "Bounty";
@@ -54,7 +57,7 @@ public sealed partial class CargoSystem
return; return;
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime; 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) private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
@@ -95,13 +98,13 @@ public sealed partial class CargoSystem
return; return;
} }
if (!TryRemoveBounty(station, bounty.Value)) if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
return; return;
FillBountyDatabase(station); FillBountyDatabase(station);
db.NextSkipTime = _timing.CurTime + db.SkipDelay; db.NextSkipTime = _timing.CurTime + db.SkipDelay;
var untilNextSkip = db.NextSkipTime - _timing.CurTime; 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); _audio.PlayPvs(component.SkipSound, uid);
} }
@@ -179,7 +182,7 @@ public sealed partial class CargoSystem
continue; continue;
} }
TryRemoveBounty(station, bounty.Value); TryRemoveBounty(station, bounty.Value, false);
FillBountyDatabase(station); FillBountyDatabase(station);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled"); _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] [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 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; 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; return true;
} }
} }
@@ -492,7 +515,7 @@ public sealed partial class CargoSystem
} }
var untilNextSkip = db.NextSkipTime - _timing.CurTime; 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));
} }
} }

View File

@@ -8,7 +8,7 @@ using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Events; using Content.Shared.Cargo.Events;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Paper; using Content.Shared.Paper;
@@ -21,6 +21,7 @@ namespace Content.Server.Cargo.Systems
public sealed partial class CargoSystem public sealed partial class CargoSystem
{ {
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
/// <summary> /// <summary>
/// How much time to wait (in seconds) before increasing bank accounts balance. /// 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, ComponentInit>(OnInit);
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing); SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated); SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
Reset(); Reset();
} }
@@ -62,6 +64,7 @@ namespace Content.Server.Cargo.Systems
_audio.PlayPvs(component.ConfirmSound, uid); _audio.PlayPvs(component.ConfirmSound, uid);
UpdateBankAccount(stationUid.Value, bank, (int) price); UpdateBankAccount(stationUid.Value, bank, (int) price);
QueueDel(args.Used); QueueDel(args.Used);
args.Handled = true;
} }
private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args) private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args)
@@ -75,6 +78,17 @@ namespace Content.Server.Cargo.Systems
_timer = 0; _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) private void UpdateConsole(float frameTime)
{ {
_timer += frameTime; _timer += frameTime;
@@ -85,9 +99,11 @@ namespace Content.Server.Cargo.Systems
{ {
_timer -= Delay; _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>(); var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
@@ -192,7 +208,7 @@ namespace Content.Server.Cargo.Systems
order.Approved = true; order.Approved = true;
_audio.PlayPvs(component.ConfirmSound, uid); _audio.PlayPvs(component.ConfirmSound, uid);
if (!HasComp<EmaggedComponent>(uid)) if (!_emag.CheckFlag(uid, EmagType.Interaction))
{ {
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, player); var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, player);
RaiseLocalEvent(tryGetIdentityShortInfoEvent); 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}"); $"{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); orderDatabase.Orders.Remove(order);
DeductFunds(bank, cost); UpdateBankAccount(station.Value, bank, -cost);
UpdateOrders(station.Value); 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 #region Station
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp) private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)

View File

@@ -60,6 +60,7 @@ namespace Content.Server.Cloning
[Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!; [Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new(); public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new();
public const float EasyModeCloningCost = 0.7f; public const float EasyModeCloningCost = 0.7f;
@@ -276,10 +277,15 @@ namespace Content.Server.Cloning
/// </summary> /// </summary>
private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) 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)) if (!this.IsPowered(uid, EntityManager))
return; return;
_audio.PlayPvs(clonePod.SparkSound, uid);
_popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid);
args.Handled = true; args.Handled = true;
} }
@@ -309,7 +315,7 @@ namespace Content.Server.Cloning
var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform)); var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform));
var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true); var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
if (HasComp<EmaggedComponent>(uid)) if (_emag.CheckFlag(uid, EmagType.Interaction))
{ {
_audio.PlayPvs(clonePod.ScreamSound, uid); _audio.PlayPvs(clonePod.ScreamSound, uid);
Spawn(clonePod.MobSpawnId, transform.Coordinates); Spawn(clonePod.MobSpawnId, transform.Coordinates);
@@ -327,7 +333,7 @@ namespace Content.Server.Cloning
} }
_puddleSystem.TrySpillAt(uid, bloodSolution, out _); _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); _material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
} }

View File

@@ -16,7 +16,6 @@ using Content.Shared.Chat;
using Content.Shared.Communications; using Content.Shared.Communications;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Emag.Components;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Popups; using Content.Shared.Popups;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -177,7 +176,7 @@ namespace Content.Server.Communications
private bool CanUse(EntityUid user, EntityUid console) 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); return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
} }

View File

@@ -13,6 +13,8 @@ using Robust.Server.GameObjects;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Security.Components; using Content.Shared.Security.Components;
using System.Linq;
using Content.Shared.Roles.Jobs;
namespace Content.Server.CriminalRecords.Systems; namespace Content.Server.CriminalRecords.Systems;
@@ -42,6 +44,7 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus); subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
subs.Event<CriminalRecordAddHistory>(OnAddHistory); subs.Event<CriminalRecordAddHistory>(OnAddHistory);
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory); subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
subs.Event<CriminalRecordSetStatusFilter>(OnStatusFilterPressed);
}); });
} }
@@ -57,6 +60,11 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
ent.Comp.ActiveKey = msg.SelectedKey; ent.Comp.ActiveKey = msg.SelectedKey;
UpdateUserInterface(ent); 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) 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 // 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); _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer);
(string, object)[] args; (string, object)[] args;
if (reason != null) 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 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 // figure out which radio message to send depending on transition
var statusString = (oldStatus, msg.Status) switch var statusString = (oldStatus, msg.Status) switch
@@ -193,8 +214,18 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
return; return;
} }
// get the listing of records to display
var listing = _records.BuildListing((owningStation.Value, stationRecords), console.Filter); 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); var state = new CriminalRecordsConsoleState(listing, console.Filter);
if (console.ActiveKey is { } id) if (console.ActiveKey is { } id)
{ {
@@ -205,6 +236,9 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
state.SelectedKey = id; 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); _ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state);
} }

View File

@@ -1112,7 +1112,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.SingleOrDefaultAsync()); .SingleOrDefaultAsync());
} }
public async Task SetLastReadRules(NetUserId player, DateTimeOffset date) public async Task SetLastReadRules(NetUserId player, DateTimeOffset? date)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1122,7 +1122,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return; return;
} }
dbPlayer.LastReadRules = date.UtcDateTime; dbPlayer.LastReadRules = date?.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }

View File

@@ -282,7 +282,7 @@ namespace Content.Server.Database
#region Rules #region Rules
Task<DateTimeOffset?> GetLastReadRules(NetUserId player); Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
Task SetLastReadRules(NetUserId player, DateTimeOffset time); Task SetLastReadRules(NetUserId player, DateTimeOffset? time);
#endregion #endregion
@@ -830,7 +830,7 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.GetLastReadRules(player)); return RunDbCommand(() => _db.GetLastReadRules(player));
} }
public Task SetLastReadRules(NetUserId player, DateTimeOffset time) public Task SetLastReadRules(NetUserId player, DateTimeOffset? time)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.SetLastReadRules(player, time)); return RunDbCommand(() => _db.SetLastReadRules(player, time));

View File

@@ -15,7 +15,6 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
/// The type of explosion. Determines damage types and tile break chance scaling. /// The type of explosion. Determines damage types and tile break chance scaling.
/// </summary> /// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))] [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
[JsonIgnore]
public string ExplosionType = default!; public string ExplosionType = default!;
/// <summary> /// <summary>
@@ -23,14 +22,12 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
/// chance. /// chance.
/// </summary> /// </summary>
[DataField] [DataField]
[JsonIgnore]
public float MaxIntensity = 5; public float MaxIntensity = 5;
/// <summary> /// <summary>
/// How quickly intensity drops off as you move away from the epicenter /// How quickly intensity drops off as you move away from the epicenter
/// </summary> /// </summary>
[DataField] [DataField]
[JsonIgnore]
public float IntensitySlope = 1; public float IntensitySlope = 1;
/// <summary> /// <summary>
@@ -41,15 +38,20 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles. /// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
/// </remarks> /// </remarks>
[DataField] [DataField]
[JsonIgnore]
public float MaxTotalIntensity = 100; public float MaxTotalIntensity = 100;
/// <summary> /// <summary>
/// The intensity of the explosion per unit reaction. /// The intensity of the explosion per unit reaction.
/// </summary> /// </summary>
[DataField] [DataField]
[JsonIgnore]
public float IntensityPerUnit = 1; 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; public override bool ShouldLog => true;
@@ -72,6 +74,7 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
ExplosionType, ExplosionType,
intensity, intensity,
IntensitySlope, IntensitySlope,
MaxIntensity); MaxIntensity,
TileBreakScale);
} }
} }

View File

@@ -50,6 +50,7 @@ public sealed class FaxSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly FaxecuteSystem _faxecute = default!; [Dependency] private readonly FaxecuteSystem _faxecute = default!;
[Dependency] private readonly EmagSystem _emag = default!;
private const string PaperSlotId = "Paper"; private const string PaperSlotId = "Paper";
@@ -227,7 +228,7 @@ public sealed class FaxSystem : EntitySystem
return; 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); _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
return; return;
@@ -246,7 +247,12 @@ public sealed class FaxSystem : EntitySystem
private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args) 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; args.Handled = true;
} }
@@ -260,7 +266,7 @@ public sealed class FaxSystem : EntitySystem
switch (command) switch (command)
{ {
case FaxConstants.FaxPingCommand: case FaxConstants.FaxPingCommand:
var isForSyndie = HasComp<EmaggedComponent>(uid) && var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
args.Data.ContainsKey(FaxConstants.FaxSyndicateData); args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
if (!isForSyndie && !component.ResponsePings) if (!isForSyndie && !component.ResponsePings)
return; return;
@@ -405,7 +411,7 @@ public sealed class FaxSystem : EntitySystem
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand } { DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
}; };
if (HasComp<EmaggedComponent>(uid)) if (_emag.CheckFlag(uid, EmagType.Interaction))
payload.Add(FaxConstants.FaxSyndicateData, true); payload.Add(FaxConstants.FaxSyndicateData, true);
_deviceNetworkSystem.QueuePacket(uid, null, payload); _deviceNetworkSystem.QueuePacket(uid, null, payload);

View 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);
}
}
}

View File

@@ -34,7 +34,7 @@ public sealed class RulesManager
{ {
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime), PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
CoreRules = _cfg.GetCVar(CCVars.RulesFile), CoreRules = _cfg.GetCVar(CCVars.RulesFile),
ShouldShowRules = !isLocalhost && !hasCooldown ShouldShowRules = !isLocalhost && !hasCooldown,
}; };
_netManager.ServerSendMessage(showRulesMessage, e.Channel); _netManager.ServerSendMessage(showRulesMessage, e.Channel);
} }

View File

@@ -16,6 +16,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Lathe; using Content.Shared.Lathe;
using Content.Shared.Materials; using Content.Shared.Materials;
@@ -42,6 +43,7 @@ namespace Content.Server.Lathe
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly UserInterfaceSystem _uiSys = default!; [Dependency] private readonly UserInterfaceSystem _uiSys = default!;
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!; [Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
[Dependency] private readonly PopupSystem _popup = 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)) if (uid != args.Lathe || !TryComp<TechnologyDatabaseComponent>(uid, out var technologyDatabase))
return; return;
if (!args.getUnavailable && !HasComp<EmaggedComponent>(uid)) if (!args.getUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction))
return; return;
foreach (var recipe in component.EmagDynamicRecipes) foreach (var recipe in component.EmagDynamicRecipes)
{ {

View File

@@ -19,4 +19,17 @@ public sealed class MagicSystem : SharedMagicSystem
{ {
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false); _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);
}
} }

View File

@@ -72,7 +72,10 @@ public sealed class HealingSystem : EntitySystem
_bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier); _bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
if (isBleeding != bloodstream.BleedAmount > 0) 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);
} }
} }

View File

@@ -35,7 +35,7 @@ public sealed class MiningSystem : EntitySystem
return; return;
var coords = Transform(uid).Coordinates; 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++) for (var i = 0; i < toSpawn; i++)
{ {
Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f))); Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f)));

View File

@@ -1,5 +1,5 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.NPC.Components; using Content.Shared.NPC.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Components;
@@ -55,34 +55,11 @@ public sealed partial class MedibotInjectOperator : HTNOperator
if (!_entMan.TryGetComponent<MedibotComponent>(owner, out var botComp)) if (!_entMan.TryGetComponent<MedibotComponent>(owner, out var botComp))
return HTNOperatorStatus.Failed; return HTNOperatorStatus.Failed;
if (!_medibot.CheckInjectable((owner, botComp), target) || !_medibot.TryInject((owner, botComp), target))
if (!_entMan.TryGetComponent<DamageableComponent>(target, out var damage))
return HTNOperatorStatus.Failed; 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); _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, hideChat: true, hideLog: true);
return HTNOperatorStatus.Finished; return HTNOperatorStatus.Finished;
} }
} }

View File

@@ -1,6 +1,6 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.NPC.Components; using Content.Shared.NPC.Components;
using Content.Server.NPC.Pathfinding; using Content.Server.NPC.Pathfinding;
using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Damage; using Content.Shared.Damage;

View File

@@ -1,4 +1,4 @@
using Content.Server.NPC.Components; using Content.Shared.NPC.Components;
namespace Content.Server.NPC.Systems; namespace Content.Server.NPC.Systems;

View File

@@ -25,6 +25,13 @@ namespace Content.Server.Nuke
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public int Timer = 300; 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> /// <summary>
/// How long until the bomb can arm again after deactivation. /// How long until the bomb can arm again after deactivation.
/// Used to prevent announcements spam. /// Used to prevent announcements spam.

View File

@@ -522,6 +522,9 @@ public sealed class NukeSystem : EntitySystem
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound)); _sound.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke); _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 // disable sound and reset it
component.PlayedAlertSound = false; component.PlayedAlertSound = false;
component.AlertAudioStream = _audio.Stop(component.AlertAudioStream); component.AlertAudioStream = _audio.Stop(component.AlertAudioStream);

View File

@@ -21,6 +21,7 @@ namespace Content.Server.Nutrition.EntitySystems;
public sealed class FatExtractorSystem : EntitySystem public sealed class FatExtractorSystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly HungerSystem _hunger = default!; [Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = 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) 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.Handled = true;
args.Repeatable = false;
} }
private void OnClosed(EntityUid uid, FatExtractorComponent component, ref StorageAfterCloseEvent args) 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) if (_hunger.GetHunger(hunger) < component.NutritionPerSecond)
return false; return false;
if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp<EmaggedComponent>(uid)) if (hunger.CurrentThreshold < component.MinHungerThreshold && !_emag.CheckFlag(uid, EmagType.Interaction))
return false; return false;
return true; return true;

View File

@@ -24,6 +24,7 @@ namespace Content.Server.Nutrition.EntitySystems
{ {
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly FoodSystem _foodSystem = default!; [Dependency] private readonly FoodSystem _foodSystem = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -63,7 +64,7 @@ namespace Content.Server.Nutrition.EntitySystems
forced = false; 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); _explosionSystem.QueueExplosion(entity.Owner, "Default", entity.Comp.ExplosionIntensity, 0.5f, 3, canCreateVacuum: false);
EntityManager.DeleteEntity(entity); EntityManager.DeleteEntity(entity);
@@ -161,8 +162,15 @@ namespace Content.Server.Nutrition.EntitySystems
args.Args.Target.Value); args.Args.Target.Value);
} }
} }
private void OnEmagged(Entity<VapeComponent> entity, ref GotEmaggedEvent args) 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; args.Handled = true;
} }
} }

View File

@@ -4,7 +4,6 @@ using Content.Server.Power.Components;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.APC; using Content.Shared.APC;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Rounding; using Content.Shared.Rounding;
@@ -19,6 +18,7 @@ public sealed class ApcSystem : EntitySystem
{ {
[Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = 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) 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; args.Handled = true;
} }
@@ -170,7 +175,7 @@ public sealed class ApcSystem : EntitySystem
private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery) private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery)
{ {
if (HasComp<EmaggedComponent>(uid)) if (_emag.CheckFlag(uid, EmagType.Interaction))
return ApcChargeState.Emag; return ApcChargeState.Emag;
if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold) if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold)

View File

@@ -3,6 +3,7 @@ using Content.Server.Research.Components;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Research.Components; using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes; using Content.Shared.Research.Prototypes;
@@ -11,6 +12,8 @@ namespace Content.Server.Research.Systems;
public sealed partial class ResearchSystem public sealed partial class ResearchSystem
{ {
[Dependency] private readonly EmagSystem _emag = default!;
private void InitializeConsole() private void InitializeConsole()
{ {
SubscribeLocalEvent<ResearchConsoleComponent, ConsoleUnlockTechnologyMessage>(OnConsoleUnlock); SubscribeLocalEvent<ResearchConsoleComponent, ConsoleUnlockTechnologyMessage>(OnConsoleUnlock);
@@ -18,6 +21,7 @@ public sealed partial class ResearchSystem
SubscribeLocalEvent<ResearchConsoleComponent, ResearchServerPointsChangedEvent>(OnPointsChanged); SubscribeLocalEvent<ResearchConsoleComponent, ResearchServerPointsChangedEvent>(OnPointsChanged);
SubscribeLocalEvent<ResearchConsoleComponent, ResearchRegistrationChangedEvent>(OnConsoleRegistrationChanged); SubscribeLocalEvent<ResearchConsoleComponent, ResearchRegistrationChangedEvent>(OnConsoleRegistrationChanged);
SubscribeLocalEvent<ResearchConsoleComponent, TechnologyDatabaseModifiedEvent>(OnConsoleDatabaseModified); SubscribeLocalEvent<ResearchConsoleComponent, TechnologyDatabaseModifiedEvent>(OnConsoleDatabaseModified);
SubscribeLocalEvent<ResearchConsoleComponent, GotEmaggedEvent>(OnEmagged);
} }
private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args) private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args)
@@ -39,7 +43,7 @@ public sealed partial class ResearchSystem
if (!UnlockTechnology(uid, args.Id, act)) if (!UnlockTechnology(uid, args.Id, act))
return; return;
if (!HasComp<EmaggedComponent>(uid)) if (!_emag.CheckFlag(uid, EmagType.Interaction))
{ {
var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, act); var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, act);
RaiseLocalEvent(getIdentityEvent); RaiseLocalEvent(getIdentityEvent);
@@ -52,7 +56,7 @@ public sealed partial class ResearchSystem
); );
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false); _radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
} }
SyncClientWithServer(uid); SyncClientWithServer(uid);
UpdateConsoleInterface(uid, component); UpdateConsoleInterface(uid, component);
} }
@@ -100,4 +104,15 @@ public sealed partial class ResearchSystem
UpdateConsoleInterface(uid, component); 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;
}
} }

View File

@@ -36,7 +36,6 @@ public sealed partial class RevenantSystem
{ {
[Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly GhostSystem _ghost = default!;
@@ -343,7 +342,8 @@ public sealed partial class RevenantSystem
_whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent)) _whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent))
continue; continue;
_emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable. var ev = new GotEmaggedEvent(uid, EmagType.Interaction | EmagType.Access);
RaiseLocalEvent(ent, ref ev);
} }
} }
} }

View File

@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Content.Shared.Emag.Systems;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Silicons.Borgs; namespace Content.Server.Silicons.Borgs;
@@ -15,6 +16,8 @@ namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/> /// <inheritdoc/>
public sealed partial class BorgSystem public sealed partial class BorgSystem
{ {
[Dependency] private readonly EmagSystem _emag = default!;
private void InitializeTransponder() private void InitializeTransponder()
{ {
SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived); SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
@@ -127,7 +130,7 @@ public sealed partial class BorgSystem
private bool CheckEmagged(EntityUid uid, string name) 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); Popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution);
return true; return true;

View File

@@ -6,7 +6,6 @@ using Content.Server.Roles;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Mind; using Content.Shared.Mind;
@@ -14,7 +13,6 @@ using Content.Shared.Mind.Components;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Stunnable;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -22,7 +20,6 @@ using Robust.Shared.Containers;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed; using Robust.Shared.Toolshed;
using Robust.Shared.Audio;
namespace Content.Server.Silicons.Laws; namespace Content.Server.Silicons.Laws;
@@ -34,8 +31,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly EmagSystem _emag = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
@@ -52,7 +49,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws); SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded); SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved); SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded); SubscribeLocalEvent<SiliconLawProviderComponent, SiliconEmaggedEvent>(OnEmagLawsAdded);
} }
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) 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) private void OnIonStormLaws(EntityUid uid, SiliconLawProviderComponent component, ref IonStormLawsEvent args)
{ {
// Emagged borgs are immune to ion storm // Emagged borgs are immune to ion storm
if (!HasComp<EmaggedComponent>(uid)) if (!_emag.CheckFlag(uid, EmagType.Interaction))
{ {
component.Lawset = args.Lawset; 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) if (component.Lawset == null)
component.Lawset = GetLawset(component.Laws); component.Lawset = GetLawset(component.Laws);
@@ -164,7 +160,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
// Add the first emag law before the others // Add the first emag law before the others
component.Lawset?.Laws.Insert(0, new SiliconLaw 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 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) private void EnsureSubvertedSiliconRole(EntityUid mindId)
{ {
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId)) if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))

View File

@@ -1,7 +1,6 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Power.Events; using Content.Server.Power.Events;
using Content.Server.Stunnable.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage.Events; using Content.Shared.Damage.Events;
using Content.Shared.Examine; using Content.Shared.Examine;

View File

@@ -39,6 +39,7 @@ namespace Content.Server.VendingMachines
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly SharedPointLightSystem _light = default!;
[Dependency] private readonly EmagSystem _emag = default!;
private const float WallVendEjectDistanceFromWall = 1f; private const float WallVendEjectDistanceFromWall = 1f;
@@ -48,7 +49,6 @@ namespace Content.Server.VendingMachines
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged); SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak); SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged); SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice); SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse); SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
@@ -123,12 +123,6 @@ namespace Content.Server.VendingMachines
TryUpdateVisualState(uid, vendComponent); 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) private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
{ {
if (!args.DamageIncreased && component.Broken) if (!args.DamageIncreased && component.Broken)
@@ -232,7 +226,7 @@ namespace Content.Server.VendingMachines
if (!TryComp<AccessReaderComponent>(uid, out var accessReader)) if (!TryComp<AccessReaderComponent>(uid, out var accessReader))
return true; return true;
if (_accessReader.IsAllowed(sender, uid, accessReader) || HasComp<EmaggedComponent>(uid)) if (_accessReader.IsAllowed(sender, uid, accessReader))
return true; return true;
Popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), uid); 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)) if (!Resolve(uid, ref component))
return null; return null;
if (type == InventoryType.Emagged && HasComp<EmaggedComponent>(uid)) if (type == InventoryType.Emagged && _emag.CheckFlag(uid, EmagType.Interaction))
return component.EmaggedInventory.GetValueOrDefault(entryId); return component.EmaggedInventory.GetValueOrDefault(entryId);
if (type == InventoryType.Contraband && component.Contraband) if (type == InventoryType.Contraband && component.Contraband)

View File

@@ -76,7 +76,7 @@ public sealed partial class AccessReaderComponent : Component
/// Whether or not emag interactions have an effect on this. /// Whether or not emag interactions have an effect on this.
/// </summary> /// </summary>
[DataField] [DataField]
public bool BreakOnEmag = true; public bool BreakOnAccessBreaker = true;
} }
[DataDefinition, Serializable, NetSerializable] [DataDefinition, Serializable, NetSerializable]

View File

@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceLinking.Events;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Inventory; using Content.Shared.Inventory;
@@ -24,6 +23,7 @@ public sealed class AccessReaderSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedGameTicker _gameTicker = default!; [Dependency] private readonly SharedGameTicker _gameTicker = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = 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. if (args.User == null) // AutoLink (and presumably future external linkers) have no user.
return; return;
if (!HasComp<EmaggedComponent>(uid) && !IsAllowed(args.User.Value, uid, component)) if (!IsAllowed(args.User.Value, uid, component))
args.Cancel(); args.Cancel();
} }
private void OnEmagged(EntityUid uid, AccessReaderComponent reader, ref GotEmaggedEvent args) private void OnEmagged(EntityUid uid, AccessReaderComponent reader, ref GotEmaggedEvent args)
{ {
if (!reader.BreakOnEmag) if (!_emag.CompareFlag(args.Type, EmagType.Access))
return; 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; args.Handled = true;
reader.Enabled = false; accessReader.Value.Comp.AccessLists.Clear();
reader.AccessLog.Clear(); accessReader.Value.Comp.AccessLog.Clear();
Dirty(uid, reader); Dirty(uid, reader);
} }
@@ -135,6 +146,7 @@ public sealed class AccessReaderSystem : EntitySystem
return true; return true;
} }
} }
return true; return true;
} }

View File

@@ -45,3 +45,6 @@ namespace Content.Shared.Access.Systems
} }
} }
} }
[ByRefEvent]
public record struct OnAccessOverriderAccessUpdatedEvent(EntityUid UserUid, bool Handled = false);

View File

@@ -89,6 +89,12 @@ public sealed partial class CCVars
public static readonly CVarDef<bool> ServerBanErasePlayer = public static readonly CVarDef<bool> ServerBanErasePlayer =
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); 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> /// <summary>
/// Minimum players sharing a connection required to create an alert. -1 to disable the alert. /// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
/// </summary> /// </summary>

View 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,
}
}

View File

@@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component
public sealed class CargoBountyConsoleState : BoundUserInterfaceState public sealed class CargoBountyConsoleState : BoundUserInterfaceState
{ {
public List<CargoBountyData> Bounties; public List<CargoBountyData> Bounties;
public List<CargoBountyHistoryData> History;
public TimeSpan UntilNextSkip; public TimeSpan UntilNextSkip;
public CargoBountyConsoleState(List<CargoBountyData> bounties, TimeSpan untilNextSkip) public CargoBountyConsoleState(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
{ {
Bounties = bounties; Bounties = bounties;
History = history;
UntilNextSkip = untilNextSkip; UntilNextSkip = untilNextSkip;
} }
} }

View File

@@ -26,7 +26,8 @@ public sealed class SolutionSpikerSystem : EntitySystem
private void OnInteractUsing(Entity<RefillableSolutionComponent> entity, ref InteractUsingEvent args) 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> /// <summary>
@@ -36,7 +37,7 @@ public sealed class SolutionSpikerSystem : EntitySystem
/// <param name="source">Source of the solution.</param> /// <param name="source">Source of the solution.</param>
/// <param name="target">Target to spike with the solution from source.</param> /// <param name="target">Target to spike with the solution from source.</param>
/// <param name="user">User spiking the target solution.</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, SolutionSpikerComponent? spikableSource = null,
SolutionContainerManagerComponent? managerSource = null, SolutionContainerManagerComponent? managerSource = null,
SolutionContainerManagerComponent? managerTarget = 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.TryGetRefillableSolution((target, spikableTarget, managerTarget), out var targetSoln, out var targetSolution)
|| !_solution.TryGetSolution((source, managerSource), spikableSource.SourceSolution, out _, out var sourceSolution)) || !_solution.TryGetSolution((source, managerSource), spikableSource.SourceSolution, out _, out var sourceSolution))
{ {
return; return false;
} }
if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty) if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty)
{ {
_popup.PopupClient(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user); _popup.PopupClient(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
return; return false;
} }
if (!_solution.ForceAddSolution(targetSoln.Value, sourceSolution)) if (!_solution.ForceAddSolution(targetSoln.Value, sourceSolution))
return; return false;
_popup.PopupClient(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user); _popup.PopupClient(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user);
sourceSolution.RemoveAllSolution(); sourceSolution.RemoveAllSolution();
if (spikableSource.Delete) if (spikableSource.Delete)
QueueDel(source); QueueDel(source);
return true;
} }
} }

View File

@@ -46,15 +46,6 @@ public sealed partial class CloningPodComponent : Component
[DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)] [DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)]
public EntProtoId MobSpawnId = "MobAbomination"; 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 // TODO: Remove this from here when cloning and/or zombies are refactored
[DataField("screamSound")] [DataField("screamSound")]
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams") public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams")

View File

@@ -1,7 +1,10 @@
using Content.Shared.CriminalRecords.Systems; using Content.Shared.CriminalRecords.Systems;
using Content.Shared.CriminalRecords.Components;
using Content.Shared.CriminalRecords;
using Content.Shared.Radio; using Content.Shared.Radio;
using Content.Shared.StationRecords; using Content.Shared.StationRecords;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Security;
namespace Content.Shared.CriminalRecords.Components; namespace Content.Shared.CriminalRecords.Components;
@@ -31,6 +34,12 @@ public sealed partial class CriminalRecordsConsoleComponent : Component
[DataField] [DataField]
public StationRecordsFilter? Filter; public StationRecordsFilter? Filter;
/// <summary>
/// Current seleced security status for the filter by criminal status dropdown.
/// </summary>
[DataField]
public SecurityStatus FilterStatus;
/// <summary> /// <summary>
/// Channel to send messages to when someone's status gets changed. /// Channel to send messages to when someone's status gets changed.
/// </summary> /// </summary>

View File

@@ -35,9 +35,9 @@ public sealed class CriminalRecordsConsoleState : BoundUserInterfaceState
/// Currently selected crewmember record key. /// Currently selected crewmember record key.
/// </summary> /// </summary>
public uint? SelectedKey = null; public uint? SelectedKey = null;
public CriminalRecord? CriminalRecord = null; public CriminalRecord? CriminalRecord = null;
public GeneralStationRecord? StationRecord = null; public GeneralStationRecord? StationRecord = null;
public SecurityStatus FilterStatus = SecurityStatus.None;
public readonly Dictionary<uint, string>? RecordListing; public readonly Dictionary<uint, string>? RecordListing;
public readonly StationRecordsFilter? Filter; public readonly StationRecordsFilter? Filter;
@@ -100,3 +100,20 @@ public sealed class CriminalRecordDeleteHistory : BoundUserInterfaceMessage
Index = index; 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;
}
}

View File

@@ -24,6 +24,7 @@ public sealed partial class DisposalDoAfterEvent : SimpleDoAfterEvent
public abstract class SharedDisposalUnitSystem : EntitySystem public abstract class SharedDisposalUnitSystem : EntitySystem
{ {
[Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] protected readonly EmagSystem _emag = default!;
[Dependency] protected readonly MetaDataSystem Metadata = default!; [Dependency] protected readonly MetaDataSystem Metadata = default!;
[Dependency] protected readonly SharedJointSystem Joints = default!; [Dependency] protected readonly SharedJointSystem Joints = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = 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) 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; component.DisablePressure = true;
args.Handled = true; args.Handled = true;
} }

View File

@@ -34,6 +34,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
[Dependency] private readonly INetManager _net = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!; [Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] protected readonly TagSystem Tags = default!; [Dependency] protected readonly TagSystem Tags = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!;
@@ -77,8 +78,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt); SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged); SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier); SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier);
SubscribeLocalEvent<DoorComponent, OnAttemptEmagEvent>(OnAttemptEmag);
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
} }
@@ -118,31 +117,24 @@ public abstract partial class SharedDoorSystem : EntitySystem
_activeDoors.Remove(door); _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) 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)) if (!SetState(uid, DoorState.Emagging, door))
return; return;
Audio.PlayPredicted(door.SparkSound, uid, args.UserUid, AudioParams.Default.WithVolume(8));
args.Repeatable = true;
args.Handled = true; args.Handled = true;
} }

View File

@@ -1,6 +1,8 @@
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -14,7 +16,21 @@ public sealed partial class EmagComponent : Component
/// <summary> /// <summary>
/// The tag that marks an entity as immune to emags /// The tag that marks an entity as immune to emags
/// </summary> /// </summary>
[DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>)), ViewVariables(VVAccess.ReadWrite)] [DataField]
[AutoNetworkedField] [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");
} }

View File

@@ -1,3 +1,4 @@
using Content.Shared.Emag.Systems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
namespace Content.Shared.Emag.Components; namespace Content.Shared.Emag.Components;
@@ -5,7 +6,12 @@ namespace Content.Shared.Emag.Components;
/// <summary> /// <summary>
/// Marker component for emagged entities /// Marker component for emagged entities
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class EmaggedComponent : Component 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;
} }

View File

@@ -6,8 +6,9 @@ using Content.Shared.Emag.Components;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Emag.Systems; namespace Content.Shared.Emag.Systems;
@@ -23,88 +24,124 @@ public sealed class EmagSystem : EntitySystem
[Dependency] private readonly SharedChargesSystem _charges = default!; [Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract); 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) private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
{ {
if (!args.CanReach || args.Target is not { } target) if (!args.CanReach || args.Target is not { } target)
return; return;
args.Handled = TryUseEmag(uid, args.User, target, comp); args.Handled = TryEmagEffect((uid, comp), args.User, target);
}
/// <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;
} }
/// <summary> /// <summary>
/// Does the emag effect on a specified entity /// Does the emag effect on a specified entity
/// </summary> /// </summary>
public bool DoEmagEffect(EntityUid user, EntityUid target) public bool TryEmagEffect(Entity<EmagComponent?> ent, EntityUid user, EntityUid target)
{ {
// prevent emagging twice if (!Resolve(ent, ref ent.Comp, false))
if (HasComp<EmaggedComponent>(target))
return false; return false;
var onAttemptEmagEvent = new OnAttemptEmagEvent(user); if (_tag.HasTag(target, ent.Comp.EmagImmuneTag))
RaiseLocalEvent(target, ref onAttemptEmagEvent);
// prevent emagging if attempt fails
if (onAttemptEmagEvent.Handled)
return false; 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); RaiseLocalEvent(target, ref emaggedEvent);
if (emaggedEvent.Handled && !emaggedEvent.Repeatable) if (!emaggedEvent.Handled)
EnsureComp<EmaggedComponent>(target); 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; 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> /// <summary>
/// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled /// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled
/// </summary> /// </summary>
/// <param name="UserUid">Emag user</param> /// <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="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> /// <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> /// <remarks>Needs to be handled in shared/client, not just the server, to actually show the emagging popup</remarks>
[ByRefEvent] [ByRefEvent]
public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false); public record struct GotEmaggedEvent(EntityUid UserUid, EmagType Type, bool Handled = false, bool Repeatable = false);
[ByRefEvent]
public record struct OnAttemptEmagEvent(EntityUid UserUid, bool Handled = false);

View File

@@ -59,12 +59,6 @@ public sealed partial class FaxMachineComponent : Component
[DataField] [DataField]
public bool ReceiveNukeCodes { get; set; } = false; public bool ReceiveNukeCodes { get; set; } = false;
/// <summary>
/// Sound to play when fax has been emagged
/// </summary>
[DataField]
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
/// <summary> /// <summary>
/// Sound to play when fax printing new message /// Sound to play when fax printing new message
/// </summary> /// </summary>

View File

@@ -21,10 +21,4 @@ public sealed partial class OrbitVisualsComponent : Component
/// How long should the orbit stop animation last in seconds? /// How long should the orbit stop animation last in seconds?
/// </summary> /// </summary>
public float OrbitStopLength = 1.0f; 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;
} }

View 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();
}

View File

@@ -1,13 +1,10 @@
namespace Content.Shared.Inventory.Events; 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 bool Active = false;
public List<T> Components = new(); public List<T> Components = new();
public RefreshEquipmentHudEvent(SlotFlags targetSlots)
{
TargetSlots = targetSlots;
}
} }

View File

@@ -55,14 +55,14 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, SolutionScanEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, SolutionScanEvent>(RelayInventoryEvent);
// ComponentActivatedClientSystems // ComponentActivatedClientSystems
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs); SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
} }

View File

@@ -18,6 +18,7 @@ public abstract class SharedLatheSystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!; [Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public readonly Dictionary<string, List<LatheRecipePrototype>> InverseRecipes = new(); 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) 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; args.Handled = true;
} }

View File

@@ -2,6 +2,7 @@ using Content.Shared.Actions;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Storage.Components;
using Content.Shared.Toggleable; using Content.Shared.Toggleable;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
@@ -22,6 +23,7 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly SharedPointLightSystem _light = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -78,6 +80,9 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args) 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)) if (!_light.TryGetLight(uid, out var light))
return; return;

View File

@@ -54,9 +54,9 @@ public sealed partial class LockComponent : Component
/// <summary> /// <summary>
/// Whether or not an emag disables it. /// Whether or not an emag disables it.
/// </summary> /// </summary>
[DataField("breakOnEmag")] [DataField]
[AutoNetworkedField] [AutoNetworkedField]
public bool BreakOnEmag = true; public bool BreakOnAccessBreaker = true;
/// <summary> /// <summary>
/// Amount of do-after time needed to lock the entity. /// Amount of do-after time needed to lock the entity.

View File

@@ -28,6 +28,7 @@ public sealed class LockSystem : EntitySystem
[Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = 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) 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; return;
_audio.PlayPredicted(component.UnlockSound, uid, args.UserUid); _audio.PlayPredicted(component.UnlockSound, uid, args.UserUid);
@@ -307,7 +311,7 @@ public sealed class LockSystem : EntitySystem
var ev = new LockToggledEvent(false); var ev = new LockToggledEvent(false);
RaiseLocalEvent(uid, ref ev, true); RaiseLocalEvent(uid, ref ev, true);
RemComp<LockComponent>(uid); //Literally destroys the lock as a tell it was emagged args.Repeatable = true;
args.Handled = true; args.Handled = true;
} }

View 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";
}

View File

@@ -7,7 +7,6 @@ using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems; using Content.Shared.Doors.Systems;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Lock; using Content.Shared.Lock;
@@ -15,9 +14,6 @@ using Content.Shared.Magic.Components;
using Content.Shared.Magic.Events; using Content.Shared.Magic.Events;
using Content.Shared.Maps; using Content.Shared.Maps;
using Content.Shared.Mind; 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.Physics;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Speech.Muting; using Content.Shared.Speech.Muting;
@@ -37,6 +33,10 @@ using Robust.Shared.Spawners;
namespace Content.Shared.Magic; 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> /// <summary>
/// Handles learning and using spells (actions) /// Handles learning and using spells (actions)
/// </summary> /// </summary>
@@ -60,7 +60,6 @@ public abstract class SharedMagicSystem : EntitySystem
[Dependency] private readonly LockSystem _lock = default!; [Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly SharedStunSystem _stun = default!;
@@ -80,79 +79,7 @@ public abstract class SharedMagicSystem : EntitySystem
SubscribeLocalEvent<ChargeSpellEvent>(OnChargeSpell); SubscribeLocalEvent<ChargeSpellEvent>(OnChargeSpell);
SubscribeLocalEvent<RandomGlobalSpawnSpellEvent>(OnRandomGlobalSpawnSpell); SubscribeLocalEvent<RandomGlobalSpawnSpellEvent>(OnRandomGlobalSpawnSpell);
SubscribeLocalEvent<MindSwapSpellEvent>(OnMindSwapSpell); SubscribeLocalEvent<MindSwapSpellEvent>(OnMindSwapSpell);
SubscribeLocalEvent<VoidApplauseSpellEvent>(OnVoidApplause);
// 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
} }
private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args) private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args)
@@ -402,8 +329,7 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
var transform = Transform(args.Performer); var transform = Transform(args.Performer);
if (transform.MapID != _transform.GetMapId(args.Target) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
if (transform.MapID != args.Target.GetMapId(EntityManager) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
return; return;
_transform.SetCoordinates(args.Performer, args.Target); _transform.SetCoordinates(args.Performer, args.Target);
@@ -411,6 +337,17 @@ public abstract class SharedMagicSystem : EntitySystem
Speak(args); Speak(args);
args.Handled = true; 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 // End Teleport Spells
#endregion #endregion
#region Spell Helpers #region Spell Helpers
@@ -435,7 +372,7 @@ public abstract class SharedMagicSystem : EntitySystem
} }
// End Spell Helpers // End Spell Helpers
#endregion #endregion
#region Smite Spells #region Touch Spells
private void OnSmiteSpell(SmiteSpellEvent ev) private void OnSmiteSpell(SmiteSpellEvent ev)
{ {
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer)) if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer))
@@ -454,7 +391,8 @@ public abstract class SharedMagicSystem : EntitySystem
_body.GibBody(ev.Target, true, body); _body.GibBody(ev.Target, true, body);
} }
// End Smite Spells
// End Touch Spells
#endregion #endregion
#region Knock Spells #region Knock Spells
/// <summary> /// <summary>

View File

@@ -29,6 +29,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] protected readonly SharedContainerSystem Container = default!; [Dependency] protected readonly SharedContainerSystem Container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public const string ActiveReclaimerContainerId = "active-material-reclaimer-container"; 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) 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; args.Handled = true;
} }
@@ -207,7 +214,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
component.Enabled && component.Enabled &&
!component.Broken && !component.Broken &&
HasComp<BodyComponent>(victim) && HasComp<BodyComponent>(victim) &&
HasComp<EmaggedComponent>(uid); _emag.CheckFlag(uid, EmagType.Interaction);
} }
/// <summary> /// <summary>

View File

@@ -20,6 +20,7 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
{ {
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly StandingStateSystem _standingStateSystem = default!; [Dependency] private readonly StandingStateSystem _standingStateSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = 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) protected void OnEmagged(EntityUid uid, CryoPodComponent? cryoPodComponent, ref GotEmaggedEvent args)
{ {
if (!Resolve(uid, ref cryoPodComponent)) if (!Resolve(uid, ref cryoPodComponent))
{
return; return;
}
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (cryoPodComponent.PermaLocked && cryoPodComponent.Locked)
return;
cryoPodComponent.PermaLocked = true; cryoPodComponent.PermaLocked = true;
cryoPodComponent.Locked = true; cryoPodComponent.Locked = true;

View File

@@ -2,6 +2,7 @@
using Content.Shared.Implants; using Content.Shared.Implants;
using Content.Shared.Implants.Components; using Content.Shared.Implants.Components;
using Content.Shared.Mindshield.Components; using Content.Shared.Mindshield.Components;
using Robust.Shared.Containers;
namespace Content.Shared.Mindshield.FakeMindShield; namespace Content.Shared.Mindshield.FakeMindShield;
@@ -13,7 +14,9 @@ public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SubdermalImplantComponent, FakeMindShieldToggleEvent>(OnFakeMindShieldToggle); SubscribeLocalEvent<SubdermalImplantComponent, FakeMindShieldToggleEvent>(OnFakeMindShieldToggle);
SubscribeLocalEvent<FakeMindShieldImplantComponent, ImplantImplantedEvent>(ImplantCheck); SubscribeLocalEvent<FakeMindShieldImplantComponent, ImplantImplantedEvent>(ImplantCheck);
SubscribeLocalEvent<FakeMindShieldImplantComponent, EntGotRemovedFromContainerMessage>(ImplantDraw);
} }
/// <summary> /// <summary>
/// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity /// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity
/// </summary> /// </summary>
@@ -33,4 +36,9 @@ public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
if (ev.Implanted != null) if (ev.Implanted != null)
EnsureComp<FakeMindShieldComponent>(ev.Implanted.Value); EnsureComp<FakeMindShieldComponent>(ev.Implanted.Value);
} }
private void ImplantDraw(Entity<FakeMindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage ev)
{
RemComp<FakeMindShieldComponent>(ev.Container.Owner);
}
} }

View File

@@ -1,8 +1,10 @@
namespace Content.Server.NPC.Components using Robust.Shared.GameStates;
namespace Content.Shared.NPC.Components
{ {
/// Added when a medibot injects someone /// Added when a medibot injects someone
/// So they don't get injected again for at least a minute. /// So they don't get injected again for at least a minute.
[RegisterComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class NPCRecentlyInjectedComponent : Component public sealed partial class NPCRecentlyInjectedComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")] [ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]

View File

@@ -1,6 +1,8 @@
using Content.Shared.Emag.Systems;
using Content.Shared.Ninja.Systems; using Content.Shared.Ninja.Systems;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -17,11 +19,23 @@ public sealed partial class EmagProviderComponent : Component
/// The tag that marks an entity as immune to emagging. /// The tag that marks an entity as immune to emagging.
/// </summary> /// </summary>
[DataField] [DataField]
public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune"; public ProtoId<TagPrototype> AccessBreakerImmuneTag = "AccessBreakerImmune";
/// <summary> /// <summary>
/// Whitelist that entities must be on to work. /// Whitelist that entities must be on to work.
/// </summary> /// </summary>
[DataField] [DataField]
public EntityWhitelist? Whitelist; 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");
} }

View File

@@ -5,6 +5,7 @@ using Content.Shared.Interaction;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
namespace Content.Shared.Ninja.Systems; namespace Content.Shared.Ninja.Systems;
@@ -13,6 +14,7 @@ namespace Content.Shared.Ninja.Systems;
/// </summary> /// </summary>
public sealed class EmagProviderSystem : EntitySystem public sealed class EmagProviderSystem : EntitySystem
{ {
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
@@ -42,14 +44,18 @@ public sealed class EmagProviderSystem : EntitySystem
return; return;
// only allowed to emag non-immune entities // only allowed to emag non-immune entities
if (_tag.HasTag(target, comp.EmagImmuneTag)) if (_tag.HasTag(target, comp.AccessBreakerImmuneTag))
return; return;
var handled = _emag.DoEmagEffect(uid, target); var emagEv = new GotEmaggedEvent(uid, EmagType.Access);
if (!handled) RaiseLocalEvent(args.Target, ref emagEv);
if (!emagEv.Handled)
return; 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); var ev = new EmaggedSomethingEvent(target);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
args.Handled = true; args.Handled = true;
@@ -57,7 +63,7 @@ public sealed class EmagProviderSystem : EntitySystem
} }
/// <summary> /// <summary>
/// Raised on the player when emagging something. /// Raised on the player when access breaking something.
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]
public record struct EmaggedSomethingEvent(EntityUid Target); public record struct EmaggedSomethingEvent(EntityUid Target);

View File

@@ -10,6 +10,7 @@ namespace Content.Shared.Pinpointer;
public abstract class SharedPinpointerSystem : EntitySystem public abstract class SharedPinpointerSystem : EntitySystem
{ {
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -137,6 +138,15 @@ public abstract class SharedPinpointerSystem : EntitySystem
private void OnEmagged(EntityUid uid, PinpointerComponent component, ref GotEmaggedEvent args) 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; args.Handled = true;
component.CanRetarget = true; component.CanRetarget = true;
} }

View File

@@ -40,13 +40,16 @@ public sealed partial class RoleTimeRequirement : JobRequirement
var formattedRoleDiff = ContentLocalizationManager.FormatPlaytime(roleDiffSpan); var formattedRoleDiff = ContentLocalizationManager.FormatPlaytime(roleDiffSpan);
var departmentColor = Color.Yellow; 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)) var jobProto = jobSystem.GetJobPrototype(proto);
departmentColor = departmentProto.Color;
} if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
departmentColor = departmentProto.Color;
if (!protoManager.TryIndex<JobPrototype>(jobProto, out var indexedJob))
return false;
if (!Inverted) if (!Inverted)
{ {
@@ -56,7 +59,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-role-insufficient", "role-timer-role-insufficient",
("time", formattedRoleDiff), ("time", formattedRoleDiff),
("job", Loc.GetString(proto)), ("job", indexedJob.LocalizedName),
("departmentColor", departmentColor.ToHex()))); ("departmentColor", departmentColor.ToHex())));
return false; return false;
} }
@@ -66,7 +69,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-role-too-high", "role-timer-role-too-high",
("time", formattedRoleDiff), ("time", formattedRoleDiff),
("job", Loc.GetString(proto)), ("job", indexedJob.LocalizedName),
("departmentColor", departmentColor.ToHex()))); ("departmentColor", departmentColor.ToHex())));
return false; return false;
} }

View File

@@ -16,13 +16,4 @@ public sealed partial class EmaggableMedibotComponent : Component
/// </summary> /// </summary>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)] [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public Dictionary<MobState, MedibotTreatment> Replacements = new(); 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)
};
} }

View File

@@ -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.Emag.Systems;
using Content.Shared.Interaction;
using Content.Shared.Mobs; 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.Audio.Systems;
using Robust.Shared.Serialization;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Silicons.Bots; namespace Content.Shared.Silicons.Bots;
@@ -11,20 +20,31 @@ namespace Content.Shared.Silicons.Bots;
public sealed class MedibotSystem : EntitySystem public sealed class MedibotSystem : EntitySystem
{ {
[Dependency] private readonly SharedAudioSystem _audio = default!; [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() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmaggableMedibotComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<EmaggableMedibotComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<MedibotComponent, UserActivateInWorldEvent>(OnInteract);
SubscribeLocalEvent<MedibotComponent, MedibotInjectDoAfterEvent>(OnInject);
} }
private void OnEmagged(EntityUid uid, EmaggableMedibotComponent comp, ref GotEmaggedEvent args) 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; 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) foreach (var (state, treatment) in comp.Replacements)
{ {
@@ -34,6 +54,25 @@ public sealed class MedibotSystem : EntitySystem
args.Handled = true; 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> /// <summary>
/// Get a treatment for a given mob state. /// Get a treatment for a given mob state.
/// </summary> /// </summary>
@@ -44,4 +83,66 @@ public sealed class MedibotSystem : EntitySystem
{ {
return comp.Treatments.TryGetValue(state, out treatment); 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 { }

View File

@@ -1,7 +1,10 @@
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Mind;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Stunnable;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Shared.Audio;
namespace Content.Shared.Silicons.Laws; namespace Content.Shared.Silicons.Laws;
@@ -11,22 +14,29 @@ namespace Content.Shared.Silicons.Laws;
public abstract partial class SharedSiliconLawSystem : EntitySystem public abstract partial class SharedSiliconLawSystem : EntitySystem
{ {
[Dependency] private readonly SharedPopupSystem _popup = default!; [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/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
InitializeUpdater(); InitializeUpdater();
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged); 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) if (uid == args.UserUid)
{ {
_popup.PopupClient(Loc.GetString("law-emag-cannot-emag-self"), uid, args.UserUid); _popup.PopupClient(Loc.GetString("law-emag-cannot-emag-self"), uid, args.UserUid);
args.Handled = true;
return; return;
} }
@@ -35,14 +45,33 @@ public abstract partial class SharedSiliconLawSystem : EntitySystem
!panel.Open) !panel.Open)
{ {
_popup.PopupClient(Loc.GetString("law-emag-require-panel"), uid, args.UserUid); _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); 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; 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);

View File

@@ -11,6 +11,7 @@ public abstract class SharedSingularityGeneratorSystem : EntitySystem
{ {
#region Dependencies #region Dependencies
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!; [Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
#endregion Dependencies #endregion Dependencies
public override void Initialize() public override void Initialize()
@@ -22,7 +23,16 @@ public abstract class SharedSingularityGeneratorSystem : EntitySystem
private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args) 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; component.FailsafeDisabled = true;
args.Handled = true; args.Handled = true;
} }
} }

View File

@@ -19,7 +19,7 @@ using Robust.Shared.Utility;
namespace Content.Shared.Slippery; namespace Content.Shared.Slippery;
[UsedImplicitly] [UsedImplicitly]
public sealed class SlipperySystem : EntitySystem public sealed class SlipperySystem : EntitySystem
{ {
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
@@ -83,7 +83,7 @@ public sealed class SlipperySystem : EntitySystem
{ {
if (HasComp<SpeedModifiedByContactComponent>(args.OtherEntity)) if (HasComp<SpeedModifiedByContactComponent>(args.OtherEntity))
_speedModifier.AddModifiedEntity(args.OtherEntity); _speedModifier.AddModifiedEntity(args.OtherEntity);
} }
private bool CanSlip(EntityUid uid, EntityUid toSlip) private bool CanSlip(EntityUid uid, EntityUid toSlip)
{ {

View File

@@ -2,7 +2,7 @@ using Content.Shared.Stunnable;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
namespace Content.Server.Stunnable.Components; namespace Content.Shared.Stunnable;
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState] [AutoGenerateComponentState]

View File

@@ -2,6 +2,7 @@ using Content.Shared.Emag.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Emag.Systems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -19,11 +20,14 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] protected readonly IRobustRandom Randomizer = default!; [Dependency] protected readonly IRobustRandom Randomizer = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract); SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract);
} }
@@ -49,9 +53,21 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
Dirty(uid, component); 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> /// <summary>
/// Returns all of the vending machine's inventory. Only includes emagged and contraband inventories if /// 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. /// are <c>true</c> respectively.
/// </summary> /// </summary>
/// <param name="uid"></param> /// <param name="uid"></param>
@@ -64,7 +80,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
var inventory = new List<VendingMachineInventoryEntry>(component.Inventory.Values); var inventory = new List<VendingMachineInventoryEntry>(component.Inventory.Values);
if (HasComp<EmaggedComponent>(uid)) if (_emag.CheckFlag(uid, EmagType.Interaction))
inventory.AddRange(component.EmaggedInventory.Values); inventory.AddRange(component.EmaggedInventory.Values);
if (component.Contraband) if (component.Contraband)

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