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>
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
<ScrollContainer HScrollEnabled="False" <ScrollContainer HScrollEnabled="False"
HorizontalExpand="True" HorizontalExpand="True"
VerticalExpand="True"> VerticalExpand="True">
<BoxContainer Name="BountyEntriesContainer" <BoxContainer Name="BountyEntriesContainer"
Orientation="Vertical" Orientation="Vertical"
VerticalExpand="True" VerticalExpand="True"
HorizontalExpand="True"> HorizontalExpand="True" />
</BoxContainer>
</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">
<!-- Record search bar -->
<BoxContainer Margin="5 5 5 10"
HorizontalExpand="true"
VerticalAlignment="Center">
<OptionButton Name="FilterType"
MinWidth="250"
Margin="0 0 10 0" />
<!-- Populated in constructor -->
<LineEdit Name="FilterText"
PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> <BoxContainer Orientation="Horizontal"
VerticalExpand="True">
<!-- Record listing --> <!-- Record listing -->
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250"> <BoxContainer Orientation="Vertical"
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/> Margin="10 10"
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> MinWidth="250"
MaxWidth="250">
<Label Name="RecordListingTitle"
Text="{Loc 'criminal-records-console-records-list-title'}"
HorizontalExpand="True"
Align="Center" />
<Label Name="NoRecords"
Text="{Loc 'criminal-records-console-no-records'}"
HorizontalExpand="True"
Align="Center"
FontColorOverride="DarkGray" />
<ScrollContainer VerticalExpand="True"> <ScrollContainer VerticalExpand="True">
<ItemList Name="RecordListing"/> <!-- Populated when loading state --> <ItemList Name="RecordListing" />
<!-- Populated when loading state -->
</ScrollContainer> </ScrollContainer>
</BoxContainer> </BoxContainer>
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> <Label Name="RecordUnselected"
Text="{Loc 'criminal-records-console-select-record-info'}"
HorizontalExpand="True"
Align="Center"
FontColorOverride="DarkGray" />
<!-- Selected record info --> <!-- Selected record info -->
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False"> <BoxContainer Name="PersonContainer"
<Label Name="PersonName" StyleClasses="LabelBig"/> Orientation="Vertical"
<Label Name="PersonPrints"/> VerticalExpand="True"
<Label Name="PersonDna"/> HorizontalExpand="True"
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" /> Margin="5"
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5"> Visible="False">
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/> <Label Name="PersonName"
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor --> 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>
<RichTextLabel Name="WantedReason" Visible="False"/> <BoxContainer Orientation="Horizontal"
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/> 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);
@@ -114,6 +147,11 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
}; };
} }
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);
@@ -216,19 +258,40 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
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));
// Job icon
if (_proto.TryIndex<JobIconPrototype>(stationRecord.JobIcon, out var proto))
{
PersonJobIcon.Texture = _spriteSystem.Frame0(proto.Icon);
}
PersonPrints.Text = stationRecord.Fingerprint ?? Loc.GetString("generic-not-available-shorthand");
PersonDna.Text = stationRecord.DNA ?? Loc.GetString("generic-not-available-shorthand");
if (criminalRecord.Status != SecurityStatus.None)
{
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/security_icons.rsi"), GetStatusIcon(criminalRecord.Status));
}
PersonStatusTX.SetFromSpriteSpecifier(specifier);
PersonStatusTX.DisplayRect.TextureScale = new Vector2(3f, 3f);
StatusOptionButton.SelectId((int)criminalRecord.Status); StatusOptionButton.SelectId((int)criminalRecord.Status);
if (criminalRecord.Reason is { } reason) 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 (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
_animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
UpdateAppearanceForDoorState(entity, args.Sprite, state);
}
private void UpdateAppearanceForDoorState(Entity<DoorComponent> entity, SpriteComponent sprite, DoorState state)
{
sprite.DrawDepth = state is DoorState.Open ? entity.Comp.OpenDrawDepth : entity.Comp.ClosedDrawDepth;
switch (state)
{
case DoorState.Open:
foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
{
sprite.LayerSetState(layer, layerState);
}
return;
case DoorState.Closed:
foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
{
sprite.LayerSetState(layer, layerState);
}
return;
case DoorState.Opening:
if (entity.Comp.OpeningAnimationTime == 0.0)
return;
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Closing:
if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
return;
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Denying:
_animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Emagging:
_animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
return;
}
}
private void UpdateSpriteLayers(SpriteComponent sprite, string baseRsi)
{ {
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res)) if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
{ {
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace); Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
} return;
foreach (var layer in args.Sprite.AllLayers)
{
layer.Rsi = res?.RSI;
}
} }
TryComp<AnimationPlayerComponent>(uid, out var animPlayer); sprite.BaseRSI = res.RSI;
if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey))
_animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
args.Sprite.DrawDepth = comp.ClosedDrawDepth;
switch(state)
{
case DoorState.Open:
args.Sprite.DrawDepth = comp.OpenDrawDepth;
foreach(var (layer, layerState) in comp.OpenSpriteStates)
{
args.Sprite.LayerSetState(layer, layerState);
}
break;
case DoorState.Closed:
foreach(var (layer, layerState) in comp.ClosedSpriteStates)
{
args.Sprite.LayerSetState(layer, layerState);
}
break;
case DoorState.Opening:
if (animPlayer != null && comp.OpeningAnimationTime != 0.0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Closing:
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Denying:
if (animPlayer != null)
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Welded:
break;
case DoorState.Emagging:
if (animPlayer != null)
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
break;
default:
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
}
} }
} }

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

@@ -2,49 +2,128 @@
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="420 320" MinSize="320 120">
SetSize="420 320">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0"> <!-- Main Container -->
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> <BoxContainer Orientation="Vertical"
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 5"> VerticalExpand="True">
<!-- Sub-Main container -->
<BoxContainer Orientation="Horizontal"
VerticalExpand="True"
HorizontalExpand="True">
<!-- Info part -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
Margin="8">
<!-- Info -->
<BoxContainer Orientation="Vertical"
SeparationOverride="4">
<!-- Status -->
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StatusLabel" HorizontalExpand="True"/> <RichTextLabel Name="StatusLabel" HorizontalExpand="True"/>
<Control MinWidth="8"/>
<RichTextLabel Name="StatusStateLabel"/> <RichTextLabel Name="StatusStateLabel"/>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<!-- Power -->
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<RichTextLabel Name="PowerLabel" Margin="0 0 20 0" HorizontalExpand="True" VerticalAlignment="Center"/> <RichTextLabel Name="PowerLabel"
<Button Name="OffButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-off-button'}" StyleClasses="OpenRight"/> HorizontalExpand="True"
<Button Name="OnButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-on-button'}" StyleClasses="OpenLeft"/> 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> </BoxContainer>
<Control MinHeight="5"/>
<!-- Strenght -->
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StrengthLabel" Margin="0 0 20 0" HorizontalExpand="True" HorizontalAlignment="Left" VerticalAlignment="Center"/> <RichTextLabel Name="StrengthLabel"
HorizontalExpand="True"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<Control MinWidth="8"/>
<SpinBox Name="StateSpinBox" Value="0"/> <SpinBox Name="StateSpinBox" Value="0"/>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<!-- Power -->
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/> <RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
<Control MinWidth="8"/>
<RichTextLabel Name="DrawValueLabel"/> <RichTextLabel Name="DrawValueLabel"/>
</BoxContainer> </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> </BoxContainer>
<Control MinHeight="10" VerticalExpand="True"/>
<Control MinHeight="8" VerticalExpand="True"/> <!-- Filler -->
<!-- Alarm -->
<BoxContainer Name="AlarmControl"
Orientation="Vertical"
VerticalAlignment="Center"
Visible="False">
<controls:StripeBack Margin="-8 0">
<BoxContainer Orientation="Vertical">
<RichTextLabel Name="BigAlarmLabel"
HorizontalAlignment="Center"/>
<RichTextLabel Name="BigAlarmLabelTwo"
HorizontalAlignment="Center"/>
</BoxContainer> </BoxContainer>
<customControls:VSeparator Margin="0 0 0 10"/> </controls:StripeBack>
<BoxContainer Orientation="Vertical" Margin="20 0 20 0" VerticalAlignment="Center">
<PanelContainer Name="BackPanel" HorizontalAlignment="Center"> <Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}"
HorizontalAlignment="Center"
StyleClasses="LabelSubText"/>
</BoxContainer>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" Margin="0 -8" HorizontalAlignment="Right"/>
<!-- PA Visual part -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Center"
Margin="8">
<PanelContainer Name="BackPanel"
HorizontalAlignment="Center">
<PanelContainer.PanelOverride> <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>
<!-- Footer -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Bottom">
<controls:StripeBack> <controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/> <Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}"
HorizontalAlignment="Center"
StyleClasses="LabelSubText"
Margin="0 4"/>
</controls:StripeBack> </controls:StripeBack>
<BoxContainer Orientation="Horizontal" Margin="12 0 0 0">
<Label Text="{Loc 'particle-accelerator-control-menu-foo-bar-baz'}" StyleClasses="LabelSubText"/> <BoxContainer Orientation="Horizontal"
Margin="12 0 6 2"
VerticalAlignment="Bottom">
<!-- Footer title -->
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-left'}"
StyleClasses="WindowFooterText" />
<!-- Version -->
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-right'}"
StyleClasses="WindowFooterText"
HorizontalAlignment="Right"
HorizontalExpand="True"
Margin="0 0 4 0" />
<TextureRect StyleClasses="NTLogoDark"
Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Right"
SetSize="19 19"/>
</BoxContainer>
</BoxContainer> </BoxContainer>
</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;
@@ -30,12 +29,10 @@ 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; comp.Enabled = true;
EntityManager.Dirty(wire.Owner, comp); 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;
if (_emag.CheckFlag(uid, EmagType.Interaction))
return;
if (!TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
return;
// Remove the atmos alarmable component permanently from this device. // Remove the atmos alarmable component permanently from this device.
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable); _atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
RemCompDeferred<AtmosAlarmableComponent>(uid); 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,16 +38,21 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles. /// 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;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
@@ -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);
@@ -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); var jobProto = jobSystem.GetJobPrototype(proto);
if (jobSystem.TryGetDepartment(jobProto, out var departmentProto)) if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
departmentColor = departmentProto.Color; 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,6 +23,15 @@ 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

@@ -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)

View File

@@ -508,7 +508,7 @@ public abstract partial class SharedGunSystem : EntitySystem
return; return;
var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle); var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle);
CreateEffect(gun, ev, gun); CreateEffect(gun, ev, user);
} }
public void CauseImpulse(EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid user, PhysicsComponent userPhysics) public void CauseImpulse(EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid user, PhysicsComponent userPhysics)

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