mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 08:07:28 +00:00
Compare commits
158
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2d05f548 | ||
|
|
4c7f99296c | ||
|
|
df1e97986a | ||
|
|
88203bb6b2 | ||
|
|
158a114804 | ||
|
|
d438aec056 | ||
|
|
3134e91fa5 | ||
|
|
3ac30b7b7b | ||
|
|
4daea082bc | ||
|
|
6a37e1268e | ||
|
|
331a25c7d3 | ||
|
|
b5121e10e9 | ||
|
|
9584a7b0ae | ||
|
|
f9880c148b | ||
|
|
25ef15f162 | ||
|
|
997a6e31e6 | ||
|
|
5a342570a4 | ||
|
|
97bb68b2a6 | ||
|
|
ac9f8852b3 | ||
|
|
8c8f4b4f2c | ||
|
|
8eeb5f6523 | ||
|
|
45432aa3bb | ||
|
|
b9153f0f13 | ||
|
|
e3d3fe72f0 | ||
|
|
0a65236363 | ||
|
|
afe6021455 | ||
|
|
e5db02c736 | ||
|
|
cbc5f819c4 | ||
|
|
339b3fb32c | ||
|
|
e96faec07d | ||
|
|
a1bbf142a4 | ||
|
|
e47a0bf455 | ||
|
|
72f3edd378 | ||
|
|
030deb6f27 | ||
|
|
4397017c5a | ||
|
|
0f19340d41 | ||
|
|
3c28ee8d20 | ||
|
|
11cc04dd87 | ||
|
|
f87ed28744 | ||
|
|
62c76332f5 | ||
|
|
34c212e4fb | ||
|
|
353290e55b | ||
|
|
0cbe9c01fb | ||
|
|
d88938887b | ||
|
|
1656a91c5e | ||
|
|
c70cbcb9f5 | ||
|
|
3e22d89d1e | ||
|
|
a790954f06 | ||
|
|
0676186fe5 | ||
|
|
db6d1e3970 | ||
|
|
fd20b74b8f | ||
|
|
d016838687 | ||
|
|
160aa17052 | ||
|
|
2b6729fa64 | ||
|
|
3439340530 | ||
|
|
28c2223a8a | ||
|
|
1b47c246c9 | ||
|
|
8141512cc4 | ||
|
|
fd986fec6e | ||
|
|
dd2a6e1c28 | ||
|
|
6c7c9de253 | ||
|
|
2b96bb79f8 | ||
|
|
c1239ef7bf | ||
|
|
62b6401e48 | ||
|
|
f79a599c65 | ||
|
|
9acd3f2ecb | ||
|
|
47215d7876 | ||
|
|
fe53298d27 | ||
|
|
1aee2519df | ||
|
|
e8cd052c31 | ||
|
|
a27fa291e7 | ||
|
|
7e6f66a651 | ||
|
|
08c27979a1 | ||
|
|
b49a94100c | ||
|
|
cfa24eb20c | ||
|
|
1787683435 | ||
|
|
389fbf99e1 | ||
|
|
74c7bf0a05 | ||
|
|
6764276a47 | ||
|
|
03934d3fbd | ||
|
|
468150c817 | ||
|
|
310ac0809b | ||
|
|
7b71611dc9 | ||
|
|
f2153d66c3 | ||
|
|
b6833cbbcf | ||
|
|
4e2bd0c244 | ||
|
|
bf0abd7ffa | ||
|
|
c4d65da8ed | ||
|
|
6dac24f03a | ||
|
|
9550a7d53d | ||
|
|
d826a15ff2 | ||
|
|
23a9a75703 | ||
|
|
79a0000547 | ||
|
|
71badde1a0 | ||
|
|
5ab49cd46b | ||
|
|
0398c527a1 | ||
|
|
cdafd4c257 | ||
|
|
8dc06f010a | ||
|
|
fee62b449f | ||
|
|
c0d20b3d79 | ||
|
|
b2944fba52 | ||
|
|
2e39916957 | ||
|
|
874bbf4fd2 | ||
|
|
e1ff1ea820 | ||
|
|
79a4a15403 | ||
|
|
0bb2b6ee7b | ||
|
|
18d9325b60 | ||
|
|
f07648bc24 | ||
|
|
f5da329b98 | ||
|
|
ad6581f2d3 | ||
|
|
430fef2a10 | ||
|
|
6970cb9d28 | ||
|
|
ea2b8df2cc | ||
|
|
b87b113736 | ||
|
|
f087b2895e | ||
|
|
0a14310574 | ||
|
|
d9c2f26438 | ||
|
|
a4f11c63dc | ||
|
|
4ff05277fa | ||
|
|
a33e12e85e | ||
|
|
62e9ebc216 | ||
|
|
25b69a3f9e | ||
|
|
7ed7dbcbb1 | ||
|
|
fcdf1cbbf4 | ||
|
|
9f786a7c1c | ||
|
|
e2e5c40f83 | ||
|
|
d0c3cff669 | ||
|
|
ed1fa64a6e | ||
|
|
cb245477b8 | ||
|
|
0fdfef7175 | ||
|
|
646d538c4a | ||
|
|
6ebcf78d08 | ||
|
|
2b7e3c8058 | ||
|
|
4736a8db6f | ||
|
|
6057388bae | ||
|
|
50a651beea | ||
|
|
7e1308dd12 | ||
|
|
338ff271c7 | ||
|
|
fddd9079d3 | ||
|
|
bedc17ae73 | ||
|
|
ec80e99e26 | ||
|
|
4ceb567439 | ||
|
|
6a9984475c | ||
|
|
030eb95c63 | ||
|
|
00abed028d | ||
|
|
0f3d319a6d | ||
|
|
a9ddc102e7 | ||
|
|
a51896b08d | ||
|
|
356106342f | ||
|
|
a3b450ac07 | ||
|
|
4ddb5901b0 | ||
|
|
972d852bf4 | ||
|
|
ef0d317d12 | ||
|
|
003e08b583 | ||
|
|
74739709c4 | ||
|
|
61840fd5ab | ||
|
|
90f7aa7999 | ||
|
|
2260af7ce1 |
@@ -269,7 +269,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
||||
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
|
||||
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
|
||||
| style="border: 1px solid black;" | 5 minutes
|
||||
| style="border: 1px solid black;" | To break and enter into a high security area where one is not authorised nor invited, with intent to commit a crime within.
|
||||
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
|
||||
|-
|
||||
| style="border: 1px solid black;" | 203
|
||||
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
|
||||
|
||||
@@ -8,12 +8,14 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.SSDIndicator; // DeltaV - SSD time indicator
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
@@ -26,6 +28,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly SharedRoleSystem _roles;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IGameTiming _timing; // DeltaV - Add timing
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private AdminOverlayAntagFormat _overlayFormat;
|
||||
@@ -53,7 +56,8 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config,
|
||||
SharedRoleSystem roles,
|
||||
IPrototypeManager prototypeManager)
|
||||
IPrototypeManager prototypeManager,
|
||||
IGameTiming timing) // DeltaV - Add timing
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
@@ -62,6 +66,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
_roles = roles;
|
||||
_prototypeManager = prototypeManager;
|
||||
_timing = timing; // DeltaV - Add timing
|
||||
ZIndex = 200;
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
@@ -231,6 +236,18 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// DeltaV - SSD Time START
|
||||
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator)
|
||||
&& ssdIndicator.SsdSince is {} ssdSince)
|
||||
{
|
||||
color = Color.MediumPurple;
|
||||
color.A = alpha;
|
||||
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss")));
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
// Determine antag symbol
|
||||
string? symbol;
|
||||
switch (_overlaySymbolStyle)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -19,6 +20,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV - added for SSD time indicator
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -36,7 +38,8 @@ namespace Content.Client.Administration.Systems
|
||||
_userInterfaceManager,
|
||||
_configurationManager,
|
||||
_roles,
|
||||
_proto);
|
||||
_proto,
|
||||
_timing); // DeltaV - Add timing
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,8 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
|
||||
_window = this.CreateWindow<SignalTimerWindow>();
|
||||
_window.OnStartTimer += StartTimer;
|
||||
_window.OnCurrentTextChanged += OnTextChanged;
|
||||
// Begin Monolith
|
||||
// _window.OnCurrentDelayMinutesChanged += OnDelayChanged;
|
||||
// _window.OnCurrentDelaySecondsChanged += OnDelayChanged;
|
||||
_window.OnCurrentDelayChanged += OnDelayChanged;
|
||||
// End Monolith
|
||||
_window.OnCurrentDelayMinutesChanged += OnDelayChanged;
|
||||
_window.OnCurrentDelaySecondsChanged += OnDelayChanged;
|
||||
}
|
||||
|
||||
public void StartTimer()
|
||||
@@ -38,11 +35,11 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new SignalTimerTextChangedMessage(newText));
|
||||
}
|
||||
|
||||
private void OnDelayChanged(TimeSpan newDelay) // Monolith - Change from string to TimeSpan
|
||||
private void OnDelayChanged(string newDelay)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
SendMessage(new SignalTimerDelayChangedMessage(newDelay)); // Mono
|
||||
SendMessage(new SignalTimerDelayChangedMessage(_window.GetDelay()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,11 +54,8 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
|
||||
return;
|
||||
|
||||
_window.SetCurrentText(cast.CurrentText);
|
||||
// Begin Monolith
|
||||
// _window.SetCurrentDelayMinutes(cast.CurrentDelayMinutes);
|
||||
// _window.SetCurrentDelaySeconds(cast.CurrentDelaySeconds);
|
||||
_window.SetCurrentDelay(cast.CurrentDelay);
|
||||
// End Monolith
|
||||
_window.SetCurrentDelayMinutes(cast.CurrentDelayMinutes);
|
||||
_window.SetCurrentDelaySeconds(cast.CurrentDelaySeconds);
|
||||
_window.SetShowText(cast.ShowText);
|
||||
_window.SetTriggerTime(cast.TriggerTime);
|
||||
_window.SetTimerStarted(cast.TimerStarted);
|
||||
|
||||
@@ -7,14 +7,10 @@
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="DelayEdit" Orientation="Horizontal">
|
||||
<Label Name="CurrentDelay" Text="{Loc signal-timer-menu-delay}" />
|
||||
<!-- Mono: Changed from integer MM:SS to float seconds
|
||||
<LineEdit Name="CurrentDelayEditMinutes" MinWidth="32" />
|
||||
<Label Name="Colon" Text=":" />
|
||||
<LineEdit Name="CurrentDelayEditSeconds" MinWidth="32" />
|
||||
<Label Name="DelayInfo" Text=" (mm:ss)" />
|
||||
-->
|
||||
<FloatSpinBox Name="CurrentDelayEdit" MinWidth="32" />
|
||||
<Label Text="s" />
|
||||
</BoxContainer>
|
||||
<Button Name="StartTimer" Text="{Loc signal-timer-menu-start}" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -15,11 +15,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
|
||||
private const int MaxTextLength = 5;
|
||||
|
||||
public event Action<string>? OnCurrentTextChanged;
|
||||
// Begin Monolith
|
||||
// public event Action<string>? OnCurrentDelayMinutesChanged;
|
||||
// public event Action<string>? OnCurrentDelaySecondsChanged;
|
||||
public event Action<TimeSpan>? OnCurrentDelayChanged;
|
||||
// End Monolith
|
||||
public event Action<string>? OnCurrentDelayMinutesChanged;
|
||||
public event Action<string>? OnCurrentDelaySecondsChanged;
|
||||
|
||||
private TimeSpan? _triggerTime;
|
||||
|
||||
@@ -33,11 +30,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
CurrentTextEdit.OnTextChanged += e => OnCurrentTextChange(e.Text);
|
||||
// Begin Monolith
|
||||
// CurrentDelayEditMinutes.OnTextChanged += e => OnCurrentDelayMinutesChange(e.Text);
|
||||
// CurrentDelayEditSeconds.OnTextChanged += e => OnCurrentDelaySecondsChange(e.Text);
|
||||
CurrentDelayEdit.OnValueChanged += e => CurrentDelayChange(TimeSpan.FromSeconds(e.Value));
|
||||
// End Monolith
|
||||
CurrentDelayEditMinutes.OnTextChanged += e => OnCurrentDelayMinutesChange(e.Text);
|
||||
CurrentDelayEditSeconds.OnTextChanged += e => OnCurrentDelaySecondsChange(e.Text);
|
||||
StartTimer.OnPressed += _ => StartTimerWeh();
|
||||
}
|
||||
|
||||
@@ -83,93 +77,81 @@ public sealed partial class SignalTimerWindow : DefaultWindow
|
||||
OnCurrentTextChanged?.Invoke(text);
|
||||
}
|
||||
|
||||
// Begin Monolith
|
||||
// public void OnCurrentDelayMinutesChange(string text)
|
||||
// {
|
||||
// List<char> toRemove = new();
|
||||
|
||||
// foreach (var a in text)
|
||||
// {
|
||||
// if (!char.IsDigit(a))
|
||||
// toRemove.Add(a);
|
||||
// }
|
||||
|
||||
// foreach (var a in toRemove)
|
||||
// {
|
||||
// CurrentDelayEditMinutes.Text = text.Replace(a.ToString(),"");
|
||||
// }
|
||||
|
||||
// if (CurrentDelayEditMinutes.Text == "")
|
||||
// return;
|
||||
|
||||
// while (CurrentDelayEditMinutes.Text[0] == '0' && CurrentDelayEditMinutes.Text.Length > 2)
|
||||
// {
|
||||
// CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(0, 1);
|
||||
// }
|
||||
|
||||
// if (CurrentDelayEditMinutes.Text.Length > 2)
|
||||
// {
|
||||
// CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(2);
|
||||
// }
|
||||
// OnCurrentDelayMinutesChanged?.Invoke(CurrentDelayEditMinutes.Text);
|
||||
// }
|
||||
|
||||
// public void OnCurrentDelaySecondsChange(string text)
|
||||
// {
|
||||
// List<char> toRemove = new();
|
||||
|
||||
// foreach (var a in text)
|
||||
// {
|
||||
// if (!char.IsDigit(a))
|
||||
// toRemove.Add(a);
|
||||
// }
|
||||
|
||||
// foreach (var a in toRemove)
|
||||
// {
|
||||
// CurrentDelayEditSeconds.Text = text.Replace(a.ToString(), "");
|
||||
// }
|
||||
|
||||
// if (CurrentDelayEditSeconds.Text == "")
|
||||
// return;
|
||||
|
||||
// while (CurrentDelayEditSeconds.Text[0] == '0' && CurrentDelayEditSeconds.Text.Length > 2)
|
||||
// {
|
||||
// CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(0, 1);
|
||||
// }
|
||||
|
||||
// if (CurrentDelayEditSeconds.Text.Length > 2)
|
||||
// {
|
||||
// CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(2);
|
||||
// }
|
||||
// OnCurrentDelaySecondsChanged?.Invoke(CurrentDelayEditSeconds.Text);
|
||||
// }
|
||||
|
||||
public void CurrentDelayChange(TimeSpan newValue)
|
||||
public void OnCurrentDelayMinutesChange(string text)
|
||||
{
|
||||
OnCurrentDelayChanged?.Invoke(newValue);
|
||||
List<char> toRemove = new();
|
||||
|
||||
foreach (var a in text)
|
||||
{
|
||||
if (!char.IsDigit(a))
|
||||
toRemove.Add(a);
|
||||
}
|
||||
|
||||
foreach (var a in toRemove)
|
||||
{
|
||||
CurrentDelayEditMinutes.Text = text.Replace(a.ToString(),"");
|
||||
}
|
||||
|
||||
if (CurrentDelayEditMinutes.Text == "")
|
||||
return;
|
||||
|
||||
while (CurrentDelayEditMinutes.Text[0] == '0' && CurrentDelayEditMinutes.Text.Length > 2)
|
||||
{
|
||||
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(0, 1);
|
||||
}
|
||||
|
||||
if (CurrentDelayEditMinutes.Text.Length > 2)
|
||||
{
|
||||
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(2);
|
||||
}
|
||||
OnCurrentDelayMinutesChanged?.Invoke(CurrentDelayEditMinutes.Text);
|
||||
}
|
||||
|
||||
public void OnCurrentDelaySecondsChange(string text)
|
||||
{
|
||||
List<char> toRemove = new();
|
||||
|
||||
foreach (var a in text)
|
||||
{
|
||||
if (!char.IsDigit(a))
|
||||
toRemove.Add(a);
|
||||
}
|
||||
|
||||
foreach (var a in toRemove)
|
||||
{
|
||||
CurrentDelayEditSeconds.Text = text.Replace(a.ToString(), "");
|
||||
}
|
||||
|
||||
if (CurrentDelayEditSeconds.Text == "")
|
||||
return;
|
||||
|
||||
while (CurrentDelayEditSeconds.Text[0] == '0' && CurrentDelayEditSeconds.Text.Length > 2)
|
||||
{
|
||||
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(0, 1);
|
||||
}
|
||||
|
||||
if (CurrentDelayEditSeconds.Text.Length > 2)
|
||||
{
|
||||
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(2);
|
||||
}
|
||||
OnCurrentDelaySecondsChanged?.Invoke(CurrentDelayEditSeconds.Text);
|
||||
}
|
||||
// End Monolith
|
||||
|
||||
public void SetCurrentText(string text)
|
||||
{
|
||||
CurrentTextEdit.Text = text;
|
||||
}
|
||||
// Begin Monolith
|
||||
// public void SetCurrentDelayMinutes(string delay)
|
||||
// {
|
||||
// CurrentDelayEditMinutes.Text = delay;
|
||||
// }
|
||||
|
||||
// public void SetCurrentDelaySeconds(string delay)
|
||||
// {
|
||||
// CurrentDelayEditSeconds.Text = delay;
|
||||
// }
|
||||
|
||||
public void SetCurrentDelay(TimeSpan delay)
|
||||
public void SetCurrentDelayMinutes(string delay)
|
||||
{
|
||||
CurrentDelayEdit.Value = (float)delay.TotalSeconds;
|
||||
CurrentDelayEditMinutes.Text = delay;
|
||||
}
|
||||
// End Monolith
|
||||
|
||||
public void SetCurrentDelaySeconds(string delay)
|
||||
{
|
||||
CurrentDelayEditSeconds.Text = delay;
|
||||
}
|
||||
|
||||
public void SetShowText(bool showTime)
|
||||
{
|
||||
TextEdit.Visible = showTime;
|
||||
@@ -194,10 +176,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
|
||||
public void SetHasAccess(bool hasAccess)
|
||||
{
|
||||
CurrentTextEdit.Editable = hasAccess;
|
||||
// Begin Monolith
|
||||
// CurrentDelayEditMinutes.Editable = hasAccess;
|
||||
// CurrentDelayEditSeconds.Editable = hasAccess;
|
||||
// End Monolith
|
||||
CurrentDelayEditMinutes.Editable = hasAccess;
|
||||
CurrentDelayEditSeconds.Editable = hasAccess;
|
||||
StartTimer.Disabled = !hasAccess;
|
||||
}
|
||||
|
||||
@@ -206,13 +186,10 @@ public sealed partial class SignalTimerWindow : DefaultWindow
|
||||
/// </summary>
|
||||
public TimeSpan GetDelay()
|
||||
{
|
||||
// Begin Monolith
|
||||
// if (!double.TryParse(CurrentDelayEditMinutes.Text, out var minutes))
|
||||
// minutes = 0;
|
||||
// if (!double.TryParse(CurrentDelayEditSeconds.Text, out var seconds))
|
||||
// seconds = 0;
|
||||
// return TimeSpan.FromMinutes(minutes) + TimeSpan.FromSeconds(seconds);
|
||||
return TimeSpan.FromSeconds(CurrentDelayEdit.Value);
|
||||
// End Monolith
|
||||
if (!double.TryParse(CurrentDelayEditMinutes.Text, out var minutes))
|
||||
minutes = 0;
|
||||
if (!double.TryParse(CurrentDelayEditSeconds.Text, out var seconds))
|
||||
seconds = 0;
|
||||
return TimeSpan.FromMinutes(minutes) + TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
||||
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||
<CheckBox Name="ChatFollowButton" Text="{Loc 'ui-options-chat-follow-button'}" />
|
||||
<Label Text="{Loc 'ui-options-general-cursor'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
|
||||
|
||||
@@ -63,6 +63,7 @@ public sealed partial class MiscTab : Control
|
||||
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
|
||||
Control.AddOptionCheckBox(CCVars.InterfaceChatFollowButton, ChatFollowButton);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly Shared.SSDIndicator.SSDIndicatorSystem _shared = default!; // DeltaV - SSD Recency, don't want to rename the upstream class
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,13 +36,26 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
!HasComp<ActiveNPCComponent>(uid) &&
|
||||
HasComp<MindExaminableComponent>(uid))
|
||||
{
|
||||
// Begin DeltaV Addition
|
||||
// Begin DeltaV Additions
|
||||
var ev = new ShowSSDIndicatorEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Hidden)
|
||||
return;
|
||||
// End DeltaV Addition
|
||||
args.StatusIcons.Add(_prototype.Index(component.Icon));
|
||||
|
||||
// SSD Recency Indicator
|
||||
var stage = _shared.GetStage(new Entity<SSDIndicatorComponent>(uid, component));
|
||||
var icon = stage switch
|
||||
{
|
||||
SsdStage.VeryRecent => component.VeryRecentIcon,
|
||||
SsdStage.Recent => component.RecentIcon,
|
||||
SsdStage.Cryoable => component.Icon,
|
||||
_ => throw new InvalidOperationException($"{ToPrettyString(uid)} has an invalid SSD stage {stage}."),
|
||||
};
|
||||
|
||||
args.StatusIcons.Add(_prototype.Index(icon));
|
||||
// End DeltaV Additions
|
||||
|
||||
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
// This does nothing here. The code is all in the shared/server version.
|
||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem;
|
||||
@@ -20,7 +20,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
|
||||
private const string AnimateKey = "particle-animation";
|
||||
|
||||
private static readonly EntProtoId InteractionParticleId = "StellarInteractionParticle";
|
||||
private static readonly Dictionary<StellarInteractionParticleType, EntProtoId> InteractionParticleIds = new ()
|
||||
{
|
||||
{ StellarInteractionParticleType.Use, "StellarInteractionParticleUse" },
|
||||
{ StellarInteractionParticleType.Pull, "StellarInteractionParticlePull" },
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -38,6 +42,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
if (!Exists(performer) || !Exists(target))
|
||||
return;
|
||||
|
||||
if (ev.Type == StellarInteractionParticleType.Pull)
|
||||
{
|
||||
(performer, target) = (target, performer);
|
||||
}
|
||||
|
||||
var performerXform = Transform(performer);
|
||||
var targetXform = Transform(target);
|
||||
if (performerXform.MapID == MapId.Nullspace || targetXform.MapID == MapId.Nullspace)
|
||||
@@ -47,7 +56,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var performerTargetDelta = targetXform.LocalPosition - performerXform.LocalPosition;
|
||||
var particle = Spawn(InteractionParticleId, performerXform.Coordinates);
|
||||
var particle = Spawn(InteractionParticleIds[ev.Type], performerXform.Coordinates);
|
||||
|
||||
if (used is { } usedEntity && Exists(usedEntity) && TryComp<SpriteComponent>(usedEntity, out var usedSprite))
|
||||
{
|
||||
@@ -58,21 +67,27 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
}
|
||||
|
||||
var spriteColor = Comp<SpriteComponent>(particle).Color;
|
||||
_animation.Play(particle, GetAnimation(performerTargetDelta, spriteColor), AnimateKey);
|
||||
var animation = ev.Type switch
|
||||
{
|
||||
StellarInteractionParticleType.Use => GetUseAnimation(performerTargetDelta, spriteColor),
|
||||
StellarInteractionParticleType.Pull => GetPullAnimation(performerTargetDelta, spriteColor),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ev), $"Interaction particle event has unknown particle type {ev.Type}"),
|
||||
};
|
||||
_animation.Play(particle, animation, AnimateKey);
|
||||
}
|
||||
|
||||
private Animation GetAnimation(Vector2 endOffset, Color color)
|
||||
private Animation GetUseAnimation(Vector2 endOffset, Color color)
|
||||
{
|
||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-80), Angle.FromDegrees(80));
|
||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-40), Angle.FromDegrees(40));
|
||||
var endRotation = Angle.Zero;
|
||||
var startScale = new Vector2(0.3f, 0.3f);
|
||||
var endScale = new Vector2(1f, 1f);
|
||||
var rotationLength = TimeSpan.FromMilliseconds(600);
|
||||
|
||||
var startOffset = new Vector2();
|
||||
var offsetLength = TimeSpan.FromMilliseconds(200);
|
||||
var offsetLength = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
var startColor = color.WithAlpha(color.A * 0.9f);
|
||||
var startColor = color.WithAlpha(color.A * 0.7f);
|
||||
var endColor = color.WithAlpha(0f);
|
||||
var colorLength = rotationLength + offsetLength;
|
||||
|
||||
@@ -89,7 +104,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
@@ -99,7 +114,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startScale, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
@@ -109,7 +124,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
@@ -126,4 +141,44 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private Animation GetPullAnimation(Vector2 endOffset, Color color)
|
||||
{
|
||||
var rotationLength = TimeSpan.FromMilliseconds(8f * (1000f / 12f));
|
||||
|
||||
var startOffset = new Vector2();
|
||||
var offsetLength = TimeSpan.FromMilliseconds(4f * (1000f / 12f));
|
||||
|
||||
var endColor = color.WithAlpha(0f);
|
||||
|
||||
return new Animation
|
||||
{
|
||||
Length = rotationLength,
|
||||
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(color, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(color, (float)offsetLength.TotalSeconds),
|
||||
new AnimationTrackProperty.KeyFrame(endColor, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +245,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"TimedDespawnDetailed", // DeltaV
|
||||
// makes an announcement on mapInit.
|
||||
"AnnounceOnSpawn",
|
||||
"ESTimedDespawn" // DeltaV
|
||||
};
|
||||
|
||||
Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
|
||||
|
||||
@@ -262,6 +262,32 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", ntAgent, Loc.GetString("admin-verb-text-make-NTAgent")),
|
||||
};
|
||||
args.Verbs.Add(agent);
|
||||
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-make-HeadInsurgent.text"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadInsurgent");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-HeadInsurgent.message"),
|
||||
});
|
||||
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-make-HeadLoyalist.text"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "Nanotrasen"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadLoyalist");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-HeadLoyalist.message"),
|
||||
});
|
||||
// End DeltaV Additions
|
||||
// start DeltaV Additions - add hitman
|
||||
var hitmanName = Loc.GetString("admin-verb-make-hitman");
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
if (anomaly.Comp.CurrentBehavior is not null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
EndAnomaly(anomaly, spawnCore: false);
|
||||
EndAnomaly(anomaly, spawnCore: false, forced: true);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||
@@ -143,7 +143,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
return 0;
|
||||
|
||||
var multiplier = 1f;
|
||||
if (component.Stability > component.GrowthThreshold)
|
||||
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
|
||||
multiplier = component.GrowingPointMultiplier; //more points for unstable
|
||||
|
||||
//penalty of up to 50% based on health
|
||||
@@ -253,7 +253,11 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
else
|
||||
{
|
||||
string stateLoc;
|
||||
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
||||
// DeltaV - Colossus Additions START
|
||||
if (anomalyComp.AlwaysGrow)
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||
// DeltaV - Colossus Additions END
|
||||
else if (anomalyComp.Stability < anomalyComp.DecayThreshold) // DeltaV - Add else
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
||||
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||
@@ -345,6 +349,15 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-unknown"));
|
||||
else
|
||||
{
|
||||
// DeltaV - Colossus Additions START
|
||||
if (anomalyComp.AlwaysGrow)
|
||||
{
|
||||
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-always-grow"));
|
||||
if (anomalyComp.CurrentBehavior != null)
|
||||
msg.PushNewline();
|
||||
}
|
||||
// DeltaV - Colossus Additions END
|
||||
|
||||
if (anomalyComp.CurrentBehavior != null)
|
||||
{
|
||||
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
|
||||
@@ -354,7 +367,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
|
||||
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
|
||||
}
|
||||
else
|
||||
else if(!anomalyComp.AlwaysGrow) // DeltaV - Add condition, previously regular else
|
||||
{
|
||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-balanced"));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ public sealed partial class AntagSelectionSystem
|
||||
/// </summary>
|
||||
public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
|
||||
[NotNullWhen(true)] out AntagSelectionDefinition? definition,
|
||||
int? players = null)
|
||||
int? players = null,
|
||||
string? preferredRole = null) // DeltaV - preferrec roles
|
||||
{
|
||||
definition = null;
|
||||
|
||||
@@ -39,6 +40,11 @@ public sealed partial class AntagSelectionSystem
|
||||
// It needs to track selected minds for each definition independently.
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
// Begin DeltaV - preferred roles
|
||||
if (preferredRole is not null && !def.PrefRoles.Contains(preferredRole))
|
||||
continue;
|
||||
// End DeltaV - preferred roles
|
||||
|
||||
var target = GetTargetAntagCount(ent, null, def);
|
||||
|
||||
if (mindCount < target)
|
||||
@@ -336,11 +342,11 @@ public sealed partial class AntagSelectionSystem
|
||||
/// This technically is a gamerule-ent-less way to make an entity an antag.
|
||||
/// You should almost never be using this.
|
||||
/// </summary>
|
||||
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule) where T : Component
|
||||
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule, string? preferredRole = null) where T : Component // DeltaV - preferred antag roles
|
||||
{
|
||||
var rule = ForceGetGameRuleEnt<T>(defaultRule);
|
||||
|
||||
if (!TryGetNextAvailableDefinition(rule, out var def))
|
||||
if (!TryGetNextAvailableDefinition(rule, out var def, preferredRole: preferredRole)) // DeltaV - preferred antag roles
|
||||
def = rule.Comp.Definitions.Last();
|
||||
MakeAntag(rule, player, def.Value);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Discord.DiscordLink;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Administration;
|
||||
@@ -15,6 +16,7 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Players; // DeltaV - OOC muting
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
@@ -47,6 +49,7 @@ internal sealed partial class ChatManager : IChatManager
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly DiscordChatLink _discordLink = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -342,12 +345,35 @@ internal sealed partial class ChatManager : IChatManager
|
||||
|
||||
#region Utility
|
||||
|
||||
private bool IsValidWarpDestination(EntityUid source)
|
||||
{
|
||||
if (!source.Valid)
|
||||
return false;
|
||||
|
||||
if (!_entityManager.TryGetComponent(source, out TransformComponent? transform))
|
||||
return false;
|
||||
|
||||
return transform.MapID != MapId.Nullspace;
|
||||
}
|
||||
|
||||
public string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient)
|
||||
{
|
||||
if (IsValidWarpDestination(source) && ShouldShowFollowButton(recipient))
|
||||
{
|
||||
var btnText = _localizationManager.GetString("chat-manager-follow-button");
|
||||
return $"[cmdlink=\"{btnText}\" command=\"{GhostFollowEntityCommand.CommandName} {_entityManager.GetNetEntity(source)}\" /] " + wrappedMessage;
|
||||
}
|
||||
|
||||
return wrappedMessage;
|
||||
}
|
||||
|
||||
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
{
|
||||
var user = author == null ? null : EnsurePlayer(author);
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
wrappedMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
|
||||
|
||||
@@ -370,8 +396,12 @@ internal sealed partial class ChatManager : IChatManager
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
|
||||
foreach (var client in clients)
|
||||
{
|
||||
var customWrapMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||
var msg = new ChatMessage(channel, message, customWrapMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendMessage(new MsgChatMessage { Message = msg }, client);
|
||||
}
|
||||
|
||||
if (!recordReplay)
|
||||
return;
|
||||
@@ -379,6 +409,7 @@ internal sealed partial class ChatManager : IChatManager
|
||||
if ((channel & ChatChannel.AdminRelated) == 0 ||
|
||||
_configurationManager.GetCVar(CCVars.ReplayRecordAdminChat))
|
||||
{
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_replay.RecordServerMessage(msg);
|
||||
}
|
||||
}
|
||||
@@ -439,6 +470,22 @@ internal sealed partial class ChatManager : IChatManager
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool ShouldShowFollowButton(INetChannel recipient)
|
||||
{
|
||||
if (!_player.TryGetSessionByChannel(recipient, out var session))
|
||||
return false;
|
||||
|
||||
if (_entityManager.TrySystem(out GhostSystem? ghost))
|
||||
{
|
||||
if (!ghost.CanGhostWarp(session, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _netConfigManager.GetClientCVar(recipient, CCVars.InterfaceChatFollowButton);
|
||||
}
|
||||
}
|
||||
|
||||
public enum OOCChatType : byte
|
||||
|
||||
@@ -93,7 +93,6 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
||||
Entry("('=", "chatsan-tearfully-smiles"),
|
||||
Entry("['=", "chatsan-tearfully-smiles"),
|
||||
Entry("?", "chatsan-confused"), //DeltaV
|
||||
Entry("!", "chatsan-surprised"), //DeltaV
|
||||
Entry("…", "chatsan-sighs"), //DeltaV (note that ... doesn't work as that turns into a radio message)
|
||||
];
|
||||
|
||||
|
||||
@@ -49,5 +49,7 @@ namespace Content.Server.Chat.Managers
|
||||
/// <param name="player">The player sending a chat message.</param>
|
||||
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
||||
RateLimitStatus HandleRateLimit(ICommonSession player);
|
||||
|
||||
string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Speech.Hushing; // DeltaV
|
||||
using Content.Server.Nyanotrasen.Chat;
|
||||
using Content.Server.Speech.Components; // DeltaV
|
||||
using Content.Server.Speech.Prototypes;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
@@ -35,6 +36,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared._DV.Chat; // DeltaV - chat enhancements
|
||||
|
||||
namespace Content.Server.Chat.Systems;
|
||||
|
||||
@@ -215,7 +217,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
// Was there an emote in the message? If so, send it.
|
||||
if (player != null && emoteStr != message && emoteStr != null)
|
||||
{
|
||||
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
|
||||
SendEntityEmote(source, emoteStr, range, nameOverride, null, ignoreActionBlocker); // DeltaV - Had to change up for SendEntityEmote
|
||||
}
|
||||
|
||||
// This can happen if the entire string is sanitized out.
|
||||
@@ -252,9 +254,18 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
case InGameICChatType.Whisper:
|
||||
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
|
||||
break;
|
||||
case InGameICChatType.Emote:
|
||||
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
break;
|
||||
case InGameICChatType.Emote: // DeltaV - Emote now has different types of emotes.
|
||||
var type = ProcessEmoteMessage(source, message, out var modMessage);
|
||||
if (type == EmoteType.Audible || type == EmoteType.AudiblePossessive)
|
||||
{
|
||||
if (checkRadioPrefix && TryProcessRadioMessage(source, modMessage, out var outputMessage, out var channel, capitalize: false))
|
||||
SendAudibleEntityEmote(source, outputMessage, range, nameOverride, channel, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
else
|
||||
SendAudibleEntityEmote(source, modMessage, range, nameOverride, null, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
}
|
||||
else
|
||||
SendEntityEmote(source, modMessage, range, nameOverride, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
break; // DeltaV - End
|
||||
//Nyano - Summary: case adds the telepathic chat sending ability.
|
||||
case InGameICChatType.Telepathic:
|
||||
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
|
||||
@@ -629,6 +640,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
EmoteType? emoteType, // DeltaV
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
@@ -642,11 +654,21 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
var ent = Identity.Entity(source, EntityManager);
|
||||
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||
|
||||
// Begin DeltaV
|
||||
string wrappedMessage;
|
||||
|
||||
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
||||
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
if (emoteType == EmoteType.Possessive) // DeltaV - Emote types now get checked.
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-possessive-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
else
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
// End DeltaV
|
||||
|
||||
if (checkEmote &&
|
||||
!TryEmoteChatInput(source, action))
|
||||
@@ -660,6 +682,59 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||
}
|
||||
|
||||
// DeltaV - Added this to differentiate between emotes that can be heard over radio and those which can't.
|
||||
protected override void SendAudibleEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
RadioChannelPrototype? channel,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
NetUserId? author = null
|
||||
)
|
||||
{
|
||||
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
|
||||
return;
|
||||
|
||||
EmoteType type = emoteType ?? EmoteType.Audible;
|
||||
|
||||
// get the entity's apparent name (if no override provided).
|
||||
var ent = Identity.Entity(source, EntityManager);
|
||||
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||
|
||||
string wrappedMessage;
|
||||
|
||||
// Audible emotes use Identity.Name, since that is the status quo. Emotes like scream doesn't currently reveal identities so this won't either. [This may be changed with feedback as you can audibly emote over radios]
|
||||
if (emoteType == EmoteType.AudiblePossessive)
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-possessive-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
else
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
|
||||
if (checkEmote &&
|
||||
!TryEmoteChatInput(source, action))
|
||||
return;
|
||||
|
||||
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
|
||||
|
||||
var ev = new EntityAudiblyEmotedEvent(source, action, channel, type);
|
||||
RaiseLocalEvent(source, ref ev, true);
|
||||
if (!hideLog)
|
||||
if (name != Name(source))
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source} as {name}: {action}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
|
||||
{
|
||||
@@ -889,6 +964,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
|
||||
var ghostHearing = GetEntityQuery<GhostHearingComponent>();
|
||||
var xforms = GetEntityQuery<TransformComponent>();
|
||||
var blockListening = GetEntityQuery<BlockListeningComponent>(); // DeltaV - block listening
|
||||
|
||||
var transformSource = xforms.GetComponent(source);
|
||||
var sourceMapId = transformSource.MapID;
|
||||
@@ -904,6 +980,11 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (transformEntity.MapID != sourceMapId)
|
||||
continue;
|
||||
|
||||
// Begin DeltaV - block listening
|
||||
if (blockListening.HasComponent(playerEntity))
|
||||
continue;
|
||||
// End DeltaV - block listening
|
||||
|
||||
var observer = ghostHearing.HasComponent(playerEntity);
|
||||
|
||||
// even if they are a ghost hearer, in some situations we still need the range
|
||||
@@ -971,4 +1052,4 @@ public sealed class CheckTargetedSpeechEvent : EntityEventArgs
|
||||
{
|
||||
public List<EntityUid> Targets = new List<EntityUid>();
|
||||
}
|
||||
// END Mono
|
||||
// END Mono
|
||||
|
||||
@@ -51,11 +51,8 @@ public sealed class SignalTimerSystem : EntitySystem
|
||||
if (_ui.HasUi(uid, SignalTimerUiKey.Key))
|
||||
{
|
||||
_ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(component.Label,
|
||||
// Begin Monolith
|
||||
// TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
|
||||
// TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
|
||||
TimeSpan.FromSeconds(component.Delay),
|
||||
// End Monolith
|
||||
TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
|
||||
TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
|
||||
component.CanEditLabel,
|
||||
time,
|
||||
active != null,
|
||||
@@ -76,11 +73,8 @@ public sealed class SignalTimerSystem : EntitySystem
|
||||
if (_ui.HasUi(uid, SignalTimerUiKey.Key))
|
||||
{
|
||||
_ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(signalTimer.Label,
|
||||
// Begin Monolith
|
||||
// TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
|
||||
// TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
|
||||
TimeSpan.FromSeconds(signalTimer.Delay),
|
||||
// End Monolith
|
||||
TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
|
||||
TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
|
||||
signalTimer.CanEditLabel,
|
||||
TimeSpan.Zero,
|
||||
false,
|
||||
|
||||
@@ -382,11 +382,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
|
||||
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||
|
||||
// camera shake
|
||||
// ES START
|
||||
// CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
||||
// ES END
|
||||
|
||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
|
||||
|
||||
@@ -416,12 +411,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
? queued.Proto.SmallSoundFar
|
||||
: queued.Proto.SoundFar;
|
||||
|
||||
// ES START
|
||||
var farTranslationShake = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
||||
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
||||
_shake.Screenshake(filter, farTranslationShake, null);
|
||||
// ES END
|
||||
CameraShake(iterationIntensity, pos, queued); // Starlight
|
||||
|
||||
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
||||
|
||||
@@ -444,8 +434,13 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
_damageableSystem);
|
||||
}
|
||||
|
||||
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
|
||||
private void CameraShake(List<float> rangeList, MapCoordinates epicenter, QueuedExplosion queued) // Starlight - replace range with rangeList, totalIntensity with queued
|
||||
{
|
||||
// Starlight BEGIN
|
||||
var range = rangeList.Count * 4f;
|
||||
var totalIntensity = queued.TotalIntensity * 10f;
|
||||
// Starlight END
|
||||
|
||||
var players = Filter.Empty();
|
||||
players.AddInRange(epicenter, range, _playerManager, EntityManager);
|
||||
|
||||
@@ -463,7 +458,40 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
var distance = delta.Length();
|
||||
var effect = 5 * MathF.Pow(totalIntensity, 0.5f) * (1 - distance / range);
|
||||
if (effect > 0.01f)
|
||||
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
||||
{
|
||||
// DeltaV - Camera kick falloff START
|
||||
// _recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
||||
|
||||
// Exponential decay: N(t) = N₀ ⋅ e^(-λ ⋅ t)
|
||||
// In this case, N = effect and we aren't decaying over time but with increasing distance from the epicenter,
|
||||
// so t = distance / range. Higher values of λ lead to faster decay, lower values to slower decay.
|
||||
//
|
||||
// severity is a fixed factor used to decrease the overall severity of the camera kick,
|
||||
// as the values were so high by default that decay was only becoming really noticeable near range.
|
||||
//
|
||||
// these changes are made here instead of directly assigning to the effect variable
|
||||
// above to maintain the same overall effect range as before.
|
||||
const float severity = 0.033f;
|
||||
const float lambda = 4f;
|
||||
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect * severity * MathF.Exp(-lambda * (distance / range)));
|
||||
// DeltaV END
|
||||
|
||||
// Starlight START
|
||||
var shakeParams = rangeList.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
||||
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
||||
|
||||
// DeltaV - Screenshake falloff START
|
||||
// Linear falloff with increasing distance from epicenter,
|
||||
// capped at certain values to avoid diminishing the effect completely near range.
|
||||
shakeParams.DecayRate *= MathF.Min(1 + distance / range, 1.75f);
|
||||
shakeParams.Frequency *= MathF.Max(1 - distance / range, 0.33f);
|
||||
shakeParams.Trauma *= MathF.Max(1 - distance / range, 0.33f);
|
||||
// DeltaV END
|
||||
|
||||
_shake.Screenshake(players, shakeParams, null);
|
||||
// Starlight END
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DeviceNetwork.Systems; // DeltaV - map init ordering
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Fax;
|
||||
using Content.Shared.Fax.Components;
|
||||
@@ -64,7 +65,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
// Hooks
|
||||
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedDeviceNetworkSystem)]); // DeltaV - map init order, we need address assigned first
|
||||
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
|
||||
|
||||
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
||||
@@ -301,6 +302,11 @@ public sealed class FaxSystem : EntitySystem
|
||||
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
|
||||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
|
||||
return;
|
||||
// Begin DeltaV - we removed the power requirement from device network but we still don't want
|
||||
// unpowered faxes to happen
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered)
|
||||
return;
|
||||
// End DeltaV
|
||||
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
||||
|
||||
@@ -35,17 +35,17 @@ public sealed partial class PuddleSystem
|
||||
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
|
||||
return;
|
||||
|
||||
// DeltaV - Beer Goggles Safe Throw
|
||||
if ( args.User is { } user && _safeSolutionThrower.GetSafeThrow(user))
|
||||
{
|
||||
_physics.SetAngularVelocity(entity, 0);
|
||||
Transform(entity).LocalRotation = Angle.Zero;
|
||||
return;
|
||||
}
|
||||
// END DeltaV
|
||||
|
||||
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
|
||||
{
|
||||
// DeltaV - Beer Goggles Safe Throw
|
||||
if (_safeSolutionThrower.GetSafeThrow(args.User.Value))
|
||||
{
|
||||
_physics.SetAngularVelocity(entity, 0);
|
||||
Transform(entity).LocalRotation = Angle.Zero;
|
||||
return;
|
||||
}
|
||||
// END DeltaV
|
||||
|
||||
AdminLogger.Add(LogType.Landed,
|
||||
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Ghost;
|
||||
|
||||
[AnyCommand]
|
||||
internal sealed partial class GhostFollowEntityCommand : LocalizedEntityCommands
|
||||
{
|
||||
public const string CommandName = "ghost_follow_entity";
|
||||
|
||||
[Dependency] private GhostSystem _ghost = null!;
|
||||
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1 || shell.Player is not { } player)
|
||||
return;
|
||||
|
||||
var target = args[0];
|
||||
if (!NetEntity.TryParse(target, out var targetEnt))
|
||||
return;
|
||||
|
||||
_ghost.GhostWarpRequest(player, targetEnt);
|
||||
}
|
||||
}
|
||||
@@ -299,10 +299,22 @@ namespace Content.Server.Ghost
|
||||
|
||||
#region Warp
|
||||
|
||||
public bool CanGhostWarp(ICommonSession session, out EntityUid entity)
|
||||
{
|
||||
if (session.AttachedEntity is not { Valid: true } sessionEntity
|
||||
|| !_ghostQuery.HasComp(sessionEntity))
|
||||
{
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
entity = sessionEntity;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not {Valid: true} entity
|
||||
|| !_ghostQuery.HasComp(entity))
|
||||
if (!CanGhostWarp(args.SenderSession, out var entity))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
|
||||
return;
|
||||
@@ -312,30 +324,33 @@ namespace Content.Server.Ghost
|
||||
RaiseNetworkEvent(response, args.SenderSession.Channel);
|
||||
}
|
||||
|
||||
public void GhostWarpRequest(ICommonSession player, NetEntity target)
|
||||
{
|
||||
if (!CanGhostWarp(player, out var attached))
|
||||
{
|
||||
Log.Warning($"User {player.Name} tried to warp to {target} without being a ghost.");
|
||||
return;
|
||||
}
|
||||
|
||||
var realTarget = GetEntity(target);
|
||||
|
||||
if (!Exists(realTarget))
|
||||
{
|
||||
Log.Warning($"User {player.Name} tried to warp to an invalid entity id: {target}");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpTo(attached, realTarget);
|
||||
}
|
||||
|
||||
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|
||||
|| !_ghostQuery.HasComp(attached))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost.");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = GetEntity(msg.Target);
|
||||
|
||||
if (!Exists(target))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpTo(attached, target);
|
||||
GhostWarpRequest(args.SenderSession, msg.Target);
|
||||
}
|
||||
|
||||
private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not {} uid
|
||||
|| !_ghostQuery.HasComp(uid))
|
||||
if (CanGhostWarp(args.SenderSession, out var uid))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost.");
|
||||
return;
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Content.Server.Medical.BiomassReclaimer
|
||||
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
|
||||
{
|
||||
component.BloodReagents = solution.Clone();
|
||||
component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume);
|
||||
//component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume); // Delta V - This doesn't need to be here. It just always makes the solution ~50u but also might divide by 0. Just use the current blood level so more blood = more mess.
|
||||
}
|
||||
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
|
||||
{
|
||||
|
||||
@@ -475,7 +475,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Force a nuclear bomb to start a countdown timer
|
||||
/// </summary>
|
||||
public void ArmBomb(EntityUid uid, NukeComponent? component = null)
|
||||
public void ArmBomb(EntityUid uid, NukeComponent? component = null, string? announcementId = null) // DeltaV - custom announcements
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
@@ -500,7 +500,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
_selectedNukeSong = _audio.ResolveSound(component.ArmMusic);
|
||||
|
||||
// warn a crew
|
||||
var announcement = Loc.GetString("nuke-component-announcement-armed",
|
||||
var announcement = Loc.GetString(announcementId ?? "nuke-component-announcement-armed", // DeltaV - custom announcements
|
||||
("time", (int) component.RemainingTime),
|
||||
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
|
||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Radio.EntitySystems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared._DV.Chat;
|
||||
|
||||
namespace Content.Server.Radio.EntitySystems;
|
||||
|
||||
@@ -26,6 +27,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
||||
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
|
||||
|
||||
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
|
||||
SubscribeLocalEvent<WearingHeadsetComponent, EntityAudiblyEmotedEvent>(OnAudibleEmote); // DeltaV
|
||||
}
|
||||
|
||||
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
|
||||
@@ -48,22 +50,43 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
||||
EnsureComp<ActiveRadioComponent>(uid).Channels = new(keyHolder.Channels);
|
||||
}
|
||||
|
||||
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
||||
// DeltaV
|
||||
// WARNING - Be very careful when modifying this method.
|
||||
// The implementation right now has null forgiving operatiors in OnSpeak() and OnAudibleEmote() since this only returns true if channel is not null
|
||||
private bool CheckRadioCapable(EntityUid uid, WearingHeadsetComponent headset, RadioChannelPrototype? channel)
|
||||
{
|
||||
if (args.Channel != null
|
||||
&& TryComp(component.Headset, out EncryptionKeyHolderComponent? keys)
|
||||
&& keys.Channels.Contains(args.Channel.ID))
|
||||
if (channel != null
|
||||
&& TryComp(headset.Headset, out EncryptionKeyHolderComponent? keys)
|
||||
&& keys.Channels.Contains(channel.ID))
|
||||
{
|
||||
// Begin DeltaV Additions: No using headsets if you lost your hands or are cuffed
|
||||
if (!TryComp<HandsComponent>(uid, out var hands) || hands.Count < 1 ||
|
||||
TryComp<CuffableComponent>(uid, out var cuffable) && _cuffable.IsCuffed((uid, cuffable)))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("headset-cant-reach"), uid, uid, PopupType.SmallCaution);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// End DeltaV Additions
|
||||
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel, component.Headset);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnAudibleEmote(EntityUid uid, WearingHeadsetComponent component, EntityAudiblyEmotedEvent args)
|
||||
{
|
||||
if (CheckRadioCapable(uid, component, args.Channel))
|
||||
{
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset, emType: args.Type);
|
||||
args.Channel = null;
|
||||
}
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
||||
{
|
||||
if (CheckRadioCapable(uid, component, args.Channel)) // DeltaV - Put all the radio checks into the CheckRadioCapable() method.
|
||||
{
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset); // DeltaV - Made the args.Channel null ignorant as CheckRadioCapable() guarantees it not null.
|
||||
args.Channel = null; // prevent duplicate messages from other listeners.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared._DV.Chat;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Radio;
|
||||
@@ -27,6 +30,8 @@ public sealed class RadioSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
// set used to prevent radio feedback loops.
|
||||
private readonly HashSet<string> _messages = new();
|
||||
@@ -38,6 +43,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
|
||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
|
||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntityAudiblyEmotedEvent>(OnIntrinsicAudibleEmote); // DeltaV - Robots should be allowed to emote over radio.
|
||||
|
||||
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
|
||||
}
|
||||
@@ -53,16 +59,43 @@ public sealed class RadioSystem : EntitySystem
|
||||
|
||||
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
|
||||
{
|
||||
if (TryComp(uid, out ActorComponent? actor))
|
||||
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
|
||||
if (!TryComp(uid, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
var msg = args.ChatMsg;
|
||||
if (_ghost.CanGhostWarp(actor.PlayerSession, out _))
|
||||
{
|
||||
msg = new MsgChatMessage
|
||||
{
|
||||
Message = new ChatMessage(args.ChatMsg.Message)
|
||||
{
|
||||
WrappedMessage = _chatManager.PrependFollowButtonIfAppropriate(
|
||||
args.ChatMsg.Message.WrappedMessage,
|
||||
args.MessageSource,
|
||||
actor.PlayerSession.Channel),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_netMan.ServerSendMessage(msg, actor.PlayerSession.Channel);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
private void OnIntrinsicAudibleEmote(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntityAudiblyEmotedEvent args)
|
||||
{
|
||||
if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
|
||||
{
|
||||
SendRadioMessage(uid, args.Message, args.Channel, uid, emType: args.Type);
|
||||
}
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
/// <summary>
|
||||
/// Send radio message to all active radio listeners
|
||||
/// </summary>
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||
{
|
||||
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
|
||||
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, emType: emType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +103,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="messageSource">Entity that spoke the message</param>
|
||||
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||
{
|
||||
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
|
||||
if (!_messages.Add(message))
|
||||
@@ -92,14 +125,31 @@ public sealed class RadioSystem : EntitySystem
|
||||
? FormattedMessage.EscapeText(message)
|
||||
: message;
|
||||
|
||||
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
||||
("color", channel.Color),
|
||||
("fontType", speech.FontId),
|
||||
("fontSize", speech.FontSize),
|
||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
// DeltaV - This change is to change up how the messages are wrapped up. Basically changing the formatting depending on the emote type.
|
||||
string wrappedMessage;
|
||||
|
||||
if (emType == EmoteType.Audible)
|
||||
wrappedMessage = Loc.GetString("chat-radio-message-audible-emote-wrap",
|
||||
("color", channel.Color),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
else if (emType == EmoteType.AudiblePossessive)
|
||||
wrappedMessage = Loc.GetString("chat-radio-message-audible-possessive-emote-wrap",
|
||||
("color", channel.Color),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
else
|
||||
wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
||||
("color", channel.Color),
|
||||
("fontType", speech.FontId),
|
||||
("fontSize", speech.FontSize),
|
||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
// DeltaV - End
|
||||
|
||||
// most radios are relayed to chat, so lets parse the chat message beforehand
|
||||
var chat = new ChatMessage(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq; // DeltaV
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Systems;
|
||||
@@ -8,6 +9,8 @@ using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Mobs; // DeltaV
|
||||
using Content.Shared.Mobs.Components; // DeltaV
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Pointing;
|
||||
@@ -47,14 +50,33 @@ namespace Content.Server.RatKing
|
||||
if (!TryComp<HungerComponent>(uid, out var hunger))
|
||||
return;
|
||||
|
||||
// DeltaV - modify cost of Raise Army based on the amount of alive servants
|
||||
// Check on how many servants are alive to calculate the cost of a new servant
|
||||
var aliveServants = component.Servants.Count(servant
|
||||
=> TryComp<MobStateComponent>(servant, out var mobState) && mobState.CurrentState == MobState.Alive);
|
||||
|
||||
// calculate the cost multiplier
|
||||
var multiplier = aliveServants switch
|
||||
{
|
||||
< 5 => 1.0f, // 1-5 servants: 10 hunger
|
||||
< 10 => 1.5f, // 6-10 servants: 15 hunger
|
||||
< 15 => 2.5f, // 11-15 servants: 25 hunger
|
||||
< 20 => 5.0f, // 16-20 servants: 50 hunger
|
||||
< 25 => 10.0f, // 21-25 servants: 100 hunger
|
||||
_ => 15.0f, // Above 25 servants: 150 hunger
|
||||
};
|
||||
var hungerPerArmyUseAdjusted = component.HungerPerArmyUse * multiplier;
|
||||
|
||||
//make sure the hunger doesn't go into the negatives
|
||||
if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
|
||||
if (_hunger.GetHunger(hunger) < hungerPerArmyUseAdjusted)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
||||
return;
|
||||
}
|
||||
args.Handled = true;
|
||||
_hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger);
|
||||
_hunger.ModifyHunger(uid, - hungerPerArmyUseAdjusted, hunger);
|
||||
// DeltaV - end to the modified cost of Raise Army
|
||||
|
||||
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
|
||||
var comp = EnsureComp<RatKingServantComponent>(servant);
|
||||
comp.King = uid;
|
||||
|
||||
@@ -27,6 +27,11 @@ public sealed partial class BorgSystem
|
||||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||
{
|
||||
// DeltaV Begin
|
||||
if (!comp.Active)
|
||||
return;
|
||||
// DeltaV End
|
||||
|
||||
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
|
||||
DoDisable((uid, comp, chassis, meta));
|
||||
|
||||
@@ -83,6 +88,11 @@ public sealed partial class BorgSystem
|
||||
|
||||
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
||||
{
|
||||
// DeltaV Begin
|
||||
if (!ent.Comp.Active)
|
||||
return;
|
||||
// DeltaV End
|
||||
|
||||
var payload = args.Data;
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return;
|
||||
@@ -197,4 +207,17 @@ public sealed partial class BorgSystem
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DeltaV Begin
|
||||
/// <summary>
|
||||
/// DeltaV - sets if the borg transponder is active
|
||||
/// </summary>
|
||||
public void SetTransponderActive(Entity<BorgTransponderComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Active = active;
|
||||
}
|
||||
// DeltaV End
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ public sealed partial class SpeechWireAction : ComponentWireAction<SpeechCompone
|
||||
public override bool Cut(EntityUid user, Wire wire, SpeechComponent component)
|
||||
{
|
||||
_speech.SetSpeech(wire.Owner, false, component);
|
||||
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = false; // DeltaV - we need this to override unblockable speech
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, SpeechComponent component)
|
||||
{
|
||||
_speech.SetSpeech(wire.Owner, true, component);
|
||||
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = true; // DeltaV - we need this to override unblockable speech
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,9 @@ namespace Content.Server.Speech.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class UnblockableSpeechComponent : Component
|
||||
{
|
||||
// Begin DeltaV
|
||||
[DataField]
|
||||
public bool Active = true;
|
||||
// End DeltaV
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Content.Server.Speech.EntitySystems
|
||||
|
||||
private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
|
||||
{
|
||||
args.IgnoreBlocker = true;
|
||||
args.IgnoreBlocker = component.Active; // DeltaV
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Content.Shared._DV.Uprising;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
|
||||
namespace Content.Server.Uprising;
|
||||
|
||||
public sealed class CommandStatusObjectiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly SharedStationSystem _station = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CommandStatusObjectiveComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
}
|
||||
|
||||
private bool CheckIncapacitated(EntityUid uid)
|
||||
{
|
||||
if (TryComp<CuffableComponent>(uid, out var cuffed) && cuffed.CuffedHandCount > 0)
|
||||
return true;
|
||||
|
||||
if (!TryComp<MobStateComponent>(uid, out var state))
|
||||
return true;
|
||||
|
||||
if (state.CurrentState is MobState.Dead or MobState.Invalid)
|
||||
return true;
|
||||
|
||||
if (_station.GetOwningStation(uid) == null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnGetProgress(Entity<CommandStatusObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
var total = 0;
|
||||
var counted = 0;
|
||||
|
||||
var query = AllEntityQuery<MindContainerComponent, CommandStaffComponent>();
|
||||
while (query.MoveNext(out var uid, out var mind, out _))
|
||||
{
|
||||
if (mind.Mind is not { } mindUid)
|
||||
continue;
|
||||
|
||||
total++;
|
||||
|
||||
var converted = false;
|
||||
foreach (var role in ent.Comp.ConvertedRoles)
|
||||
{
|
||||
if (!EntityManager.ComponentFactory.TryGetRegistration(role, out var roleReg))
|
||||
{
|
||||
Log.Error($"Role component not found for CrewConversionObjective: {role}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_role.MindHasRole(mindUid, roleReg.Type, out _))
|
||||
continue;
|
||||
|
||||
converted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var incapacitated = CheckIncapacitated(uid);
|
||||
|
||||
if (converted && !incapacitated || !converted && ent.Comp.ShouldUnconvertedBeIncapacitated && incapacitated)
|
||||
{
|
||||
counted++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total == 0)
|
||||
return;
|
||||
|
||||
args.Progress = (float)counted / (float)total;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Components;
|
||||
@@ -14,7 +13,6 @@ using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared._DV.Psionics.Components; // DeltaV
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
@@ -27,7 +25,6 @@ using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.NPC.Components; // DeltaV
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -72,7 +69,6 @@ public sealed partial class ZombieSystem
|
||||
[Dependency] private readonly NPCSystem _npc = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
|
||||
|
||||
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||
@@ -134,6 +130,9 @@ public sealed partial class ZombieSystem
|
||||
//you're a real zombie now, son.
|
||||
var zombiecomp = AddComp<ZombieComponent>(target);
|
||||
|
||||
// DeltaV - Save factions, psionics, etc. before modifications are made.
|
||||
PreserveEntityComponentState((target, zombiecomp));
|
||||
|
||||
//we need to basically remove all of these because zombies shouldn't
|
||||
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
|
||||
RemComp<RespiratorComponent>(target);
|
||||
@@ -146,12 +145,6 @@ public sealed partial class ZombieSystem
|
||||
RemComp<ComplexInteractionComponent>(target);
|
||||
RemComp<SentienceTargetComponent>(target);
|
||||
|
||||
// DeltaV Start - Prevent Psionic Zombies
|
||||
RemComp<PotentialPsionicComponent>(target);
|
||||
if (HasComp<PsionicComponent>(target))
|
||||
_psionic.MindBreakEntity(target, false, true);
|
||||
// DeltaV End - Prevent Psionic Zombies
|
||||
|
||||
//funny voice
|
||||
var accentType = "zombie";
|
||||
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
|
||||
@@ -245,6 +238,9 @@ public sealed partial class ZombieSystem
|
||||
//Should prevent instances of zombies using comms for information they shouldnt be able to have.
|
||||
_inventory.TryUnequip(target, "ears", true, true);
|
||||
|
||||
// DeltaV - Extra zombification removals, etc.
|
||||
ZombifyEntityDV((target, zombiecomp), mobState);
|
||||
|
||||
//popup
|
||||
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
|
||||
|
||||
@@ -261,7 +257,6 @@ public sealed partial class ZombieSystem
|
||||
|
||||
_faction.ClearFactions(target, dirty: false);
|
||||
_faction.AddFaction(target, ZombieFaction);
|
||||
EnsureComp<NoFriendlyFireComponent>(target); // DeltaV - prevent shitters biting other zombies
|
||||
|
||||
//gives it the funny "Zombie ___" name.
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
|
||||
@@ -78,6 +78,8 @@ namespace Content.Server.Zombies
|
||||
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
|
||||
|
||||
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
|
||||
|
||||
InitializeDV(); // DeltaV - Additional Subscriptions for ZombieComponent
|
||||
}
|
||||
|
||||
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
|
||||
@@ -325,7 +327,7 @@ namespace Content.Server.Zombies
|
||||
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
|
||||
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
|
||||
{
|
||||
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
||||
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
|
||||
public sealed class CosmicColossusBuffsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MultiplyAttackRateOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleAttackRate);
|
||||
SubscribeLocalEvent<FlatAttackBonusOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleDamageBonus);
|
||||
SubscribeLocalEvent<MultiplyCorruptingSpeedOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleCorruptingSpeed);
|
||||
SubscribeLocalEvent<HealOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleHeal);
|
||||
}
|
||||
|
||||
private void HandleAttackRate(Entity<MultiplyAttackRateOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<MeleeWeaponComponent>(ent, out var weapon))
|
||||
{
|
||||
weapon.AttackRate *= ent.Comp.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDamageBonus(Entity<FlatAttackBonusOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<CosmicColossusComponent>(ent, out var colossusComp))
|
||||
return;
|
||||
|
||||
colossusComp.BonusDamage +=
|
||||
new DamageSpecifier(_proto.Index(ent.Comp.BonusDamageType), ent.Comp.BonusDamage);
|
||||
}
|
||||
|
||||
private void HandleCorruptingSpeed(Entity<MultiplyCorruptingSpeedOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<CosmicCorruptingComponent>(ent, out var corrupting))
|
||||
{
|
||||
corrupting.CorruptionSpeed *= ent.Comp.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleHeal(Entity<HealOnSupercriticalComponent> ent, ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||
{
|
||||
_damage.TryChangeDamage(ent.Owner,
|
||||
-damageable.Damage * Math.Abs(ent.Comp.DamageFractionHealed), // just in case someone sets a negative value in YML
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct CosmicColossusEffigySupercriticalEvent(
|
||||
Entity<CosmicEffigyComponent> Effigy
|
||||
);
|
||||
@@ -1,15 +1,24 @@
|
||||
using System.Numerics;
|
||||
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
@@ -23,12 +32,93 @@ public sealed class CosmicEffigySystem : EntitySystem
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly CosmicCultObjectiveSystem _cultObjective = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicColossusComponent, EventCosmicColossusEffigy>(OnColossusEffigy);
|
||||
SubscribeLocalEvent<CosmicEffigyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||
SubscribeLocalEvent<CosmicEffigyComponent, AnomalyShutdownEvent>(OnAnomShutdown);
|
||||
}
|
||||
|
||||
private void OnAnomShutdown(Entity<CosmicEffigyComponent> ent, ref AnomalyShutdownEvent args)
|
||||
{
|
||||
if (args.Forced || args.Supercritical || !Exists(ent.Comp.Colossus) || !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp))
|
||||
return;
|
||||
|
||||
colossusComp.DeathTimer = _time.CurTime;
|
||||
colossusComp.Timed = true;
|
||||
}
|
||||
|
||||
private void OnSupercritical(Entity<CosmicEffigyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!Exists(ent.Comp.Colossus)
|
||||
|| !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp)
|
||||
|| !_mind.TryGetMind(ent.Comp.Colossus.Value, out _, out var mind))
|
||||
return;
|
||||
|
||||
var colossus = ent.Comp.Colossus.Value;
|
||||
|
||||
var ev = new CosmicColossusEffigySupercriticalEvent(ent);
|
||||
RaiseLocalEvent(colossus, ref ev);
|
||||
|
||||
var transform = Transform(ent.Comp.Colossus.Value);
|
||||
Spawn(colossusComp.BuffVfx, transform.Coordinates);
|
||||
|
||||
if (colossusComp.CompletedEffigies == 0)
|
||||
{
|
||||
_audio.PlayStatic(colossusComp.ReawakenSfx,
|
||||
Filter.BroadcastMap(transform.MapID),
|
||||
transform.Coordinates,
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_audio.PlayPvs(colossusComp.ReawakenSfx, ent);
|
||||
}
|
||||
|
||||
colossusComp.CompletedEffigies += 1;
|
||||
|
||||
if (colossusComp.CompletedEffigies >= colossusComp.MaxEffigies)
|
||||
{
|
||||
colossusComp.Timed = false;
|
||||
_popup.PopupEntity(Loc.GetString("colossus-buff-final-popup"), colossus, PopupType.Large);
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("colossus-buff-popup"), colossus, PopupType.Large);
|
||||
|
||||
var objIndex = mind.Objectives.FindIndex(HasComp<CosmicEffigyConditionComponent>);
|
||||
if (objIndex == -1 ||
|
||||
!TryComp<CosmicEffigyConditionComponent>(mind.Objectives[objIndex], out var conditionComp))
|
||||
{
|
||||
Log.Error($"Failed to find effigy objective on {ToPrettyString(colossus)}!");
|
||||
return;
|
||||
}
|
||||
|
||||
var objective = mind.Objectives[objIndex];
|
||||
if (_cultObjective.RandomizeEffigyTarget(objective, conditionComp, setDescription: true) is not {} nextTarget)
|
||||
{
|
||||
Log.Error("Failed to randomize effigy objective location!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mind.UserId is { } userId)
|
||||
{
|
||||
_chat.DispatchServerMessage(_player.GetSessionById(userId), Loc.GetString("colossus-next-target", ("location", nextTarget)));
|
||||
}
|
||||
|
||||
_codeCondition.SetCompleted(objective, false);
|
||||
|
||||
colossusComp.EffigyPlaceActionEntity = _actions.AddAction(colossus, colossusComp.EffigyPlaceAction);
|
||||
colossusComp.DeathTimer = _time.CurTime + colossusComp.DeathWaitEffigy;
|
||||
colossusComp.Timed = true;
|
||||
}
|
||||
|
||||
private void OnColossusEffigy(Entity<CosmicColossusComponent> ent, ref EventCosmicColossusEffigy args)
|
||||
@@ -38,8 +128,16 @@ public sealed class CosmicEffigySystem : EntitySystem
|
||||
|
||||
_actions.RemoveAction(ent.Owner, ent.Comp.EffigyPlaceActionEntity);
|
||||
_codeCondition.SetCompleted(ent.Owner, ent.Comp.EffigyObjective);
|
||||
Spawn(ent.Comp.EffigyPrototype, pos);
|
||||
var effigy = Spawn(ent.Comp.EffigyPrototype, pos);
|
||||
ent.Comp.Timed = false;
|
||||
|
||||
if (!TryComp<CosmicEffigyComponent>(effigy, out var effigyComp))
|
||||
{
|
||||
Log.Error("Colossus tried to place Effigy prototype missing CosmicEffigyComponent!");
|
||||
return;
|
||||
}
|
||||
|
||||
effigyComp.Colossus = ent.Owner;
|
||||
}
|
||||
|
||||
private bool VerifyPlacement(Entity<CosmicColossusComponent> ent, out EntityCoordinates outPos)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus's attack rate will be multiplied by
|
||||
/// the given multiplier once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MultiplyAttackRateOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Multiplier = 1.1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus's corrupting speed will be multiplied by
|
||||
/// the given multiplier once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MultiplyCorruptingSpeedOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Multiplier = 0.9f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus will receive a flat bonus to
|
||||
/// its melee attack damage once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class FlatAttackBonusOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public FixedPoint2 BonusDamage = 10;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Blunt";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus will be healed for `-CurrentDamage * Abs(DamageFractionHealed)` damage
|
||||
/// once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float DamageFractionHealed = 1.0f;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -5,7 +6,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem))]
|
||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem), typeof(CosmicColossusBuffsSystem))]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicCorruptingComponent : Component
|
||||
{
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Content.Server._DV.CosmicCult.Abilities;
|
||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for cosmic effigy anomalies, spawned by Cosmic Colossi.
|
||||
/// </summary>
|
||||
/// <seelso cref="CosmicEffigySystem"/>.
|
||||
/// <seelso cref="CosmicColossusSystem"/>.
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicEffigyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The colossus that placed this effigy.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Colossus;
|
||||
}
|
||||
@@ -35,6 +35,12 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
||||
if (args.Cancelled || !_roles.MindHasRole<CosmicColossusRoleComponent>(args.MindId))
|
||||
return;
|
||||
|
||||
if (RandomizeEffigyTarget(uid, comp) is null)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
public string? RandomizeEffigyTarget(EntityUid uid, CosmicEffigyConditionComponent comp, bool setDescription = false)
|
||||
{
|
||||
var warps = new List<EntityUid>();
|
||||
var query = EntityQueryEnumerator<WarpPointComponent>();
|
||||
var effigyBlacklist = comp.Blacklist;
|
||||
@@ -51,10 +57,22 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
||||
|
||||
if (warps.Count <= 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
comp.EffigyTarget = _random.Pick(warps);
|
||||
|
||||
var newWarp = _random.Pick(warps);
|
||||
var warpComp = Comp<WarpPointComponent>(newWarp);
|
||||
|
||||
comp.EffigyTarget = newWarp;
|
||||
|
||||
if (setDescription)
|
||||
{
|
||||
_metaData.SetEntityDescription(uid,
|
||||
warpComp.Location != null
|
||||
? Loc.GetString("objective-condition-effigy", ("location", warpComp.Location))
|
||||
: Loc.GetString("objective-condition-effigy-no-target"));
|
||||
}
|
||||
return warpComp.Location;
|
||||
}
|
||||
|
||||
private void OnEffigyAfterAssign(EntityUid uid, CosmicEffigyConditionComponent comp, ref ObjectiveAfterAssignEvent args)
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Warps;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -41,6 +42,7 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CosmicColossusComponent, ComponentInit>(OnSpawn);
|
||||
SubscribeLocalEvent<CosmicColossusComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<CosmicColossusComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -82,7 +84,7 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
|
||||
private void OnSpawn(Entity<CosmicColossusComponent> ent, ref ComponentInit args) // I WANT THIS BIG GUY HURLED TOWARDS THE STATION
|
||||
{
|
||||
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWait;
|
||||
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWaitSpawn;
|
||||
var station = _station.GetStationInMap(Transform(ent).MapID);
|
||||
if (TryComp<StationDataComponent>(station, out var stationData))
|
||||
{
|
||||
@@ -115,4 +117,9 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
RemComp<WarpPointComponent>(ent);
|
||||
RemComp<CosmicCorruptingComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnMeleeHit(Entity<CosmicColossusComponent> colossus, ref MeleeHitEvent args)
|
||||
{
|
||||
args.BonusDamage += colossus.Comp.BonusDamage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared._DV.Traits.Assorted;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
@@ -17,10 +18,12 @@ public sealed partial class ParadoxCloneRuleSystem
|
||||
private void FilterTargets(HashSet<Entity<MindComponent>> minds)
|
||||
{
|
||||
// TODO: use generic IMindFilter
|
||||
// no picking other antags or non-crew
|
||||
// no picking other antags or non-crew and entities with no paradox clone trait
|
||||
minds.RemoveWhere(mind => _role.MindIsAntagonist(mind) ||
|
||||
!_role.MindHasRole<JobRoleComponent>((mind, mind), out var role) ||
|
||||
role?.Comp1.JobPrototype == null);
|
||||
role?.Comp1.JobPrototype == null ||
|
||||
(mind.Comp.OwnedEntity is { } entity && HasComp<NoParadoxCloneComponent>(entity))
|
||||
);
|
||||
}
|
||||
|
||||
private void PostClone(EntityUid mob)
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
||||
using Content.Shared._DV.Species;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly CloningSystem _cloning = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
|
||||
|
||||
// holy initialize performance? but better for it to happen once than the double dict lookup every tick!!
|
||||
private EntityQuery<FracturedFormBodyComponent> _bodyQuery;
|
||||
private EntityQuery<SleepingComponent> _sleepingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_bodyQuery = GetEntityQuery<FracturedFormBodyComponent>();
|
||||
_sleepingQuery = GetEntityQuery<SleepingComponent>();
|
||||
}
|
||||
|
||||
protected override void OnPowerInit(Entity<FracturedFormPowerComponent> power, ref MapInitEvent args)
|
||||
{
|
||||
base.OnPowerInit(power, ref args);
|
||||
|
||||
// The next random swap is between 5 and 20 minutes.
|
||||
var randomTime = Random.Next(power.Comp.NextSwapMinTime, power.Comp.NextSwapMaxTime);
|
||||
power.Comp.NextSwap = Timing.CurTime + randomTime;
|
||||
power.Comp.NextVoluntarySwap = Timing.CurTime + power.Comp.VoluntarySwapCooldown;
|
||||
|
||||
// Don't generate a new body if we're already part of a network.
|
||||
if (HasComp<FracturedFormBodyComponent>(power))
|
||||
return;
|
||||
|
||||
// Don't make bodies if there is no body. This is solely for test fails.
|
||||
if (!HasComp<BodyComponent>(power))
|
||||
return;
|
||||
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(power);
|
||||
bodyComp.ControllingForm = power.Owner;
|
||||
power.Comp.Bodies.Add(power);
|
||||
var body = GenerateForm(power);
|
||||
// hide the SSD indicator.
|
||||
if (SsdQuery.TryComp(body, out var ssdComp))
|
||||
ssdComp.IsSSD = false;
|
||||
}
|
||||
|
||||
private EntityUid GenerateForm(Entity<FracturedFormPowerComponent> original)
|
||||
{
|
||||
// Form:
|
||||
// - Same appearance as original
|
||||
// - Different apperance, still humanoid
|
||||
// Equipment:
|
||||
// - Same as original body
|
||||
// - Nude and helpless
|
||||
|
||||
var xform = Transform(original);
|
||||
|
||||
var hasGear = Random.Prob(original.Comp.HasGearChance);
|
||||
|
||||
if (Random.Prob(original.Comp.DifferentSpeciesChance) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasGear ? original.Comp.CopyClothed : original.Comp.CopyNaked, out var newBody)) // Slightly lower chance to copy the original body
|
||||
{
|
||||
// Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to)
|
||||
var validSpecies = new List<ProtoId<SpeciesPrototype>>();
|
||||
var speciesPrototypes = _prototype.EnumeratePrototypes<SpeciesPrototype>();
|
||||
foreach (var proto in speciesPrototypes)
|
||||
{
|
||||
var speciesEntityPrototype = _prototype.Index<EntityPrototype>(proto.Prototype);
|
||||
// If they have the PotentialPsionicComponent, they can be psionic.
|
||||
if (proto.RoundStart && speciesEntityPrototype.TryGetComponent<PotentialPsionicComponent>(out _, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID))
|
||||
validSpecies.Add(proto.ID);
|
||||
}
|
||||
var species = Random.Pick(validSpecies);
|
||||
var character = HumanoidCharacterProfile.RandomWithSpecies(species);
|
||||
newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasGear ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner));
|
||||
if (newBody is not { } bodyV || Deleted(bodyV))
|
||||
{
|
||||
Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug.");
|
||||
return EntityUid.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (newBody is not { } body || Deleted(body))
|
||||
return default!;
|
||||
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(body);
|
||||
original.Comp.Bodies.Add(body);
|
||||
bodyComp.ControllingForm = original.Owner;
|
||||
|
||||
if (_player.TryGetSessionByEntity(original, out var session))
|
||||
_pvsOverride.AddSessionOverride(body, session);
|
||||
|
||||
Dirty(original);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
private bool TryGetValidBody(Entity<FracturedFormPowerComponent> psionic, out EntityUid validBody)
|
||||
{
|
||||
foreach (var body in psionic.Comp.Bodies)
|
||||
{
|
||||
if (!IsValidBody(psionic, body))
|
||||
continue;
|
||||
|
||||
validBody = body;
|
||||
return true;
|
||||
}
|
||||
validBody = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Swap(Entity<FracturedFormPowerComponent> psionic)
|
||||
{
|
||||
if (!TryGetValidBody(psionic, out var targetBody))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(psionic.Comp.SwapSound, psionic);
|
||||
// Transfer mind if present
|
||||
if (MindContainerQuery.TryComp(psionic, out var mindContainer) && mindContainer.Mind.HasValue)
|
||||
_mind.TransferTo(mindContainer.Mind.Value, targetBody);
|
||||
// Wake up the new body
|
||||
Sleeping.TryWaking(targetBody);
|
||||
// Remove the action.
|
||||
Action.RemoveAction(psionic.Comp.ActionEntity);
|
||||
// Create new component on target and copy data
|
||||
var duplicate = EnsureComp<FracturedFormPowerComponent>(targetBody);
|
||||
duplicate.Bodies = psionic.Comp.Bodies;
|
||||
// Update all body references
|
||||
foreach (var body in duplicate.Bodies)
|
||||
{
|
||||
if (_bodyQuery.TryComp(body, out var bodyComp))
|
||||
bodyComp.ControllingForm = targetBody;
|
||||
}
|
||||
|
||||
if (_player.TryGetSessionByEntity(targetBody, out var session))
|
||||
{
|
||||
_pvsOverride.AddSessionOverride(psionic, session);
|
||||
_pvsOverride.RemoveSessionOverride(targetBody, session);
|
||||
}
|
||||
|
||||
RemCompDeferred<FracturedFormPowerComponent>(psionic);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
List<Entity<FracturedFormPowerComponent>> swapTargets = [];
|
||||
|
||||
var entities = EntityQueryEnumerator<FracturedFormPowerComponent, MobStateComponent>();
|
||||
while (entities.MoveNext(out var uid, out var comp, out var mobState))
|
||||
{
|
||||
// Check sleep warning
|
||||
if (!comp.SleepWarned && Timing.CurTime > comp.NextSwap - comp.WarningTimeBeforeSleep)
|
||||
{
|
||||
comp.SleepWarned = true;
|
||||
Popup.PopupEntity(Loc.GetString("psionic-power-fractured-form-sleepy"), uid, uid, PopupType.LargeCaution);
|
||||
Chat.TryEmoteWithChat(uid, "Yawn");
|
||||
}
|
||||
// Swap check
|
||||
if ((_sleepingQuery.HasComp(uid) || MobState.IsIncapacitated(uid, mobState)) && Timing.CurTime > comp.NextVoluntarySwap
|
||||
|| Timing.CurTime > comp.NextSwap)
|
||||
swapTargets.Add((uid, comp));
|
||||
}
|
||||
|
||||
foreach (var target in swapTargets)
|
||||
{
|
||||
Swap(target);
|
||||
}
|
||||
|
||||
// Process bodies
|
||||
var bodies = EntityQueryEnumerator<FracturedFormBodyComponent, MobStateComponent>();
|
||||
while (bodies.MoveNext(out var uid, out var comp, out var mobState))
|
||||
{
|
||||
// Put to sleep if no sleeping component and no mind
|
||||
if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue && !FracturedQuery.HasComp(uid))
|
||||
Sleeping.TrySleeping((uid, mobState));
|
||||
// Cleanup invalid bodies
|
||||
if (!comp.ControllingForm.IsValid()
|
||||
|| Deleted(comp.ControllingForm)
|
||||
|| !FracturedQuery.HasComp(comp.ControllingForm))
|
||||
{
|
||||
RemCompDeferred<FracturedFormBodyComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons.Borgs;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's ID chip slot is active
|
||||
/// </summary>
|
||||
public sealed partial class BorgIdChipWireAction : ComponentWireAction<IdChipSlotComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-borg-id-chip";
|
||||
public override Color Color { get; set; } = Color.Thistle;
|
||||
public override object StatusKey => BorgWireActionKey.ChipKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
EntityManager.System<IdChipSlotSystem>()
|
||||
.SetActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
EntityManager.System<IdChipSlotSystem>()
|
||||
.SetActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Silicons.Borgs;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's transponder is active
|
||||
/// </summary>
|
||||
public sealed partial class BorgTransponderWireAction : ComponentWireAction<BorgTransponderComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-borg-transponder";
|
||||
public override Color Color { get; set; } = Color.YellowGreen;
|
||||
public override object StatusKey => BorgWireActionKey.TransponderKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
EntityManager.System<BorgSystem>()
|
||||
.SetTransponderActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
EntityManager.System<BorgSystem>()
|
||||
.SetTransponderActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server._DV.Silicons.Laws;
|
||||
public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedSiliconLawSystem _siliconLaws = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -24,12 +25,12 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
|
||||
private void OnGetSiliconLaws(Entity<SlavedBorgComponent> ent, ref GetSiliconLawsEvent args)
|
||||
{
|
||||
if (ent.Comp.Added || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
||||
if (ent.Comp.HasBeenAdded || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
||||
return;
|
||||
|
||||
if (provider.Lawset is {} lawset)
|
||||
if (provider.Lawset is {} lawset && ent.Comp.ShouldBeAdded)
|
||||
AddLaw(lawset, ent.Comp.Law);
|
||||
ent.Comp.Added = true; // prevent opening the ui adding more law 0's
|
||||
ent.Comp.HasBeenAdded = true; // prevent opening the ui adding more law 0's
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,4 +40,30 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
{
|
||||
lawset.Laws.Insert(0, _proto.Index(law).ShallowClone());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the slaving is active.
|
||||
/// </summary>
|
||||
public void SetShouldBeAdded(Entity<SlavedBorgComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
if (!TryComp<SiliconLawProviderComponent>(ent, out var provider) || provider.Lawset is not { } lawset)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.ShouldBeAdded && active)
|
||||
{
|
||||
AddLaw(lawset, ent.Comp.Law);
|
||||
ent.Comp.HasBeenAdded = true;
|
||||
_siliconLaws.NotifyLawsChanged(ent);
|
||||
}
|
||||
else if (ent.Comp.ShouldBeAdded && !active)
|
||||
{
|
||||
lawset.Laws.Remove(_proto.Index(ent.Comp.Law));
|
||||
ent.Comp.HasBeenAdded = false;
|
||||
_siliconLaws.NotifyLawsChanged(ent);
|
||||
}
|
||||
ent.Comp.ShouldBeAdded = active;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the power cell slot is active
|
||||
/// </summary>
|
||||
public sealed partial class PowerCellSlotWireAction : ComponentWireAction<PowerCellSlotComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-power-cell";
|
||||
public override Color Color { get; set; } = Color.BurlyWood;
|
||||
public override object StatusKey => BorgWireActionKey.CellKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
EntityManager.System<PowerCellSystem>()
|
||||
.SetSlotActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
EntityManager.System<PowerCellSystem>()
|
||||
.SetSlotActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Server._DV.Silicons.Laws;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared._DV.Silicons.Laws;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's transponder is active
|
||||
/// </summary>
|
||||
public sealed partial class SlavedBorgWireAction : ComponentWireAction<SlavedBorgComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-slaved-borg";
|
||||
public override Color Color { get; set; } = Color.Coral;
|
||||
public override object StatusKey => BorgWireActionKey.SlavedKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
return component.ShouldBeAdded ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
EntityManager.System<SlavedBorgSystem>()
|
||||
.SetShouldBeAdded((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
EntityManager.System<SlavedBorgSystem>()
|
||||
.SetShouldBeAdded((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
[RegisterComponent, Access(typeof(UprisingRuleSystem))]
|
||||
public sealed partial class UprisingConsoleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Content.Shared._DV.Roles;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
[RegisterComponent, Access(typeof(UprisingRuleSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class UprisingRuleComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? NukeAnnouncementAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? NukeTimeAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? FirstWarningAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? ImpendingWarningAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? FinalWarningAt;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan NukeAnnouncementDelay = TimeSpan.FromMinutes(30);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan NukeTimeDelay = TimeSpan.FromMinutes(90);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan FirstWarning = TimeSpan.FromMinutes(30);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan ImpendingWarning = TimeSpan.FromMinutes(15);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan FinalWarning = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public float NukeDuration = 60f;
|
||||
|
||||
[DataField]
|
||||
public (UprisingSide, TimeSpan)? SideWinsAt;
|
||||
|
||||
[DataField]
|
||||
public UprisingSide? SideWon;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<UprisingSide, TimeSpan> SideTimes = new()
|
||||
{
|
||||
{ UprisingSide.Insurgent , TimeSpan.FromMinutes(3) },
|
||||
{ UprisingSide.Loyalist , TimeSpan.FromMinutes(3) },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Nuke;
|
||||
using Content.Server.Pinpointer;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared._DV.Roles;
|
||||
using Content.Shared._DV.Uprising;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
public sealed class UprisingRuleSystem : GameRuleSystem<UprisingRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedChatSystem _chat = default!;
|
||||
[Dependency] private readonly NukeSystem _nuke = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly NavMapSystem _navMap = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LoyalistRoleComponent, GetBriefingEvent>(OnGetLoyalistBriefing);
|
||||
SubscribeLocalEvent<InsurgentRoleComponent, GetBriefingEvent>(OnGetInsurgentBriefing);
|
||||
|
||||
SubscribeLocalEvent<UprisingVictoryObjectiveComponent, ObjectiveGetProgressEvent>(OnUprisingVictoryProgress);
|
||||
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, GetVerbsEvent<AlternativeVerb>>(OnGetConsoleVerbs);
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, UprisingArmDoAfter>(OnArm);
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, UprisingDisarmDoAfter>(OnDisarm);
|
||||
}
|
||||
|
||||
private void OnUprisingVictoryProgress(Entity<UprisingVictoryObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
if (rule.Comp1.SideWinsAt is var (side, _) && ent.Comp.Side == side)
|
||||
args.Progress = 0.5f;
|
||||
else if (rule.Comp1.SideWon == ent.Comp.Side)
|
||||
args.Progress = 1f;
|
||||
else
|
||||
args.Progress = 0f;
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.NukeAnnouncementAt = Timing.CurTime + component.NukeAnnouncementDelay;
|
||||
component.NukeTimeAt = Timing.CurTime + component.NukeTimeDelay;
|
||||
component.FirstWarningAt = component.NukeTimeAt - component.FirstWarning;
|
||||
component.ImpendingWarningAt = component.NukeTimeAt - component.ImpendingWarning;
|
||||
component.FinalWarningAt = component.NukeTimeAt - component.FinalWarning;
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (component.NukeAnnouncementAt is { } nukeAnnouncement && nukeAnnouncement <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.NukeTimeDelay;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-nuke-announcement", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.NukeAnnouncementAt = null;
|
||||
}
|
||||
|
||||
if (component.FirstWarningAt is { } firstWarning && firstWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FirstWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-first-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.FirstWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.ImpendingWarningAt is { } impendingWarning && impendingWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.ImpendingWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-impending-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.ImpendingWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.FinalWarningAt is { } finalWarning && finalWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FinalWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-final-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.FinalWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.NukeTimeAt is { } nukeTime && nukeTime <= Timing.CurTime)
|
||||
{
|
||||
component.NukeTimeAt = null;
|
||||
var query = EntityQueryEnumerator<NukeComponent>();
|
||||
while (query.MoveNext(out var bombUid, out var bomb))
|
||||
{
|
||||
_nuke.SetRemainingTime(bombUid, component.NukeDuration, bomb);
|
||||
_itemSlots.SetLock(bombUid, bomb.DiskSlot, false);
|
||||
_itemSlots.TryEject(bombUid, bomb.DiskSlot, null, out _, true);
|
||||
_itemSlots.SetLock(bombUid, bomb.DiskSlot, true);
|
||||
_nuke.ArmBomb(bombUid, bomb, "uprising-announcement-nuke-armed");
|
||||
}
|
||||
}
|
||||
|
||||
if (component.SideWinsAt is var (winningSide, winningTime) && winningTime <= Timing.CurTime)
|
||||
{
|
||||
component.SideWinsAt = null;
|
||||
var query = EntityQueryEnumerator<NukeComponent>();
|
||||
while (query.MoveNext(out var bombUid, out var bomb))
|
||||
{
|
||||
_nuke.DisarmBomb(bombUid, bomb);
|
||||
}
|
||||
|
||||
component.SideWon = winningSide;
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}"),
|
||||
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.color")));
|
||||
|
||||
GameTicker.EndRound();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
UprisingRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
if (component.SideWinsAt is var (partialSide, _))
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-outcome-{partialSide}-partial"));
|
||||
}
|
||||
else if (component.SideWon is { } winningSide)
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-outcome-{winningSide}-full"));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.AddLine(Loc.GetString("uprising-outcome-none"));
|
||||
}
|
||||
|
||||
args.AddLine(Loc.GetString("uprising-leads"));
|
||||
|
||||
var antags = _antag.GetAntagIdentifiers(uid);
|
||||
var leads = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var (mind, sessionData, name) in antags)
|
||||
{
|
||||
leads.Add(mind);
|
||||
|
||||
if (_role.MindHasRole<UprisingRoleComponent>(mind, out var it))
|
||||
args.AddLine(Loc.GetString($"uprising-leads-user.{it.Value.Comp2.Side}", ("name", name), ("user", sessionData.UserName)));
|
||||
else
|
||||
args.AddLine(Loc.GetString("uprising-leads-user.Unknown", ("name", name), ("user", sessionData.UserName)));
|
||||
}
|
||||
args.AddLine(string.Empty);
|
||||
|
||||
var lines = new Dictionary<UprisingSide, List<string>>();
|
||||
foreach (var value in Enum.GetValues<UprisingSide>())
|
||||
{
|
||||
lines[value] = new();
|
||||
}
|
||||
|
||||
var minds = EntityQueryEnumerator<MindComponent>();
|
||||
while (minds.MoveNext(out var mindUid, out var mindComponent))
|
||||
{
|
||||
if (leads.Contains(mindUid))
|
||||
continue;
|
||||
|
||||
if (!_role.MindHasRole<UprisingRoleComponent>((mindUid, mindComponent), out var role) || mindComponent.OriginalOwnerUserId is not { } owner)
|
||||
continue;
|
||||
|
||||
if (!_player.TryGetPlayerData(owner, out var data))
|
||||
continue;
|
||||
|
||||
lines[role.Value.Comp2.Side].Add(Loc.GetString($"uprising-members-user", ("name", mindComponent.CharacterName ?? string.Empty), ("user", data.UserName)));
|
||||
}
|
||||
|
||||
foreach (var value in Enum.GetValues<UprisingSide>())
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-members.{value}"));
|
||||
foreach (var line in lines[value])
|
||||
{
|
||||
args.AddLine(line);
|
||||
}
|
||||
args.AddLine(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetLoyalistBriefing(Entity<LoyalistRoleComponent> ent, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("antag-Loyalist.briefing"));
|
||||
}
|
||||
|
||||
private void OnGetInsurgentBriefing(Entity<InsurgentRoleComponent> ent, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("antag-Insurgent.briefing"));
|
||||
}
|
||||
|
||||
private Entity<UprisingRuleComponent, GameRuleComponent>? GetRule()
|
||||
{
|
||||
var enumerator = QueryActiveRules();
|
||||
while (enumerator.MoveNext(out var uid, out _, out var uprising, out var gamerule))
|
||||
{
|
||||
return (uid, uprising, gamerule);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void StartWinning(UprisingSide side, EntityUid console)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
var location = _navMap.GetNearestBeaconString(console);
|
||||
var announcement = Loc.GetString($"uprising-announcement-start-victory-announcement-{side}",
|
||||
("time", (int) Math.Round(rule.Comp1.SideTimes[side].TotalSeconds)),
|
||||
("location", FormattedMessage.RemoveMarkupOrThrow(location)));
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
announcement,
|
||||
Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.color")));
|
||||
|
||||
rule.Comp1.SideWinsAt = (side, Timing.CurTime + rule.Comp1.SideTimes[side]);
|
||||
}
|
||||
|
||||
private void StopWinning()
|
||||
{
|
||||
if (GetRule() is not { } rule || rule.Comp1.SideWinsAt is not var (side, time))
|
||||
return;
|
||||
|
||||
rule.Comp1.SideTimes[side] = Timing.CurTime - time;
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}"),
|
||||
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.color")));
|
||||
|
||||
rule.Comp1.SideWinsAt = null;
|
||||
}
|
||||
|
||||
private void OnGetConsoleVerbs(Entity<UprisingConsoleComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
if (rule.Comp1.SideWinsAt is var (disarmSide, _))
|
||||
{
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Act = () => StartDisarming(ent, user),
|
||||
Text = Loc.GetString($"uprising-disarm-verb.{disarmSide}"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckCanArm(args.User, out var armSide))
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new()
|
||||
{
|
||||
Act = () => StartArming(ent, user),
|
||||
Text = Loc.GetString($"uprising-arm-verb.{armSide}"),
|
||||
});
|
||||
}
|
||||
|
||||
private bool CheckCanArm(EntityUid user, [NotNullWhen(true)] out UprisingSide? side)
|
||||
{
|
||||
side = null;
|
||||
if (_mind.GetMind(user) is not { } mind || !TryComp<MindComponent>(mind, out var mindComp))
|
||||
return false;
|
||||
|
||||
if (!_role.MindHasRole<UprisingRoleComponent>(mind, out var uprisingRole))
|
||||
return false;
|
||||
|
||||
foreach (var objective in mindComp.Objectives)
|
||||
{
|
||||
if (!HasComp<UprisingArmRequiredObjectiveComponent>(objective))
|
||||
continue;
|
||||
|
||||
if (_objectives.GetProgress(objective, (mind, mindComp)) is not >= 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
side = uprisingRole.Value.Comp2.Side;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void StartDisarming(Entity<UprisingConsoleComponent> ent, EntityUid user)
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
ent.Comp.Delay,
|
||||
new UprisingDisarmDoAfter(),
|
||||
ent.Owner));
|
||||
}
|
||||
|
||||
private void StartArming(Entity<UprisingConsoleComponent> ent, EntityUid user)
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
ent.Comp.Delay,
|
||||
new UprisingArmDoAfter(),
|
||||
ent.Owner));
|
||||
}
|
||||
|
||||
private void OnArm(Entity<UprisingConsoleComponent> ent, ref UprisingArmDoAfter args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || !CheckCanArm(args.User, out var side))
|
||||
return;
|
||||
|
||||
StartWinning(side.Value, ent);
|
||||
}
|
||||
|
||||
private void OnDisarm(Entity<UprisingConsoleComponent> ent, ref UprisingDisarmDoAfter args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
StopWinning();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Server.Antag;
|
||||
using Content.Shared._DV.Movement.Components;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared.CharacterInfo;
|
||||
using Content.Shared.Cloning.Events;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Zombies;
|
||||
|
||||
public sealed partial class ZombieSystem
|
||||
{
|
||||
//private static readonly string MindRoleInitialInfected = "MindRoleInitialInfected";
|
||||
private static readonly EntProtoId InitialInfectedFailureSurviveObjective = "InitialInfectedFailureSurviveObjective";
|
||||
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
|
||||
[Dependency] private readonly SharedJetpackSystem _jetpack = default!; // DeltaV - Prevent Jetpacks on Zombies
|
||||
|
||||
private void InitializeDV()
|
||||
{
|
||||
// This needs to be done before CloneEvent or else the proper attributes won't be restored or modified on
|
||||
// the new entity.
|
||||
SubscribeLocalEvent<ZombieComponent, CloningAttemptEvent>(OnBeforeUnzombify);
|
||||
SubscribeLocalEvent<InitialInfectedComponent, CloningEvent>(OnInitialInfectedCloning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Extra things we want to ensure, remove, or take care of when someone is zombified.
|
||||
/// </summary>
|
||||
private void ZombifyEntityDV(Entity<ZombieComponent> ent, MobStateComponent? mobState = null)
|
||||
{
|
||||
// Remove innate radio
|
||||
RemComp<ActiveRadioComponent>(ent); // If the zombie has an innate radio, get rid of it.
|
||||
|
||||
// Remove headsets in pockets
|
||||
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
|
||||
{
|
||||
if (_inventory.TryGetSlotEntity(ent, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
|
||||
_inventory.TryUnequip(ent, $"pocket{i}", true, true);
|
||||
}
|
||||
|
||||
// Prevent Psionic Zombies
|
||||
RemComp<PotentialPsionicComponent>(ent);
|
||||
_psionic.MindBreakEntity(ent.Owner, false, true);
|
||||
|
||||
// Prevent Jetpacks on Zombies
|
||||
if (TryComp<JetpackUserComponent>(ent, out var jetpackUser))
|
||||
{
|
||||
if (TryComp<JetpackComponent>(jetpackUser.Jetpack, out var jetpack))
|
||||
_jetpack.SetEnabled(jetpackUser.Jetpack, jetpack, false, ent);
|
||||
}
|
||||
RemComp<AutomaticJetpackUserComponent>(ent);
|
||||
|
||||
// Prevent shitters biting other zombies
|
||||
EnsureComp<NoFriendlyFireComponent>(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - We need to save some extra things to the ZombieComponent so we can restore them when
|
||||
/// they are about to be cloned.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
private void PreserveEntityComponentState(Entity<ZombieComponent> ent)
|
||||
{
|
||||
SaveFactionsBeforeZombification(ent);
|
||||
|
||||
// Zombification removes pacifism but lets track if they have it so the pacifist players will be... pacified.
|
||||
ent.Comp.WasPacisfistBeforeZombification = HasComp<PacifiedComponent>(ent);
|
||||
}
|
||||
|
||||
private void SaveFactionsBeforeZombification(Entity<ZombieComponent> ent)
|
||||
{
|
||||
if (!TryComp<NpcFactionMemberComponent>(ent, out var faction))
|
||||
return;
|
||||
|
||||
ent.Comp.FactionsBeforeZombification.AddRange(faction.Factions);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Restores certain components to their previous state that the upstream unzombify doesn't handle.
|
||||
/// We need to do this before CloningEvent so the cloned body will have the updated components BEFORE cloning happens.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnBeforeUnzombify(Entity<ZombieComponent> ent, ref CloningAttemptEvent args)
|
||||
{
|
||||
// Restore factions
|
||||
_faction.ClearFactions(ent.Owner); // Should only be zombie, but might as well clear the whole list in case.
|
||||
ent.Comp.FactionsBeforeZombification.ForEach(previousFaction => _faction.AddFaction(ent.Owner, previousFaction));
|
||||
|
||||
// Restore pacifism
|
||||
if (ent.Comp.WasPacisfistBeforeZombification)
|
||||
EnsureComp<PacifiedComponent>(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Adds a survival objective to Initial Infected who are cloned. Chances are that the crew has recovered, so
|
||||
/// bioterrorism time is probably over.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnInitialInfectedCloning(Entity<InitialInfectedComponent> ent, ref CloningEvent args)
|
||||
{
|
||||
// Change II objectives so they no longer have infect objectives. Now, they'll have survival objectives. Live to infect another day. :)
|
||||
_mind.TryGetMind(ent.Owner, out var mindId, out var mindContainer);
|
||||
|
||||
// If we can't find the mind, oh well. We tried.
|
||||
if (mindContainer is not { } mind)
|
||||
return;
|
||||
|
||||
if (_role.MindHasRole<InitialInfectedRoleComponent>(mindId))
|
||||
_mind.TryAddObjective(mindId, mind, InitialInfectedFailureSurviveObjective);
|
||||
}
|
||||
}
|
||||
@@ -33,37 +33,33 @@ public sealed class InteractorSystem : SharedInteractorSystem
|
||||
return;
|
||||
}
|
||||
|
||||
// Mono
|
||||
for (var i = count - 1; i >= 0; i--)
|
||||
var i = count - 1;
|
||||
var netEnt = ent.Comp.TargetEntities[i].Item1;
|
||||
var target = GetEntity(netEnt);
|
||||
_constructionQuery.TryComp(target, out var construction);
|
||||
var originalCount = construction?.InteractionQueue?.Count ?? 0;
|
||||
if (!InteractWith(ent, target))
|
||||
{
|
||||
var netEnt = ent.Comp.TargetEntities[i].Item1;
|
||||
var target = GetEntity(netEnt);
|
||||
_constructionQuery.TryComp(target, out var construction);
|
||||
var originalCount = construction?.InteractionQueue?.Count ?? 0;
|
||||
if (!InteractWith(ent, target))
|
||||
{
|
||||
// have to remove it since user's filter was bad due to unhandled interaction
|
||||
// RemoveTarget(ent, target); // Mono
|
||||
Machine.Failed(ent.Owner);
|
||||
continue; // Mono
|
||||
}
|
||||
// have to remove it since user's filter was bad due to unhandled interaction
|
||||
RemoveTarget(ent, target);
|
||||
Machine.Failed(ent.Owner);
|
||||
return;
|
||||
}
|
||||
|
||||
// construction supercode queues it instead of starting a doafter now, assume that queuing means it has started
|
||||
var newCount = construction?.InteractionQueue?.Count ?? 0;
|
||||
if (newCount > originalCount
|
||||
|| HasDoAfter(ent))
|
||||
{
|
||||
Machine.Started(ent.Owner);
|
||||
UpdateAppearance(ent, InteractorState.Active);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no doafter, complete it immediately
|
||||
TryRemoveTarget(ent, target);
|
||||
Machine.Completed(ent.Owner);
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
break; // Mono
|
||||
// construction supercode queues it instead of starting a doafter now, assume that queuing means it has started
|
||||
var newCount = construction?.InteractionQueue?.Count ?? 0;
|
||||
if (newCount > originalCount
|
||||
|| HasDoAfter(ent))
|
||||
{
|
||||
Machine.Started(ent.Owner);
|
||||
UpdateAppearance(ent, InteractorState.Active);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no doafter, complete it immediately
|
||||
TryRemoveTarget(ent, target);
|
||||
Machine.Completed(ent.Owner);
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Goobstation.Lathe;
|
||||
|
||||
/// <summary>
|
||||
/// Lets a lathe produce the last made recipe, controlled by signal port.
|
||||
/// The port must be added by something else e.g. AutomationSlots
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(LatheAutomationSystem))]
|
||||
public sealed partial class LatheAutomationComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public LatheRecipePrototype? LastRecipe;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> PrintPort = "LathePrint";
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Shared._Goobstation.Factory;
|
||||
using Content.Server.Lathe;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.Lathe;
|
||||
|
||||
namespace Content.Server._Goobstation.Lathe;
|
||||
|
||||
public sealed class LatheAutomationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AutomationSystem _automation = default!;
|
||||
[Dependency] private readonly LatheSystem _lathe = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LatheAutomationComponent, LatheStartPrintingEvent>(OnStartPrinting);
|
||||
SubscribeLocalEvent<LatheAutomationComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
}
|
||||
|
||||
private void OnStartPrinting(Entity<LatheAutomationComponent> ent, ref LatheStartPrintingEvent args)
|
||||
{
|
||||
ent.Comp.LastRecipe = args.Recipe;
|
||||
}
|
||||
|
||||
private void OnSignalReceived(Entity<LatheAutomationComponent> ent, ref SignalReceivedEvent args)
|
||||
{
|
||||
if (!_automation.IsAutomated(ent))
|
||||
return;
|
||||
|
||||
if (args.Port != ent.Comp.PrintPort)
|
||||
return;
|
||||
|
||||
if (ent.Comp.LastRecipe is not {} recipe)
|
||||
return;
|
||||
|
||||
_lathe.TryAddToQueue(ent.Owner, recipe, 1);
|
||||
_lathe.TryStartProducing(ent.Owner); // Won't do anything otherwise
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,11 @@ public sealed partial class AnomalyComponent : Component
|
||||
|
||||
[DataField]
|
||||
public bool DeleteEntity = true;
|
||||
|
||||
// DeltaV - Colossus additions START
|
||||
[DataField]
|
||||
public bool AlwaysGrow;
|
||||
// DeltaV - Colossus additions END
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -308,7 +313,7 @@ public readonly record struct AnomalySupercriticalEvent(EntityUid Anomaly, float
|
||||
/// <param name="Anomaly">The anomaly being shut down.</param>
|
||||
/// <param name="Supercritical">Whether or not the anomaly shut down passively or via a supercritical event.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical);
|
||||
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical, bool Forced); // DeltaV - Add Forced
|
||||
|
||||
/// <summary>
|
||||
/// Event broadcast when an anomaly's severity is changed.
|
||||
|
||||
@@ -84,7 +84,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
Log.Info($"Performing anomaly pulse. Entity: {ToPrettyString(uid)}");
|
||||
|
||||
// if we are above the growth threshold, then grow before the pulse
|
||||
if (component.Stability > component.GrowthThreshold)
|
||||
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
|
||||
{
|
||||
ChangeAnomalySeverity(uid, GetSeverityIncreaseFromGrowth(component), component);
|
||||
}
|
||||
@@ -192,7 +192,8 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
/// <param name="supercritical">Whether or not the anomaly ended via supercritical event</param>
|
||||
/// <param name="spawnCore">Create anomaly cores based on the result of completing an anomaly?</param>
|
||||
/// <param name="logged">Whether or not the anomaly decaying/going supercritical is logged</param>
|
||||
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true, bool logged = false)
|
||||
/// <param name="forced">Whether or not the anomaly shutdown was caused by component shutdown</param> // DeltaV - Add forced
|
||||
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true, bool logged = false, bool forced = false) // DeltaV - Add forced
|
||||
{
|
||||
if (logged)
|
||||
{
|
||||
@@ -207,7 +208,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var ev = new AnomalyShutdownEvent(uid, supercritical);
|
||||
var ev = new AnomalyShutdownEvent(uid, supercritical, forced); // DeltaV - Add forced
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
if (Terminating(uid) || _net.IsClient)
|
||||
@@ -345,7 +346,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
|
||||
// if the stability is under the death threshold,
|
||||
// update it every second to start killing it slowly.
|
||||
if (anomaly.Stability < anomaly.DecayThreshold)
|
||||
if (!anomaly.AlwaysGrow && anomaly.Stability < anomaly.DecayThreshold) // DeltaV - Add AlwaysGrow
|
||||
{
|
||||
ChangeAnomalyHealth(ent, anomaly.HealthChangePerSecond * frameTime, anomaly);
|
||||
}
|
||||
@@ -479,6 +480,14 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
// DeltaV - Colossus Additions START
|
||||
if (ent.Comp.AlwaysGrow)
|
||||
{
|
||||
visual = AnomalyStabilityVisuals.Growing;
|
||||
return true;
|
||||
}
|
||||
// DeltaV - Colossus Additions END
|
||||
|
||||
visual = AnomalyStabilityVisuals.Stable;
|
||||
if (ent.Comp.Stability <= ent.Comp.DecayThreshold)
|
||||
{
|
||||
|
||||
@@ -135,4 +135,10 @@ public sealed partial class CCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> AdminOverlayStackMax =
|
||||
CVarDef.Create("ui.admin_overlay_stack_max", 3, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// If true, ghosts will see an "(F)" button next to chat messages, which can be used to follow the sender.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> InterfaceChatFollowButton =
|
||||
CVarDef.Create("ui.chat_follow_button", true, CVar.CLIENT | CVar.REPLICATED | CVar.ARCHIVE);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,20 @@ namespace Content.Shared.Chat
|
||||
AudioPath = audioPath;
|
||||
AudioVolume = audioVolume;
|
||||
}
|
||||
|
||||
public ChatMessage(ChatMessage copyFrom)
|
||||
{
|
||||
Channel = copyFrom.Channel;
|
||||
Message = copyFrom.Message;
|
||||
WrappedMessage = copyFrom.WrappedMessage;
|
||||
SenderEntity = copyFrom.SenderEntity;
|
||||
SenderKey = copyFrom.SenderKey;
|
||||
HideChat = copyFrom.HideChat;
|
||||
MessageColorOverride = copyFrom.MessageColorOverride;
|
||||
AudioPath = copyFrom.AudioPath;
|
||||
AudioVolume = copyFrom.AudioVolume;
|
||||
Read = copyFrom.Read;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Content.Shared._DV.Chat; // DeltaV - chat
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -98,7 +99,7 @@ public abstract partial class SharedChatSystem
|
||||
{
|
||||
// not all emotes are loc'd, but for the ones that are we pass in entity
|
||||
var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source));
|
||||
SendEntityEmote(source, action, range, nameOverride, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker);
|
||||
SendEntityEmote(source, action, range, nameOverride, null, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); // DeltaV
|
||||
}
|
||||
|
||||
return didEmote;
|
||||
@@ -208,6 +209,48 @@ public abstract partial class SharedChatSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
/// <summary>
|
||||
/// Checks which type of emote the message contains and returns the type while outputting the string without the prefixes.
|
||||
/// </summary>
|
||||
public static EmoteType? ProcessEmoteMessage(EntityUid source, string input, out string output)
|
||||
{
|
||||
output = input.Trim();
|
||||
EmoteType? type = null;
|
||||
|
||||
if (input.Length == 0)
|
||||
return type;
|
||||
|
||||
if (!(input.StartsWith(AudibleEmotePrefix) || input.StartsWith(PossessiveEmotePrefix)))
|
||||
{
|
||||
type = EmoteType.Normal;
|
||||
return type;
|
||||
}
|
||||
|
||||
var emoteType = input[0];
|
||||
output = input[1..].TrimStart();
|
||||
|
||||
if (emoteType == AudibleEmotePrefix)
|
||||
{
|
||||
emoteType = input[1]; // Check for if we want AudiblePossessiveEmote
|
||||
type = EmoteType.Audible;
|
||||
}
|
||||
|
||||
if (emoteType == PossessiveEmotePrefix)
|
||||
{
|
||||
if (type == EmoteType.Audible)
|
||||
{
|
||||
type = EmoteType.AudiblePossessive;
|
||||
output = input[2..].TrimStart();
|
||||
}
|
||||
else
|
||||
type = EmoteType.Possessive;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
/// <summary>
|
||||
/// Creates and raises <see cref="BeforeEmoteEvent"/> and then <see cref="EmoteEvent"/> to let other systems do things like play audio.
|
||||
/// In the case that the Before event is cancelled, EmoteEvent will NOT be raised, and will optionally show a message to the player
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared._DV.Chat;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Popups;
|
||||
@@ -29,6 +30,8 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
public const char OOCPrefix = '[';
|
||||
public const char EmotesPrefix = '@';
|
||||
public const char EmotesAltPrefix = '*';
|
||||
public const char AudibleEmotePrefix = '!'; // DeltaV - You may now scream audibly!
|
||||
public const char PossessiveEmotePrefix = '\''; // DeltaV - You may now be possessive of things! Whatever that means.
|
||||
public const char AdminPrefix = ']';
|
||||
public const char WhisperPrefix = ',';
|
||||
public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix.
|
||||
@@ -152,6 +155,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
string input,
|
||||
out string output,
|
||||
out RadioChannelPrototype? channel,
|
||||
bool capitalize = true, // DeltaV - We might not want to capitalize the first letter if we send in emotes.
|
||||
bool quiet = false)
|
||||
{
|
||||
output = input.Trim();
|
||||
@@ -162,7 +166,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
if (input.StartsWith(RadioCommonPrefix))
|
||||
{
|
||||
output = SanitizeMessageCapital(input[1..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[1..].TrimStart()) : input[1..].TrimStart(); // DeltaV
|
||||
channel = _prototypeManager.Index<RadioChannelPrototype>(CommonChannel);
|
||||
return true;
|
||||
}
|
||||
@@ -172,7 +176,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
if (input.Length < 2 || char.IsWhiteSpace(input[1]))
|
||||
{
|
||||
output = SanitizeMessageCapital(input[1..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[1..].TrimStart()) : input[1..].TrimStart(); // DeltaV
|
||||
if (!quiet)
|
||||
_popup.PopupEntity(Loc.GetString("chat-manager-no-radio-key"), source, source);
|
||||
return true;
|
||||
@@ -180,7 +184,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
var channelKey = input[1];
|
||||
channelKey = char.ToLower(channelKey);
|
||||
output = SanitizeMessageCapital(input[2..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[2..].TrimStart()) : input[2..].TrimStart(); // DeltaV
|
||||
|
||||
if (channelKey == DefaultChannelKey)
|
||||
{
|
||||
@@ -314,11 +318,29 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
return rawmsg.Substring(tagStart, tagEnd - tagStart);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
protected virtual void SendAudibleEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
RadioChannelPrototype? channel,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
NetUserId? author = null
|
||||
)
|
||||
{
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
protected virtual void SendEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
|
||||
@@ -17,6 +17,17 @@ namespace Content.Shared.Humanoid
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// DeltaV - i hate this hack but https://github.com/space-wizards/RobustToolbox/issues/6576
|
||||
private ISawmill _locSawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_locSawmill = Logger.GetSawmill("loc");
|
||||
}
|
||||
// End DeltaV - i hate this hack
|
||||
|
||||
public string GetName(string species, Gender? gender = null)
|
||||
{
|
||||
// if they have an old species or whatever just fall back to human I guess?
|
||||
@@ -47,31 +58,67 @@ namespace Content.Shared.Humanoid
|
||||
return Loc.GetString("namepreset-lastfirst",
|
||||
("last", GetLastName(speciesProto)), ("first", GetFirstName(speciesProto, gender)));
|
||||
case SpeciesNaming.FirstLast:
|
||||
// Begin DeltaV - more complex naming
|
||||
default:
|
||||
{
|
||||
var firstId = GetFirstNameId(speciesProto, gender);
|
||||
var lastId = GetLastNameId(speciesProto);
|
||||
var plural = false;
|
||||
|
||||
var oldLevel =_locSawmill.Level;
|
||||
_locSawmill.Level = LogLevel.Fatal; // this is a hack to avoid testfails because TryGetString still logs errors for not-found stuff anyways (wtf)
|
||||
if (Loc.TryGetString($"{lastId}.plural", out var pluralStr))
|
||||
plural = pluralStr == "true";
|
||||
|
||||
var last = Loc.GetString(lastId);
|
||||
|
||||
if (Loc.TryGetString($"{firstId}.intersperse", out var firstIntersperse, ("last", last), ("lastPlural", plural)))
|
||||
{
|
||||
_locSawmill.Level = oldLevel;
|
||||
return firstIntersperse;
|
||||
}
|
||||
|
||||
_locSawmill.Level = oldLevel;
|
||||
return Loc.GetString("namepreset-firstlast",
|
||||
("first", GetFirstName(speciesProto, gender)), ("last", GetLastName(speciesProto)));
|
||||
("first", Loc.GetString(firstId)),
|
||||
("last", last),
|
||||
("lastPlural", plural));
|
||||
}
|
||||
// End DeltaV - more complex naming
|
||||
}
|
||||
}
|
||||
|
||||
// Begin DeltaV - we want IDs
|
||||
public string GetFirstName(SpeciesPrototype speciesProto, Gender? gender = null)
|
||||
{
|
||||
return Loc.GetString(GetFirstNameId(speciesProto, gender));
|
||||
}
|
||||
|
||||
public string GetFirstNameId(SpeciesPrototype speciesProto, Gender? gender = null)
|
||||
{
|
||||
switch (gender)
|
||||
{
|
||||
case Gender.Male:
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
case Gender.Female:
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
default:
|
||||
if (_random.Prob(0.5f))
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
else
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetLastNameId(SpeciesPrototype speciesProto)
|
||||
{
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.LastNames));
|
||||
}
|
||||
|
||||
public string GetLastName(SpeciesPrototype speciesProto)
|
||||
{
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.LastNames));
|
||||
return Loc.GetString(GetLastNameId(speciesProto));
|
||||
}
|
||||
// End DeltaV - we want IDs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ public sealed class ActivateInWorldEvent : HandledEntityEventArgs, ITargetedInte
|
||||
/// </summary>
|
||||
public bool WasLogged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - set to false if this interaction shouldn't have an interaction particle
|
||||
/// </summary>
|
||||
public bool InteractionParticle = true;
|
||||
|
||||
public ActivateInWorldEvent(EntityUid user, EntityUid target, bool complex)
|
||||
{
|
||||
User = user;
|
||||
|
||||
@@ -30,6 +30,11 @@ public sealed class InteractUsingEvent : HandledEntityEventArgs
|
||||
/// </summary>
|
||||
public EntityCoordinates ClickLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - set to false if this interaction shouldn't have an interaction particle
|
||||
/// </summary>
|
||||
public bool InteractionParticle = true;
|
||||
|
||||
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
|
||||
{
|
||||
// Interact using should not have the same used and target.
|
||||
|
||||
@@ -31,6 +31,7 @@ using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wall;
|
||||
using Content.Shared._Goobstation.DoAfter; // Goobstation
|
||||
using Content.Shared.Inventory.VirtualItem; // Stellar - interaction particles
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input;
|
||||
@@ -1073,8 +1074,8 @@ namespace Content.Shared.Interaction
|
||||
var userInteractUsingEvent = new UserInteractUsingEvent(user, used, target, clickLocation);
|
||||
RaiseLocalEvent(user, userInteractUsingEvent, true);
|
||||
|
||||
DoContactInteraction(user, used, null, true, interactUsingEvent); // Stellar - interaction particles
|
||||
DoContactInteraction(user, target, used, true, interactUsingEvent); // Stellar - interaction particles
|
||||
DoContactInteraction(user, used, null, true, interactUsingEvent, interactionParticles: interactUsingEvent.InteractionParticle); // Stellar - interaction particles
|
||||
DoContactInteraction(user, target, used, true, interactUsingEvent, interactionParticles: interactUsingEvent.InteractionParticle); // Stellar - interaction particles
|
||||
// Contact interactions are currently only used for forensics, so we don't raise used -> target
|
||||
if (interactUsingEvent.Handled || userInteractUsingEvent.Handled)
|
||||
return true;
|
||||
@@ -1189,7 +1190,7 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(used, activateMsg, true);
|
||||
if (activateMsg.Handled)
|
||||
{
|
||||
DoContactInteraction(user, used, null, true); // Interaction particles
|
||||
DoContactInteraction(user, used, null, true, interactionParticles: activateMsg.InteractionParticle); // Stellar - Interaction particles
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
|
||||
@@ -1437,7 +1438,8 @@ namespace Content.Shared.Interaction
|
||||
/// <param name="predicted">Whether this interaction is predicted. <see cref="uidA"/> is assumed to be the client entity.</param>
|
||||
/// <param name="args">Optional handleable entity event to check.</param>
|
||||
/// <param name="interactionParticles">Whether to spawn interaction particles on this contact.</param>
|
||||
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, EntityUid? used, bool predicted, HandledEntityEventArgs? args = null, bool interactionParticles = true) // Stellar/ES - interaction particles
|
||||
/// <param name="interactionParticleType">The type of interaction particle to spawn for this event.</param>
|
||||
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, EntityUid? used, bool predicted, HandledEntityEventArgs? args = null, bool interactionParticles = true, StellarInteractionParticleType interactionParticleType = StellarInteractionParticleType.Use) // Stellar/ES - interaction particles
|
||||
{
|
||||
if (uidB == null || args?.Handled == false)
|
||||
return;
|
||||
@@ -1459,7 +1461,7 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(uidB.Value, ev);
|
||||
|
||||
// Begin Stellar/ES Additions - Interaction particles
|
||||
if (!interactionParticles)
|
||||
if (!interactionParticles || HasComp<VirtualItemComponent>(uidB))
|
||||
return;
|
||||
|
||||
if (_net.IsServer)
|
||||
@@ -1468,11 +1470,11 @@ namespace Content.Shared.Interaction
|
||||
? Filter.PvsExcept(uidA, entityManager: EntityManager)
|
||||
: Filter.Pvs(uidA, entityManager: EntityManager);
|
||||
|
||||
RaiseNetworkEvent(new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), false), filter);
|
||||
RaiseNetworkEvent(new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), false, interactionParticleType), filter);
|
||||
}
|
||||
else if (_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
var evt = new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), true);
|
||||
var evt = new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), true, interactionParticleType);
|
||||
RaiseLocalEvent(evt);
|
||||
}
|
||||
// End Stellar/ES Additions - Interaction particles
|
||||
|
||||
@@ -15,33 +15,24 @@ public enum SignalTimerUiKey : byte
|
||||
public sealed class SignalTimerBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public string CurrentText;
|
||||
// Begin Monolith
|
||||
// public string CurrentDelayMinutes;
|
||||
// public string CurrentDelaySeconds;
|
||||
public TimeSpan CurrentDelay;
|
||||
// End Monolith
|
||||
public string CurrentDelayMinutes;
|
||||
public string CurrentDelaySeconds;
|
||||
public bool ShowText;
|
||||
public TimeSpan TriggerTime;
|
||||
public bool TimerStarted;
|
||||
public bool HasAccess;
|
||||
|
||||
public SignalTimerBoundUserInterfaceState(string currentText,
|
||||
// Begin Monolith
|
||||
// string currentDelayMinutes,
|
||||
// string currentDelaySeconds,
|
||||
TimeSpan currentDelay,
|
||||
// End Monolith
|
||||
string currentDelayMinutes,
|
||||
string currentDelaySeconds,
|
||||
bool showText,
|
||||
TimeSpan triggerTime,
|
||||
bool timerStarted,
|
||||
bool hasAccess)
|
||||
{
|
||||
CurrentText = currentText;
|
||||
// Begin Monolith
|
||||
// CurrentDelayMinutes = currentDelayMinutes;
|
||||
// CurrentDelaySeconds = currentDelaySeconds;
|
||||
CurrentDelay = currentDelay;
|
||||
// End Monolith
|
||||
CurrentDelayMinutes = currentDelayMinutes;
|
||||
CurrentDelaySeconds = currentDelaySeconds;
|
||||
ShowText = showText;
|
||||
TriggerTime = triggerTime;
|
||||
TimerStarted = timerStarted;
|
||||
|
||||
@@ -122,6 +122,11 @@ public sealed class MindExamineSystem : EntitySystem
|
||||
else
|
||||
ent.Comp.State = MindState.None;
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
var ev = new MindStateUpdatedEvent(ent.Comp.State);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
// DeltaV END
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared._ST.Interaction; // Stellar - interaction particles
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
@@ -545,7 +546,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
|
||||
// Pulling confirmed
|
||||
|
||||
_interaction.DoContactInteraction(pullableUid, pullerUid, null, true); // Stellar - Interaction particles
|
||||
_interaction.DoContactInteraction(pullerUid, pullableUid,null, true, interactionParticleType: StellarInteractionParticleType.Pull); // Stellar - Interaction particles
|
||||
|
||||
// Use net entity so it's consistent across client and server.
|
||||
pullableComp.PullJointId = $"pull-joint-{GetNetEntity(pullableUid)}";
|
||||
|
||||
@@ -23,5 +23,12 @@ public sealed partial class PowerCellSlotComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool FitsInCharger = true;
|
||||
|
||||
// Begin DeltaV
|
||||
/// <summary>
|
||||
/// DeltaV - power cell slots
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Active = true;
|
||||
// End DeltaV
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,14 @@ public sealed partial class PowerCellSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin DeltaV
|
||||
if (!ent.Comp.Active)
|
||||
{
|
||||
battery = null;
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.CellSlotId, out var slot))
|
||||
{
|
||||
battery = null;
|
||||
@@ -153,6 +161,21 @@ public sealed partial class PowerCellSystem
|
||||
[PublicAPI]
|
||||
public bool HasCharge(Entity<PowerCellSlotComponent?> ent, float charge, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
// Begin DeltaV
|
||||
if (Resolve(ent, ref ent.Comp, false) && !ent.Comp.Active)
|
||||
{
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
if (predicted)
|
||||
_popup.PopupClient(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
else
|
||||
_popup.PopupEntity(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!TryGetBatteryFromSlot(ent, out var battery))
|
||||
{
|
||||
if (user == null)
|
||||
@@ -192,6 +215,21 @@ public sealed partial class PowerCellSystem
|
||||
[PublicAPI]
|
||||
public bool TryUseCharge(Entity<PowerCellSlotComponent?> ent, float charge, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
// Begin DeltaV
|
||||
if (Resolve(ent, ref ent.Comp, false) && !ent.Comp.Active)
|
||||
{
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
if (predicted)
|
||||
_popup.PopupClient(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
else
|
||||
_popup.PopupEntity(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!TryGetBatteryFromSlot(ent, out var battery))
|
||||
{
|
||||
if (user == null)
|
||||
@@ -247,6 +285,29 @@ public sealed partial class PowerCellSystem
|
||||
|
||||
return _battery.GetMaxUses(battery.Value.AsNullable(), cost);
|
||||
}
|
||||
|
||||
// Begin DeltaV
|
||||
/// <summary>
|
||||
/// Sets whether a power cell slot is capable of drawing from a battery
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void SetSlotActive(Entity<PowerCellSlotComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Active = active;
|
||||
Dirty(ent, ent.Comp);
|
||||
|
||||
if (!active)
|
||||
{
|
||||
var emptyEv = new PowerCellSlotEmptyEvent();
|
||||
RaiseLocalEvent(ent, ref emptyEv);
|
||||
}
|
||||
|
||||
_battery.RefreshChargeRate(ent.Owner);
|
||||
}
|
||||
// End DeltaV
|
||||
}
|
||||
|
||||
// Begin DeltaV - event-based search for battery
|
||||
|
||||
@@ -14,13 +14,21 @@ namespace Content.Shared.Random.Helpers
|
||||
return random.Pick(prototype.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Randomly selects an entry from <paramref name="prototype"/> and returns the result.
|
||||
/// </summary>
|
||||
public static string PickId(this IRobustRandom random, LocalizedDatasetPrototype prototype)
|
||||
{
|
||||
var index = random.Next(prototype.Values.Count);
|
||||
return prototype.Values[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly selects an entry from <paramref name="prototype"/>, attempts to localize it, and returns the result.
|
||||
/// </summary>
|
||||
public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype)
|
||||
{
|
||||
var index = random.Next(prototype.Values.Count);
|
||||
return Loc.GetString(prototype.Values[index]);
|
||||
return Loc.GetString(random.PickId(prototype)); // DeltaV - we need LocIds too
|
||||
}
|
||||
|
||||
public static string Pick(this IWeightedRandomPrototype prototype, System.Random random)
|
||||
|
||||
@@ -18,11 +18,13 @@ public sealed partial class RatKingComponent : Component
|
||||
[DataField("actionRaiseArmyEntity")]
|
||||
public EntityUid? ActionRaiseArmyEntity;
|
||||
|
||||
// Delta-V - switched to a base cost modified by the amount of living servants
|
||||
/// <summary>
|
||||
/// The amount of hunger one use of Raise Army consumes
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("hungerPerArmyUse", required: true)]
|
||||
public float HungerPerArmyUse = 25f;
|
||||
public float HungerPerArmyUse = 10f;
|
||||
// end DeltaV
|
||||
|
||||
/// <summary>
|
||||
/// The entity prototype of the mob that Raise Army summons
|
||||
|
||||
@@ -46,4 +46,49 @@ public sealed partial class SSDIndicatorComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
// DeltaV - SSD Recency Additions START
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the player became SSD.
|
||||
/// This will remain unset on SSD entities that never had minds attached, such as newly spawn ghost roles.
|
||||
/// </summary>
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? SsdSince;
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed next to the associated entity when it is recently SSD (stage 2).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SsdIconPrototype> RecentIcon = "RecentSSDIcon";
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed next to the associated entity when it is very recently SSD (stage 1).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SsdIconPrototype> VeryRecentIcon = "VeryRecentSSDIcon";
|
||||
|
||||
// DeltaV END
|
||||
}
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
// If you change this enum, remember to update `Resources/Locale/en-US/_DV/ssdIndicator/examine.ftl`
|
||||
public enum SsdStage : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Stage 1: SSD Indicator is red. They might just be recovering from a crash/timeout.
|
||||
/// </summary>
|
||||
VeryRecent,
|
||||
|
||||
/// <summary>
|
||||
/// Stage 2: SSD Indicator is yellow. They've been gone for a bit, but they shouldn't be moved to cryo yet.
|
||||
/// </summary>
|
||||
Recent,
|
||||
|
||||
/// <summary>
|
||||
/// Stage 3: SSD Indicator is green/default. They've been gone for a long time, they can be moved to cryo.
|
||||
/// </summary>
|
||||
Cryoable,
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using Content.Shared._DV.CCVars; // DeltaV - SSD Recency
|
||||
using Content.Shared._DV.Mind; // DeltaV - SSD Recency
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine; // DeltaV - SSD Recency
|
||||
using Content.Shared.Mobs.Systems; // DeltaV - SSD Recency
|
||||
using Content.Shared.Mind.Components; // DeltaV - SSD Recency
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
@@ -17,20 +22,75 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!; // DeltaV - SSD Recency
|
||||
|
||||
private bool _icSsdSleep;
|
||||
private float _icSsdSleepTime;
|
||||
|
||||
private TimeSpan _cryoableSsdSeconds; // DeltaV - SSD Recency
|
||||
private TimeSpan _recentSsdSeconds; // DeltaV - SSD Recency
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, MindStateUpdatedEvent>(OnMindStateUpdated); // DeltaV - SSD Recency
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleep, obj => _icSsdSleep = obj, true);
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleepTime, obj => _icSsdSleepTime = obj, true);
|
||||
_cfg.OnValueChanged(DCCVars.SsdIndicatorCryoableAfterSeconds, obj => _cryoableSsdSeconds = TimeSpan.FromSeconds(obj), true); // DeltaV - SSD Recency
|
||||
_cfg.OnValueChanged(DCCVars.SsdIndicatorRecentAfterSeconds, obj => _recentSsdSeconds = TimeSpan.FromSeconds(obj), true); // DeltaV - SSD Recency
|
||||
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, ExaminedEvent>(OnExamine); // DeltaV - SSD Recency
|
||||
}
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
private void OnExamine(Entity<SSDIndicatorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.SsdSince is not { } ssdSince)
|
||||
return;
|
||||
|
||||
if (_mobState.IsDead(ent))
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(SSDIndicatorComponent)))
|
||||
{
|
||||
var timestamp = (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss");
|
||||
args.PushMarkup(Loc.GetString("ssd-examine-duration", ("time", timestamp)));
|
||||
args.PushMarkup(Loc.GetString($"ssd-examine-{GetStage(ent).ToString().ToLower()}"));
|
||||
}
|
||||
}
|
||||
|
||||
public SsdStage GetStage(Entity<SSDIndicatorComponent> ent)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
if (ent.Comp.SsdSince + _recentSsdSeconds >= curTime)
|
||||
{
|
||||
return SsdStage.VeryRecent;
|
||||
}
|
||||
|
||||
if (ent.Comp.SsdSince + _cryoableSsdSeconds >= curTime)
|
||||
{
|
||||
return SsdStage.Recent;
|
||||
}
|
||||
|
||||
return SsdStage.Cryoable;
|
||||
}
|
||||
|
||||
public void OnMindStateUpdated(Entity<SSDIndicatorComponent> ent, ref MindStateUpdatedEvent args)
|
||||
{
|
||||
if (args.State is MindState.SSD or MindState.DeadSSD) {
|
||||
if (ent.Comp.SsdSince is null)
|
||||
ent.Comp.SsdSince = _timing.CurTime;
|
||||
}
|
||||
else
|
||||
ent.Comp.SsdSince = null;
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, SSDIndicatorComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
component.IsSSD = false;
|
||||
|
||||
@@ -77,4 +77,10 @@ public sealed partial class BorgTransponderComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FakeDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - whether the transponder is active
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Active = true;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ public abstract class SharedEmitSoundSystem : EntitySystem
|
||||
if (_whitelistSystem.IsWhitelistFail(component.Blacklist, args.User))
|
||||
{
|
||||
TryEmitSound(uid, component, args.User);
|
||||
args.InteractionParticle = true; // Stellar - interaction particles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ public abstract partial class SharedStackSystem : EntitySystem
|
||||
|
||||
var localRotation = Transform(args.Used).LocalRotation;
|
||||
_storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
|
||||
args.InteractionParticle = false; // Stellar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -718,8 +718,13 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (args.Handled || !args.Complex || args.Target == args.User)
|
||||
return;
|
||||
|
||||
// Begin Stellar Changes - don't play an interact particle for examining the strip UI
|
||||
if (TryOpenStrippingUi(args.User, (uid, component)))
|
||||
{
|
||||
args.Handled = true;
|
||||
args.InteractionParticle = false;
|
||||
}
|
||||
// End Stellar Changes - don't play an interact particle for examining the strip UI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed partial class ConversionAcceptActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public EntProtoId MindRole;
|
||||
|
||||
[DataField(required: true)]
|
||||
public EntProtoId<MindRoleComponent> RemoveMindRole;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed class ConversionAcceptActionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindContainerComponent, ConversionAcceptActionEvent>(OnAcceptConversion);
|
||||
}
|
||||
|
||||
private void OnAcceptConversion(Entity<MindContainerComponent> ent, ref ConversionAcceptActionEvent args)
|
||||
{
|
||||
if (_mind.GetMind(ent) is not { } mind)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
if (_net.IsServer)
|
||||
{
|
||||
_role.MindAddRole(mind, args.MindRole);
|
||||
_role.MindRemoveRole(mind, args.RemoveMindRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed partial class ConversionOfferActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
[DataField(readOnly: true)]
|
||||
public List<Type> IncompatibleMindRoleTypes;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId AcceptAction;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed class ConversionOfferActionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindRoleComponent, ConversionOfferActionEvent>(OnOffer);
|
||||
}
|
||||
|
||||
private void OnOffer(Entity<MindRoleComponent> ent, ref ConversionOfferActionEvent args)
|
||||
{
|
||||
if (_mind.GetMind(args.Target) is not { } mind)
|
||||
return;
|
||||
|
||||
foreach (var incompatible in args.IncompatibleMindRoleTypes)
|
||||
{
|
||||
if (_role.MindHasRole(mind, incompatible, out _))
|
||||
return;
|
||||
}
|
||||
|
||||
_actions.AddAction(args.Target, args.AcceptAction);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,11 @@ public sealed class AfterActivatableUIOpenEvent(EntityUid user) : EntityEventArg
|
||||
/// The player that opened the UI.
|
||||
/// </summary>
|
||||
public readonly EntityUid User = user;
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - if an interaction particle should be played for this event.
|
||||
/// </summary>
|
||||
public bool InteractionParticle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -72,7 +73,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
args.Verbs.Add(new ActivationVerb
|
||||
{
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Act = () => InteractUI(args.User, (uid, component)), // Stellar - interaction particles
|
||||
Text = Loc.GetString(component.VerbText),
|
||||
// TODO VERB ICON find a better icon
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
@@ -86,7 +87,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Act = () => InteractUI(args.User, (uid, component)), // Stellar - interaction particles
|
||||
Text = Loc.GetString(component.VerbText),
|
||||
// TODO VERB ICON find a better icon
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
@@ -133,7 +134,8 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (component.RequiredItems != null)
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
var interactionParticle = false; // Stellar - interaction particles
|
||||
args.Handled = InteractUI(args.User, uid, component, ref interactionParticle); // Stellar - interaction particles
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
|
||||
@@ -147,7 +149,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (component.RequiredItems != null)
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
args.Handled = InteractUI(args.User, uid, component, ref args.InteractionParticle); // Stellar - interaction particles
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
|
||||
@@ -164,7 +166,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used))
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
args.Handled = InteractUI(args.User, uid, component, ref args.InteractionParticle); // Stellar - interaction particles
|
||||
}
|
||||
|
||||
private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
|
||||
@@ -180,13 +182,23 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
SetCurrentSingleUser(uid, null, component);
|
||||
}
|
||||
|
||||
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui)
|
||||
// Begin Stellar - interaction particles
|
||||
private void InteractUI(EntityUid user, Entity<ActivatableUIComponent> ui)
|
||||
{
|
||||
var interactionParticle = false;
|
||||
InteractUI(user, ui, ui, ref interactionParticle);
|
||||
_interaction.DoContactInteraction(user, ui, null, true, interactionParticles: interactionParticle);
|
||||
}
|
||||
// End Stellar - interaction particles
|
||||
|
||||
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui, ref bool interactionParticle) // Stellar - interaction particles
|
||||
{
|
||||
if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key))
|
||||
return false;
|
||||
|
||||
if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user))
|
||||
{
|
||||
interactionParticle = false; // Stellar - interaction particles
|
||||
_uiSystem.CloseUi(uiEntity, aui.Key, user);
|
||||
return true;
|
||||
}
|
||||
@@ -243,6 +255,8 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
var aae = new AfterActivatableUIOpenEvent(user);
|
||||
RaiseLocalEvent(uiEntity, aae);
|
||||
|
||||
interactionParticle = aae.InteractionParticle; // Stellar
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Wieldable.Components; // Starlight | ES Screenshake
|
||||
using Content.Shared.Zombies; // DeltaV - Buff Zombies
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -597,18 +598,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
if (damageResult.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
DoDamageEffect(targets, user, targetXform);
|
||||
|
||||
// ES START
|
||||
// dog shit copy plaste but thats melee for you
|
||||
var userShakeRotation = new ESScreenshakeParameters()
|
||||
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
|
||||
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
|
||||
_shake.Screenshake(user, null, userShakeRotation);
|
||||
foreach (var shakeTarget in targets)
|
||||
{
|
||||
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
|
||||
}
|
||||
// ES END
|
||||
DoScreenshake(meleeUid, damageResult, user, targets); // Starlight | ES Screenshake
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,21 +764,10 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
_meleeSound.PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component);
|
||||
}
|
||||
|
||||
// ES START
|
||||
// dog shit copy plaste but thats melee for you
|
||||
var userShakeRotation = new ESScreenshakeParameters()
|
||||
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
|
||||
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
|
||||
_shake.Screenshake(user, null, userShakeRotation);
|
||||
foreach (var shakeTarget in targets)
|
||||
{
|
||||
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
|
||||
}
|
||||
// ES END
|
||||
|
||||
if (appliedDamage.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
DoDamageEffect(targets, user, Transform(targets[0]));
|
||||
DoScreenshake(meleeUid, damage, user, targets); // Starlight | ES Screenshake
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1104,4 +1083,55 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Starlight begin | ES Screenshake
|
||||
private void DoScreenshake(EntityUid weapon, DamageSpecifier damage, EntityUid attacker, List<EntityUid> targets)
|
||||
{
|
||||
if(damage.GetTotal()>4) // only show to others if it hurts real bad // DeltaV - reduce from 8 to 4
|
||||
{
|
||||
var otherTranslation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.45f,
|
||||
DecayRate = 1.1f,
|
||||
Frequency = 0.04f,
|
||||
};
|
||||
foreach(var target in targets)
|
||||
_shake.Screenshake(target, otherTranslation, null);
|
||||
}
|
||||
|
||||
// only show to attacker if they put real oompf into it, or the weapon is just THAT strong
|
||||
// var bluntRequirement = damage.DamageDict.TryGetValue(BluntDamageName, out var blunt) && blunt >= 20; // DeltaV - unused
|
||||
var isWielding = TryComp<WieldableComponent>(weapon, out var wieldable) && wieldable.Wielded;
|
||||
|
||||
// DeltaV - unused
|
||||
// if (!bluntRequirement && !wieldRequirement)
|
||||
// return;
|
||||
|
||||
// DeltaV - heavy/light screenshake variants START
|
||||
ESScreenshakeParameters userRotation;
|
||||
if (damage.GetTotal() >= 15 || isWielding)
|
||||
{
|
||||
// heavy damage or two-handed
|
||||
userRotation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.08f,
|
||||
DecayRate = 1,
|
||||
Frequency = 0.009f,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// light damage
|
||||
userRotation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.06f,
|
||||
DecayRate = 1,
|
||||
Frequency = 0.0045f,
|
||||
};
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
_shake.Screenshake(attacker, null, userRotation);
|
||||
}
|
||||
//Starlight end
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Actions;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class DeleteOnPerformedActionComponent : Component;
|
||||
@@ -0,0 +1,18 @@
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Shared._DV.Actions;
|
||||
|
||||
public sealed class DeleteOnPerformedActionSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeleteOnPerformedActionComponent, ActionPerformedEvent>(OnActionPerformed);
|
||||
}
|
||||
|
||||
private void OnActionPerformed(Entity<DeleteOnPerformedActionComponent> ent, ref ActionPerformedEvent args)
|
||||
{
|
||||
PredictedQueueDel(ent);
|
||||
}
|
||||
}
|
||||
@@ -316,4 +316,19 @@ public sealed partial class DCCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> EsScreenshakeDisabled =
|
||||
CVarDef.Create("deltav.es_screenshake.disabled", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// The total time a player has to be SSD to be considered cryoable (stage 3).
|
||||
/// Default is 20 minutes. Value should be bigger than <see cref="SsdIndicatorRecentAfterSeconds"/>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> SsdIndicatorCryoableAfterSeconds =
|
||||
CVarDef.Create("deltav.ssd.cryoable_after_seconds", 1200f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// The total time a player has to be SSD to be considered recently SSD (stage 2).
|
||||
/// If the player has been SSD for less than this time, they are considered "very recently" SSD (stage 1).
|
||||
/// Default is 5 minutes. Value should be smaller than <see cref="SsdIndicatorCryoableAfterSeconds"/>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> SsdIndicatorRecentAfterSeconds =
|
||||
CVarDef.Create("deltav.ssd.recent_after_seconds", 300f, CVar.SERVER | CVar.REPLICATED);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Radio;
|
||||
|
||||
namespace Content.Shared._DV.Chat;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct EntityAudiblyEmotedEvent(EntityUid Source, string Message, RadioChannelPrototype? Channel, EmoteType? Type);
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.Shared._DV.Chat;
|
||||
|
||||
|
||||
// Note for future: If you want to make this more robust to handle more types of emotes while still being able to check off audible as an option for it then it would
|
||||
// probably be better to make it a struct and have audible be a flag for it and then the type of emote. This would avoid having to make two different types for audible and visual.
|
||||
/// <summary>
|
||||
/// Different ways of emoting. For that little extra in RP!
|
||||
/// </summary>
|
||||
public enum EmoteType : byte
|
||||
{
|
||||
Normal, // Character emotes
|
||||
Audible, // Character screams
|
||||
Possessive, // Character's emote
|
||||
AudiblePossessive // Character's scream
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -36,6 +38,8 @@ public sealed partial class CosmicColossusComponent : Component
|
||||
|
||||
[DataField] public EntProtoId Attack1Vfx = "CosmicColossusAttack1Vfx";
|
||||
|
||||
[DataField] public EntProtoId BuffVfx = "ColossusBuffVfx";
|
||||
|
||||
[DataField] public EntProtoId TileDetonations = "MobTileDamageZone";
|
||||
|
||||
[DataField] public EntProtoId EffigyPrototype = "CosmicEffigy";
|
||||
@@ -52,13 +56,23 @@ public sealed partial class CosmicColossusComponent : Component
|
||||
|
||||
[DataField] public TimeSpan HibernationWait = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField] public TimeSpan DeathWait = TimeSpan.FromMinutes(15);
|
||||
[DataField] public TimeSpan DeathWaitSpawn = TimeSpan.FromMinutes(15);
|
||||
|
||||
[DataField] public TimeSpan DeathWaitEffigy = TimeSpan.FromMinutes(10);
|
||||
|
||||
[DataField] public bool Attacking;
|
||||
|
||||
[DataField] public bool Hibernating;
|
||||
|
||||
[DataField] public bool Timed;
|
||||
|
||||
[DataField] public short CompletedEffigies;
|
||||
|
||||
[DataField] public short MaxEffigies = 3;
|
||||
|
||||
[DataField] public DamageSpecifier BonusDamage = new();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared._DV.Mind;
|
||||
|
||||
using Content.Shared.Mind.Components;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct MindStateUpdatedEvent(MindState State);
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared._DV.Movement.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
@@ -7,12 +8,19 @@ namespace Content.Shared.Movement.Systems;
|
||||
public abstract partial class SharedJetpackSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
|
||||
private void OnJetpackToggle(Entity<JetpackComponent> jetpack, ref ToggleJetpackEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!_actionBlocker.CanComplexInteract(args.Performer))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("jetpack-too-complex"), jetpack, args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
jetpack.Comp.AutomaticMode = !jetpack.Comp.AutomaticMode;
|
||||
jetpack.Comp.AutomaticUser = args.Performer;
|
||||
Dirty(jetpack);
|
||||
|
||||
@@ -42,6 +42,9 @@ public sealed class PagerSystem : EntitySystem
|
||||
|
||||
private void OnAfterInteract(Entity<PagerComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Handled || !TryComp<DeviceNetworkComponent>(args.Target, out var targetNetwork) || !HasComp<PageSenderComponent>(args.Target))
|
||||
return;
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class FracturedFormBodyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity uid of the form that is currently being controlled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid ControllingForm { get; set; }
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using Content.Shared.Cloning;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class FracturedFormPowerComponent : BasePsionicPowerComponent
|
||||
{
|
||||
public override EntProtoId ActionProtoId { get; set; } = "ActionFracturedForm";
|
||||
|
||||
public override string PowerName { get; set; } = "psionic-power-name-fractured-form";
|
||||
|
||||
public override int MinGlimmerChanged { get; set; } = 5;
|
||||
|
||||
public override int MaxGlimmerChanged { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
/// The chance for the clone to have the starting gear of the psionic.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HasGearChance = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// The chance for the clone to have a different species than the psionic.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DifferentSpeciesChance = 0.6f;
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> CopyNaked = "CloningPod";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> CopyClothed = "Antag";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<JobPrototype> VisitorJob = "Passenger";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<JobPrototype>? NakedJob; // Scary null, but we explicitly want no job for naked spawns.
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between forced swaps.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan NextSwapMinTime = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time between swaps.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan NextSwapMaxTime = TimeSpan.FromMinutes(20);
|
||||
|
||||
/// <summary>
|
||||
/// The current timer for when the next swap is forced.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextSwap = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between voluntary swaps by sleeping.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan VoluntarySwapCooldown = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between voluntary swaps by sleeping.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextVoluntarySwap = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// The length of the DoAfter for swapping bodies.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ManualSwapTime = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The time the psionic needs to be asleep to swap bodies.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SwapTimeAsleep = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// The time length where the warning is sent before the forced swap.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan WarningTimeBeforeSleep = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// A boolean check for when the person has been warned.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool SleepWarned;
|
||||
|
||||
/// <summary>
|
||||
/// The bodies that the psionic user can swap into.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<EntityUid> Bodies { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays when the swap happens.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SwapSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.05f),
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user