diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml
index 47f9fd1a514..0189aa91a70 100644
--- a/.github/workflows/build-test-debug.yml
+++ b/.github/workflows/build-test-debug.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
- dotnet-version: 8.0.x
+ dotnet-version: 8.0.203
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 58cc9ba4d20..9f9cc0bf554 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -77,7 +77,7 @@ jobs:
with:
host: ${{ secrets.BUILDS_HOST }}
username: ${{ secrets.BUILDS_USERNAME }}
- password: ${{ secrets.BUILD_PASSWORD }}
+ key: ${{ secrets.BUILDS_SSH_KEY }}
port: ${{ secrets.BUILDS_PORT }}
script: python3 ~/manifest.py --version ${{ github.sha }}
diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml
index e3cd5159d5d..be0399b3b75 100644
--- a/.github/workflows/test-packaging.yml
+++ b/.github/workflows/test-packaging.yml
@@ -51,7 +51,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
- dotnet-version: 8.0.x
+ dotnet-version: 8.0.203
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml
index dfe497b100c..32b933d6f72 100644
--- a/.github/workflows/yaml-linter.yml
+++ b/.github/workflows/yaml-linter.yml
@@ -26,7 +26,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
- dotnet-version: 8.0.x
+ dotnet-version: 8.0.203
- name: Install dependencies
run: dotnet restore
- name: Build
diff --git a/ADT_STATION b/ADT_STATION
index ed73f1253d5..97bff099ae4 160000
--- a/ADT_STATION
+++ b/ADT_STATION
@@ -1 +1 @@
-Subproject commit ed73f1253d585bbab612647b94eb4341b4344a09
+Subproject commit 97bff099ae4400e9d077e34f73ee7ca7996ee14f
diff --git a/Content.Client/ADT/Changeling/ChangelingPanelSystem.cs b/Content.Client/ADT/Changeling/ChangelingPanelSystem.cs
new file mode 100644
index 00000000000..e0e8bd60e19
--- /dev/null
+++ b/Content.Client/ADT/Changeling/ChangelingPanelSystem.cs
@@ -0,0 +1,104 @@
+/// Made for Adventure Time Project by ModerN. https://github.com/modern-nm mailto:modern-nm@yandex.by
+/// see also https://github.com/DocNITE/liebendorf-station/tree/feature/emote-radial-panel
+using Content.Client.Humanoid;
+using Content.Client.UserInterface.Systems.Radial;
+using Content.Client.UserInterface.Systems.Radial.Controls;
+using Content.Shared.Changeling;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Map;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using System.Numerics;
+
+namespace Content.Client.ADT.Language;
+
+public sealed class ChangelingPanelSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+ [Dependency] private readonly IPlayerManager _playerMan = default!;
+ [Dependency] private readonly SpriteSystem _spriteSystem = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly HumanoidAppearanceSystem _appearanceSystem = default!;
+
+ ///
+ /// We should enable radial for single target
+ ///
+ private RadialContainer? _openedMenu;
+
+ private const string DefaultIcon = "/Textures/Interface/AdminActions/play.png";
+
+ private const string EmptyIcon = "/Textures/Interface/AdminActions/emptyIcon.png";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ SubscribeNetworkEvent(HandleChangelingFormsMenuEvent);
+ }
+
+ private void HandleChangelingFormsMenuEvent(RequestChangelingFormsMenuEvent args)
+ {
+ if (_openedMenu != null)
+ return;
+ if (_playerMan.LocalEntity == null)
+ {
+ return;
+ }
+
+ //if (!TryComp(_playerMan.LocalEntity.Value, out var changelingComponent)) // нет на клиенте
+ // return;
+
+ _openedMenu = _userInterfaceManager.GetUIController()
+ .CreateRadialContainer();
+
+ foreach (var humanoid in args.HumanoidData)
+ {
+ var dummy = _entManager.SpawnEntity(_proto.Index(humanoid.Species).DollPrototype, MapCoordinates.Nullspace);
+ //var humanoidEntityUid = GetEntity(humanoid); // Entities on the client outside of the FOV are nonexistant. You can see that if you zoom out. //So it'll give you UID 0 which is EntityUid.Invalid.
+ _appearanceSystem.LoadProfile(dummy, humanoid.Profile);
+ var face = new SpriteView();
+ face.SetEntity(dummy);
+
+ var actionName = humanoid.Name;
+ var texturePath = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath(EmptyIcon)));
+
+ var emoteButton = _openedMenu.AddButton(actionName, texturePath, face);
+ emoteButton.Opacity = 210;
+ emoteButton.Tooltip = null;
+ emoteButton.Controller.OnPressed += (_) =>
+ {
+ var ev = new SelectChangelingFormEvent(args.Target, entitySelected: humanoid.NetEntity);
+ RaiseNetworkEvent(ev);
+ _openedMenu.Dispose();
+ };
+ }
+ _openedMenu.OnClose += (_) =>
+ {
+ _openedMenu = null;
+ };
+ if (_playerMan.LocalEntity != null)
+ _openedMenu.OpenAttached(_playerMan.LocalEntity.Value);
+
+ }
+
+ private void OnPlayerAttached(PlayerAttachedEvent args)
+ {
+ _openedMenu?.Dispose();
+ }
+
+ private void OnPlayerDetached(PlayerDetachedEvent args)
+ {
+ _openedMenu?.Dispose();
+ }
+}
diff --git a/Content.Client/ADT/Chaplain/ChaplainSystem.cs b/Content.Client/ADT/Chaplain/ChaplainSystem.cs
new file mode 100644
index 00000000000..90a64f77fcd
--- /dev/null
+++ b/Content.Client/ADT/Chaplain/ChaplainSystem.cs
@@ -0,0 +1,53 @@
+using Content.Shared.Bible.Components;
+using Content.Shared.Phantom.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Content.Client.UserInterface.Systems.Radial;
+using Content.Client.UserInterface.Systems.Radial.Controls;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using Robust.Client.UserInterface;
+using Content.Shared.StatusIcon.Components;
+using Content.Shared.Ghost;
+using Content.Shared.Antag;
+using Content.Shared.Actions;
+using Robust.Client.Graphics;
+using Robust.Client.Utility;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Client.Humanoid;
+using System.Numerics;
+using Content.Shared.Preferences;
+
+namespace Content.Client.Chaplain;
+
+public sealed class ChaplainSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+ [Dependency] private readonly IPlayerManager _playerMan = default!;
+ [Dependency] private readonly SpriteSystem _spriteSystem = default!;
+ [Dependency] private readonly HumanoidAppearanceSystem _appearanceSystem = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnCanDisplayStatusIcons);
+ }
+
+ private void OnCanDisplayStatusIcons(EntityUid uid, ChaplainComponent component, ref CanDisplayStatusIconsEvent args)
+ {
+ if (HasComp(args.User) || HasComp(args.User) || HasComp(args.User))
+ return;
+
+ if (component.IconVisibleToGhost && HasComp(args.User))
+ return;
+
+ args.Cancelled = true;
+ }
+}
diff --git a/Content.Client/ADT/Chaplain/EUI/AcceptReligionEui.cs b/Content.Client/ADT/Chaplain/EUI/AcceptReligionEui.cs
new file mode 100644
index 00000000000..42d974fba95
--- /dev/null
+++ b/Content.Client/ADT/Chaplain/EUI/AcceptReligionEui.cs
@@ -0,0 +1,46 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Chaplain;
+using Content.Shared.Bible.Components;
+
+namespace Content.Client.Chaplain;
+
+[UsedImplicitly]
+public sealed class AcceptReligionEui : BaseEui
+{
+ private readonly AcceptReligionWindow _window;
+
+ public AcceptReligionEui()
+ {
+ _window = new AcceptReligionWindow();
+
+ _window.DenyButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptReligionChoiceMessage(AcceptReligionButton.Deny));
+ _window.Close();
+ };
+
+ _window.OnClose += () => SendMessage(new AcceptReligionChoiceMessage(AcceptReligionButton.Deny));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptReligionChoiceMessage(AcceptReligionButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
+
diff --git a/Content.Client/ADT/Chaplain/EUI/AcceptReligionEuiWindow.cs b/Content.Client/ADT/Chaplain/EUI/AcceptReligionEuiWindow.cs
new file mode 100644
index 00000000000..dba84289dbb
--- /dev/null
+++ b/Content.Client/ADT/Chaplain/EUI/AcceptReligionEuiWindow.cs
@@ -0,0 +1,61 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Chaplain;
+
+public sealed class AcceptReligionWindow : DefaultWindow
+{
+ public readonly Button DenyButton;
+ public readonly Button AcceptButton;
+
+ public AcceptReligionWindow()
+ {
+
+ Title = Loc.GetString("accept-religion-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("accept-religion-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("accept-religion-window-accept-button"),
+ }),
+
+ (new Control()
+ {
+ MinSize = new Vector2(20, 0)
+ }),
+
+ (DenyButton = new Button
+ {
+ Text = Loc.GetString("accept-religion-window-deny-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandEui.cs b/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandEui.cs
new file mode 100644
index 00000000000..164d7fd301e
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandEui.cs
@@ -0,0 +1,45 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Phantom;
+
+namespace Content.Client.Phantom;
+
+[UsedImplicitly]
+public sealed class AcceptHelpingHandEui : BaseEui
+{
+ private readonly AcceptHelpingHandWindow _window;
+
+ public AcceptHelpingHandEui()
+ {
+ _window = new AcceptHelpingHandWindow();
+
+ _window.DenyButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptHelpingHandChoiceMessage(AcceptHelpingHandButton.Deny));
+ _window.Close();
+ };
+
+ _window.OnClose += () => SendMessage(new AcceptHelpingHandChoiceMessage(AcceptHelpingHandButton.Deny));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptHelpingHandChoiceMessage(AcceptHelpingHandButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
+
diff --git a/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandWindow.cs b/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandWindow.cs
new file mode 100644
index 00000000000..847cdac6188
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AcceptHelpingHandWindow.cs
@@ -0,0 +1,61 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Phantom;
+
+public sealed class AcceptHelpingHandWindow : DefaultWindow
+{
+ public readonly Button DenyButton;
+ public readonly Button AcceptButton;
+
+ public AcceptHelpingHandWindow()
+ {
+
+ Title = Loc.GetString("accept-phantom-help-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("accept-phantom-help-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("accept-phantom-help-window-accept-button"),
+ }),
+
+ (new Control()
+ {
+ MinSize = new Vector2(20, 0)
+ }),
+
+ (DenyButton = new Button
+ {
+ Text = Loc.GetString("accept-phantom-help-window-deny-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs b/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs
new file mode 100644
index 00000000000..66dbb7884ca
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs
@@ -0,0 +1,45 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Phantom;
+
+namespace Content.Client.Phantom;
+
+[UsedImplicitly]
+public sealed class AcceptPhantomPowersEui : BaseEui
+{
+ private readonly AcceptPhantomPowersWindow _window;
+
+ public AcceptPhantomPowersEui()
+ {
+ _window = new AcceptPhantomPowersWindow();
+
+ _window.DenyButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptPhantomPowersChoiceMessage(AcceptPhantomPowersButton.Deny));
+ _window.Close();
+ };
+
+ _window.OnClose += () => SendMessage(new AcceptPhantomPowersChoiceMessage(AcceptPhantomPowersButton.Deny));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new AcceptPhantomPowersChoiceMessage(AcceptPhantomPowersButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
+
diff --git a/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersWindow.cs b/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersWindow.cs
new file mode 100644
index 00000000000..898493c5102
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AcceptPhantomPowersWindow.cs
@@ -0,0 +1,61 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Phantom;
+
+public sealed class AcceptPhantomPowersWindow : DefaultWindow
+{
+ public readonly Button DenyButton;
+ public readonly Button AcceptButton;
+
+ public AcceptPhantomPowersWindow()
+ {
+
+ Title = Loc.GetString("accept-phantom-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("accept-phantom-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("accept-phantom-window-accept-button"),
+ }),
+
+ (new Control()
+ {
+ MinSize = new Vector2(20, 0)
+ }),
+
+ (DenyButton = new Button
+ {
+ Text = Loc.GetString("accept-phantom-window-deny-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/Phantom/EUI/AmnesiaEui.cs b/Content.Client/ADT/Phantom/EUI/AmnesiaEui.cs
new file mode 100644
index 00000000000..46ebec2c2fb
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AmnesiaEui.cs
@@ -0,0 +1,38 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Phantom;
+
+namespace Content.Client.Phantom;
+
+[UsedImplicitly]
+public sealed class PhantomAmnesiaEui : BaseEui
+{
+ private readonly PhantomAmnesiaWindow _window;
+
+ public PhantomAmnesiaEui()
+ {
+ _window = new PhantomAmnesiaWindow();
+
+ _window.OnClose += () => SendMessage(new PhantomAmnesiaChoiceMessage(PhantomAmnesiaButton.Accept));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new PhantomAmnesiaChoiceMessage(PhantomAmnesiaButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
diff --git a/Content.Client/ADT/Phantom/EUI/AmnesiaWindow.cs b/Content.Client/ADT/Phantom/EUI/AmnesiaWindow.cs
new file mode 100644
index 00000000000..d038a8e80e5
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/AmnesiaWindow.cs
@@ -0,0 +1,50 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Phantom;
+
+public sealed class PhantomAmnesiaWindow : DefaultWindow
+{
+ public readonly Button AcceptButton;
+
+ public PhantomAmnesiaWindow()
+ {
+
+ Title = Loc.GetString("phantom-oblivion-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("phantom-oblivion-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("phantom-oblivion-window-accept-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/Phantom/EUI/FinaleEui.cs b/Content.Client/ADT/Phantom/EUI/FinaleEui.cs
new file mode 100644
index 00000000000..121fdc1a1fb
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/FinaleEui.cs
@@ -0,0 +1,44 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Phantom;
+
+namespace Content.Client.Phantom;
+
+[UsedImplicitly]
+public sealed class PhantomFinaleEui : BaseEui
+{
+ private readonly PhantomFinaleWindow _window;
+
+ public PhantomFinaleEui()
+ {
+ _window = new PhantomFinaleWindow();
+
+ _window.DenyButton.OnPressed += _ =>
+ {
+ SendMessage(new PhantomFinaleChoiceMessage(PhantomFinaleButton.Deny));
+ _window.Close();
+ };
+
+ _window.OnClose += () => SendMessage(new PhantomFinaleChoiceMessage(PhantomFinaleButton.Deny));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new PhantomFinaleChoiceMessage(PhantomFinaleButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
diff --git a/Content.Client/ADT/Phantom/EUI/FinaleWindow.cs b/Content.Client/ADT/Phantom/EUI/FinaleWindow.cs
new file mode 100644
index 00000000000..45e47b1a5f1
--- /dev/null
+++ b/Content.Client/ADT/Phantom/EUI/FinaleWindow.cs
@@ -0,0 +1,61 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Phantom;
+
+public sealed class PhantomFinaleWindow : DefaultWindow
+{
+ public readonly Button DenyButton;
+ public readonly Button AcceptButton;
+
+ public PhantomFinaleWindow()
+ {
+
+ Title = Loc.GetString("phantom-finale-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("phantom-finale-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("phantom-finale-window-accept-button"),
+ }),
+
+ (new Control()
+ {
+ MinSize = new Vector2(20, 0)
+ }),
+
+ (DenyButton = new Button
+ {
+ Text = Loc.GetString("phantom-finale-window-deny-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/Phantom/PhantomHudSystem.cs b/Content.Client/ADT/Phantom/PhantomHudSystem.cs
new file mode 100644
index 00000000000..4c8e2ffcf61
--- /dev/null
+++ b/Content.Client/ADT/Phantom/PhantomHudSystem.cs
@@ -0,0 +1,44 @@
+using Content.Shared.Overlays;
+using Content.Shared.StatusIcon.Components;
+using Content.Shared.Phantom.Components;
+using Content.Shared.StatusIcon;
+using Robust.Shared.Prototypes;
+using Content.Client.Overlays;
+
+namespace Content.Client.Phantom;
+public sealed class ShowHauntedIconsSystem : EquipmentHudSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, PhantomHolderIconComponent haunted, ref GetStatusIconsEvent args)
+ {
+ if (!IsActive || args.InContainer)
+ {
+ return;
+ }
+
+ var syndicateIcons = SyndicateIcon(uid, haunted);
+
+ args.StatusIcons.AddRange(syndicateIcons);
+ }
+
+ private IReadOnlyList SyndicateIcon(EntityUid uid, PhantomHolderIconComponent haunted)
+ {
+ var result = new List();
+
+ if (_prototype.TryIndex(haunted.StatusIcon, out var icon))
+ {
+ result.Add(icon);
+ }
+
+ return result;
+ }
+}
+
diff --git a/Content.Client/ADT/Phantom/PhantomSystem.cs b/Content.Client/ADT/Phantom/PhantomSystem.cs
new file mode 100644
index 00000000000..71a7a04a3ee
--- /dev/null
+++ b/Content.Client/ADT/Phantom/PhantomSystem.cs
@@ -0,0 +1,248 @@
+using Content.Shared.Phantom;
+using Content.Shared.Phantom.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Content.Client.UserInterface.Systems.Radial;
+using Content.Client.UserInterface.Systems.Radial.Controls;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using Robust.Client.UserInterface;
+using Content.Shared.StatusIcon.Components;
+using Content.Shared.Ghost;
+using Content.Shared.Antag;
+using Content.Shared.Actions;
+using Robust.Client.Graphics;
+using Robust.Client.Utility;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Client.Humanoid;
+using Content.Client.UserInterface.Systems.Radial;
+using Content.Client.UserInterface.Systems.Radial.Controls;
+using Content.Shared.Changeling;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Map;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using System.Numerics;
+using Content.Shared.Preferences;
+
+namespace Content.Client.Phantom;
+
+public sealed class PhantomSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+ [Dependency] private readonly IPlayerManager _playerMan = default!;
+ [Dependency] private readonly SpriteSystem _spriteSystem = default!;
+ [Dependency] private readonly HumanoidAppearanceSystem _appearanceSystem = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ SubscribeNetworkEvent(HandleMenuEvent);
+ SubscribeNetworkEvent(HandleFreedomMenuEvent);
+ SubscribeNetworkEvent(HandleVesselMenuEvent);
+
+ SubscribeLocalEvent(OnCanDisplayStatusIcons);
+ SubscribeLocalEvent(OnCanDisplayStatusIcons);
+ SubscribeLocalEvent(OnCanDisplayStatusIcons);
+ }
+
+ #region Radial Menu
+
+ private RadialContainer? _openedMenu;
+
+ private const string DefaultIcon = "/Textures/Interface/AdminActions/play.png";
+
+ private const string EmptyIcon = "/Textures/Interface/AdminActions/empty.png";
+
+ private void OnPlayerAttached(PlayerAttachedEvent args)
+ {
+ _openedMenu?.Dispose();
+ }
+
+ private void OnPlayerDetached(PlayerDetachedEvent args)
+ {
+ _openedMenu?.Dispose();
+ }
+
+ ///
+ /// Draws RadialUI.
+ ///
+ ///
+ private void HandleMenuEvent(RequestPhantomStyleMenuEvent args)
+ {
+ if (_openedMenu != null)
+ return;
+
+ _openedMenu = _userInterfaceManager.GetUIController()
+ .CreateRadialContainer();
+
+ foreach (var protoId in args.Prototypes)
+ {
+ if (_proto.TryIndex(protoId, out var prototype))
+ {
+ var actionName = prototype.Name;
+ var texturePath = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath(DefaultIcon)));
+ if (actionName == null)
+ actionName = "Unnamed";
+ if (prototype.Icon != null)
+ texturePath = _spriteSystem.Frame0(prototype.Icon);
+
+ var emoteButton = _openedMenu.AddButton(actionName, texturePath);
+ emoteButton.Opacity = 210;
+ emoteButton.Tooltip = null;
+ emoteButton.Controller.OnPressed += (_) =>
+ {
+ var ev = new SelectPhantomStyleEvent(args.Target, protoId);
+ RaiseNetworkEvent(ev);
+ _openedMenu.Dispose();
+ };
+ }
+ }
+
+ _openedMenu.OnClose += (_) =>
+ {
+ _openedMenu = null;
+ };
+ if (_playerMan.LocalEntity != null)
+ _openedMenu.OpenAttached(_playerMan.LocalEntity.Value);
+
+ }
+
+ private void HandleFreedomMenuEvent(RequestPhantomFreedomMenuEvent args)
+ {
+ if (_openedMenu != null)
+ return;
+
+ _openedMenu = _userInterfaceManager.GetUIController()
+ .CreateRadialContainer();
+
+ foreach (var protoId in args.Prototypes)
+ {
+ if (_proto.TryIndex(protoId, out var prototype))
+ {
+ if (!prototype.TryGetComponent(out var actionComp))
+ continue;
+
+ var actionName = prototype.Name;
+ var texturePath = actionComp.Icon;
+ if (actionName == null)
+ actionName = "Unnamed";
+ if (texturePath == null)
+ continue;
+
+ var emoteButton = _openedMenu.AddButton(actionName, _spriteSystem.Frame0(texturePath));
+ emoteButton.Opacity = 210;
+ emoteButton.Tooltip = null;
+ emoteButton.Controller.OnPressed += (_) =>
+ {
+ var ev = new SelectPhantomFreedomEvent(args.Target, protoId);
+ RaiseNetworkEvent(ev);
+ _openedMenu.Dispose();
+ };
+ }
+ }
+
+ _openedMenu.OnClose += (_) =>
+ {
+ _openedMenu = null;
+ };
+ if (_playerMan.LocalEntity != null)
+ _openedMenu.OpenAttached(_playerMan.LocalEntity.Value);
+
+ }
+
+ private void HandleVesselMenuEvent(RequestPhantomVesselMenuEvent args)
+ {
+ if (_openedMenu != null)
+ return;
+
+ _openedMenu = _userInterfaceManager.GetUIController()
+ .CreateRadialContainer();
+
+ foreach (var (vessel, humanoid, name) in args.Vessels)
+ {
+ var dummy = _entManager.SpawnEntity(_proto.Index(humanoid.Species).DollPrototype, MapCoordinates.Nullspace);
+ //var humanoidEntityUid = GetEntity(humanoid); // Entities on the client outside of the FOV are nonexistant. You can see that if you zoom out. //So it'll give you UID 0 which is EntityUid.Invalid.
+ _appearanceSystem.LoadProfile(dummy, humanoid);
+ var face = new SpriteView();
+ face.SetEntity(dummy);
+
+ var actionName = name;
+ var texturePath = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath(EmptyIcon)));
+
+ var button = _openedMenu.AddButton(actionName, texturePath, face);
+ button.Opacity = 210;
+ button.Tooltip = null;
+ button.Controller.OnPressed += (_) =>
+ {
+ var ev = new SelectPhantomVesselEvent(args.Uid, vessel);
+ RaiseNetworkEvent(ev);
+ _openedMenu.Dispose();
+ };
+ }
+
+ _openedMenu.OnClose += (_) =>
+ {
+ _openedMenu = null;
+ };
+ if (_playerMan.LocalEntity != null)
+ _openedMenu.OpenAttached(_playerMan.LocalEntity.Value);
+
+ }
+ #endregion
+
+
+ private void OnCanDisplayStatusIcons(EntityUid uid, T component, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent
+ {
+ if (HasComp(args.User) || HasComp(args.User) || HasComp(args.User))
+ return;
+
+ if (component.IconVisibleToGhost && HasComp(args.User))
+ return;
+
+ args.Cancelled = true;
+ }
+
+ private void OnAppearanceChange(EntityUid uid, PhantomComponent component, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ if (_appearance.TryGetData(uid, PhantomVisuals.Haunting, out var haunt, args.Component))
+ {
+ if (haunt)
+ args.Sprite.LayerSetState(0, component.HauntingState);
+ else
+ args.Sprite.LayerSetState(0, component.State);
+ }
+ else if (_appearance.TryGetData(uid, PhantomVisuals.Stunned, out var stunned, args.Component) && stunned)
+ {
+ args.Sprite.LayerSetState(0, component.StunnedState);
+ }
+ else if (_appearance.TryGetData(uid, PhantomVisuals.Corporeal, out var corporeal, args.Component))
+ {
+ if (corporeal)
+ args.Sprite.LayerSetState(0, component.CorporealState);
+ else
+ args.Sprite.LayerSetState(0, component.State);
+ }
+ }
+}
diff --git a/Content.Client/ADT/Poltergeist/RestInPeaceEui.cs b/Content.Client/ADT/Poltergeist/RestInPeaceEui.cs
new file mode 100644
index 00000000000..f86c26b541c
--- /dev/null
+++ b/Content.Client/ADT/Poltergeist/RestInPeaceEui.cs
@@ -0,0 +1,45 @@
+using Content.Client.Eui;
+using Content.Shared.Cloning;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Content.Shared.Poltergeist;
+
+namespace Content.Client.Poltergeist;
+
+[UsedImplicitly]
+public sealed class RestInPeaceEui : BaseEui
+{
+ private readonly RestInPeaceWindow _window;
+
+ public RestInPeaceEui()
+ {
+ _window = new RestInPeaceWindow();
+
+ _window.DenyButton.OnPressed += _ =>
+ {
+ SendMessage(new RestInPeaceChoiceMessage(RestInPeaceButton.Deny));
+ _window.Close();
+ };
+
+ _window.OnClose += () => SendMessage(new RestInPeaceChoiceMessage(RestInPeaceButton.Deny));
+
+ _window.AcceptButton.OnPressed += _ =>
+ {
+ SendMessage(new RestInPeaceChoiceMessage(RestInPeaceButton.Accept));
+ _window.Close();
+ };
+ }
+
+ public override void Opened()
+ {
+ IoCManager.Resolve().RequestWindowAttention();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+
+}
+
diff --git a/Content.Client/ADT/Poltergeist/RestInPeaceWindow.cs b/Content.Client/ADT/Poltergeist/RestInPeaceWindow.cs
new file mode 100644
index 00000000000..7b45032c02e
--- /dev/null
+++ b/Content.Client/ADT/Poltergeist/RestInPeaceWindow.cs
@@ -0,0 +1,61 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Poltergeist;
+
+public sealed class RestInPeaceWindow : DefaultWindow
+{
+ public readonly Button DenyButton;
+ public readonly Button AcceptButton;
+
+ public RestInPeaceWindow()
+ {
+
+ Title = Loc.GetString("poltergeist-die-window-title");
+
+ Contents.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Children =
+ {
+ (new Label()
+ {
+ Text = Loc.GetString("poltergeist-die-window-prompt-text-part")
+ }),
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Align = AlignMode.Center,
+ Children =
+ {
+ (AcceptButton = new Button
+ {
+ Text = Loc.GetString("poltergeist-die-window-accept-button"),
+ }),
+
+ (new Control()
+ {
+ MinSize = new Vector2(20, 0)
+ }),
+
+ (DenyButton = new Button
+ {
+ Text = Loc.GetString("poltergeist-die-window-deny-button"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
diff --git a/Content.Client/ADT/license.txt b/Content.Client/ADT/license.txt
new file mode 100644
index 00000000000..79f9846ed4e
--- /dev/null
+++ b/Content.Client/ADT/license.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2024 Adventure Station
+
+1. The provided software code in this and nested directories (hereinafter referred to as the "Adventure Station" project code) is intended solely for use within the "Adventure Station" project.
+
+2. Any use, copying, distribution, or modification of the "Adventure Station" project code outside of "Adventure Station" project is prohibited.
+
+3. This license does not grant any rights to own, use, or distribute the "Adventure Station" project code outside of the "Adventure Station" project.
+
+4. This license is valid indefinitely, or until a decision is made by the copyright holder of the "Adventure Station" project to revoke/modify it.
+
+Copyright (c) 2024 Adventure Station
+
+1. Представленный программный код в данном и вложенных директориях (далее - код проекта "Adventure Station") предназначен исключительно для использования в рамках проекта "Adventure Station"
+
+2. Любое использование, копирование, распространение и изменение кода проекта "Adventure Station" за пределами данного проекта запрещены.
+
+3. Настоящая лицензия не предоставляет никаких прав на владение, использование или распространение кода проекта "Adventure Station" вне проекта "Adventure Station".
+
+4. Настоящая лицензия действительна бессрочно, или до решения об упразднении/изменении правообладателем проекта "Adventure Station".
\ No newline at end of file
diff --git a/Content.Client/Antag/AntagStatusIconSystem.cs b/Content.Client/Antag/AntagStatusIconSystem.cs
index 5d87837893c..a70e16cb489 100644
--- a/Content.Client/Antag/AntagStatusIconSystem.cs
+++ b/Content.Client/Antag/AntagStatusIconSystem.cs
@@ -5,6 +5,8 @@
using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
+using Content.Shared.Phantom.Components;
+using Content.Shared.Bible.Components;
namespace Content.Client.Antag;
@@ -22,6 +24,14 @@ public override void Initialize()
SubscribeLocalEvent(GetRevIcon);
SubscribeLocalEvent(GetIcon);
SubscribeLocalEvent(GetIcon);
+
+ // ADT Phantom update start
+ SubscribeLocalEvent(GetVesselIcon);
+ SubscribeLocalEvent(GetIcon);
+ SubscribeLocalEvent(GetIcon);
+ SubscribeLocalEvent(GetIcon);
+ SubscribeLocalEvent(GetIcon);
+ // ADT Phantom update end
}
///
@@ -51,4 +61,13 @@ private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatu
GetIcon(uid, comp, ref ev);
}
+
+ private void GetVesselIcon(EntityUid uid, VesselComponent comp, ref GetStatusIconsEvent ev)
+ {
+ if (HasComp(uid))
+ return;
+
+ GetIcon(uid, comp, ref ev);
+
+ }
}
diff --git a/Content.Client/BluespaceHarvester/BluespaceHarvesterBoundUserInterface.cs b/Content.Client/BluespaceHarvester/BluespaceHarvesterBoundUserInterface.cs
new file mode 100644
index 00000000000..dc64dcf751c
--- /dev/null
+++ b/Content.Client/BluespaceHarvester/BluespaceHarvesterBoundUserInterface.cs
@@ -0,0 +1,53 @@
+using Content.Shared.BluespaceHarvester;
+using JetBrains.Annotations;
+
+namespace Content.Client.BluespaceHarvester;
+
+[UsedImplicitly]
+public sealed class BluespaceHarvesterBoundUserInterface : BoundUserInterface
+{
+ private BluespaceHarvesterMenu? _window;
+
+ public BluespaceHarvesterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new BluespaceHarvesterMenu(this);
+ _window.OnClose += Close;
+ _window?.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _window?.Dispose();
+ _window = null;
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (state is not BluespaceHarvesterBoundUserInterfaceState current)
+ return;
+
+ _window?.UpdateState(current);
+ }
+
+ public void SendTargetLevel(int level)
+ {
+ SendMessage(new BluespaceHarvesterTargetLevelMessage(level));
+ }
+
+ public void SendBuy(Shared.BluespaceHarvester.BluespaceHarvesterCategory category)
+ {
+ SendMessage(new BluespaceHarvesterBuyMessage(category));
+ }
+}
diff --git a/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml b/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml
new file mode 100644
index 00000000000..eef4ac89883
--- /dev/null
+++ b/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml.cs b/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml.cs
new file mode 100644
index 00000000000..b6350cbb999
--- /dev/null
+++ b/Content.Client/BluespaceHarvester/BluespaceHarvesterCategory.xaml.cs
@@ -0,0 +1,20 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Content.Shared.BluespaceHarvester;
+
+namespace Content.Client.BluespaceHarvester;
+
+[GenerateTypedNameReferences]
+public sealed partial class BluespaceHarvesterCategory : Control
+{
+ public BluespaceHarvesterCategory(BluespaceHarvesterCategoryInfo category, bool canBuy)
+ {
+ RobustXamlLoader.Load(this);
+
+ CategoryLabel.Text = Loc.GetString($"bluespace-harvester-category-{Enum.GetName(typeof(Shared.BluespaceHarvester.BluespaceHarvesterCategory), category.Type)}");
+
+ CategoryButton.Text = $"{category.Cost}";
+ CategoryButton.Disabled = !canBuy;
+ }
+}
diff --git a/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml b/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml
new file mode 100644
index 00000000000..8c1e8842d7e
--- /dev/null
+++ b/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml.cs b/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml.cs
new file mode 100644
index 00000000000..46eda80af91
--- /dev/null
+++ b/Content.Client/BluespaceHarvester/BluespaceHarvesterMenu.xaml.cs
@@ -0,0 +1,60 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.BluespaceHarvester;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.BluespaceHarvester;
+
+[GenerateTypedNameReferences]
+public sealed partial class BluespaceHarvesterMenu : FancyWindow
+{
+ private readonly BluespaceHarvesterBoundUserInterface _owner;
+
+ public BluespaceHarvesterMenu(BluespaceHarvesterBoundUserInterface owner)
+ {
+ RobustXamlLoader.Load(this);
+
+ _owner = owner;
+
+ InputLevelBar.OnTextEntered += (args) =>
+ {
+ if (!int.TryParse(args.Text, out var level) || level < 0 || level > 20)
+ {
+ InputLevelBar.Text = "0";
+ return;
+ }
+
+ _owner.SendTargetLevel(level);
+ };
+
+ // EntityView.SetEntity(_owner.Owner);
+ }
+
+ public void UpdateState(BluespaceHarvesterBoundUserInterfaceState state)
+ {
+ TargetLevel.Text = $"{state.TargetLevel}";
+ CurrentLevel.Text = $"{state.CurrentLevel}";
+ DesiredBar.Value = ((float)state.CurrentLevel) / ((float)state.MaxLevel);
+
+ PowerUsageLabel.Text = Loc.GetString("power-monitoring-window-value", ("value", state.PowerUsage));
+ PowerUsageNextLabel.Text = Loc.GetString("power-monitoring-window-value", ("value", state.PowerUsageNext));
+ PowerSuppliertLabel.Text = Loc.GetString("power-monitoring-window-value", ("value", state.PowerSuppliert));
+
+ AvailablePointsLabel.Text = $"{state.Points}";
+ TotalPontsLabel.Text = $"{state.TotalPoints}";
+ GenerationPointsLabel.Text = $"{state.PointsGen}";
+
+ Categories.RemoveAllChildren();
+ foreach (var category in state.Categories)
+ {
+ var child = new BluespaceHarvesterCategory(category, state.Points >= category.Cost);
+
+ child.CategoryButton.OnButtonDown += (args) =>
+ {
+ _owner.SendBuy(category.Type);
+ };
+
+ Categories.AddChild(child);
+ }
+ }
+}
diff --git a/Content.Client/Movement/Systems/ClientWaddleAnimationSystem.cs b/Content.Client/Movement/Systems/ClientWaddleAnimationSystem.cs
new file mode 100644
index 00000000000..12288e20cf6
--- /dev/null
+++ b/Content.Client/Movement/Systems/ClientWaddleAnimationSystem.cs
@@ -0,0 +1,144 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using Content.Client.Buckle;
+using Content.Client.Gravity;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+
+namespace Content.Client.Movement.Systems;
+
+public sealed class ClientWaddleAnimationSystem : SharedWaddleAnimationSystem
+{
+ [Dependency] private readonly AnimationPlayerSystem _animation = default!;
+ [Dependency] private readonly GravitySystem _gravity = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly BuckleSystem _buckle = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // Start waddling
+ SubscribeAllEvent((msg, args) => StartWaddling(args.SenderSession.AttachedEntity));
+
+ // Handle concluding animations
+ SubscribeLocalEvent(OnAnimationCompleted);
+
+ // Stop waddling
+ SubscribeAllEvent((msg, args) => StopWaddling(args.SenderSession.AttachedEntity));
+ }
+
+ private void StartWaddling(EntityUid? uid)
+ {
+ if (
+ !EntityIsValid(uid, out var entity, out var component) ||
+ _animation.HasRunningAnimation(entity.Value, component.KeyName) ||
+ _gravity.IsWeightless(entity.Value) ||
+ _buckle.IsBuckled(entity.Value) ||
+ _mobState.IsIncapacitated(entity.Value) ||
+ !TryComp(entity, out var mover) ||
+ !_actionBlocker.CanMove(entity.Value, mover)
+ )
+ return;
+
+ PlayWaddleAnimationUsing(entity.Value, component, CalculateAnimationLength(component, mover), CalculateTumbleIntensity(component));
+ }
+
+ private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
+ {
+ return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
+ }
+
+ private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
+ {
+ return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
+ }
+
+ private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
+ {
+ if (args.Key != component.KeyName)
+ return;
+
+ if (!TryComp(uid, out var mover))
+ return;
+
+ PlayWaddleAnimationUsing(uid, component, CalculateAnimationLength(component, mover), CalculateTumbleIntensity(component));
+ }
+
+ private void StopWaddling(EntityUid? uid)
+ {
+ if (
+ !EntityIsValid(uid, out var entity, out var component) ||
+ !_animation.HasRunningAnimation(entity.Value, component.KeyName)
+ )
+ return;
+
+ _animation.Stop(entity.Value, component.KeyName);
+
+ if (!TryComp(entity.Value, out var sprite))
+ return;
+
+ // Note that this is a hard-write to this sprite, not some layer-based operation. If this is called whilst a sprite
+ // is lying down, it will make the sprite stand up, which usually looks wrong.
+ sprite.Offset = new Vector2();
+ sprite.Rotation = Angle.FromDegrees(0);
+ }
+
+ private bool EntityIsValid(EntityUid? uid, [NotNullWhen(true)] out EntityUid? entity, [NotNullWhen(true)] out WaddleAnimationComponent? component)
+ {
+ entity = null;
+ component = null;
+
+ if (!uid.HasValue)
+ return false;
+
+ entity = uid.Value;
+
+ return TryComp(entity, out component);
+ }
+
+ private void PlayWaddleAnimationUsing(EntityUid uid, WaddleAnimationComponent component, float len, float tumbleIntensity)
+ {
+ component.LastStep = !component.LastStep;
+
+ var anim = new Animation()
+ {
+ Length = TimeSpan.FromSeconds(len),
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Rotation),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
+ }
+ },
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Offset),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
+ new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
+ new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
+ }
+ }
+ }
+ };
+
+ _animation.Play(uid, anim, component.KeyName);
+ }
+}
diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
deleted file mode 100644
index fe010500c59..00000000000
--- a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using System.Numerics;
-using Content.Client.Gravity;
-using Content.Shared.Movement.Components;
-using Content.Shared.Movement.Events;
-using Robust.Client.Animations;
-using Robust.Client.GameObjects;
-using Robust.Shared.Animations;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Movement.Systems;
-
-public sealed class WaddleAnimationSystem : EntitySystem
-{
- [Dependency] private readonly AnimationPlayerSystem _animation = default!;
- [Dependency] private readonly GravitySystem _gravity = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent(OnMovementInput);
- SubscribeLocalEvent(OnStartedWalking);
- SubscribeLocalEvent(OnStoppedWalking);
- SubscribeLocalEvent(OnAnimationCompleted);
- }
-
- private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
- {
- // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
- // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
- if (!_timing.IsFirstTimePredicted)
- {
- return;
- }
-
- if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
- {
- component.IsCurrentlyWaddling = false;
-
- var stopped = new StoppedWaddlingEvent(entity);
-
- RaiseLocalEvent(entity, ref stopped);
-
- return;
- }
-
- // Only start waddling if we're not currently AND we're actually moving.
- if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
- return;
-
- component.IsCurrentlyWaddling = true;
-
- var started = new StartedWaddlingEvent(entity);
-
- RaiseLocalEvent(entity, ref started);
- }
-
- private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
- {
- if (_animation.HasRunningAnimation(uid, component.KeyName))
- {
- return;
- }
-
- if (!TryComp(uid, out var mover))
- {
- return;
- }
-
- if (_gravity.IsWeightless(uid))
- {
- return;
- }
-
- var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
- var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
-
- component.LastStep = !component.LastStep;
- component.IsCurrentlyWaddling = true;
-
- var anim = new Animation()
- {
- Length = TimeSpan.FromSeconds(len),
- AnimationTracks =
- {
- new AnimationTrackComponentProperty()
- {
- ComponentType = typeof(SpriteComponent),
- Property = nameof(SpriteComponent.Rotation),
- InterpolationMode = AnimationInterpolationMode.Linear,
- KeyFrames =
- {
- new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
- new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
- new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
- }
- },
- new AnimationTrackComponentProperty()
- {
- ComponentType = typeof(SpriteComponent),
- Property = nameof(SpriteComponent.Offset),
- InterpolationMode = AnimationInterpolationMode.Linear,
- KeyFrames =
- {
- new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
- new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
- new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
- }
- }
- }
- };
-
- _animation.Play(uid, anim, component.KeyName);
- }
-
- private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
- {
- _animation.Stop(uid, component.KeyName);
-
- if (!TryComp(uid, out var sprite))
- {
- return;
- }
-
- sprite.Offset = new Vector2();
- sprite.Rotation = Angle.FromDegrees(0);
- component.IsCurrentlyWaddling = false;
- }
-
- private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
- {
- var started = new StartedWaddlingEvent(uid);
-
- RaiseLocalEvent(uid, ref started);
- }
-}
diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs
index 9e562b5dd37..2390d7219e5 100644
--- a/Content.Client/Overlays/EntityHealthBarOverlay.cs
+++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs
@@ -11,6 +11,8 @@
using Content.Client.UserInterface.Systems;
using Robust.Shared.Prototypes;
using static Robust.Shared.Maths.Color;
+using Content.Shared.Stealth;
+using Content.Shared.Stealth.Components;
namespace Content.Client.Overlays;
@@ -76,6 +78,11 @@ protected override void Draw(in OverlayDrawArgs args)
continue;
}
+ if (_entManager.TryGetComponent(uid, out var comp) && comp.Enabled)
+ {
+ continue;
+ }
+
// we use the status icon component bounds if specified otherwise use sprite
var bounds = _entManager.GetComponentOrNull(uid)?.Bounds ?? spriteComponent.Bounds;
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs
index a546cf4d828..d9510ca707f 100644
--- a/Content.Client/Overlays/ShowHealthIconsSystem.cs
+++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs
@@ -46,7 +46,7 @@ protected override void DeactivateInternal()
private void OnGetStatusIconsEvent(Entity entity, ref GetStatusIconsEvent args)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive || args.InContainer || args.HasStealthComponent)
return;
var healthIcons = DecideHealthIcons(entity);
diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs
index 58551b30c26..d5b03d6777d 100644
--- a/Content.Client/Overlays/ShowHungerIconsSystem.cs
+++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs
@@ -19,7 +19,7 @@ public override void Initialize()
private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent hungerComponent, ref GetStatusIconsEvent args)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive || args.InContainer || args.HasStealthComponent)
return;
var hungerIcons = DecideHungerIcon(uid, hungerComponent);
diff --git a/Content.Client/Overlays/ShowSecurityIconsSystem.cs b/Content.Client/Overlays/ShowSecurityIconsSystem.cs
index 7a4abd05e00..7b8ce26280c 100644
--- a/Content.Client/Overlays/ShowSecurityIconsSystem.cs
+++ b/Content.Client/Overlays/ShowSecurityIconsSystem.cs
@@ -27,7 +27,7 @@ public override void Initialize()
private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent @event)
{
- if (!IsActive || @event.InContainer)
+ if (!IsActive || @event.InContainer || @event.HasStealthComponent)
{
return;
}
diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs
index f9d6d0ab259..2b0a48438b4 100644
--- a/Content.Client/Overlays/ShowThirstIconsSystem.cs
+++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs
@@ -19,7 +19,7 @@ public override void Initialize()
private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent thirstComponent, ref GetStatusIconsEvent args)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive || args.InContainer || args.HasStealthComponent)
return;
var thirstIcons = DecideThirstIcon(uid, thirstComponent);
diff --git a/Content.Client/Revolutionary/UI/DeconvertedEui.cs b/Content.Client/Revolutionary/UI/DeconvertedEui.cs
deleted file mode 100644
index 33495f8a974..00000000000
--- a/Content.Client/Revolutionary/UI/DeconvertedEui.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Content.Client.Eui;
-
-namespace Content.Client.Revolutionary.UI;
-
-public sealed class DeconvertedEui : BaseEui
-{
- private readonly DeconvertedMenu _menu;
-
- public DeconvertedEui()
- {
- _menu = new DeconvertedMenu();
- }
-
- public override void Opened()
- {
- _menu.OpenCentered();
- }
-
- public override void Closed()
- {
- base.Closed();
-
- _menu.Close();
- }
-}
diff --git a/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml b/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml
deleted file mode 100644
index 14b31c1950d..00000000000
--- a/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml.cs b/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml.cs
deleted file mode 100644
index ed32473a88a..00000000000
--- a/Content.Client/Revolutionary/UI/DeconvertedMenu.xaml.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Client.UserInterface.Controls;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.XAML;
-
-namespace Content.Client.Revolutionary.UI;
-
-[GenerateTypedNameReferences]
-public sealed partial class DeconvertedMenu : FancyWindow
-{
- public DeconvertedMenu()
- {
- RobustXamlLoader.Load(this);
-
- ConfirmButton.OnPressed += _ => Close();
- }
-}
diff --git a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
index 587450a2f66..4cb574378ae 100644
--- a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
+++ b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
@@ -1,4 +1,4 @@
-using Content.Shared.CCVar;
+using Content.Shared.CCVar;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC;
@@ -31,6 +31,7 @@ private void OnGetStatusIcon(EntityUid uid, SSDIndicatorComponent component, ref
if (component.IsSSD &&
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
!args.InContainer &&
+ !args.HasStealthComponent &&
!_mobState.IsDead(uid) &&
!HasComp(uid) &&
TryComp(uid, out var mindContainer) &&
diff --git a/Content.Client/StatusIcon/StatusIconSystem.cs b/Content.Client/StatusIcon/StatusIconSystem.cs
index 980fd9f2a92..8b67be3d5b4 100644
--- a/Content.Client/StatusIcon/StatusIconSystem.cs
+++ b/Content.Client/StatusIcon/StatusIconSystem.cs
@@ -1,6 +1,7 @@
using Content.Shared.CCVar;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
+using Content.Shared.Stealth.Components;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
@@ -13,6 +14,7 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
private bool _globalEnabled;
private bool _localEnabled;
@@ -55,7 +57,8 @@ public List GetStatusIcons(EntityUid uid, MetaDataComponent? met
return list;
var inContainer = (meta.Flags & MetaDataFlags.InContainer) != 0;
- var ev = new GetStatusIconsEvent(list, inContainer);
+ var hasStealthComponent = _entManager.HasComponent(uid);
+ var ev = new GetStatusIconsEvent(list, inContainer, hasStealthComponent);
RaiseLocalEvent(uid, ref ev);
return ev.StatusIcons;
}
diff --git a/Content.Client/UserInterface/Systems/Radial/Controls/RadialContainer.xaml.cs b/Content.Client/UserInterface/Systems/Radial/Controls/RadialContainer.xaml.cs
index 0901d6515e2..d3587492f37 100644
--- a/Content.Client/UserInterface/Systems/Radial/Controls/RadialContainer.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Radial/Controls/RadialContainer.xaml.cs
@@ -35,12 +35,12 @@ public partial class RadialContainer : Control
///
/// Radial item size, when cursor was focused on button
///
- public float FocusSize { get; set; } = 64f;
+ public float FocusSize { get; set; } = 82f;
///
/// Normal radial item size, when cursor not focused
///
- public float NormalSize { get; set; } = 50f;
+ public float NormalSize { get; set; } = 64f;
///
/// Items moving animation time, when radial was opened
@@ -136,7 +136,7 @@ public void OpenCentered()
if (Parent == null)
return;
- LayoutContainer.SetPosition(this, (Parent.Size/2) - (Size/2));
+ LayoutContainer.SetPosition(this, (Parent.Size / 2) - (Size / 2));
UpdateButtons();
}
@@ -156,7 +156,7 @@ public void OpenCenteredAt(Vector2 position)
if (Parent == null)
return;
- LayoutContainer.SetPosition(this, (Parent.Size * position) - (Size/2));
+ LayoutContainer.SetPosition(this, (Parent.Size * position) - (Size / 2));
UpdateButtons();
}
@@ -211,12 +211,13 @@ public RadialItem AddButton(string action, string? texturePath = null)
/// Item content text
/// Item's icon texture
///
- public RadialItem AddButton(string action, Texture? texture)
+ public RadialItem AddButton(string action, Texture? texture, SpriteView spriteView = default!)
{
var button = new RadialItem();
button.Content = action;
button.Controller.TexturePath = ItemBackgroundTexturePath;
-
+ if (spriteView != null)
+ button.EntityView.SetEntity(spriteView.NetEnt != null ? spriteView.NetEnt.Value : NetEntity.Invalid);
if (texture != null)
button.Icon.Texture = texture;
@@ -229,13 +230,13 @@ private void UpdateButtons()
{
Visible = true;
- var angleDegrees = 360/Layout.ChildCount;
+ var angleDegrees = 360 / Layout.ChildCount;
var stepAngle = -angleDegrees + -90;
var distance = GetDistance();
foreach (var child in Layout.Children)
{
- var button = (RadialItem)child;
+ var button = (RadialItem) child;
button.ButtonSize = new Vector2(NormalSize, NormalSize);
stepAngle += angleDegrees;
var pos = GetPointFromPolar(stepAngle, distance);
@@ -329,8 +330,8 @@ protected override void Draw(DrawingHandleScreen handle)
foreach (var child in Layout.Children)
{
- var button = (RadialItem)child;
- LayoutContainer.SetPosition(child, button.Offset - (button.Size/2));
+ var button = (RadialItem) child;
+ LayoutContainer.SetPosition(child, button.Offset - (button.Size / 2));
}
// FIXME: We use item's offset like "local item position" for animation. Need make some better way to do it;
diff --git a/Content.Client/UserInterface/Systems/Radial/Controls/RadialItem.xaml b/Content.Client/UserInterface/Systems/Radial/Controls/RadialItem.xaml
index 1edfc504a25..e3af6fb2b8a 100644
--- a/Content.Client/UserInterface/Systems/Radial/Controls/RadialItem.xaml
+++ b/Content.Client/UserInterface/Systems/Radial/Controls/RadialItem.xaml
@@ -6,6 +6,10 @@
Access="Public"
VerticalExpand="True"
HorizontalExpand="True">
+
+
{
var entity = sEntities.SpawnEntity(null, coordinates);
diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
index e8cfa673cd9..ddfe7b3481e 100644
--- a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
@@ -81,7 +81,7 @@ await server.WaitAssertion(() =>
.ToDictionary(x => x, _ => false);
foreach (var (reagent, quantity) in solution.Contents)
{
- Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.Prototype && x.Key.Value == quantity, out var foundProduct), Is.True);
+ Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.Prototype && x.Key.Value == quantity, out var foundProduct));
foundProductsMap[foundProduct.Value.Key] = true;
}
diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs
index dc46179c1c3..56892077422 100644
--- a/Content.IntegrationTests/Tests/PostMapInitTest.cs
+++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs
@@ -71,10 +71,11 @@ public sealed class PostMapInitTest
"ADTCore",
"ADTMarathon",
"ADTAtlas",
-
+ "ADTDelta",
+ "ADTTrain",
+ "ADTAvrite",
// Corvax-Start
"CorvaxAvrite",
- "CorvaxDelta",
// Corvax-End
"Dev",
diff --git a/Content.Server/ADT/Changeling/EntitySystems/ChangelingSystem.cs b/Content.Server/ADT/Changeling/EntitySystems/ChangelingSystem.cs
index fcbe163324e..a556cb7852d 100644
--- a/Content.Server/ADT/Changeling/EntitySystems/ChangelingSystem.cs
+++ b/Content.Server/ADT/Changeling/EntitySystems/ChangelingSystem.cs
@@ -37,7 +37,17 @@
using Content.Shared.Weapons.Melee;
using Content.Shared.Sirena.CollectiveMind;
using Content.Shared.Effects;
-
+using System.Linq;
+using Content.Shared.Weapons.Ranged.Events;
+using Content.Shared.Preferences;
+using Content.Server.Database;
+using Content.Server.Humanoid;
+using FastAccessors;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Shared.Utility;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.Hallucinations;
+using Content.Server.Hallucinations;
namespace Content.Server.Changeling.EntitySystems;
@@ -64,6 +74,7 @@ public sealed partial class ChangelingSystem : EntitySystem
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly FlashSystem _flashSystem = default!;
+ [Dependency] private readonly HallucinationsSystem _hallucinations = default!;
public override void Initialize()
{
@@ -77,9 +88,21 @@ public override void Initialize()
SubscribeLocalEvent(OnCycleDNA);
SubscribeLocalEvent(OnTransform);
+ SubscribeNetworkEvent(OnSelectChangelingForm);
+
InitializeLingAbilities();
}
+ private void OnSelectChangelingForm(SelectChangelingFormEvent ev)
+ {
+ var uid = GetEntity(ev.Target);
+
+ if (!TryComp(uid, out var comp))
+ return;
+
+ TransformChangeling(uid, comp, ev);
+ }
+
private void OnStartup(EntityUid uid, ChangelingComponent component, ComponentStartup args)
{
//RemComp(uid); // TODO: Исправить проблему с волосами слаймов
@@ -98,6 +121,9 @@ private void OnStartup(EntityUid uid, ChangelingComponent component, ComponentSt
[ValidatePrototypeId]
public const string ChangelingShopPresetPrototype = "StorePresetChangeling";
+ [ValidatePrototypeId]
+ public const string ChangelingHallucinationsPrototype = "Changeling";
+
public bool ChangeChemicalsAmount(EntityUid uid, float amount, ChangelingComponent? component = null, bool regenCap = true)
{
if (!Resolve(uid, ref component))
@@ -156,8 +182,6 @@ private void OnMapInit(EntityUid uid, ChangelingComponent component, MapInitEven
_action.AddAction(uid, ref component.ChangelingAbsorbActionEntity, component.ChangelingAbsorbAction);
_action.AddAction(uid, ref component.ChangelingDNAStingActionEntity, component.ChangelingDNAStingAction);
_action.AddAction(uid, ref component.ChangelingDNACycleActionEntity, component.ChangelingDNACycleAction);
- _action.AddAction(uid, ref component.ChangelingTransformActionEntity, component.ChangelingTransformAction);
- _action.AddAction(uid, ref component.ChangelingStasisDeathActionEntity, component.ChangelingStasisDeathAction);
EnsureComp(uid);
var collectiveMind = EnsureComp(uid);
@@ -174,7 +198,6 @@ private void OnShutdown(EntityUid uid, ChangelingComponent component, ComponentS
_action.RemoveAction(uid, component.ChangelingAbsorbActionEntity);
_action.RemoveAction(uid, component.ChangelingDNAStingActionEntity);
_action.RemoveAction(uid, component.ChangelingDNACycleActionEntity);
- _action.RemoveAction(uid, component.ChangelingTransformActionEntity);
_action.RemoveAction(uid, component.ChangelingStasisDeathActionEntity);
RemComp(uid);
@@ -282,30 +305,171 @@ public bool StealDNA(EntityUid uid, EntityUid target, ChangelingComponent compon
return true;
}
- public void OnCycleDNA(EntityUid uid, ChangelingComponent component, ChangelingCycleDNAActionEvent args)
+ public void OnCycleDNA(EntityUid uid, ChangelingComponent component, ChangelingCycleDNAActionEvent args) ///radial-menu
{
if (args.Handled)
return;
- args.Handled = true;
- component.SelectedDNA += 1;
+ if (EntityManager.TryGetComponent(uid, out var actorComponent))
+ {
+ var ev = new RequestChangelingFormsMenuEvent(GetNetEntity(uid));
- if (component.StoredDNA.Count >= component.DNAStrandCap || component.SelectedDNA >= component.StoredDNA.Count)
- component.SelectedDNA = 0;
+ foreach (var item in component.StoredDNA)
+ {
+ var netEntity = GetNetEntity(item.EntityUid);
+ if (netEntity == null)
+ continue;
+ if (item.EntityUid == null)
+ continue;
+ HumanoidCharacterAppearance hca = new();
+ if (item.HumanoidAppearanceComponent == null)
+ continue;
+
+ if (item.HumanoidAppearanceComponent.MarkingSet.Markings.TryGetValue(Shared.Humanoid.Markings.MarkingCategories.FacialHair, out var facialHair))
+ if (facialHair.TryGetValue(0, out var marking))
+ {
+ hca = hca.WithFacialHairStyleName(marking.MarkingId);
+ hca = hca.WithFacialHairColor(marking.MarkingColors.First());
+ }
+ if (item.HumanoidAppearanceComponent.MarkingSet.Markings.TryGetValue(Shared.Humanoid.Markings.MarkingCategories.Hair, out var hair))
+ if (hair.TryGetValue(0, out var marking))
+ {
+ hca = hca.WithHairStyleName(marking.MarkingId);
+ hca = hca.WithHairColor(marking.MarkingColors.First());
+ }
- var selectedHumanoidData = component.StoredDNA[component.SelectedDNA];
- if (selectedHumanoidData.MetaDataComponent == null)
- {
- var selfFailMessage = Loc.GetString("changeling-nodna-saved");
- _popup.PopupEntity(selfFailMessage, uid, uid);
- return;
- }
+ hca = hca.WithSkinColor(item.HumanoidAppearanceComponent.SkinColor);
- var selfMessage = Loc.GetString("changeling-dna-switchdna", ("target", selectedHumanoidData.MetaDataComponent.EntityName));
- _popup.PopupEntity(selfMessage, uid, uid);
+ ev.HumanoidData.Add(new()
+ {
+ NetEntity = netEntity.Value,
+ Name = Name(item.EntityUid.Value),
+ Species = item.HumanoidAppearanceComponent.Species.Id,
+ Profile = new HumanoidCharacterProfile().WithCharacterAppearance(hca).WithSpecies(item.HumanoidAppearanceComponent.Species.Id)
+ });
+ }
+
+ // реализовать сортировку
+ RaiseNetworkEvent(ev, actorComponent.PlayerSession);
+ }
+ args.Handled = true;
}
+ public void TransformChangeling(EntityUid uid, ChangelingComponent component, SelectChangelingFormEvent ev)
+ {
+ int i = 0;
+ var selectedEntity = GetEntity(ev.EntitySelected);
+ foreach (var item in component.StoredDNA)
+ {
+ if (item.EntityUid == selectedEntity)
+ {
+ // transform
+ var selectedHumanoidData = component.StoredDNA[i];
+ if (ev.Handled)
+ return;
+
+ var dnaComp = EnsureComp(uid);
+
+ if (selectedHumanoidData.EntityPrototype == null)
+ {
+ var selfFailMessage = Loc.GetString("changeling-nodna-saved");
+ _popup.PopupEntity(selfFailMessage, uid, uid);
+ return;
+ }
+ if (selectedHumanoidData.HumanoidAppearanceComponent == null)
+ {
+ var selfFailMessage = Loc.GetString("changeling-nodna-saved");
+ _popup.PopupEntity(selfFailMessage, uid, uid);
+ return;
+ }
+ if (selectedHumanoidData.MetaDataComponent == null)
+ {
+ var selfFailMessage = Loc.GetString("changeling-nodna-saved");
+ _popup.PopupEntity(selfFailMessage, uid, uid);
+ return;
+ }
+ if (selectedHumanoidData.DNA == null)
+ {
+ var selfFailMessage = Loc.GetString("changeling-nodna-saved");
+ _popup.PopupEntity(selfFailMessage, uid, uid);
+ return;
+ }
+
+ if (selectedHumanoidData.DNA == dnaComp.DNA)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-already", ("target", selectedHumanoidData.MetaDataComponent.EntityName));
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+
+ else if (component.ArmBladeActive)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-mutation");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+ else if (component.LingArmorActive)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-mutation");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+ else if (component.ChameleonSkinActive)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-mutation");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+ else if (component.MusclesActive)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-mutation");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+ else if (component.LesserFormActive)
+ {
+ var selfMessage = Loc.GetString("changeling-transform-fail-lesser-form");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ }
+
+ else
+ {
+ if (!TryUseAbility(uid, component, component.ChemicalsCostFive))
+ return;
+
+ ev.Handled = true;
+
+ var transformedUid = _polymorph.PolymorphEntityAsHumanoid(uid, selectedHumanoidData);
+ if (transformedUid == null)
+ return;
+
+ var selfMessage = Loc.GetString("changeling-transform-activate", ("target", selectedHumanoidData.MetaDataComponent.EntityName));
+ _popup.PopupEntity(selfMessage, transformedUid.Value, transformedUid.Value);
+
+ var newLingComponent = EnsureComp(transformedUid.Value);
+ newLingComponent.Chemicals = component.Chemicals;
+ newLingComponent.ChemicalsPerSecond = component.ChemicalsPerSecond;
+ newLingComponent.StoredDNA = component.StoredDNA;
+ newLingComponent.SelectedDNA = component.SelectedDNA;
+ newLingComponent.ArmBladeActive = component.ArmBladeActive;
+ newLingComponent.ChameleonSkinActive = component.ChameleonSkinActive;
+ newLingComponent.LingArmorActive = component.LingArmorActive;
+ newLingComponent.CanRefresh = component.CanRefresh;
+ newLingComponent.AbsorbedDnaModifier = component.AbsorbedDnaModifier;
+ RemComp(uid, component);
+
+ if (TryComp(uid, out StoreComponent? storeComp))
+ {
+ var copiedStoreComponent = (Component) _serialization.CreateCopy(storeComp, notNullableOverride: true);
+ RemComp(transformedUid.Value);
+ EntityManager.AddComponent(transformedUid.Value, copiedStoreComponent);
+ }
+
+ _actionContainer.TransferAllActionsWithNewAttached(uid, transformedUid.Value, transformedUid.Value);
+
+ if (!TryComp(transformedUid.Value, out InventoryComponent? inventory))
+ return;
+ }
+ }
+ i++;
+ }
+ }
public void OnTransform(EntityUid uid, ChangelingComponent component, ChangelingTransformActionEvent args)
{
var selectedHumanoidData = component.StoredDNA[component.SelectedDNA];
@@ -373,42 +537,41 @@ public void OnTransform(EntityUid uid, ChangelingComponent component, Changeling
else
{
- if (!TryUseAbility(uid, component, component.ChemicalsCostFive))
- return;
-
- args.Handled = true;
+ if (!TryUseAbility(uid, component, component.ChemicalsCostFive))
+ return;
- var transformedUid = _polymorph.PolymorphEntityAsHumanoid(uid, selectedHumanoidData);
- if (transformedUid == null)
- return;
+ args.Handled = true;
- var selfMessage = Loc.GetString("changeling-transform-activate", ("target", selectedHumanoidData.MetaDataComponent.EntityName));
- _popup.PopupEntity(selfMessage, transformedUid.Value, transformedUid.Value);
+ var transformedUid = _polymorph.PolymorphEntityAsHumanoid(uid, selectedHumanoidData);
+ if (transformedUid == null)
+ return;
- var newLingComponent = EnsureComp(transformedUid.Value);
- newLingComponent.Chemicals = component.Chemicals;
- newLingComponent.ChemicalsPerSecond = component.ChemicalsPerSecond;
- newLingComponent.StoredDNA = component.StoredDNA;
- newLingComponent.SelectedDNA = component.SelectedDNA;
- newLingComponent.ArmBladeActive = component.ArmBladeActive;
- newLingComponent.ChameleonSkinActive = component.ChameleonSkinActive;
- newLingComponent.LingArmorActive = component.LingArmorActive;
- newLingComponent.CanRefresh = component.CanRefresh;
- newLingComponent.AbsorbedDnaModifier = component.AbsorbedDnaModifier;
+ var selfMessage = Loc.GetString("changeling-transform-activate", ("target", selectedHumanoidData.MetaDataComponent.EntityName));
+ _popup.PopupEntity(selfMessage, transformedUid.Value, transformedUid.Value);
+
+ var newLingComponent = EnsureComp(transformedUid.Value);
+ newLingComponent.Chemicals = component.Chemicals;
+ newLingComponent.ChemicalsPerSecond = component.ChemicalsPerSecond;
+ newLingComponent.StoredDNA = component.StoredDNA;
+ newLingComponent.SelectedDNA = component.SelectedDNA;
+ newLingComponent.ArmBladeActive = component.ArmBladeActive;
+ newLingComponent.ChameleonSkinActive = component.ChameleonSkinActive;
+ newLingComponent.LingArmorActive = component.LingArmorActive;
+ newLingComponent.CanRefresh = component.CanRefresh;
+ newLingComponent.AbsorbedDnaModifier = component.AbsorbedDnaModifier;
RemComp(uid, component);
- if (TryComp(uid, out StoreComponent? storeComp))
- {
- var copiedStoreComponent = (Component) _serialization.CreateCopy(storeComp, notNullableOverride: true);
- RemComp(transformedUid.Value);
- EntityManager.AddComponent(transformedUid.Value, copiedStoreComponent);
- }
+ if (TryComp(uid, out StoreComponent? storeComp))
+ {
+ var copiedStoreComponent = (Component) _serialization.CreateCopy(storeComp, notNullableOverride: true);
+ RemComp(transformedUid.Value);
+ EntityManager.AddComponent(transformedUid.Value, copiedStoreComponent);
+ }
_actionContainer.TransferAllActionsWithNewAttached(uid, transformedUid.Value, transformedUid.Value);
if (!TryComp(transformedUid.Value, out InventoryComponent? inventory))
return;
-
}
}
public bool BlindSting(EntityUid uid, EntityUid target, ChangelingComponent component) /// Ослепление
@@ -474,21 +637,7 @@ public bool DrugSting(EntityUid uid, EntityUid target, ChangelingComponent compo
return false;
}
- if (HasComp(target))
- {
- var selfMessage = Loc.GetString("changeling-sting-fail-self", ("target", Identity.Entity(target, EntityManager)));
- _popup.PopupEntity(selfMessage, uid, uid);
-
- var targetMessage = Loc.GetString("changeling-sting-fail-target");
- _popup.PopupEntity(targetMessage, target, target);
- return false;
- }
-
- if (!_entityManager.TryGetComponent(target, out var bloodstream))
- return false;
-
- var drugInjection = new Solution(component.ChemicalSpaceDrugs, component.SpaceDrugsAmount);
- _bloodstreamSystem.TryAddToChemicals(target, drugInjection, bloodstream);
+ _hallucinations.StartHallucinations(target, "ADTHallucinations", TimeSpan.FromSeconds(30), true, ChangelingHallucinationsPrototype);
return true;
}
@@ -512,9 +661,6 @@ public bool Adrenaline(EntityUid uid, ChangelingComponent component) /// Ад
var adrenalineInjection = new Solution(component.ChemicalMorphine, component.AdrenalineAmount);
_bloodstreamSystem.TryAddToChemicals(uid, adrenalineInjection, bloodstream);
- var adrenalineInjectionTr = new Solution(component.ChemicalTranex, component.AdrenalineAmount);
- _bloodstreamSystem.TryAddToChemicals(uid, adrenalineInjectionTr, bloodstream);
-
return true;
}
diff --git a/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationSystem.cs b/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationSystem.cs
deleted file mode 100644
index 7b65b31f3c0..00000000000
--- a/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationSystem.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using Content.Server.Mind;
-using Content.Server.Roles.Jobs;
-using Content.Shared.Actions;
-using Content.Shared.Eye;
-using Content.Shared.Follower;
-using Content.Shared.Mind;
-using Content.Shared.Mobs.Systems;
-using Robust.Server.GameObjects;
-using Robust.Server.Player;
-using Content.Shared.NarcoticEffects.Components;
-using Robust.Shared.Physics.Systems;
-using Content.Shared.Changeling.Components;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Changeling;
-
-public sealed class LingHallucinationSystem : EntitySystem
-{
- [Dependency] private readonly SharedEyeSystem _eye = default!;
- [Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnShutdown);
- }
-
-
- private void OnMapInit(EntityUid uid, LingHallucinationComponent component, MapInitEvent args)
- {
- // Allow this entity to be seen by other ghosts.
- var visibility = EnsureComp(uid);
-
- _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
- _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
- }
-
- private void OnShutdown(EntityUid uid, LingHallucinationComponent component, ComponentShutdown args)
- {
- // Perf: If the entity is deleting itself, no reason to change these back.
- if (Terminating(uid))
- return;
-
- // Entity can't be seen by ghosts anymore.
- if (TryComp(uid, out VisibilityComponent? visibility))
- {
- _visibilitySystem.RemoveLayer(uid, visibility, component.Layer, false);
- _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
- _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
- if (!_entityManager.TryGetComponent(uid, out var eye))
- return;
-
- _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~component.Layer, eye);
- _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
- }
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var stat, out var xform))
- {
- TryComp(uid, out var curVisibility);
- if (curVisibility != null)
- {
- if (stat.Layer == curVisibility.Layer)
- return;
- }
- // Allow this entity to be seen by other ghosts.
- var visibility = EnsureComp(uid);
-
- _visibilitySystem.AddLayer(uid, visibility, stat.Layer, false);
- _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
- _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
- if (!_entityManager.TryGetComponent(uid, out var eye))
- return;
-
- _eye.SetVisibilityMask(uid, eye.VisibilityMask | stat.Layer, eye);
- _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
- }
- }
-
-}
diff --git a/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationsSystem.cs b/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationsSystem.cs
deleted file mode 100644
index 048c94b4c11..00000000000
--- a/Content.Server/ADT/Changeling/EntitySystems/LingHallucinationsSystem.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using Content.Server.Actions;
-using Content.Server.Store.Systems;
-using Robust.Shared.Audio.Systems;
-using Content.Shared.Popups;
-using Content.Shared.Humanoid;
-using Content.Shared.Mobs.Systems;
-using Content.Server.Polymorph.Systems;
-using Content.Shared.Actions;
-using Robust.Shared.Serialization.Manager;
-using Content.Shared.Alert;
-using Content.Shared.Tag;
-using Content.Shared.StatusEffect;
-using Robust.Shared.Timing;
-using Content.Shared.Eye;
-using Content.Shared.Movement.Systems;
-using Content.Shared.Database;
-using Content.Shared.Changeling.Components;
-using Robust.Server.GameObjects;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-using Content.Server.Mind;
-using Content.Shared.Administration.Logs;
-
-namespace Content.Server.Changeling.EntitySystems;
-
-public sealed partial class LingHallucinationsSystem : EntitySystem
-{
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly SharedEyeSystem _eye = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly MindSystem _mindSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
-
- public static string HallucinatingKey = "Hallucinations";
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnHallucinationsInit);
- SubscribeLocalEvent(OnHallucinationsShutdown);
- }
-
- private void OnHallucinationsInit(EntityUid uid, LingHallucinationsComponent component, MapInitEvent args)
- {
- component.Layer = _random.Next(50, 500);
- if (!_entityManager.TryGetComponent(uid, out var eye))
- return;
- _eye.SetVisibilityMask(uid, eye.VisibilityMask | component.Layer, eye);
-
- _adminLogger.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(uid):player} began to hallucinate.");
- }
-
- private void OnHallucinationsShutdown(EntityUid uid, LingHallucinationsComponent component, ComponentShutdown args)
- {
- if (!_entityManager.TryGetComponent(uid, out var eye))
- return;
- _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~component.Layer, eye);
- _adminLogger.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(uid):player} stopped hallucinating.");
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var stat, out var xform))
- {
- if (_timing.CurTime < stat.NextSecond)
- continue;
- var rate = stat.SpawnRate;
- stat.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(stat.SpawnRate);
-
- if (!_random.Prob(stat.Chance))
- continue;
-
- var range = stat.Range * 4;
-
- foreach (var (ent, comp) in _lookup.GetEntitiesInRange(xform.MapPosition, range))
- {
- var newCoords = Transform(ent).MapPosition.Offset(_random.NextVector2(stat.Range));
-
- var hallucination = Spawn(_random.Pick(stat.Spawns), newCoords);
- EnsureComp(hallucination, out var visibility);
- _visibilitySystem.SetLayer(hallucination, visibility, (int) stat.Layer, false);
- _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility);
- }
-
- var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range));
-
- var uidhallucination = Spawn(_random.Pick(stat.Spawns), uidnewCoords);
- EnsureComp(uidhallucination, out var uidvisibility);
- _visibilitySystem.SetLayer(uidhallucination, uidvisibility, (int) stat.Layer, false);
- _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility);
- }
- }
-}
diff --git a/Content.Server/ADT/Chaplain/AcceptReligionEui.cs b/Content.Server/ADT/Chaplain/AcceptReligionEui.cs
new file mode 100644
index 00000000000..91aef1dbe45
--- /dev/null
+++ b/Content.Server/ADT/Chaplain/AcceptReligionEui.cs
@@ -0,0 +1,38 @@
+using Content.Server.EUI;
+using Content.Shared.Chaplain;
+using Content.Shared.Eui;
+using Content.Shared.Bible.Components;
+using Content.Server.Bible;
+
+namespace Content.Server.Chaplain;
+
+public sealed class AcceptReligionEui : BaseEui
+{
+ private readonly EntityUid _uid;
+ private readonly EntityUid _target;
+ private readonly ChaplainComponent _comp;
+ private readonly ChaplainSystem _chaplain;
+
+ public AcceptReligionEui(EntityUid uid, EntityUid target, ChaplainComponent comp, ChaplainSystem chaplain)
+ {
+ _uid = uid;
+ _target = target;
+ _comp = comp;
+ _chaplain = chaplain;
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ if (msg is not AcceptReligionChoiceMessage choice ||
+ choice.Button == AcceptReligionButton.Deny)
+ {
+ Close();
+ return;
+ }
+
+ _chaplain.MakeBeliever(_uid, _target, _comp);
+ Close();
+ }
+}
diff --git a/Content.Server/ADT/Chaplain/ChaplainSystem.cs b/Content.Server/ADT/Chaplain/ChaplainSystem.cs
new file mode 100644
index 00000000000..1e97a9f613d
--- /dev/null
+++ b/Content.Server/ADT/Chaplain/ChaplainSystem.cs
@@ -0,0 +1,381 @@
+using Content.Server.Bible.Components;
+using Content.Server.Ghost.Roles.Components;
+using Content.Server.Ghost.Roles.Events;
+using Content.Server.Popups;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Actions;
+using Content.Shared.Bible;
+using Content.Shared.Bible.Components;
+using Content.Shared.Damage;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Inventory;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Popups;
+using Content.Shared.Timing;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+using Content.Shared.FixedPoint;
+using Content.Shared.Alert;
+using Content.Shared.DoAfter;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Server.Chemistry.Containers.EntitySystems;
+using Content.Server.Chemistry.ReagentEffects;
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
+using Content.Shared.Phantom.Components;
+using Content.Shared.Revenant.Components;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.Humanoid;
+using Content.Server.EUI;
+using Content.Shared.Mind;
+using Content.Server.Chaplain;
+
+namespace Content.Server.Bible;
+
+public sealed class ChaplainSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+ [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+ [Dependency] private readonly InventorySystem _invSystem = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly UseDelaySystem _delay = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly EuiManager _euiManager = null!;
+ [Dependency] private readonly SharedMindSystem _mindSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnShutdown);
+
+ SubscribeLocalEvent(OnMakeBeliever);
+ SubscribeLocalEvent(OnMakeBelieverDoAfter);
+
+ SubscribeLocalEvent(OnPrayersHand);
+ SubscribeLocalEvent(OnPrayersHandDoAfter);
+
+ SubscribeLocalEvent(OnHolyTouch);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (comp.Power >= comp.PowerRegenCap)
+ {
+ comp.Accumulator = 0f;
+ continue;
+ }
+
+
+ comp.Accumulator += frameTime;
+
+ if (comp.Accumulator <= comp.RegenDelay)
+ continue;
+
+ comp.Accumulator -= comp.RegenDelay;
+
+ if (comp.Power < comp.PowerRegenCap)
+ {
+ ChangePowerAmount(uid, 1, comp);
+ }
+ }
+ }
+
+ private void OnStartup(EntityUid uid, ChaplainComponent component, ComponentStartup args)
+ {
+ //update the icon
+ ChangePowerAmount(uid, 0, component);
+ }
+
+ private void OnMapInit(EntityUid uid, ChaplainComponent component, MapInitEvent args)
+ {
+ _actionsSystem.AddAction(uid, ref component.BelieverActionEntity, "ActionChaplainBeliever");
+ _actionsSystem.AddAction(uid, ref component.TransferActionEntity, "ActionChaplainTransfer");
+ _actionsSystem.AddAction(uid, ref component.HolyTouchActionEntity, "ActionChaplainHolyWater");
+ }
+
+ private void OnShutdown(EntityUid uid, ChaplainComponent component, ComponentShutdown args)
+ {
+ _actionsSystem.RemoveAction(uid, component.BelieverActionEntity);
+ _actionsSystem.RemoveAction(uid, component.TransferActionEntity);
+ _actionsSystem.RemoveAction(uid, component.HolyTouchActionEntity);
+ }
+
+ public bool ChangePowerAmount(EntityUid uid, FixedPoint2 amount, ChaplainComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (component.Power + amount < 0)
+ return false;
+
+ component.Power += amount;
+
+ FixedPoint2.Min(component.Power, component.PowerRegenCap);
+
+ _alerts.ShowAlert(uid, AlertType.ADTChaplain, (short) Math.Clamp(Math.Round(component.Power.Float()), 0, 5));
+
+ return true;
+ }
+
+ public bool TryUseAbility(EntityUid uid, ChaplainComponent component, FixedPoint2 abilityCost)
+ {
+ var cost = -abilityCost;
+ if (component.Power < abilityCost)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-not-enough-power"), uid, uid);
+ return false;
+ }
+
+ ChangePowerAmount(uid, cost, component);
+
+ return true;
+ }
+
+ private void OnMakeBeliever(EntityUid uid, ChaplainComponent component, ChaplainMakeBelieverActionEvent args)
+ {
+ var target = args.Target;
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+
+ if (!TryUseAbility(uid, component, component.BelieverCost))
+ return;
+
+ if (HasComp(target))
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-already", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+
+ if (!HasComp(target))
+ {
+ return;
+ }
+
+ if (component.Believers >= component.MaxBelievers)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-too-much"), uid, uid);
+ return;
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-start-self", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-start-target", ("issuer", Identity.Entity(uid, EntityManager))), target, target);
+
+ var doAfter = new DoAfterArgs(EntityManager, uid, component.MakeBelieverDuration, new ChaplainMakeBelieverDoAfter(), uid, target: target)
+ {
+ DistanceThreshold = 2,
+ BreakOnUserMove = true,
+ BreakOnTargetMove = true,
+ BreakOnDamage = true,
+ AttemptFrequency = AttemptFrequency.StartAndEnd
+ };
+
+ _doAfter.TryStartDoAfter(doAfter);
+
+ }
+
+ private void OnMakeBelieverDoAfter(EntityUid uid, ChaplainComponent component, ChaplainMakeBelieverDoAfter args)
+ {
+ if (args.Handled || args.Args.Target == null)
+ return;
+ if (args.Cancelled)
+ {
+ ChangePowerAmount(uid, component.BelieverCost, component);
+ return;
+ }
+ if (component.Believers >= component.MaxBelievers)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-too-much"), uid, uid);
+ return;
+ }
+
+ args.Handled = true;
+ var target = args.Args.Target.Value;
+ if (_mindSystem.TryGetMind(target, out var mindId, out var mind) && mind.Session != null)
+ _euiManager.OpenEui(new AcceptReligionEui(uid, target, component, this), mind.Session);
+ }
+
+ public void MakeBeliever(EntityUid uid, EntityUid target, ChaplainComponent component)
+ {
+ component.Believers += 1;
+ var newComponent = EnsureComp(target);
+ newComponent.MaxBelievers = 0;
+ newComponent.PowerRegenCap = 2;
+ newComponent.Power = 2;
+ newComponent.TransferDuration = component.TransferDuration * 2;
+ newComponent.PowerPerPray = 1;
+ ChangePowerAmount(target, 0, newComponent);
+
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-success-self", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-believer-success-target", ("issuer", Identity.Entity(uid, EntityManager))), target, target);
+ }
+ private void OnPrayersHand(EntityUid uid, ChaplainComponent component, ChaplainPrayersHandActionEvent args)
+ {
+ var target = args.Target;
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+
+ if (!TryComp(target, out var chaplain))
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-not-chaplain", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+
+ if (chaplain.Power >= chaplain.PowerRegenCap)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-full", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+
+ if (component.Power < component.PowerTransfered)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-not-enough-power"), uid, uid);
+ return;
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-start-self", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-start-target", ("issuer", Identity.Entity(uid, EntityManager))), target, target);
+
+ var doAfter = new DoAfterArgs(EntityManager, uid, component.MakeBelieverDuration, new ChaplainPrayersHandDoAfter(), uid, target: target)
+ {
+ DistanceThreshold = 2,
+ BreakOnUserMove = true,
+ BreakOnTargetMove = true,
+ BreakOnDamage = true,
+ AttemptFrequency = AttemptFrequency.StartAndEnd
+ };
+
+ _doAfter.TryStartDoAfter(doAfter);
+
+ }
+
+ private void OnPrayersHandDoAfter(EntityUid uid, ChaplainComponent component, ChaplainPrayersHandDoAfter args)
+ {
+ if (args.Handled || args.Args.Target == null)
+ return;
+
+ args.Handled = true;
+ var target = args.Args.Target.Value;
+
+ if (!TryComp(target, out var chaplain))
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-not-chaplain", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+
+ if (chaplain.Power >= chaplain.PowerRegenCap)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-full", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+
+ if (component.Power < component.PowerTransfered)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-not-enough-power"), uid, uid);
+ return;
+ }
+
+ ChangePowerAmount(target, component.PowerTransfered, chaplain);
+ ChangePowerAmount(uid, -component.PowerTransfered, component);
+
+
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-success-self", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-transfer-success-target", ("issuer", Identity.Entity(uid, EntityManager))), target, target);
+ }
+ public ProtoId BruteDamageGroup = "Brute";
+ private void OnHolyTouch(EntityUid uid, ChaplainComponent component, ChaplainHolyTouchActionEvent args)
+ {
+ var target = args.Target;
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+
+ if (!TryUseAbility(uid, component, component.HolyWaterCost))
+ return;
+
+ if (TryComp(target, out var solutionContainer) && solutionContainer.Containers != null && !HasComp(target))
+ {
+ bool success = false;
+ foreach (var sol in solutionContainer.Containers)
+ {
+ if (_solutionContainer.TryGetSolution((target, solutionContainer), sol, out var soln, out var solution))
+ {
+ var water = component.WaterSolution;
+ var blood = component.BloodSolution;
+ var waterQuantity = solution.GetTotalPrototypeQuantity(water);
+ var bloodQuantity = solution.GetTotalPrototypeQuantity(blood);
+ if (waterQuantity != FixedPoint2.Zero)
+ {
+ solution.RemoveReagent(water, waterQuantity);
+ solution.AddReagent(component.WaterReplaceSolution, waterQuantity);
+ success = true;
+ }
+ if (bloodQuantity != FixedPoint2.Zero)
+ {
+ solution.RemoveReagent(blood, bloodQuantity);
+ solution.AddReagent(component.BloodReplaceSolution, bloodQuantity);
+ success = true;
+ }
+ _solutionContainer.UpdateChemicals(soln.Value, false);
+ }
+ }
+
+ if (success)
+ {
+
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-holy-water-success", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _audio.PlayPvs(component.HolyWaterSoundPath, uid);
+ }
+
+ else
+ {
+ ChangePowerAmount(uid, component.HolyWaterCost, component);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-holy-water-nothing", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ return;
+ }
+ }
+
+ if (HasComp(target) || HasComp(target))
+ {
+ var damage = new DamageSpecifier(_proto.Index(BruteDamageGroup), 50);
+ var damageSelf = new DamageSpecifier(_proto.Index(BruteDamageGroup), 10);
+ _damageableSystem.TryChangeDamage(target, damage);
+ _damageableSystem.TryChangeDamage(uid, damageSelf);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-cursed-touch"), uid, uid, PopupType.LargeCaution);
+ }
+ else
+ {
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-holy-touch-fail-self", ("target", Identity.Entity(target, EntityManager))), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("chaplain-holy-touch-fail-target", ("issuer", Identity.Entity(uid, EntityManager))), target, target);
+ }
+ }
+}
diff --git a/Content.Server/ADT/GhostInteractions/GhostRadioSystem.cs b/Content.Server/ADT/GhostInteractions/GhostRadioSystem.cs
new file mode 100644
index 00000000000..b723e5249c6
--- /dev/null
+++ b/Content.Server/ADT/GhostInteractions/GhostRadioSystem.cs
@@ -0,0 +1,62 @@
+using System.Linq;
+using Content.Server.Popups;
+using Content.Server.PowerCell;
+using Content.Shared.Hands;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Inventory;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Language;
+using Content.Shared.Language.Components;
+using Content.Shared.Language.Systems;
+using Content.Shared.PowerCell;
+using Content.Shared.GhostInteractions;
+
+namespace Content.Server.GhostInteractions;
+
+// this does not support holding multiple translators at once yet.
+// that should not be an issue for now, but it better get fixed later.
+public sealed class GhostRadioSystem : SharedGhostRadioSystem
+{
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly PowerCellSystem _powerCell = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnTranslatorToggle);
+ SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ }
+
+ private void OnTranslatorToggle(EntityUid translator, GhostRadioComponent component, ActivateInWorldEvent args)
+ {
+ if (!component.ToggleOnInteract)
+ return;
+
+ var hasPower = _powerCell.HasDrawCharge(translator);
+
+ var isEnabled = !component.Enabled;
+
+ isEnabled &= hasPower;
+ component.Enabled = isEnabled;
+ _powerCell.SetPowerCellDrawEnabled(translator, isEnabled);
+
+ OnAppearanceChange(translator, component);
+
+ // HasPower shows a popup when there's no power, so we do not proceed in that case
+ if (hasPower)
+ {
+ var message =
+ Loc.GetString(component.Enabled ? "ghost-radio-component-turnon" : "ghost-radio-component-shutoff");
+ _popup.PopupEntity(message, component.Owner, args.User);
+ }
+ }
+
+ private void OnPowerCellSlotEmpty(EntityUid translator, GhostRadioComponent component, PowerCellSlotEmptyEvent args)
+ {
+ component.Enabled = false;
+ _powerCell.SetPowerCellDrawEnabled(translator, false);
+ OnAppearanceChange(translator, component);
+ }
+}
diff --git a/Content.Server/ADT/GhostInteractions/OuijaBoardSetup.cs b/Content.Server/ADT/GhostInteractions/OuijaBoardSetup.cs
new file mode 100644
index 00000000000..1d2215f1216
--- /dev/null
+++ b/Content.Server/ADT/GhostInteractions/OuijaBoardSetup.cs
@@ -0,0 +1,24 @@
+using JetBrains.Annotations;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Content.Server.Tabletop;
+
+namespace Content.Server.OuijaBoard;
+
+[UsedImplicitly]
+public sealed partial class OuijaBoardSetup : TabletopSetup
+{
+
+ [DataField("prototypePiece", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string PrototypePiece = default!;
+
+ public override void SetupTabletop(TabletopSession session, IEntityManager entityManager)
+ {
+ session.Entities.Add(
+ entityManager.SpawnEntity(BoardPrototype, session.Position.Offset(0, 0))
+ );
+
+ entityManager.SpawnEntity(PrototypePiece, session.Position.Offset(-1, 0));
+ }
+}
diff --git a/Content.Server/ADT/GhostInteractions/OuijaBoardUserSystem.cs b/Content.Server/ADT/GhostInteractions/OuijaBoardUserSystem.cs
new file mode 100644
index 00000000000..b9997f5b0ff
--- /dev/null
+++ b/Content.Server/ADT/GhostInteractions/OuijaBoardUserSystem.cs
@@ -0,0 +1,55 @@
+using System.Linq;
+using Content.Server.Popups;
+using Content.Server.PowerCell;
+using Content.Shared.Hands;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Inventory;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Language;
+using Content.Shared.Language.Components;
+using Content.Shared.Language.Systems;
+using Content.Server.Tabletop;
+using Content.Shared.GhostInteractions;
+using Content.Server.Light.Components;
+using Content.Server.Ghost;
+using Content.Server.Tabletop.Components;
+using Robust.Shared.Player;
+
+namespace Content.Server.GhostInteractions;
+
+// this does not support holding multiple translators at once yet.
+// that should not be an issue for now, but it better get fixed later.
+public sealed class OuijaBoardUserSystem : EntitySystem
+{
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly TabletopSystem _tabletop = default!;
+ [Dependency] private readonly GhostSystem _ghost = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInteract);
+ }
+ private void OnInteract(EntityUid uid, OuijaBoardUserComponent component, InteractNoHandEvent args)
+ {
+ if (args.Target == args.User || args.Target == null)
+ return;
+ var target = args.Target.Value;
+
+ if (HasComp(target))
+ {
+ args.Handled = _ghost.DoGhostBooEvent(target);
+ return;
+ }
+ if (HasComp(target) && HasComp(target))
+ {
+ if (!TryComp(uid, out var actor))
+ return;
+
+ _tabletop.OpenSessionFor(actor.PlayerSession, target);
+ }
+ }
+
+}
diff --git a/Content.Server/ADT/Hallucinations/HallucinationsEffect.cs b/Content.Server/ADT/Hallucinations/HallucinationsEffect.cs
new file mode 100644
index 00000000000..a9005a1b4fb
--- /dev/null
+++ b/Content.Server/ADT/Hallucinations/HallucinationsEffect.cs
@@ -0,0 +1,68 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Server.Hallucinations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+using Content.Shared.Hallucinations;
+using Content.Shared.StatusEffect;
+
+namespace Content.Server.Chemistry.ReagentEffects
+{
+ ///
+ /// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target,
+ /// adding one if not there and to change the movespeed
+ ///
+ public sealed partial class HallucinationsReagentEffect : ReagentEffect
+ {
+ [DataField("key")]
+ public string Key = "ADTHallucinations";
+
+ [DataField(required: true)]
+ public string Proto = String.Empty;
+
+ [DataField]
+ public float Time = 2.0f;
+
+ [DataField]
+ public bool Refresh = true;
+
+ [DataField]
+ public HallucinationsMetabolismType Type = HallucinationsMetabolismType.Add;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return Loc.GetString("reagent-effect-guidebook-hallucinations",
+ ("chance", Probability),
+ ("time", Time));
+ }
+
+ public override void Effect(ReagentEffectArgs args)
+ {
+ var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem();
+ var hallucinationsSys = args.EntityManager.EntitySysManager.GetEntitySystem();
+
+ var time = Time;
+ time *= args.Scale;
+
+ if (Type == HallucinationsMetabolismType.Add)
+ {
+ if (!hallucinationsSys.StartHallucinations(args.SolutionEntity, Key, TimeSpan.FromSeconds(Time), Refresh, Proto))
+ return;
+ }
+ else if (Type == HallucinationsMetabolismType.Remove)
+ {
+ statusSys.TryRemoveTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
+ }
+ else if (Type == HallucinationsMetabolismType.Set)
+ {
+ statusSys.TrySetTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
+ }
+ }
+ }
+ public enum HallucinationsMetabolismType
+ {
+ Add,
+ Remove,
+ Set
+ }
+}
diff --git a/Content.Server/ADT/Hallucinations/HallucinationsSystem.cs b/Content.Server/ADT/Hallucinations/HallucinationsSystem.cs
new file mode 100644
index 00000000000..dcc2d773901
--- /dev/null
+++ b/Content.Server/ADT/Hallucinations/HallucinationsSystem.cs
@@ -0,0 +1,242 @@
+using Robust.Shared.Audio.Systems;
+using Content.Shared.Humanoid;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Timing;
+using Content.Shared.Database;
+using Content.Shared.Hallucinations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Content.Server.Chat;
+using Content.Server.Chat.Systems;
+using Content.Shared.Speech;
+using Content.Shared.Administration.Logs;
+
+namespace Content.Server.Hallucinations;
+
+public sealed partial class HallucinationsSystem : EntitySystem
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedEyeSystem _eye = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ public static string HallucinatingKey = "Hallucinations";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHallucinationsInit);
+ SubscribeLocalEvent(OnHallucinationsShutdown);
+ SubscribeLocalEvent(OnHallucinationsDiseaseInit);
+ SubscribeLocalEvent(OnEntitySpoke);
+
+ }
+
+ private void OnHallucinationsInit(EntityUid uid, HallucinationsComponent component, MapInitEvent args)
+ {
+ component.Layer = _random.Next(100, 150);
+ if (!_entityManager.TryGetComponent(uid, out var eye))
+ return;
+ UpdatePreset(component);
+ _eye.SetVisibilityMask(uid, eye.VisibilityMask | component.Layer, eye);
+ _adminLogger.Add(LogType.Action, LogImpact.Medium,
+ $"{ToPrettyString(uid):player} began to hallucinate.");
+ }
+
+ private void OnHallucinationsDiseaseInit(EntityUid uid, HallucinationsDiseaseComponent component, MapInitEvent args)
+ {
+ component.Layer = _random.Next(100, 150);
+ if (!_entityManager.TryGetComponent(uid, out var eye))
+ return;
+ _eye.SetVisibilityMask(uid, eye.VisibilityMask | component.Layer, eye);
+ _adminLogger.Add(LogType.Action, LogImpact.Medium,
+ $"{ToPrettyString(uid):player} began to hallucinate.");
+ }
+
+ public void UpdatePreset(HallucinationsComponent component)
+ {
+ if (component.Proto == null)
+ return;
+ var preset = component.Proto;
+
+ component.Spawns = preset.Entities;
+ component.Range = preset.Range;
+ component.SpawnRate = preset.SpawnRate;
+ component.MinChance = preset.MinChance;
+ component.MaxChance = preset.MaxChance;
+ component.MaxSpawns = preset.MaxSpawns;
+ component.IncreaseChance = preset.IncreaseChance;
+ }
+ private void OnHallucinationsShutdown(EntityUid uid, HallucinationsComponent component, ComponentShutdown args)
+ {
+ if (!_entityManager.TryGetComponent(uid, out var eye))
+ return;
+ _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~component.Layer, eye);
+ _adminLogger.Add(LogType.Action, LogImpact.Medium,
+ $"{ToPrettyString(uid):player} stopped hallucinating.");
+ }
+
+ ///
+ /// Attempts to start hallucinations for target
+ ///
+ /// The target.
+ /// Status effect key.
+ /// Duration of hallucinations effect.
+ /// Refresh active effects.
+ /// Hallucinations pack prototype.
+ public bool StartHallucinations(EntityUid target, string key, TimeSpan time, bool refresh, string proto)
+ {
+ if (proto == null)
+ return false;
+ if (!_proto.TryIndex(proto, out var prototype))
+ return false;
+ if (!_status.TryAddStatusEffect(target, key, time, refresh))
+ return false;
+
+ var hallucinations = _entityManager.GetComponent(target);
+ hallucinations.Proto = prototype;
+ UpdatePreset(hallucinations);
+ hallucinations.CurChance = prototype.MinChance;
+
+ return true;
+ }
+
+ ///
+ /// Attempts to start epidemic hallucinations. Spreads by speech
+ ///
+ /// The target.
+ /// Hallucinations pack prototype.
+ public bool StartEpidemicHallucinations(EntityUid target, string proto)
+ {
+ if (proto == null)
+ return false;
+ if (!_proto.TryIndex(proto, out var prototype))
+ return false;
+
+ var hallucinations = EnsureComp(target);
+ hallucinations.Proto = prototype;
+ hallucinations.Spawns = prototype.Entities;
+ hallucinations.Range = prototype.Range;
+ hallucinations.SpawnRate = prototype.SpawnRate;
+ hallucinations.MinChance = prototype.MinChance;
+ hallucinations.MaxChance = prototype.MaxChance;
+ hallucinations.MaxSpawns = prototype.MaxSpawns;
+ hallucinations.IncreaseChance = prototype.IncreaseChance;
+ hallucinations.CurChance = prototype.MinChance;
+
+ return true;
+ }
+
+ private void OnEntitySpoke(EntityUid uid, HallucinationsDiseaseComponent component, EntitySpokeEvent args)
+ {
+ if (component.Proto == null)
+ return;
+
+ foreach (var ent in _lookup.GetEntitiesInRange(uid, 7f))
+ {
+ StartEpidemicHallucinations(ent, component.Proto.ID);
+ }
+ }
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var stat, out var xform))
+ {
+ if (_timing.CurTime < stat.NextSecond)
+ continue;
+ var rate = stat.SpawnRate;
+ stat.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(rate);
+
+ if (stat.CurChance < stat.MaxChance && stat.CurChance + stat.IncreaseChance <= 1)
+ stat.CurChance = stat.CurChance + stat.IncreaseChance;
+
+ if (!_random.Prob(stat.CurChance))
+ continue;
+
+ stat.SpawnedCount = 0;
+
+ var range = stat.Range * 4;
+ UpdatePreset(stat);
+
+ foreach (var (ent, comp) in _lookup.GetEntitiesInRange(xform.MapPosition, range))
+ {
+ var newCoords = Transform(ent).MapPosition.Offset(_random.NextVector2(stat.Range));
+
+ if (stat.SpawnedCount >= stat.MaxSpawns)
+ continue;
+ stat.SpawnedCount = stat.SpawnedCount += 1;
+
+ var hallucination = Spawn(_random.Pick(stat.Spawns), newCoords);
+ EnsureComp(hallucination, out var visibility);
+ _visibilitySystem.SetLayer(hallucination, visibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility);
+ }
+
+ var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range));
+ if (stat.SpawnedCount >= stat.MaxSpawns)
+ continue;
+ stat.SpawnedCount = stat.SpawnedCount += 1;
+
+ var uidhallucination = Spawn(_random.Pick(stat.Spawns), uidnewCoords);
+ EnsureComp(uidhallucination, out var uidvisibility);
+ _visibilitySystem.SetLayer(uidhallucination, uidvisibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility);
+
+ }
+
+ var diseaseQuery = EntityQueryEnumerator();
+ while (diseaseQuery.MoveNext(out var uid, out var stat, out var xform))
+ {
+ if (_timing.CurTime < stat.NextSecond)
+ continue;
+ var rate = stat.SpawnRate;
+ stat.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(rate);
+
+ if (stat.CurChance < stat.MaxChance && stat.CurChance + stat.IncreaseChance <= 1)
+ stat.CurChance = stat.CurChance + stat.IncreaseChance;
+
+ if (!_random.Prob(stat.CurChance))
+ continue;
+
+ stat.SpawnedCount = 0;
+
+ var range = stat.Range * 4;
+
+ foreach (var (ent, comp) in _lookup.GetEntitiesInRange(xform.MapPosition, range))
+ {
+ var newCoords = Transform(ent).MapPosition.Offset(_random.NextVector2(stat.Range));
+
+ if (stat.SpawnedCount >= stat.MaxSpawns)
+ continue;
+ stat.SpawnedCount = stat.SpawnedCount += 1;
+
+ var hallucination = Spawn(_random.Pick(stat.Spawns), newCoords);
+ EnsureComp(hallucination, out var visibility);
+ _visibilitySystem.SetLayer(hallucination, visibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility);
+ }
+
+ var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range));
+ if (stat.SpawnedCount >= stat.MaxSpawns)
+ continue;
+ stat.SpawnedCount = stat.SpawnedCount += 1;
+
+ var uidhallucination = Spawn(_random.Pick(stat.Spawns), uidnewCoords);
+ EnsureComp(uidhallucination, out var uidvisibility);
+ _visibilitySystem.SetLayer(uidhallucination, uidvisibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility);
+
+ }
+ }
+
+}
diff --git a/Content.Server/ADT/Hemophilia/HemophiliaSystem.cs b/Content.Server/ADT/Hemophilia/HemophiliaSystem.cs
new file mode 100644
index 00000000000..eb84218b8e0
--- /dev/null
+++ b/Content.Server/ADT/Hemophilia/HemophiliaSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Body.Components;
+using Content.Shared.Traits;
+
+namespace Content.Server.Hemophilia;
+
+public sealed partial class HemophiliaSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInitHemophilia);
+ SubscribeLocalEvent(OnShutdown);
+
+ }
+
+ private void OnInitHemophilia(EntityUid uid, HemophiliaComponent component, MapInitEvent args)
+ {
+ if (TryComp(uid, out var bldcomp))
+ {
+ bldcomp.BleedReductionAmount *= component.Modifier;
+ }
+ }
+ private void OnShutdown(EntityUid uid, HemophiliaComponent component, ComponentShutdown args)
+ {
+ if (TryComp(uid, out var bldcomp))
+ {
+ bldcomp.BleedReductionAmount /= component.Modifier;
+ }
+ }
+}
diff --git a/Content.Server/ADT/HungerEffect/HungerEffectSystem.cs b/Content.Server/ADT/HungerEffect/HungerEffectSystem.cs
new file mode 100644
index 00000000000..3bb559fef0b
--- /dev/null
+++ b/Content.Server/ADT/HungerEffect/HungerEffectSystem.cs
@@ -0,0 +1,54 @@
+using Robust.Shared.Audio.Systems;
+using Content.Shared.Humanoid;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Timing;
+using Content.Shared.Database;
+using Content.Shared.Hallucinations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Content.Server.Mind;
+using Content.Server.Body.Systems;
+using Content.Shared.Nutrition.Components;
+using Content.Server.Power.Components;
+
+namespace Content.Server.Hallucinations;
+
+public sealed partial class HungerEffectSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHungerInit);
+ SubscribeLocalEvent(OnHungerShutdown);
+ }
+
+ private void OnHungerInit(EntityUid uid, HungerEffectComponent component, MapInitEvent args)
+ {
+ if (TryComp(uid, out var hunger))
+ {
+ hunger.ActualDecayRate *= 300f;
+ hunger.BaseDecayRate *= 300f;
+ }
+ if (TryComp(uid, out var thirst))
+ {
+ thirst.ActualDecayRate *= 300f;
+ thirst.BaseDecayRate *= 300f;
+ }
+ }
+
+ private void OnHungerShutdown(EntityUid uid, HungerEffectComponent component, ComponentShutdown args)
+ {
+ if (TryComp(uid, out var hunger))
+ {
+ hunger.ActualDecayRate /= 300f;
+ hunger.BaseDecayRate /= 300f;
+ }
+ if (TryComp(uid, out var thirst))
+ {
+ thirst.ActualDecayRate /= 300f;
+ thirst.BaseDecayRate /= 300f;
+ }
+ }
+}
diff --git a/Content.Server/ADT/Phantom/EUI/AcceptHelpingHandEui.cs b/Content.Server/ADT/Phantom/EUI/AcceptHelpingHandEui.cs
new file mode 100644
index 00000000000..0ae672ce147
--- /dev/null
+++ b/Content.Server/ADT/Phantom/EUI/AcceptHelpingHandEui.cs
@@ -0,0 +1,36 @@
+using Content.Server.EUI;
+using Content.Shared.Phantom;
+using Content.Shared.Eui;
+using Content.Shared.Phantom.Components;
+using Content.Server.Phantom.EntitySystems;
+
+namespace Content.Server.Phantom;
+
+public sealed class AcceptHelpingHandEui : BaseEui
+{
+ private readonly PhantomSystem _phantom;
+ private readonly PhantomComponent _component;
+ private readonly EntityUid _uid;
+
+ public AcceptHelpingHandEui(EntityUid uid, PhantomSystem phantom, PhantomComponent comp)
+ {
+ _uid = uid;
+ _phantom = phantom;
+ _component = comp;
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ if (msg is not AcceptHelpingHandChoiceMessage choice ||
+ choice.Button == AcceptHelpingHandButton.Deny)
+ {
+ Close();
+ return;
+ }
+
+ _phantom.OnHelpingHandAccept(_uid, _component);
+ Close();
+ }
+}
diff --git a/Content.Server/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs b/Content.Server/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs
new file mode 100644
index 00000000000..945678d1d4e
--- /dev/null
+++ b/Content.Server/ADT/Phantom/EUI/AcceptPhantomPowersEui.cs
@@ -0,0 +1,36 @@
+using Content.Server.EUI;
+using Content.Shared.Phantom;
+using Content.Shared.Eui;
+using Content.Shared.Phantom.Components;
+using Content.Server.Phantom.EntitySystems;
+
+namespace Content.Server.Phantom;
+
+public sealed class AcceptPhantomPowersEui : BaseEui
+{
+ private readonly PhantomSystem _phantom;
+ private readonly PhantomComponent _component;
+ private readonly EntityUid _uid;
+
+ public AcceptPhantomPowersEui(EntityUid uid, PhantomSystem phantom, PhantomComponent comp)
+ {
+ _uid = uid;
+ _phantom = phantom;
+ _component = comp;
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ if (msg is not AcceptPhantomPowersChoiceMessage choice ||
+ choice.Button == AcceptPhantomPowersButton.Deny)
+ {
+ Close();
+ return;
+ }
+
+ _phantom.MakePuppet(_uid, _component);
+ Close();
+ }
+}
diff --git a/Content.Server/ADT/Phantom/EUI/AmnesiaEui.cs b/Content.Server/ADT/Phantom/EUI/AmnesiaEui.cs
new file mode 100644
index 00000000000..ae9e73113b6
--- /dev/null
+++ b/Content.Server/ADT/Phantom/EUI/AmnesiaEui.cs
@@ -0,0 +1,22 @@
+using Content.Server.EUI;
+using Content.Shared.Phantom;
+using Content.Shared.Eui;
+using Content.Shared.Phantom.Components;
+using Content.Server.Phantom.EntitySystems;
+
+namespace Content.Server.Phantom;
+
+public sealed class PhantomAmnesiaEui : BaseEui
+{
+
+ public PhantomAmnesiaEui()
+ {
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ Close();
+ }
+}
diff --git a/Content.Server/ADT/Phantom/EUI/FinaleEui.cs b/Content.Server/ADT/Phantom/EUI/FinaleEui.cs
new file mode 100644
index 00000000000..bf3328af96e
--- /dev/null
+++ b/Content.Server/ADT/Phantom/EUI/FinaleEui.cs
@@ -0,0 +1,40 @@
+using Content.Server.EUI;
+using Content.Shared.Phantom;
+using Content.Shared.Eui;
+using Content.Shared.Phantom.Components;
+using Content.Server.Phantom.EntitySystems;
+
+namespace Content.Server.Phantom;
+
+public sealed class PhantomFinaleEui : BaseEui
+{
+ private readonly PhantomSystem _phantom;
+ private readonly PhantomComponent _component;
+ private readonly EntityUid _uid;
+ private readonly PhantomFinaleType _type;
+
+ public PhantomFinaleEui(EntityUid uid, PhantomSystem phantom, PhantomComponent comp, PhantomFinaleType type)
+ {
+ _uid = uid;
+ _phantom = phantom;
+ _component = comp;
+ _type = type;
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ if (msg is not PhantomFinaleChoiceMessage choice ||
+ choice.Button == PhantomFinaleButton.Deny)
+ {
+ Close();
+ return;
+ }
+
+ _phantom.Finale(_uid, _component, _type);
+
+ Close();
+ }
+}
+
diff --git a/Resources/Prototypes/NES/Entities/Objects/Clotches/clotches.yml b/Content.Server/ADT/Phantom/EntitySystems/HolyDamageSystem.cs
similarity index 100%
rename from Resources/Prototypes/NES/Entities/Objects/Clotches/clotches.yml
rename to Content.Server/ADT/Phantom/EntitySystems/HolyDamageSystem.cs
diff --git a/Content.Server/ADT/Phantom/EntitySystems/PhantomSystem.cs b/Content.Server/ADT/Phantom/EntitySystems/PhantomSystem.cs
new file mode 100644
index 00000000000..2dac7c4a85d
--- /dev/null
+++ b/Content.Server/ADT/Phantom/EntitySystems/PhantomSystem.cs
@@ -0,0 +1,2627 @@
+using Content.Server.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Physics;
+using Robust.Shared.Physics;
+using Content.Shared.Phantom;
+using Content.Shared.Phantom.Components;
+using Content.Shared.Popups;
+using Robust.Shared.Map;
+using Content.Shared.Movement.Events;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Mobs.Systems;
+using Robust.Shared.Random;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Chat;
+using Content.Server.Chat;
+using System.Linq;
+using Robust.Shared.Serialization.Manager;
+using Content.Shared.Alert;
+using Robust.Server.GameObjects;
+using Content.Server.Chat.Systems;
+using Content.Shared.ActionBlocker;
+using Content.Shared.StatusEffect;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mind;
+using Content.Shared.Humanoid;
+using Robust.Shared.Containers;
+using Content.Shared.DoAfter;
+using System.Numerics;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Content.Server.Chat.Managers;
+using Robust.Shared.Prototypes;
+using Content.Shared.Stunnable;
+using Content.Server.Power.Components;
+using Content.Shared.Eye.Blinding.Systems;
+using Content.Shared.Eye;
+using Content.Server.Light.Components;
+using Content.Server.Light.EntitySystems;
+using Content.Shared.SimpleStation14.Silicon.Components;
+using Content.Shared.PowerCell.Components;
+using Robust.Shared.Timing;
+using Content.Shared.Inventory;
+using Content.Shared.Interaction.Components;
+using Content.Shared.Mindshield.Components;
+using Content.Shared.Bible.Components;
+using Content.Server.Body.Systems;
+using Robust.Shared.GameStates;
+using Content.Server.Station.Systems;
+using Content.Server.EUI;
+using Content.Server.Body.Components;
+using Content.Shared.Eye.Blinding.Components;
+using Content.Server.Hallucinations;
+using Content.Server.AlertLevel;
+using Content.Shared.Controlled;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Content.Shared.Weapons.Melee;
+using Content.Shared.CombatMode;
+using Content.Server.Cuffs;
+using Content.Server.Humanoid;
+using Content.Shared.Preferences;
+using Content.Shared.Ghost;
+using Content.Shared.Tag;
+using Content.Server.Hands.Systems;
+using Content.Shared.Cuffs.Components;
+using Content.Shared.Rejuvenate;
+using Content.Shared.Weapons.Ranged.Events;
+using Content.Server.Database;
+using FastAccessors;
+using Content.Shared.Hallucinations;
+using Robust.Shared.Utility;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.Projectiles;
+using Content.Shared.Throwing;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.GhostInteractions;
+using Content.Shared.Revenant.Components;
+using Content.Shared.Mobs.Components;
+
+namespace Content.Server.Phantom.EntitySystems;
+
+public sealed partial class PhantomSystem : SharedPhantomSystem
+{
+ #region Dependency
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+ [Dependency] private readonly SharedActionsSystem _action = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedControlledSystem _controlled = default!;
+ [Dependency] private readonly ISerializationManager _serialization = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly INetManager _netMan = default!;
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly VisibilitySystem _visibility = default!;
+ [Dependency] private readonly StaminaSystem _stamina = default!;
+ [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedMindSystem _mindSystem = default!;
+ [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+ [Dependency] private readonly SharedEyeSystem _eye = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
+ [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly ChatSystem _chatSystem = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly PoweredLightSystem _poweredLight = default!;
+ [Dependency] private readonly ApcSystem _apcSystem = default!;
+ [Dependency] protected readonly IGameTiming GameTiming = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly SharedStunSystem _sharedStun = default!;
+ [Dependency] private readonly EuiManager _euiManager = null!;
+ [Dependency] private readonly BatterySystem _batterySystem = default!;
+ [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
+ [Dependency] private readonly BlindableSystem _blindable = default!;
+ [Dependency] private readonly HallucinationsSystem _hallucinations = default!;
+ [Dependency] private readonly AlertLevelSystem _alertLevel = default!;
+ [Dependency] private readonly IEntitySystemManager _entitySystems = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly HandsSystem _handsSystem = default!;
+ [Dependency] private readonly CuffableSystem _cuffable = default!;
+ #endregion
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // Startup
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnStatusAdded);
+ SubscribeLocalEvent(OnStatusEnded);
+
+ SubscribeLocalEvent(OnPupMapInit);
+ SubscribeLocalEvent(OnPupShutdown);
+ SubscribeLocalEvent(OnPupClaws);
+ SubscribeLocalEvent(OnPupHeal);
+
+ // Haunting
+ SubscribeLocalEvent(OnMakeHolder);
+ SubscribeLocalEvent(OnStopHaunting);
+ SubscribeLocalEvent(OnTryMove);
+
+ // Vessels manipulations
+ SubscribeLocalEvent(OnRequestVesselMenu);
+ SubscribeNetworkEvent(OnSelectVessel);
+
+ SubscribeLocalEvent(OnMakeVessel);
+ SubscribeLocalEvent(MakeVesselDoAfter);
+
+ // Abilities
+ SubscribeLocalEvent(OnParalysis);
+ SubscribeLocalEvent(OnCorporeal);
+ SubscribeLocalEvent(OnBreakdown);
+ SubscribeLocalEvent(OnStarvation);
+ SubscribeLocalEvent(OnShieldBreak);
+ SubscribeLocalEvent(OnGhostClaws);
+ SubscribeLocalEvent(OnGhostInjury);
+ SubscribeLocalEvent(OnGhostHeal);
+ SubscribeLocalEvent(OnPuppeter);
+ SubscribeLocalEvent(PuppeterDoAfter);
+ SubscribeLocalEvent(OnRepair);
+ SubscribeLocalEvent(OnBlinding);
+ SubscribeLocalEvent(OnPortal);
+ SubscribeLocalEvent(OnPsychoEpidemic);
+ SubscribeLocalEvent(OnHelpingHand);
+ SubscribeLocalEvent(OnControl);
+
+ // Finales
+ SubscribeLocalEvent(OnNightmare);
+ SubscribeLocalEvent(OnTyrany);
+ SubscribeLocalEvent(OnRequestFreedomMenu);
+ SubscribeNetworkEvent(OnSelectFreedom);
+
+ // Other
+ SubscribeLocalEvent(OnTrySpeak);
+ SubscribeLocalEvent(OnDamage);
+ SubscribeLocalEvent(OnLevelChanged);
+ SubscribeLocalEvent(OnRequestStyleMenu);
+ SubscribeNetworkEvent(OnSelectStyle);
+
+
+ // IDK why the fuck this is not working in another file
+ SubscribeLocalEvent(OnProjectileHit);
+ SubscribeLocalEvent(OnThrowHit);
+
+ SubscribeLocalEvent(OnMeleeHit);
+ }
+
+ public ProtoId BruteDamageGroup = "Brute";
+ public ProtoId BurnDamageGroup = "Burn";
+ public ProtoId GeneticDamageGroup = "Genetic";
+ public const string GlovesId = "gloves";
+ public const string OuterClothingId = "outerClothing";
+
+ private void OnStartup(EntityUid uid, PhantomComponent component, ComponentStartup args)
+ {
+ _appearance.SetData(uid, PhantomVisuals.Haunting, false);
+ _appearance.SetData(uid, PhantomVisuals.Stunned, false);
+ _appearance.SetData(uid, PhantomVisuals.Corporeal, false);
+ ChangeEssenceAmount(uid, 0, component);
+ }
+
+ private void OnMapInit(EntityUid uid, PhantomComponent component, MapInitEvent args)
+ {
+ _action.AddAction(uid, ref component.PhantomHauntActionEntity, component.PhantomHauntAction);
+ _action.AddAction(uid, ref component.PhantomMakeVesselActionEntity, component.PhantomMakeVesselAction);
+ _action.AddAction(uid, ref component.PhantomStyleActionEntity, component.PhantomStyleAction);
+ _action.AddAction(uid, ref component.PhantomHauntVesselActionEntity, component.PhantomHauntVesselAction);
+ SelectStyle(uid, component, component.CurrentStyle, true);
+
+ if (TryComp(uid, out var eyeComponent))
+ _eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask | (int) VisibilityFlags.PhantomVessel, eyeComponent);
+
+ component.HelpingHand = _container.EnsureContainer(uid, "HelpingHand");
+ }
+
+ private void OnShutdown(EntityUid uid, PhantomComponent component, ComponentShutdown args)
+ {
+ _action.RemoveAction(uid, component.PhantomHauntActionEntity);
+ _action.RemoveAction(uid, component.PhantomMakeVesselActionEntity);
+ _action.RemoveAction(uid, component.PhantomStyleActionEntity);
+ //_action.RemoveAction(uid, component.PhantomSelectVesselActionEntity);
+ _action.RemoveAction(uid, component.PhantomHauntVesselActionEntity);
+ foreach (var action in component.CurrentActions)
+ {
+ _action.RemoveAction(uid, action);
+ if (action != null)
+ QueueDel(action.Value);
+ }
+ if (TryComp(uid, out var eyeComponent))
+ _eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask | ~(int) VisibilityFlags.PhantomVessel, eyeComponent);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ comp.Accumulator += frameTime;
+
+ if (comp.Accumulator <= 1)
+ continue;
+ comp.Accumulator -= 1;
+
+ if (comp.HelpingHandTimer > 0)
+ comp.HelpingHandTimer -= 1;
+
+ if (comp.SpeechTimer > 0)
+ comp.SpeechTimer -= 1;
+
+ if (comp.HelpingHandTimer <= 0 && comp.HelpingHand.ContainedEntities.Count > 0 && !Deleted(comp.TransferringEntity))
+ _container.TryRemoveFromContainer(comp.TransferringEntity, true);
+
+ if (comp.Essence < comp.EssenceRegenCap && !CheckAltars(uid, comp) && comp.HasHaunted)
+ ChangeEssenceAmount(uid, comp.EssencePerSecond, comp, regenCap: true);
+
+ if (CheckAltars(uid, comp))
+ ChangeEssenceAmount(uid, comp.ChurchDamage, comp, true);
+ }
+ }
+
+ private void OnStatusAdded(EntityUid uid, PhantomComponent component, StatusEffectAddedEvent args)
+ {
+ if (args.Key == "Stun")
+ _appearance.SetData(uid, PhantomVisuals.Stunned, true);
+ }
+
+ private void OnStatusEnded(EntityUid uid, PhantomComponent component, StatusEffectEndedEvent args)
+ {
+ if (args.Key == "Stun")
+ {
+ _appearance.SetData(uid, PhantomVisuals.Stunned, false);
+ _appearance.SetData(uid, PhantomVisuals.Corporeal, component.IsCorporeal);
+ }
+ }
+
+ #region Radial Menu
+ ///
+ /// Requests radial menu for the styles
+ ///
+ /// Phantom uid
+ /// Phantom component
+ /// Event
+ private void OnRequestStyleMenu(EntityUid uid, PhantomComponent component, OpenPhantomStylesMenuActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (_entityManager.TryGetComponent(uid, out var actorComponent))
+ {
+ var ev = new RequestPhantomStyleMenuEvent(GetNetEntity(uid));
+
+ foreach (var prototype in _proto.EnumeratePrototypes())
+ {
+ if (prototype.Icon == null)
+ continue;
+ ev.Prototypes.Add(prototype.ID);
+ }
+ ev.Prototypes.Sort();
+ RaiseNetworkEvent(ev, actorComponent.PlayerSession);
+ }
+
+ args.Handled = true;
+ }
+
+ ///
+ /// Raised when style selected
+ ///
+ /// Event
+ private void OnSelectStyle(SelectPhantomStyleEvent args)
+ {
+ var uid = GetEntity(args.Target);
+ if (!TryComp(uid, out var comp))
+ return;
+ if (args.Handled)
+ return;
+ if (args.PrototypeId == comp.CurrentStyle)
+ {
+ var selfMessage = Loc.GetString("phantom-style-already");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ SelectStyle(uid, comp, args.PrototypeId);
+ args.Handled = true;
+ }
+
+ ///
+ /// Requests radial menu for freedom finale
+ ///
+ /// Phantom uid
+ /// Phantom component
+ /// Event
+ private void OnRequestFreedomMenu(EntityUid uid, PhantomComponent component, FreedomFinaleActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (component.FinalAbilityUsed)
+ {
+ var selfMessage = Loc.GetString("phantom-final-already");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ if (_entityManager.TryGetComponent(uid, out var actorComponent))
+ {
+ var ev = new RequestPhantomFreedomMenuEvent(GetNetEntity(uid));
+
+ foreach (var prototype in _proto.EnumeratePrototypes())
+ {
+ if (!prototype.TryGetComponent(out var actionComp))
+ continue;
+ if (!prototype.TryGetComponent(out var tag))
+ continue;
+ if (actionComp.Icon == null)
+ continue;
+ foreach (var whitelisted in tag.Tags.ToList())
+ {
+ if (whitelisted == "PhantomFreedom")
+ ev.Prototypes.Add(prototype.ID);
+ }
+ }
+ ev.Prototypes.Sort();
+ RaiseNetworkEvent(ev, actorComponent.PlayerSession);
+ }
+
+ args.Handled = true;
+ }
+
+ ///
+ /// Raised when freedom type selected
+ ///
+ /// Event
+ private void OnSelectFreedom(SelectPhantomFreedomEvent args)
+ {
+ var uid = GetEntity(args.Target);
+ if (!TryComp(uid, out var component))
+ return;
+
+ var stationUid = _entitySystems.GetEntitySystem().GetOwningStation(uid);
+ if (stationUid == null)
+ return;
+ if (!_mindSystem.TryGetMind(uid, out _, out var mind) || mind.Session == null)
+ return;
+
+ if (args.PrototypeId == "ActionPhantomOblivion")
+ {
+ var eui = new PhantomFinaleEui(uid, this, component, PhantomFinaleType.Oblivion);
+ _euiManager.OpenEui(eui, mind.Session);
+ }
+ if (args.PrototypeId == "ActionPhantomDeathmatch")
+ {
+ var eui = new PhantomFinaleEui(uid, this, component, PhantomFinaleType.Deathmatch);
+ _euiManager.OpenEui(eui, mind.Session);
+ }
+ if (args.PrototypeId == "ActionPhantomHelpFin")
+ {
+ var eui = new PhantomFinaleEui(uid, this, component, PhantomFinaleType.Help);
+ _euiManager.OpenEui(eui, mind.Session);
+ }
+ }
+
+ ///
+ /// Requests radial menu for vessel haunting
+ ///
+ /// Phantom uid
+ /// Phantom component
+ /// Event
+ private void OnRequestVesselMenu(EntityUid uid, PhantomComponent component, HauntVesselActionEvent args)
+ {
+ if (args.Handled)
+ return;
+ if (component.Vessels.Count <= 0)
+ {
+ var selfMessage = Loc.GetString("phantom-no-vessels");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+
+ }
+ if (_entityManager.TryGetComponent(uid, out var actorComponent))
+ {
+ var ev = new RequestPhantomVesselMenuEvent(GetNetEntity(uid), new());
+
+ foreach (var vessel in component.Vessels)
+ {
+ if (!TryComp(vessel, out var humanoid))
+ continue;
+ if (!TryComp(vessel, out var meta))
+ continue;
+ var netEnt = GetNetEntity(vessel);
+
+ HumanoidCharacterAppearance hca = new();
+
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.FacialHair, out var facialHair))
+ if (facialHair.TryGetValue(0, out var marking))
+ {
+ hca = hca.WithFacialHairStyleName(marking.MarkingId);
+ hca = hca.WithFacialHairColor(marking.MarkingColors.First());
+ }
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Hair, out var hair))
+ if (hair.TryGetValue(0, out var marking))
+ {
+ hca = hca.WithHairStyleName(marking.MarkingId);
+ hca = hca.WithHairColor(marking.MarkingColors.First());
+ }
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Head, out var head))
+ hca = hca.WithMarkings(head);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.HeadSide, out var headSide))
+ hca = hca.WithMarkings(headSide);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.HeadTop, out var headTop))
+ hca = hca.WithMarkings(headTop);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Snout, out var snout))
+ hca = hca.WithMarkings(snout);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Chest, out var chest))
+ hca = hca.WithMarkings(chest);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Arms, out var arms))
+ hca = hca.WithMarkings(arms);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Legs, out var legs))
+ hca = hca.WithMarkings(legs);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Tail, out var tail))
+ hca = hca.WithMarkings(tail);
+ if (humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Overlay, out var overlay))
+ hca = hca.WithMarkings(overlay);
+
+ hca = hca.WithSkinColor(humanoid.SkinColor);
+ hca = hca.WithEyeColor(humanoid.EyeColor);
+ //hca = hca.WithMarkings(humanoid.MarkingSet.Markings.);
+
+ HumanoidCharacterProfile profile = new HumanoidCharacterProfile().WithCharacterAppearance(hca).WithSpecies(humanoid.Species);
+
+ ev.Vessels.Add((netEnt, profile, meta.EntityName));
+ }
+ ev.Vessels.Sort();
+ RaiseNetworkEvent(ev, actorComponent.PlayerSession);
+ }
+
+ args.Handled = true;
+ }
+
+ ///
+ /// Raised when vessel selected
+ ///
+ /// Event
+ private void OnSelectVessel(SelectPhantomVesselEvent args)
+ {
+ var uid = GetEntity(args.Uid);
+ var target = GetEntity(args.Vessel);
+ if (!TryComp(uid, out var comp))
+ return;
+ if (!HasComp(target))
+ {
+ var selfMessage = Loc.GetString("phantom-puppeter-fail-notvessel", ("target", Identity.Entity(target, EntityManager)));
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ if (!TryUseAbility(uid, target))
+ return;
+
+ if (!comp.HasHaunted)
+ Haunt(uid, target, comp);
+ else
+ {
+ StopHaunt(uid, comp.Holder, comp);
+ Haunt(uid, target, comp);
+ }
+ }
+ #endregion
+
+ #region Abilities
+ private void OnMakeHolder(EntityUid uid, PhantomComponent component, MakeHolderActionEvent args)
+ {
+ if (args.Handled)
+ return;
+ if (!component.CanHaunt)
+ return;
+
+ var target = args.Target;
+
+ if (!TryUseAbility(uid, target))
+ return;
+
+ if (!component.HasHaunted)
+ Haunt(uid, target, component);
+ else
+ {
+ StopHaunt(uid, component.Holder, component);
+ Haunt(uid, target, component);
+ }
+
+ args.Handled = true;
+
+ }
+
+ private void OnStopHaunting(EntityUid uid, PhantomComponent component, StopHauntingActionEvent args)
+ {
+ if (args.Handled)
+ return;
+ if (!component.CanHaunt)
+ return;
+
+ StopHaunt(uid, component.Holder, component);
+
+ args.Handled = true;
+ }
+
+ private void OnMakeVessel(EntityUid uid, PhantomComponent component, MakeVesselActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!component.HasHaunted)
+ {
+ var selfMessage = Loc.GetString("phantom-no-holder");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ var target = component.Holder;
+
+ if (!TryUseAbility(uid, target))
+ return;
+
+ if (!HasComp(target))
+ {
+ var selfMessage = Loc.GetString("changeling-dna-fail-nohuman", ("target", Identity.Entity(target, EntityManager)));
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ if (HasComp(target))
+ {
+ var selfMessage = Loc.GetString("phantom-vessel-fail-mindshield", ("target", Identity.Entity(target, EntityManager)));
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ if (HasComp(target))
+ {
+ RemComp(target);
+
+ var selfMessage = Loc.GetString("phantom-vessel-removed", ("target", Identity.Entity(target, EntityManager)));
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+
+ args.Handled = true;
+ var makeVesselDoAfter = new DoAfterArgs(EntityManager, uid, component.MakeVesselDuration, new MakeVesselDoAfterEvent(), uid, target: target)
+ {
+ DistanceThreshold = 15,
+ BreakOnUserMove = false,
+ BreakOnTargetMove = false,
+ BreakOnDamage = true,
+ CancelDuplicate = true,
+ AttemptFrequency = AttemptFrequency.StartAndEnd
+ };
+ _doAfter.TryStartDoAfter(makeVesselDoAfter);
+ }
+
+ private void OnParalysis(EntityUid uid, PhantomComponent component, ParalysisActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ var target = args.Target;
+
+ if (!TryUseAbility(uid, target))
+ return;
+
+ if (IsHolder(target, component))
+ {
+ if (!component.ParalysisOn)
+ {
+ UpdateEctoplasmSpawn(uid);
+ var timeHaunted = TimeSpan.FromHours(1);
+ _status.TryAddStatusEffect(target, "KnockedDown", timeHaunted, false);
+ _status.TryAddStatusEffect(target, "Stun", timeHaunted, false);
+ if (_mindSystem.TryGetMind(uid, out _, out var mind) && mind.Session != null)
+ _audio.PlayGlobal(component.ParalysisSound, mind.Session);
+ }
+ else
+ {
+ _status.TryRemoveStatusEffect(target, "KnockedDown");
+ _status.TryRemoveStatusEffect(target, "Stun");
+
+ args.Handled = true;
+ }
+ component.ParalysisOn = !component.ParalysisOn;
+ }
+ else
+ {
+ if (component.ParalysisOn)
+ {
+ var selfMessage = Loc.GetString("phantom-paralysis-fail-active");
+ _popup.PopupEntity(selfMessage, uid, uid);
+ return;
+ }
+ else
+ {
+ UpdateEctoplasmSpawn(uid);
+ var time = TimeSpan.FromSeconds(10);
+ if (_status.TryAddStatusEffect(target, "KnockedDown", time, false) && _status.TryAddStatusEffect(target, "Stun", time, false))
+ {
+ if (_mindSystem.TryGetMind(uid, out _, out var mind) && mind.Session != null)
+ _audio.PlayGlobal(component.ParalysisSound, mind.Session);
+ args.Handled = true;
+ }
+ }
+ }
+ }
+
+ private void OnCorporeal(EntityUid uid, PhantomComponent component, MaterializeActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (component.HasHaunted)
+ return;
+
+ UpdateEctoplasmSpawn(uid);
+ args.Handled = true;
+
+ if (TryComp(uid, out var fixtures) && fixtures.FixtureCount >= 1)
+ {
+ var fixture = fixtures.Fixtures.First();
+
+ _physicsSystem.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.SmallMobMask | CollisionGroup.GhostImpassable), fixtures);
+ _physicsSystem.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.SmallMobLayer, fixtures);
+ }
+ var visibility = EnsureComp(uid);
+ RemComp(uid);
+
+ _visibility.SetLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
+ _visibility.RefreshVisibility(uid);
+
+ component.IsCorporeal = true;
+ }
+
+ private void OnBreakdown(EntityUid uid, PhantomComponent component, BreakdownActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ var target = args.Target;
+
+ if (!TryUseAbility(uid, target))
+ return;
+
+ var time = TimeSpan.FromSeconds(2);
+ var timeStatic = TimeSpan.FromSeconds(15);
+ var timeHaunted = TimeSpan.FromHours(1);
+ var chance = 0.2f;
+ if (IsHolder(target, component))
+ {
+ if (TryComp(target, out var status) && _status.TryAddStatusEffect(target, "SeeingStatic", timeHaunted, true, status))
+ {
+ if (!component.BreakdownOn)
+ {
+ UpdateEctoplasmSpawn(uid);
+ _status.TryAddStatusEffect(target, "KnockedDown", time, false, status);
+ _status.TryAddStatusEffect(target, "Stun", time, false, status);
+ _status.TryAddStatusEffect(target, "SlowedDown", timeHaunted, false, status);
+ if (_mindSystem.TryGetMind(uid, out _, out var mind) && mind.Session != null)
+ _audio.PlayGlobal(component.BreakdownSound, mind.Session);
+ if (_mindSystem.TryGetMind(target, out _, out var targetMind) && targetMind.Session != null)
+ _audio.PlayGlobal(component.BreakdownSound, targetMind.Session);
+
+ }
+ else
+ {
+ args.Handled = true;
+
+ _status.TryRemoveStatusEffect(target, "SlowedDown");
+ _status.TryRemoveStatusEffect(target, "SeeingStatic");
+ }
+ component.BreakdownOn = !component.BreakdownOn;
+ }
+
+ if (HasComp(target))
+ {
+ if (_random.Prob(chance))
+ {
+ UpdateEctoplasmSpawn(uid);
+ var stunTime = TimeSpan.FromSeconds(4);
+ RemComp