diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index ba2e83614b6..e7a4b9c6a26 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -60,33 +60,22 @@ jobs:
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
- - name: Update Build Info
- env:
- FORK_ID: ${{ vars.FORK_ID }}
- run: Tools/gen_build_info.py
-
- - name: Shuffle files around
- run: |
- mkdir "release/${{ github.sha }}"
- mv release/*.zip "release/${{ github.sha }}"
-
- - name: Upload files to mothership
- uses: burnett01/rsync-deployments@5.2
- with:
- switches: -avzr --ignore-existing
- path: "release/${{ github.sha }}"
- remote_path: ${{ secrets.BUILDS_PATH }}
- remote_host: ${{ secrets.BUILDS_HOST }}
- remote_user: ${{ secrets.BUILDS_USERNAME }}
- remote_key: ${{ secrets.BUILDS_SSH_KEY }}
-
- - name: Update manifest JSON
- uses: appleboy/ssh-action@master
+ - name: Upload build artifact
+ id: artifact-upload-step
+ uses: actions/upload-artifact@v4
with:
- host: ${{ secrets.BUILDS_HOST }}
- username: ${{ secrets.BUILDS_USERNAME }}
- key: ${{ secrets.BUILDS_SSH_KEY }}
- script: node ~/scripts/push_to_manifest.js -fork ${{ vars.FORK_ID }} -id ${{ github.sha }}
+ name: build
+ path: release/*.zip
+ compression-level: 0
+ retention-days: 0
+
+ - name: Publish version
+ run: Tools/publish_github_artifact.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
+ ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
+ GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
# - name: Publish changelog (Discord)
# run: Tools/actions_changelogs_since_last_run.py
@@ -99,3 +88,8 @@ jobs:
# run: Tools/actions_changelog_rss.py
# env:
# CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
+
+ - uses: geekyeggo/delete-artifact@v5
+ if: always()
+ with:
+ name: build
diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml
index e5936298a80..9d9679f8136 100644
--- a/.github/workflows/test-packaging.yml
+++ b/.github/workflows/test-packaging.yml
@@ -78,13 +78,3 @@ jobs:
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
-
- - name: Update Build Info
- env:
- FORK_ID: ${{ vars.FORK_ID }}
- run: Tools/gen_build_info.py
-
- - name: Shuffle files around
- run: |
- mkdir "release/${{ github.sha }}"
- mv release/*.zip "release/${{ github.sha }}"
diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs
index ddc498b6e91..f805f6643d2 100644
--- a/Content.Client/Actions/UI/ActionAlertTooltip.cs
+++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs
@@ -1,4 +1,4 @@
-using Content.Client.Stylesheets;
+using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -77,9 +77,12 @@ public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string?
MaxWidth = TooltipTextMaxWidth,
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
- requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
- requires +
- "[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
+ return;
+
+ requiresLabel.SetMessage(markup);
+
vbox.AddChild(requiresLabel);
}
}
@@ -97,8 +100,11 @@ protected override void FrameUpdate(FrameEventArgs args)
if (timeLeft > TimeSpan.Zero)
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
- _cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
- $"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
+ return;
+
+ _cooldownLabel.SetMessage(markup);
_cooldownLabel.Visible = true;
}
else
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml
index 311d67b826c..d3d3df02d93 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml
@@ -6,7 +6,8 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
- xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
+ xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
+ xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
@@ -14,6 +15,7 @@
+
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index 51330a547ec..d5c43e2a500 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -15,14 +15,18 @@ public AdminMenuWindow()
MinSize = new Vector2(650, 250);
Title = Loc.GetString("admin-menu-title");
RobustXamlLoader.Load(this);
- MasterTabContainer.SetTabTitle(0, Loc.GetString("admin-menu-admin-tab"));
- MasterTabContainer.SetTabTitle(1, Loc.GetString("admin-menu-adminbus-tab"));
- MasterTabContainer.SetTabTitle(2, Loc.GetString("admin-menu-atmos-tab"));
- MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
- MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
- MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
- MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
- MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Admin, Loc.GetString("admin-menu-admin-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Adminbus, Loc.GetString("admin-menu-adminbus-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Atmos, Loc.GetString("admin-menu-atmos-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ MasterTabContainer.SetTabTitle((int) TabIndex.BabyJail, Loc.GetString("admin-menu-baby-jail-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
MasterTabContainer.OnTabChanged += OnTabChanged;
}
diff --git a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
index f978138ca58..4a3c0ef7ace 100644
--- a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
+++ b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
@@ -1,10 +1,10 @@
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
new file mode 100644
index 00000000000..b8034faf52a
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
new file mode 100644
index 00000000000..9e1d53818f2
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
@@ -0,0 +1,21 @@
+using Content.Client.Message;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailStatusWindow : FancyWindow
+{
+ public BabyJailStatusWindow()
+ {
+ RobustXamlLoader.Load(this);
+ MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
new file mode 100644
index 00000000000..dd770c2be53
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
new file mode 100644
index 00000000000..aa9d6ced951
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
@@ -0,0 +1,75 @@
+using Content.Shared.Administration.Events;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Console;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailTab : Control
+{
+ [Dependency] private readonly IConsoleHost _console = default!;
+
+ private string _maxAccountAge;
+ private string _maxOverallMinutes;
+
+ public BabyJailTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
+ MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
+ MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+
+ private void SendMaxAccountAge(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxAccountAge ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_account_age {minutes}");
+ }
+
+ private void SendMaxOverallMinutes(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxOverallMinutes ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
+ }
+
+ public void UpdateStatus(BabyJailStatus status)
+ {
+ EnabledButton.Pressed = status.Enabled;
+ EnabledButton.Text = Loc.GetString(status.Enabled
+ ? "admin-ui-baby-jail-enabled"
+ : "admin-ui-baby-jail-disabled"
+ );
+ EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
+ ShowReasonButton.Pressed = status.ShowReason;
+
+ MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+}
diff --git a/Content.Client/Clock/ClockSystem.cs b/Content.Client/Clock/ClockSystem.cs
new file mode 100644
index 00000000000..7097ada9df6
--- /dev/null
+++ b/Content.Client/Clock/ClockSystem.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Clock;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Clock;
+
+public sealed class ClockSystem : SharedClockSystem
+{
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp, out var sprite))
+ {
+ if (!sprite.LayerMapTryGet(ClockVisualLayers.HourHand, out var hourLayer) ||
+ !sprite.LayerMapTryGet(ClockVisualLayers.MinuteHand, out var minuteLayer))
+ continue;
+
+ var time = GetClockTime((uid, comp));
+ var hourState = $"{comp.HoursBase}{time.Hours % 12}";
+ var minuteState = $"{comp.MinutesBase}{time.Minutes / 5}";
+ sprite.LayerSetState(hourLayer, hourState);
+ sprite.LayerSetState(minuteLayer, minuteState);
+ }
+ }
+}
diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs
index 453658bebf9..889c992f7f6 100644
--- a/Content.Client/Construction/ConstructionSystem.cs
+++ b/Content.Client/Construction/ConstructionSystem.cs
@@ -48,11 +48,11 @@ public override void Initialize()
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCraftingMenu,
- new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction:true))
+ new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction: true))
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUse, outsidePrediction: true))
.Bind(ContentKeyFunctions.EditorFlipObject,
- new PointerInputCmdHandler(HandleFlip, outsidePrediction:true))
+ new PointerInputCmdHandler(HandleFlip, outsidePrediction: true))
.Register();
SubscribeLocalEvent(HandleConstructionGhostExamined);
@@ -196,7 +196,7 @@ public bool TrySpawnGhost(
if (GhostPresent(loc))
return false;
- var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
+ var predicate = GetPredicate(prototype.CanBuildInImpassable, _transformSystem.ToMapCoordinates(loc));
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;
diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
index a60619baa35..0462c095ba8 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
@@ -170,7 +170,7 @@ private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs
if (_combatMode.IsInCombatMode(args.Session?.AttachedEntity))
return false;
- var coords = args.Coordinates.ToMap(_entityManager, _xform);
+ var coords = _xform.ToMapCoordinates(args.Coordinates);
if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
OpenRootMenu(entities);
diff --git a/Content.Client/Extinguisher/FireExtinguisherComponent.cs b/Content.Client/Extinguisher/FireExtinguisherComponent.cs
deleted file mode 100644
index 324b05a93d4..00000000000
--- a/Content.Client/Extinguisher/FireExtinguisherComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Extinguisher;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.Extinguisher;
-
-[RegisterComponent]
-public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent;
diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs
index 9ea00275e84..046be2aa621 100644
--- a/Content.Client/Flash/FlashOverlay.cs
+++ b/Content.Client/Flash/FlashOverlay.cs
@@ -1,27 +1,22 @@
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
-using Content.Client.Viewport;
using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
-using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Flash
{
public sealed class FlashOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IClyde _displayManager = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
- private readonly StatusEffectsSystem _statusSys;
+ private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
@@ -56,20 +51,6 @@ protected override void FrameUpdate(FrameEventArgs args)
PercentComplete = timeDone / lastsFor;
}
- public void ReceiveFlash()
- {
- if (_stateManager.CurrentState is IMainViewportState state)
- {
- // take a screenshot
- // note that the callback takes a while and ScreenshotTexture will be null the first few Draws
- state.Viewport.Viewport.Screenshot(image =>
- {
- var rgba32Image = image.CloneAs(SixLabors.ImageSharp.Configuration.Default);
- ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
- });
- }
- }
-
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
@@ -82,6 +63,11 @@ protected override bool BeforeDraw(in OverlayDrawArgs args)
protected override void Draw(in OverlayDrawArgs args)
{
+ if (RequestScreenTexture && ScreenTexture != null)
+ {
+ ScreenshotTexture = ScreenTexture;
+ RequestScreenTexture = false; // we only need the first frame, so we can stop the request now for performance reasons
+ }
if (ScreenshotTexture == null)
return;
@@ -96,7 +82,6 @@ protected override void DisposeBehavior()
{
base.DisposeBehavior();
ScreenshotTexture = null;
- PercentComplete = 1.0f;
}
}
}
diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs
index 9a0579f6aa3..146d84b990f 100644
--- a/Content.Client/Flash/FlashSystem.cs
+++ b/Content.Client/Flash/FlashSystem.cs
@@ -22,7 +22,6 @@ public override void Initialize()
SubscribeLocalEvent(OnShutdown);
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
- SubscribeLocalEvent(OnStatusAdded);
_overlay = new();
}
@@ -34,8 +33,8 @@ private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPl
private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
{
- _overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
+ _overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
@@ -43,6 +42,7 @@ private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit arg
{
if (_player.LocalEntity == uid)
{
+ _overlay.RequestScreenTexture = true;
_overlayMan.AddOverlay(_overlay);
}
}
@@ -51,17 +51,9 @@ private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShut
{
if (_player.LocalEntity == uid)
{
- _overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
+ _overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
}
-
- private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
- {
- if (_player.LocalEntity == uid && args.Key == FlashedKey)
- {
- _overlay.ReceiveFlash();
- }
- }
}
diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs
index 6236cd8e958..63cbfdb09c6 100644
--- a/Content.Client/Gameplay/GameplayStateBase.cs
+++ b/Content.Client/Gameplay/GameplayStateBase.cs
@@ -104,7 +104,8 @@ private bool HandleInspect(ICommonSession? session, EntityCoordinates coords, En
public IEnumerable GetClickableEntities(EntityCoordinates coordinates)
{
- return GetClickableEntities(coordinates.ToMap(_entityManager, _entitySystemManager.GetEntitySystem()));
+ var transformSystem = _entitySystemManager.GetEntitySystem();
+ return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates));
}
public IEnumerable GetClickableEntities(MapCoordinates coordinates)
diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
deleted file mode 100644
index 46d6f1b464d..00000000000
--- a/Content.Client/Items/Systems/ItemToggleSystem.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-///
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-
-}
diff --git a/Content.Client/MainMenu/MainMenu.cs b/Content.Client/MainMenu/MainMenu.cs
index 43c5bfe5674..3c709d2d15b 100644
--- a/Content.Client/MainMenu/MainMenu.cs
+++ b/Content.Client/MainMenu/MainMenu.cs
@@ -25,6 +25,9 @@ public sealed class MainScreen : Robust.Client.State.State
[Dependency] private readonly IGameController _controllerProxy = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
+
+ private ISawmill _sawmill = default!;
private MainMenuControl _mainMenuControl = default!;
private bool _isConnecting;
@@ -35,6 +38,8 @@ public sealed class MainScreen : Robust.Client.State.State
///
protected override void Startup()
{
+ _sawmill = _logManager.GetSawmill("mainmenu");
+
_mainMenuControl = new MainMenuControl(_resourceCache, _configurationManager);
_userInterfaceManager.StateRoot.AddChild(_mainMenuControl);
@@ -116,7 +121,7 @@ private void TryConnect(string address)
catch (ArgumentException e)
{
_userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error.");
- Logger.Warning(e.ToString());
+ _sawmill.Warning(e.ToString());
_netManager.ConnectFailed -= _onConnectFailed;
_setConnectingState(false);
}
diff --git a/Content.Client/Materials/MaterialStorageSystem.cs b/Content.Client/Materials/MaterialStorageSystem.cs
index edd07391f7b..592471d6736 100644
--- a/Content.Client/Materials/MaterialStorageSystem.cs
+++ b/Content.Client/Materials/MaterialStorageSystem.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Materials;
+using Content.Shared.Materials;
using Robust.Client.GameObjects;
namespace Content.Client.Materials;
@@ -49,7 +49,7 @@ public override bool TryInsertMaterialEntity(EntityUid user,
{
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
return false;
- _transform.DetachParentToNull(toInsert, Transform(toInsert));
+ _transform.DetachEntity(toInsert, Transform(toInsert));
return true;
}
}
diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs
index b7f5e48821f..e25300d44c5 100644
--- a/Content.Client/Movement/Systems/JetpackSystem.cs
+++ b/Content.Client/Movement/Systems/JetpackSystem.cs
@@ -15,6 +15,7 @@ public sealed class JetpackSystem : SharedJetpackSystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override void Initialize()
{
@@ -73,11 +74,11 @@ private void CreateParticles(EntityUid uid)
var uidXform = Transform(uid);
var coordinates = uidXform.Coordinates;
- var gridUid = coordinates.GetGridUid(EntityManager);
+ var gridUid = _transform.GetGrid(coordinates);
if (TryComp(gridUid, out var grid))
{
- coordinates = new EntityCoordinates(gridUid.Value, grid.WorldToLocal(coordinates.ToMapPos(EntityManager, _transform)));
+ coordinates = new EntityCoordinates(gridUid.Value, _mapSystem.WorldToLocal(gridUid.Value, grid, _transform.ToMapCoordinates(coordinates).Position));
}
else if (uidXform.MapUid != null)
{
diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs
index d3ae5091528..0c72a8f99ff 100644
--- a/Content.Client/NPC/PathfindingSystem.cs
+++ b/Content.Client/NPC/PathfindingSystem.cs
@@ -203,7 +203,7 @@ private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
if (found || !_system.Breadcrumbs.TryGetValue(netGrid, out var crumbs) || !xformQuery.TryGetComponent(grid, out var gridXform))
continue;
- var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
var localAABB = invWorldMatrix.TransformBox(aabb.Enlarged(float.Epsilon - SharedPathfindingSystem.ChunkSize));
foreach (var chunk in crumbs)
@@ -287,7 +287,7 @@ private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
return;
}
- var invGridMatrix = gridXform.InvWorldMatrix;
+ var invGridMatrix = _transformSystem.GetInvWorldMatrix(gridXform);
DebugPathPoly? nearest = null;
foreach (var poly in tile)
@@ -359,7 +359,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
continue;
}
- var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(aabb);
@@ -419,7 +419,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
- var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(aabb);
@@ -458,7 +458,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
- var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
+ var (_, _, worldMatrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(aabb);
@@ -483,7 +483,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
if (neighborPoly.NetEntity != poly.GraphUid)
{
color = Color.Green;
- var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager, _transformSystem);
+ var neighborMap = _transformSystem.ToMapCoordinates(_entManager.GetCoordinates(neighborPoly));
if (neighborMap.MapId != args.MapId)
continue;
@@ -517,7 +517,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
- var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(args.WorldBounds);
@@ -544,7 +544,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
if (!_entManager.TryGetComponent(_entManager.GetEntity(node.GraphUid), out var graphXform))
continue;
- worldHandle.SetTransform(graphXform.WorldMatrix);
+ worldHandle.SetTransform(_transformSystem.GetWorldMatrix(graphXform));
worldHandle.DrawRect(node.Box, Color.Orange.WithAlpha(0.10f));
}
}
@@ -568,7 +568,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
continue;
matrix = graph;
- worldHandle.SetTransform(graphXform.WorldMatrix);
+ worldHandle.SetTransform(_transformSystem.GetWorldMatrix(graphXform));
}
worldHandle.DrawRect(node.Box, new Color(0f, cost / highestGScore, 1f - (cost / highestGScore), 0.10f));
diff --git a/Content.Client/Ninja/Systems/ItemCreatorSystem.cs b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..9ab62cc12db
--- /dev/null
+++ b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
index 7758c3d7e2b..5b07b1588fd 100644
--- a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
@@ -2,9 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Does nothing special, only exists to provide a client implementation.
-///
-public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
-{
-}
+public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
index fde1801b37d..852ea8af46e 100644
--- a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
@@ -1,24 +1,5 @@
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Disables cloak prediction since client has no knowledge of battery power.
-/// Cloak will still be enabled after server tells it.
-///
-public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnAttemptStealth);
- }
-
- private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
- {
- args.Cancel();
- }
-}
+public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSystem.cs b/Content.Client/Ninja/Systems/NinjaSystem.cs
index aa2fa2047f1..958dc6a5d9a 100644
--- a/Content.Client/Ninja/Systems/NinjaSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSystem.cs
@@ -2,11 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Currently does nothing special clientside.
-/// All functionality is in shared and server.
-/// Only exists to prevent crashing.
-///
-public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
-{
-}
+public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;
diff --git a/Content.Client/Ninja/Systems/SpiderChargeSystem.cs b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
new file mode 100644
index 00000000000..b107fd3867d
--- /dev/null
+++ b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
diff --git a/Content.Client/Physics/JointVisualsOverlay.cs b/Content.Client/Physics/JointVisualsOverlay.cs
index 09c02746e2e..e0b3499a974 100644
--- a/Content.Client/Physics/JointVisualsOverlay.cs
+++ b/Content.Client/Physics/JointVisualsOverlay.cs
@@ -58,8 +58,8 @@ protected override void Draw(in OverlayDrawArgs args)
coordsA = coordsA.Offset(rotA.RotateVec(visuals.OffsetA));
coordsB = coordsB.Offset(rotB.RotateVec(visuals.OffsetB));
- var posA = coordsA.ToMapPos(_entManager, xformSystem);
- var posB = coordsB.ToMapPos(_entManager, xformSystem);
+ var posA = xformSystem.ToMapCoordinates(coordsA).Position;
+ var posB = xformSystem.ToMapCoordinates(coordsB).Position;
var diff = (posB - posA);
var length = diff.Length();
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index 3c99a188181..413b41c36a6 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -228,8 +228,8 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
if (!blip.Selectable)
continue;
-
- var currentDistance = (blip.Coordinates.ToMapPos(EntManager, _transformSystem) - worldPosition).Length();
+
+ var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
continue;
@@ -397,7 +397,7 @@ protected override void Draw(DrawingHandleScreen handle)
{
if (lit && value.Visible)
{
- var mapPos = coord.ToMap(EntManager, _transformSystem);
+ var mapPos = _transformSystem.ToMapCoordinates(coord);
if (mapPos.MapId != MapId.Nullspace)
{
@@ -418,7 +418,7 @@ protected override void Draw(DrawingHandleScreen handle)
if (blip.Texture == null)
continue;
- var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
+ var mapPos = _transformSystem.ToMapCoordinates(blip.Coordinates);
if (mapPos.MapId != MapId.Nullspace)
{
@@ -535,7 +535,7 @@ private void UpdateNavMapWallLines()
// East edge
neighborData = 0;
if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
- neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize];
+ neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
@@ -548,7 +548,7 @@ private void UpdateNavMapWallLines()
// South edge
neighborData = 0;
if (relativeTile.Y != 0)
- neighborData = chunk.TileData[i-1];
+ neighborData = chunk.TileData[i - 1];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
@@ -561,7 +561,7 @@ private void UpdateNavMapWallLines()
// West edge
neighborData = 0;
if (relativeTile.X != 0)
- neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize];
+ neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];
diff --git a/Content.Client/RCD/AlignRCDConstruction.cs b/Content.Client/RCD/AlignRCDConstruction.cs
index d5425425a76..ef99b01855c 100644
--- a/Content.Client/RCD/AlignRCDConstruction.cs
+++ b/Content.Client/RCD/AlignRCDConstruction.cs
@@ -45,7 +45,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
- var gridId = MouseCoords.GetGridUid(_entityManager);
+ var gridId = _transformSystem.GetGrid(MouseCoords);
if (!_entityManager.TryGetComponent(gridId, out var mapGrid))
return;
@@ -75,7 +75,7 @@ public override bool IsValidPosition(EntityCoordinates position)
if (!_entityManager.TryGetComponent(player, out var xform))
return false;
- if (!xform.Coordinates.InRange(_entityManager, _transformSystem, position, SharedInteractionSystem.InteractionRange))
+ if (!_transformSystem.InRange(xform.Coordinates, position, SharedInteractionSystem.InteractionRange))
{
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(0);
return false;
@@ -105,8 +105,8 @@ public override bool IsValidPosition(EntityCoordinates position)
if (currentState is not GameplayStateBase screen)
return false;
-
- var target = screen.GetClickedEntity(_unalignedMouseCoords.ToMap(_entityManager, _transformSystem));
+
+ var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_unalignedMouseCoords));
// Determine if the RCD operation is valid or not
if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false))
diff --git a/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs
index 8d5607af2d0..9ec24fae0ef 100644
--- a/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs
+++ b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs
@@ -116,7 +116,9 @@ private void RadiationQuery(IEye? currentEye)
var shaderInstance = _pulses[pulseEntity];
shaderInstance.instance.CurrentMapCoords = _transform.GetMapCoordinates(pulseEntity);
shaderInstance.instance.Range = pulse.VisualRange;
- } else {
+ }
+ else
+ {
_pulses[pulseEntity].shd.Dispose();
_pulses.Remove(pulseEntity);
}
@@ -129,7 +131,7 @@ private bool PulseQualifies(EntityUid pulseEntity, MapCoordinates currentEyeLoc)
var transformComponent = _entityManager.GetComponent(pulseEntity);
var transformSystem = _entityManager.System();
return transformComponent.MapID == currentEyeLoc.MapId
- && transformComponent.Coordinates.InRange(_entityManager, transformSystem, EntityCoordinates.FromMap(transformComponent.ParentUid, currentEyeLoc, transformSystem, _entityManager), MaxDist);
+ && transformSystem.InRange(transformComponent.Coordinates, transformSystem.ToCoordinates(transformComponent.ParentUid, currentEyeLoc), MaxDist);
}
private sealed record RadiationShaderInstance(MapCoordinates CurrentMapCoords, float Range, TimeSpan Start, float Duration)
diff --git a/Content.Client/Sandbox/SandboxSystem.cs b/Content.Client/Sandbox/SandboxSystem.cs
index 6a1129bb75d..8a4c93fa354 100644
--- a/Content.Client/Sandbox/SandboxSystem.cs
+++ b/Content.Client/Sandbox/SandboxSystem.cs
@@ -17,6 +17,7 @@ public sealed class SandboxSystem : SharedSandboxSystem
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedMapSystem _mapSystem = default!;
private bool _sandboxEnabled;
public bool SandboxAllowed { get; private set; }
@@ -92,7 +93,7 @@ public bool Copy(ICommonSession? session, EntityCoordinates coords, EntityUid ui
&& EntityManager.TryGetComponent(uid, out MetaDataComponent? comp)
&& !comp.EntityDeleted)
{
- if (comp.EntityPrototype == null || comp.EntityPrototype.NoSpawn || comp.EntityPrototype.Abstract)
+ if (comp.EntityPrototype == null || comp.EntityPrototype.HideSpawnMenu || comp.EntityPrototype.Abstract)
return false;
if (_placement.Eraser)
@@ -109,7 +110,8 @@ public bool Copy(ICommonSession? session, EntityCoordinates coords, EntityUid ui
}
// Try copy tile.
- if (!_map.TryFindGridAt(coords.ToMap(EntityManager, _transform), out _, out var grid) || !grid.TryGetTileRef(coords, out var tileRef))
+
+ if (!_map.TryFindGridAt(_transform.ToMapCoordinates(coords), out var gridUid, out var grid) || !_mapSystem.TryGetTileRef(gridUid, grid, coords, out var tileRef))
return false;
if (_placement.Eraser)
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.Console.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.Console.cs
index c134b7157c4..eb9ec285f8c 100644
--- a/Content.Client/Shuttles/Systems/ShuttleSystem.Console.cs
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.Console.cs
@@ -35,9 +35,9 @@ public MapCoordinates GetMapCoordinates(IMapObject mapObj)
switch (mapObj)
{
case ShuttleBeaconObject beacon:
- return GetCoordinates(beacon.Coordinates).ToMap(EntityManager, XformSystem);
+ return XformSystem.ToMapCoordinates(GetCoordinates(beacon.Coordinates));
case ShuttleExclusionObject exclusion:
- return GetCoordinates(exclusion.Coordinates).ToMap(EntityManager, XformSystem);
+ return XformSystem.ToMapCoordinates(GetCoordinates(exclusion.Coordinates));
case GridMapObject grid:
var gridXform = Transform(grid.Entity);
diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
index 489dbc8c90c..0d7df38b912 100644
--- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs
+++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
@@ -311,7 +311,7 @@ private void RebuildMapObjects()
};
_mapHeadings.Add(mapComp.MapId, gridContents);
- foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
+ foreach (var grid in _mapManager.GetAllGrids(mapComp.MapId))
{
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
index 31f0eecad79..61ae0699266 100644
--- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
@@ -107,7 +107,7 @@ protected override void Draw(DrawingHandleScreen handle)
DrawCircles(handle);
var gridNent = EntManager.GetNetEntity(GridEntity);
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
- var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner);
+ var ourGridMatrix = _xformSystem.GetWorldMatrix(GridEntity.Value);
var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix);
diff --git a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
index 8bd4a338cb0..53ad4a0b23a 100644
--- a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
@@ -519,7 +519,7 @@ private void AddMapObject(List edges, List verts, ValueList UIManager.GetActiveUIWidgetOrNull()?.AdminButton;
private PanicBunkerStatus? _panicBunker;
+ private BabyJailStatus? _babyJail;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(OnPanicBunkerUpdated);
+ SubscribeNetworkEvent(OnBabyJailUpdated);
}
private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEventArgs args)
@@ -56,6 +59,18 @@ private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEven
}
}
+ private void OnBabyJailUpdated(BabyJailChangedEvent msg, EntitySessionEventArgs args)
+ {
+ var showDialog = _babyJail == null && msg.Status.Enabled;
+ _babyJail = msg.Status;
+ _window?.BabyJailControl.UpdateStatus(msg.Status);
+
+ if (showDialog)
+ {
+ UIManager.CreateWindow().OpenCentered();
+ }
+ }
+
public void OnStateEntered(GameplayState state)
{
EnsureWindow();
@@ -101,6 +116,13 @@ private void EnsureWindow()
if (_panicBunker != null)
_window.PanicBunkerControl.UpdateStatus(_panicBunker);
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ if (_babyJail != null)
+ _window.BabyJailControl.UpdateStatus(_babyJail);
+
_window.PlayerTabControl.OnEntryKeyBindDown += PlayerTabEntryKeyBindDown;
_window.ObjectsTabControl.OnEntryKeyBindDown += ObjectsTabEntryKeyBindDown;
_window.OnOpen += OnWindowOpen;
diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
index eba4f21acbc..7604d5f8808 100644
--- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
+++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
@@ -220,7 +220,7 @@ private void ClientHeavyAttack(EntityUid user, EntityCoordinates coordinates, En
return;
}
- var targetMap = coordinates.ToMap(EntityManager, TransformSystem);
+ var targetMap = TransformSystem.ToMapCoordinates(coordinates);
if (targetMap.MapId != userXform.MapID)
return;
diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
index ac5914d47c0..840d1a9091f 100644
--- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
+++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
@@ -176,7 +176,7 @@ public override void Update(float frameTime)
}
// Define target coordinates relative to gun entity, so that network latency on moving grids doesn't fuck up the target location.
- var coordinates = EntityCoordinates.FromMap(entity, mousePos, TransformSystem, EntityManager);
+ var coordinates = TransformSystem.ToCoordinates(entity, mousePos);
NetEntity? target = null;
if (_state.CurrentState is GameplayStateBase screen)
@@ -200,7 +200,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid?
// Rather than splitting client / server for every ammo provider it's easier
// to just delete the spawned entities. This is for programmer sanity despite the wasted perf.
// This also means any ammo specific stuff can be grabbed as necessary.
- var direction = fromCoordinates.ToMapPos(EntityManager, TransformSystem) - toCoordinates.ToMapPos(EntityManager, TransformSystem);
+ var direction = TransformSystem.ToMapCoordinates(fromCoordinates).Position - TransformSystem.ToMapCoordinates(toCoordinates).Position;
var worldAngle = direction.ToAngle().Opposite();
foreach (var (ent, shootable) in ammo)
@@ -276,6 +276,14 @@ protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message,
if (!Timing.IsFirstTimePredicted)
return;
+ // EntityUid check added to stop throwing exceptions due to https://github.com/space-wizards/space-station-14/issues/28252
+ // TODO: Check to see why invalid entities are firing effects.
+ if (gunUid == EntityUid.Invalid)
+ {
+ Log.Debug($"Invalid Entity sent MuzzleFlashEvent (proto: {message.Prototype}, user: {user})");
+ return;
+ }
+
var gunXform = Transform(gunUid);
var gridUid = gunXform.GridUid;
EntityCoordinates coordinates;
@@ -375,6 +383,6 @@ protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message,
var uidPlayer = EnsureComp(gunUid);
_animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light");
- _animPlayer.Play((gunUid, uidPlayer), animTwo,"muzzle-flash-light");
+ _animPlayer.Play((gunUid, uidPlayer), animTwo, "muzzle-flash-light");
}
}
diff --git a/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
new file mode 100644
index 00000000000..3a1ec7fd40e
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
@@ -0,0 +1,53 @@
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Atmos.Piping.Components;
+using Content.Server.Atmos.Piping.EntitySystems;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Atmos;
+
+[TestFixture]
+public sealed class GridJoinTest
+{
+ private const string CanisterProtoId = "AirCanister";
+
+ [Test]
+ public async Task TestGridJoinAtmosphere()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var protoMan = server.ProtoMan;
+ var atmosSystem = entMan.System();
+ var atmosDeviceSystem = entMan.System();
+ var transformSystem = entMan.System();
+
+ var testMap = await pair.CreateTestMap();
+
+ await server.WaitPost(() =>
+ {
+ // Spawn an atmos device on the grid
+ var canister = entMan.Spawn(CanisterProtoId);
+ transformSystem.SetCoordinates(canister, testMap.GridCoords);
+ var deviceComp = entMan.GetComponent(canister);
+ var canisterEnt = (canister, deviceComp);
+
+ // Make sure the canister is tracked as an off-grid device
+ Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt));
+
+ // Add an atmosphere to the grid
+ entMan.AddComponent(testMap.Grid);
+
+ // Force AtmosDeviceSystem to update off-grid devices
+ // This means the canister is now considered on-grid,
+ // but it's still tracked as off-grid!
+ Assert.DoesNotThrow(() => atmosDeviceSystem.Update(atmosSystem.AtmosTime));
+
+ // Make sure that the canister is now properly tracked as on-grid
+ Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt), Is.False);
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs b/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs
new file mode 100644
index 00000000000..31d33ba6174
--- /dev/null
+++ b/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs
@@ -0,0 +1,133 @@
+using System.Linq;
+using Content.Server.Antag.Components;
+using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Mind;
+using Content.Server.Roles;
+using Content.Shared.GameTicking;
+using Content.Shared.GameTicking.Components;
+using Content.Shared.Mind;
+using Content.Shared.NPC.Systems;
+using Content.Shared.Objectives.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.GameRules;
+
+[TestFixture]
+public sealed class TraitorRuleTest
+{
+ private const string TraitorGameRuleProtoId = "Traitor";
+ private const string TraitorAntagRoleName = "Traitor";
+
+ [Test]
+ public async Task TestTraitorObjectives()
+ {
+ await using var pair = await PoolManager.GetServerClient(new PoolSettings()
+ {
+ Dirty = true,
+ DummyTicker = false,
+ Connected = true,
+ InLobby = true,
+ });
+ var server = pair.Server;
+ var client = pair.Client;
+ var entMan = server.EntMan;
+ var protoMan = server.ProtoMan;
+ var compFact = server.ResolveDependency();
+ var ticker = server.System();
+ var mindSys = server.System();
+ var roleSys = server.System();
+ var factionSys = server.System();
+ var traitorRuleSys = server.System();
+
+ // Look up the minimum player count and max total objective difficulty for the game rule
+ var minPlayers = 1;
+ var maxDifficulty = 0f;
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(protoMan.TryIndex(TraitorGameRuleProtoId, out var gameRuleEnt),
+ $"Failed to lookup traitor game rule entity prototype with ID \"{TraitorGameRuleProtoId}\"!");
+
+ Assert.That(gameRuleEnt.TryGetComponent(out var gameRule, compFact),
+ $"Game rule entity {TraitorGameRuleProtoId} does not have a GameRuleComponent!");
+
+ Assert.That(gameRuleEnt.TryGetComponent(out var randomObjectives, compFact),
+ $"Game rule entity {TraitorGameRuleProtoId} does not have an AntagRandomObjectivesComponent!");
+
+ minPlayers = gameRule.MinPlayers;
+ maxDifficulty = randomObjectives.MaxDifficulty;
+ });
+
+ // Initially in the lobby
+ Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
+ Assert.That(client.AttachedEntity, Is.Null);
+ Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
+
+ // Add enough dummy players for the game rule
+ var dummies = await pair.Server.AddDummySessions(minPlayers);
+ await pair.RunTicksSync(5);
+
+ // Initially, the players have no attached entities
+ Assert.That(pair.Player?.AttachedEntity, Is.Null);
+ Assert.That(dummies.All(x => x.AttachedEntity == null));
+
+ // Opt-in the player for the traitor role
+ await pair.SetAntagPreference(TraitorAntagRoleName, true);
+
+ // Add the game rule
+ var gameRuleEnt = ticker.AddGameRule(TraitorGameRuleProtoId);
+ Assert.That(entMan.TryGetComponent(gameRuleEnt, out var traitorRule));
+
+ // Ready up
+ ticker.ToggleReadyAll(true);
+ Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
+
+ // Start the round
+ await server.WaitPost(() =>
+ {
+ ticker.StartRound();
+ // Force traitor mode to start (skip the delay)
+ ticker.StartGameRule(gameRuleEnt);
+ });
+ await pair.RunTicksSync(10);
+
+ // Game should have started
+ Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
+ Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame));
+ Assert.That(client.EntMan.EntityExists(client.AttachedEntity));
+
+ // Check the player and dummies are spawned
+ var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray();
+ var player = pair.Player!.AttachedEntity!.Value;
+ Assert.That(entMan.EntityExists(player));
+ Assert.That(dummyEnts.All(entMan.EntityExists));
+
+ // Make sure the player is a traitor.
+ var mind = mindSys.GetMind(player)!.Value;
+ Assert.That(roleSys.MindIsAntagonist(mind));
+ Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
+ Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
+ Assert.That(traitorRule.TotalTraitors, Is.EqualTo(1));
+ Assert.That(traitorRule.TraitorMinds[0], Is.EqualTo(mind));
+
+ // Check total objective difficulty
+ Assert.That(entMan.TryGetComponent(mind, out var mindComp));
+ var totalDifficulty = mindComp.Objectives.Sum(o => entMan.GetComponent(o).Difficulty);
+ Assert.That(totalDifficulty, Is.AtMost(maxDifficulty),
+ $"MaxDifficulty exceeded! Objectives: {string.Join(", ", mindComp.Objectives.Select(o => FormatObjective(o, entMan)))}");
+ Assert.That(mindComp.Objectives, Is.Not.Empty,
+ $"No objectives assigned!");
+
+
+ await pair.CleanReturnAsync();
+ }
+
+ private static string FormatObjective(Entity entity, IEntityManager entMan)
+ {
+ var meta = entMan.GetComponent(entity);
+ var objective = entMan.GetComponent(entity);
+ return $"{meta.EntityName} ({objective.Difficulty})";
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Hands/HandTests.cs b/Content.IntegrationTests/Tests/Hands/HandTests.cs
index 9ecabbeebf6..5e96015feb7 100644
--- a/Content.IntegrationTests/Tests/Hands/HandTests.cs
+++ b/Content.IntegrationTests/Tests/Hands/HandTests.cs
@@ -1,8 +1,10 @@
using System.Linq;
+using Content.Server.Storage.EntitySystems;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Server.Player;
+using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -11,6 +13,19 @@ namespace Content.IntegrationTests.Tests.Hands;
[TestFixture]
public sealed class HandTests
{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: entity
+ id: TestPickUpThenDropInContainerTestBox
+ name: box
+ components:
+ - type: EntityStorage
+ - type: ContainerContainer
+ containers:
+ entity_storage: !type:Container
+";
+
+
[Test]
public async Task TestPickupDrop()
{
@@ -57,4 +72,69 @@ await server.WaitPost(() =>
await server.WaitPost(() => mapMan.DeleteMap(data.MapId));
await pair.CleanReturnAsync();
}
+
+ [Test]
+ public async Task TestPickUpThenDropInContainer()
+ {
+ await using var pair = await PoolManager.GetServerClient(new PoolSettings
+ {
+ Connected = true,
+ DummyTicker = false
+ });
+ var server = pair.Server;
+ var map = await pair.CreateTestMap();
+ await pair.RunTicksSync(5);
+
+ var entMan = server.ResolveDependency();
+ var playerMan = server.ResolveDependency();
+ var mapMan = server.ResolveDependency();
+ var sys = entMan.System();
+ var tSys = entMan.System();
+ var containerSystem = server.System();
+
+ EntityUid item = default;
+ EntityUid box = default;
+ EntityUid player = default;
+ HandsComponent hands = default!;
+
+ // spawn the elusive box and crowbar at the coordinates
+ await server.WaitPost(() => box = server.EntMan.SpawnEntity("TestPickUpThenDropInContainerTestBox", map.GridCoords));
+ await server.WaitPost(() => item = server.EntMan.SpawnEntity("Crowbar", map.GridCoords));
+ // place the player at the exact same coordinates and have them grab the crowbar
+ await server.WaitPost(() =>
+ {
+ player = playerMan.Sessions.First().AttachedEntity!.Value;
+ tSys.PlaceNextTo(player, item);
+ hands = entMan.GetComponent(player);
+ sys.TryPickup(player, item, hands.ActiveHand!);
+ });
+ await pair.RunTicksSync(5);
+ Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
+
+ // Open then close the box to place the player, who is holding the crowbar, inside of it
+ var storage = server.System();
+ await server.WaitPost(() =>
+ {
+ storage.OpenStorage(box);
+ storage.CloseStorage(box);
+ });
+ await pair.RunTicksSync(5);
+ Assert.That(containerSystem.IsEntityInContainer(player), Is.True);
+
+ // Dropping the item while the player is inside the box should cause the item
+ // to also be inside the same container the player is in now,
+ // with the item not being in the player's hands
+ await server.WaitPost(() =>
+ {
+ sys.TryDrop(player, item, null!);
+ });
+ await pair.RunTicksSync(5);
+ var xform = entMan.GetComponent(player);
+ var itemXform = entMan.GetComponent(item);
+ Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
+ Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform)));
+
+ await server.WaitPost(() => mapMan.DeleteMap(map.MapId));
+ await pair.CleanReturnAsync();
+ }
}
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs
index 053152dbe1b..194bc54fba6 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs
@@ -114,7 +114,7 @@ await Server.WaitPost(() =>
return await SpawnEntity((stack.StackTypeId, spec.Quantity), coords);
Assert.That(spec.Quantity, Is.EqualTo(1), "SpawnEntity only supports returning a singular entity");
- await Server.WaitPost(() => uid = SEntMan.SpawnEntity(spec.Prototype, coords));
+ await Server.WaitPost(() => uid = SEntMan.SpawnAtPosition(spec.Prototype, coords));
return uid;
}
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
index a09126a7f7c..0f2c314ed01 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
@@ -91,7 +91,7 @@ protected async Task SpawnTarget(string prototype)
Target = NetEntity.Invalid;
await Server.WaitPost(() =>
{
- Target = SEntMan.GetNetEntity(SEntMan.SpawnEntity(prototype, SEntMan.GetCoordinates(TargetCoords)));
+ Target = SEntMan.GetNetEntity(SEntMan.SpawnAtPosition(prototype, SEntMan.GetCoordinates(TargetCoords)));
});
await RunTicks(5);
@@ -171,7 +171,7 @@ await Server.WaitPost(() =>
// turn on welders
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
{
- Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle));
+ Assert.That(ItemToggleSys.TryActivate((item, itemToggle), user: playerEnt));
}
});
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 457d3e31920..b3d684e01a0 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -104,7 +104,7 @@ public abstract partial class InteractionTest
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
protected SharedDoAfterSystem DoAfterSys = default!;
protected ToolSystem ToolSys = default!;
- protected SharedItemToggleSystem ItemToggleSys = default!;
+ protected ItemToggleSystem ItemToggleSys = default!;
protected InteractionTestSystem STestSystem = default!;
protected SharedTransformSystem Transform = default!;
protected SharedMapSystem MapSystem = default!;
@@ -165,7 +165,7 @@ public virtual async Task Setup()
HandSys = SEntMan.System();
InteractSys = SEntMan.System();
ToolSys = SEntMan.System();
- ItemToggleSys = SEntMan.System();
+ ItemToggleSys = SEntMan.System();
DoAfterSys = SEntMan.System();
Transform = SEntMan.System();
MapSystem = SEntMan.System();
diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs
index 557e1b7c098..610b314d096 100644
--- a/Content.Server.Database/Model.cs
+++ b/Content.Server.Database/Model.cs
@@ -902,6 +902,10 @@ public enum ConnectionDenyReason : byte
Whitelist = 1,
Full = 2,
Panic = 3,
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ BabyJail = 4,
}
public class ServerBanHit
diff --git a/Content.Server/ADT/Language/TranslatorSystem.cs b/Content.Server/ADT/Language/TranslatorSystem.cs
index e19b2b29d71..7e3b8cfd0a7 100644
--- a/Content.Server/ADT/Language/TranslatorSystem.cs
+++ b/Content.Server/ADT/Language/TranslatorSystem.cs
@@ -162,7 +162,7 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen
isEnabled &= hasPower;
UpdateBoundIntrinsicComp(component, intrinsic, isEnabled);
component.Enabled = isEnabled;
- _powerCell.SetPowerCellDrawEnabled(translator, isEnabled);
+ _powerCell.SetDrawEnabled(translator, isEnabled);
_language.EnsureValidLanguage(holder);
UpdatedLanguages(holder);
@@ -171,7 +171,7 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen
{
// This is a standalone translator (e.g. lying on the ground). Simply toggle its state.
component.Enabled = !component.Enabled && hasPower;
- _powerCell.SetPowerCellDrawEnabled(translator, !component.Enabled && hasPower);
+ _powerCell.SetDrawEnabled(translator, !component.Enabled && hasPower);
}
OnAppearanceChange(translator, component);
@@ -188,7 +188,7 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen
private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args)
{
component.Enabled = false;
- _powerCell.SetPowerCellDrawEnabled(translator, false);
+ _powerCell.SetDrawEnabled(translator, false);
OnAppearanceChange(translator, component);
if (Transform(translator).ParentUid is { Valid: true } holder && EntityManager.HasComponent(holder))
diff --git a/Content.Server/Administration/Commands/BabyJailCommand.cs b/Content.Server/Administration/Commands/BabyJailCommand.cs
new file mode 100644
index 00000000000..058b67ca528
--- /dev/null
+++ b/Content.Server/Administration/Commands/BabyJailCommand.cs
@@ -0,0 +1,139 @@
+using Content.Shared.Administration;
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Server.Administration.Commands;
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var toggle = Toggle(CCVars.BabyJailEnabled, shell, args, _cfg);
+ if (toggle == null)
+ return;
+
+ shell.WriteLine(Loc.GetString(toggle.Value ? "babyjail-command-enabled" : "babyjail-command-disabled"));
+ }
+
+ public static bool? Toggle(CVarDef cvar, IConsoleShell shell, string[] args, IConfigurationManager config)
+ {
+ if (args.Length > 1)
+ {
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return null;
+ }
+
+ var enabled = config.GetCVar(cvar);
+
+ switch (args.Length)
+ {
+ case 0:
+ enabled = !enabled;
+ break;
+ case 1 when !bool.TryParse(args[0], out enabled):
+ shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
+ return null;
+ }
+
+ config.SetCVar(cvar, enabled);
+
+ return enabled;
+ }
+}
+
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailShowReasonCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_show_reason";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var toggle = BabyJailCommand.Toggle(CCVars.BabyJailShowReason, shell, args, _cfg);
+ if (toggle == null)
+ return;
+
+ shell.WriteLine(Loc.GetString(toggle.Value
+ ? "babyjail-command-show-reason-enabled"
+ : "babyjail-command-show-reason-disabled"
+ ));
+ }
+}
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailMinAccountAgeCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_max_account_age";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 0:
+ {
+ var current = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-is", ("minutes", current)));
+ break;
+ }
+ case > 1:
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var minutes))
+ {
+ shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
+ return;
+ }
+
+ _cfg.SetCVar(CCVars.BabyJailMaxAccountAge, minutes);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-set", ("minutes", minutes)));
+ }
+}
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailMinOverallHoursCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_max_overall_minutes";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 0:
+ {
+ var current = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-overall-minutes-is", ("minutes", current)));
+ break;
+ }
+ case > 1:
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var hours))
+ {
+ shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
+ return;
+ }
+
+ _cfg.SetCVar(CCVars.BabyJailMaxOverallMinutes, hours);
+ shell.WriteLine(Loc.GetString("babyjail-command-overall-minutes-set", ("hours", hours)));
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs
index f1a9fcdb218..fb0cd3d82e4 100644
--- a/Content.Server/Administration/Systems/AdminSystem.cs
+++ b/Content.Server/Administration/Systems/AdminSystem.cs
@@ -62,6 +62,7 @@ public sealed class AdminSystem : EntitySystem
private readonly HashSet _roundActivePlayers = new();
public readonly PanicBunkerStatus PanicBunker = new();
+ public readonly BabyJailStatus BabyJail = new();
public override void Initialize()
{
@@ -71,6 +72,7 @@ public override void Initialize()
_adminManager.OnPermsChanged += OnAdminPermsChanged;
_playTime.SessionPlayTimeUpdated += OnSessionPlayTimeUpdated;
+ // Panic Bunker Settings
Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true);
@@ -80,6 +82,16 @@ public override void Initialize()
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
Subs.CVar(_config, CCCVars.PanicBunkerDenyVPN, OnPanicBunkerDenyVpnChanged, true); // Corvax-VPNGuard
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ // Baby Jail Settings
+ Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true);
+
SubscribeLocalEvent(OnIdentityChanged);
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
@@ -252,6 +264,17 @@ private void OnPanicBunkerChanged(bool enabled)
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailChanged(bool enabled)
+ {
+ BabyJail.Enabled = enabled;
+ _chat.SendAdminAlert(Loc.GetString(enabled
+ ? "admin-ui-baby-jail-enabled-admin-alert"
+ : "admin-ui-baby-jail-disabled-admin-alert"
+ ));
+
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
{
PanicBunker.DisableWithAdmins = enabled;
@@ -276,18 +299,36 @@ private void OnPanicBunkerShowReasonChanged(bool enabled)
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailShowReasonChanged(bool enabled)
+ {
+ BabyJail.ShowReason = enabled;
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
{
PanicBunker.MinAccountAgeMinutes = minutes;
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailMaxAccountAgeChanged(int minutes)
+ {
+ BabyJail.MaxAccountAgeMinutes = minutes;
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerMinOverallMinutesChanged(int minutes)
{
PanicBunker.MinOverallMinutes = minutes;
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailMaxOverallMinutesChanged(int minutes)
+ {
+ BabyJail.MaxOverallMinutes = minutes;
+ SendBabyJailStatusAll();
+ }
+
// Corvax-VPNGuard-Start
private void OnPanicBunkerDenyVpnChanged(bool deny)
{
@@ -337,6 +378,15 @@ private void SendPanicBunkerStatusAll()
}
}
+ private void SendBabyJailStatusAll()
+ {
+ var ev = new BabyJailChangedEvent(BabyJail);
+ foreach (var admin in _adminManager.AllAdmins)
+ {
+ RaiseNetworkEvent(ev, admin);
+ }
+ }
+
///
/// Erases a player from the round.
/// This removes them and any trace of them from the round, deleting their
diff --git a/Content.Server/Antag/AntagRandomObjectivesSystem.cs b/Content.Server/Antag/AntagRandomObjectivesSystem.cs
index c935b8c0648..b60759a3d5c 100644
--- a/Content.Server/Antag/AntagRandomObjectivesSystem.cs
+++ b/Content.Server/Antag/AntagRandomObjectivesSystem.cs
@@ -39,7 +39,8 @@ private void OnAntagSelected(Entity ent, ref Aft
for (var pick = 0; pick < set.MaxPicks && ent.Comp.MaxDifficulty > difficulty; pick++)
{
- if (_objectives.GetRandomObjective(mindId, mind, set.Groups) is not {} objective)
+ var remainingDifficulty = ent.Comp.MaxDifficulty - difficulty;
+ if (_objectives.GetRandomObjective(mindId, mind, set.Groups, remainingDifficulty) is not { } objective)
continue;
_mind.AddObjective(mindId, mind, objective);
diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
index c2cdd4a1072..0f4490cd7eb 100644
--- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
@@ -162,7 +162,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul
if (component.LastPosition.HasValue)
{
// Check if position is out of range => don't update and disable
- if (!component.LastPosition.Value.InRange(EntityManager, _transform, userPos, SharedInteractionSystem.InteractionRange))
+ if (!_transform.InRange(component.LastPosition.Value, userPos, SharedInteractionSystem.InteractionRange))
{
if (component.User is { } userId && component.Enabled)
_popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
index baad739804b..2d703439c0f 100644
--- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
@@ -81,7 +81,7 @@ public void UpdateUserInterface(Entity ent, bool initialUpdate
TankPressure = component.Air?.Pressure ?? 0,
OutputPressure = initialUpdate ? component.OutputPressure : null,
InternalsConnected = component.IsConnected,
- CanConnectInternals = CanConnectToInternals(component)
+ CanConnectInternals = CanConnectToInternals(ent)
});
}
@@ -217,24 +217,24 @@ public GasMixture RemoveAirVolume(Entity gasTank, float volume
return air;
}
- public bool CanConnectToInternals(GasTankComponent component)
+ public bool CanConnectToInternals(Entity ent)
{
- var internals = GetInternalsComponent(component, component.User);
- return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen;
+ TryGetInternalsComp(ent, out _, out var internalsComp, ent.Comp.User);
+ return internalsComp != null && internalsComp.BreathTools.Count != 0 && !ent.Comp.IsValveOpen;
}
public void ConnectToInternals(Entity ent)
{
var (owner, component) = ent;
- if (component.IsConnected || !CanConnectToInternals(component))
+ if (component.IsConnected || !CanConnectToInternals(ent))
return;
- var internals = GetInternalsComponent(component);
- if (internals == null)
+ TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, ent.Comp.User);
+ if (internalsUid == null || internalsComp == null)
return;
- if (_internals.TryConnectTank((internals.Owner, internals), owner))
- component.User = internals.Owner;
+ if (_internals.TryConnectTank((internalsUid.Value, internalsComp), owner))
+ component.User = internalsUid.Value;
_actions.SetToggled(component.ToggleActionEntity, component.IsConnected);
@@ -243,7 +243,7 @@ public void ConnectToInternals(Entity ent)
return;
component.ConnectStream = _audioSys.Stop(component.ConnectStream);
- component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, component.Owner)?.Entity;
+ component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, owner)?.Entity;
UpdateUserInterface(ent);
}
@@ -251,29 +251,59 @@ public void ConnectToInternals(Entity ent)
public void DisconnectFromInternals(Entity ent)
{
var (owner, component) = ent;
+
if (component.User == null)
return;
- var internals = GetInternalsComponent(component);
+ TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, component.User);
component.User = null;
_actions.SetToggled(component.ToggleActionEntity, false);
- _internals.DisconnectTank(internals);
+ if (internalsUid != null && internalsComp != null)
+ _internals.DisconnectTank((internalsUid.Value, internalsComp));
component.DisconnectStream = _audioSys.Stop(component.DisconnectStream);
- component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, component.Owner)?.Entity;
+ component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, owner)?.Entity;
UpdateUserInterface(ent);
}
- private InternalsComponent? GetInternalsComponent(GasTankComponent component, EntityUid? owner = null)
+ ///
+ /// Tries to retrieve the internals component of either the gas tank's user,
+ /// or the gas tank's... containing container
+ ///
+ /// The user of the gas tank
+ /// True if internals comp isn't null, false if it is null
+ private bool TryGetInternalsComp(Entity ent, out EntityUid? internalsUid, out InternalsComponent? internalsComp, EntityUid? user = null)
{
- owner ??= component.User;
- if (Deleted(component.Owner))return null;
- if (owner != null) return CompOrNull(owner.Value);
- return _containers.TryGetContainingContainer(component.Owner, out var container)
- ? CompOrNull(container.Owner)
- : null;
+ internalsUid = default;
+ internalsComp = default;
+
+ // If the gas tank doesn't exist for whatever reason, don't even bother
+ if (TerminatingOrDeleted(ent.Owner))
+ return false;
+
+ user ??= ent.Comp.User;
+ // Check if the gas tank's user actually has the component that allows them to use a gas tank and mask
+ if (TryComp(user, out var userInternalsComp) && userInternalsComp != null)
+ {
+ internalsUid = user;
+ internalsComp = userInternalsComp;
+ return true;
+ }
+
+ // Yeah I have no clue what this actually does, I appreciate the lack of comments on the original function
+ if (_containers.TryGetContainingContainer((ent.Owner, Transform(ent.Owner)), out var container) && container != null)
+ {
+ if (TryComp(container.Owner, out var containerInternalsComp) && containerInternalsComp != null)
+ {
+ internalsUid = container.Owner;
+ internalsComp = containerInternalsComp;
+ return true;
+ }
+ }
+
+ return false;
}
public void AssumeAir(Entity ent, GasMixture giver)
@@ -321,7 +351,7 @@ public void CheckStatus(Entity ent)
if(environment != null)
_atmosphereSystem.Merge(environment, component.Air);
- _audioSys.PlayPvs(component.RuptureSound, Transform(component.Owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
+ _audioSys.PlayPvs(component.RuptureSound, Transform(owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
QueueDel(owner);
return;
diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs
index 3c73a8f64ee..f932ef36208 100644
--- a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs
+++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs
@@ -132,10 +132,19 @@ public override void Update(float frameTime)
var ev = new AtmosDeviceUpdateEvent(_atmosphereSystem.AtmosTime, null, null);
foreach (var device in _joinedDevices)
{
- DebugTools.Assert(!HasComp(Transform(device).GridUid));
+ var deviceGrid = Transform(device).GridUid;
+ if (HasComp(deviceGrid))
+ {
+ RejoinAtmosphere(device);
+ }
RaiseLocalEvent(device, ref ev);
device.Comp.LastProcess = time;
}
}
+
+ public bool IsJoinedOffGrid(Entity device)
+ {
+ return _joinedDevices.Contains(device);
+ }
}
}
diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs
index d6ece39d590..10ebd934aa4 100644
--- a/Content.Server/Body/Systems/InternalsSystem.cs
+++ b/Content.Server/Body/Systems/InternalsSystem.cs
@@ -102,7 +102,7 @@ public void ToggleInternals(
{
if (force)
{
- DisconnectTank(internals);
+ DisconnectTank((uid, internals));
return;
}
@@ -199,16 +199,13 @@ public void ConnectBreathTool(Entity ent, EntityUid toolEnti
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
}
- public void DisconnectTank(InternalsComponent? component)
+ public void DisconnectTank(Entity ent)
{
- if (component is null)
- return;
-
- if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
- _gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank));
+ if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
+ _gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
- component.GasTankEntity = null;
- _alerts.ShowAlert(component.Owner, component.InternalsAlert, GetSeverity(component));
+ ent.Comp.GasTankEntity = null;
+ _alerts.ShowAlert(ent.Owner, ent.Comp.InternalsAlert, GetSeverity(ent.Comp));
}
public bool TryConnectTank(Entity ent, EntityUid tankEntity)
@@ -267,21 +264,21 @@ private short GetSeverity(InternalsComponent component)
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
TryComp(backEntity, out var backGasTank) &&
- _gasTank.CanConnectToInternals(backGasTank))
+ _gasTank.CanConnectToInternals((backEntity.Value, backGasTank)))
{
return (backEntity.Value, backGasTank);
}
if (_inventory.TryGetSlotEntity(user, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
TryComp(entity, out var gasTank) &&
- _gasTank.CanConnectToInternals(gasTank))
+ _gasTank.CanConnectToInternals((entity.Value, gasTank)))
{
return (entity.Value, gasTank);
}
foreach (var item in _inventory.GetHandOrInventoryEntities((user.Owner, user.Comp1, user.Comp2)))
{
- if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
+ if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals((item, gasTank)))
return (item, gasTank);
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
index a288d7b07d4..dd408755e67 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
@@ -40,6 +40,7 @@ private void InitializeConsole()
SubscribeLocalEvent(OnOrderUIOpened);
SubscribeLocalEvent(OnInit);
SubscribeLocalEvent(OnInteractUsing);
+ SubscribeLocalEvent(OnOrderBalanceUpdated);
Reset();
}
@@ -315,6 +316,15 @@ private void OnOrderUIOpened(EntityUid uid, CargoOrderConsoleComponent component
#endregion
+
+ private void OnOrderBalanceUpdated(Entity ent, ref BankBalanceUpdatedEvent args)
+ {
+ if (!_uiSystem.IsUiOpen(ent.Owner, CargoConsoleUiKey.Orders))
+ return;
+
+ UpdateOrderState(ent, args.Station);
+ }
+
private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
{
if (station == null ||
diff --git a/Content.Server/Cargo/Systems/CargoSystem.cs b/Content.Server/Cargo/Systems/CargoSystem.cs
index a93a7bdcc22..1b33404e355 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.cs
@@ -81,18 +81,18 @@ public override void Update(float frameTime)
public void UpdateBankAccount(EntityUid uid, StationBankAccountComponent component, int balanceAdded)
{
component.Balance += balanceAdded;
- var query = EntityQueryEnumerator();
+ var query = EntityQueryEnumerator();
- while (query.MoveNext(out var oUid, out var _))
+ var ev = new BankBalanceUpdatedEvent(uid, component.Balance);
+ while (query.MoveNext(out var client, out var comp, out var xform))
{
- if (!_uiSystem.IsUiOpen(oUid, CargoConsoleUiKey.Orders))
- continue;
-
- var station = _station.GetOwningStation(oUid);
+ var station = _station.GetOwningStation(client, xform);
if (station != uid)
continue;
- UpdateOrderState(oUid, station);
+ comp.Balance = component.Balance;
+ Dirty(client, comp);
+ RaiseLocalEvent(client, ref ev);
}
}
}
diff --git a/Content.Server/Charges/Components/AutoRechargeComponent.cs b/Content.Server/Charges/Components/AutoRechargeComponent.cs
index 9dcf555ea93..165b181dcbc 100644
--- a/Content.Server/Charges/Components/AutoRechargeComponent.cs
+++ b/Content.Server/Charges/Components/AutoRechargeComponent.cs
@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
/// Something with limited charges that can be recharged automatically.
/// Requires LimitedChargesComponent to function.
///
+// TODO: no reason this cant be predicted and server system deleted
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(ChargesSystem))]
public sealed partial class AutoRechargeComponent : Component
diff --git a/Content.Server/Charges/Systems/ChargesSystem.cs b/Content.Server/Charges/Systems/ChargesSystem.cs
index 03e192e680e..974928ee4bb 100644
--- a/Content.Server/Charges/Systems/ChargesSystem.cs
+++ b/Content.Server/Charges/Systems/ChargesSystem.cs
@@ -37,15 +37,17 @@ protected override void OnExamine(EntityUid uid, LimitedChargesComponent comp, E
args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
}
- public override void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ public override void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
{
- if (!Resolve(uid, ref comp, false))
+ if (!Query.Resolve(uid, ref comp, false))
return;
var startRecharge = comp.Charges == comp.MaxCharges;
- base.UseCharge(uid, comp);
- // start the recharge time after first use at full charge
- if (startRecharge && TryComp(uid, out var recharge))
+ base.AddCharges(uid, change, comp);
+
+ // if a charge was just used from full, start the recharge timer
+ // TODO: probably make this an event instead of having le server system that just does this
+ if (change < 0 && startRecharge && TryComp(uid, out var recharge))
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
}
}
diff --git a/Content.Server/Chat/Systems/AnnounceOnSpawnSystem.cs b/Content.Server/Chat/Systems/AnnounceOnSpawnSystem.cs
index 0f0365e56ba..cc46545237b 100644
--- a/Content.Server/Chat/Systems/AnnounceOnSpawnSystem.cs
+++ b/Content.Server/Chat/Systems/AnnounceOnSpawnSystem.cs
@@ -16,7 +16,7 @@ public override void Initialize()
private void OnInit(EntityUid uid, AnnounceOnSpawnComponent comp, MapInitEvent args)
{
var message = Loc.GetString(comp.Message);
- var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : "Central Command";
+ var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : Loc.GetString("chat-manager-sender-announcement");
_chat.DispatchGlobalAnnouncement(message, sender, playSound: true, comp.Sound, comp.Color);
}
}
diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs
index 93818d0df64..9826deafda4 100644
--- a/Content.Server/Chat/Systems/ChatSystem.cs
+++ b/Content.Server/Chat/Systems/ChatSystem.cs
@@ -325,12 +325,14 @@ public void TrySendInGameOOCMessage(
/// Optional color for the announcement message
public void DispatchGlobalAnnouncement(
string message,
- string sender = "Central Command",
+ string? sender = null,
bool playSound = true,
SoundSpecifier? announcementSound = null,
Color? colorOverride = null
)
{
+ sender ??= Loc.GetString("chat-manager-sender-announcement");
+
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
if (playSound)
@@ -352,11 +354,13 @@ public void DispatchGlobalAnnouncement(
public void DispatchStationAnnouncement(
EntityUid source,
string message,
- string sender = "Central Command",
+ string? sender = null,
bool playDefaultSound = true,
SoundSpecifier? announcementSound = null,
Color? colorOverride = null)
{
+ sender ??= Loc.GetString("chat-manager-sender-announcement");
+
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
var station = _stationSystem.GetOwningStation(source);
diff --git a/Content.Server/Clock/ClockSystem.cs b/Content.Server/Clock/ClockSystem.cs
new file mode 100644
index 00000000000..29400a59f7b
--- /dev/null
+++ b/Content.Server/Clock/ClockSystem.cs
@@ -0,0 +1,42 @@
+using Content.Server.GameTicking.Events;
+using Content.Shared.Clock;
+using Content.Shared.Destructible;
+using Robust.Server.GameStates;
+using Robust.Shared.Random;
+
+namespace Content.Server.Clock;
+
+public sealed class ClockSystem : SharedClockSystem
+{
+ [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnRoundStart);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnBreak);
+ }
+
+ private void OnRoundStart(RoundStartingEvent ev)
+ {
+ var manager = Spawn();
+ AddComp(manager);
+ }
+
+ private void OnMapInit(Entity ent, ref MapInitEvent args)
+ {
+ ent.Comp.TimeOffset = TimeSpan.FromHours(_robustRandom.NextFloat(0, 24));
+ _pvsOverride.AddGlobalOverride(ent);
+ Dirty(ent);
+ }
+
+ private void OnBreak(Entity ent, ref BreakageEventArgs args)
+ {
+ ent.Comp.StuckTime = GetClockTime(ent);
+ Dirty(ent, ent.Comp);
+ }
+}
diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs
index 99bb44db998..214f83268af 100644
--- a/Content.Server/Connection/ConnectionManager.cs
+++ b/Content.Server/Connection/ConnectionManager.cs
@@ -19,6 +19,9 @@
using Robust.Shared.Player;
using Robust.Shared.Timing;
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
namespace Content.Server.Connection
{
@@ -274,6 +277,14 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame
}
}
+ if (_cfg.GetCVar(CCVars.BabyJailEnabled) && adminData == null)
+ {
+ var result = await IsInvalidConnectionDueToBabyJail(userId, e);
+
+ if (result.IsInvalid)
+ return (ConnectionDenyReason.BabyJail, result.Reason, null);
+ }
+
var wasInGame = EntitySystem.TryGet(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
@@ -306,6 +317,61 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame
return null;
}
+ private async Task<(bool IsInvalid, string Reason)> IsInvalidConnectionDueToBabyJail(NetUserId userId, NetConnectingArgs e)
+ {
+ // If you're whitelisted then bypass this whole thing
+ if (await _db.GetWhitelistStatusAsync(userId))
+ return (false, "");
+
+ // Initial cvar retrieval
+ var showReason = _cfg.GetCVar(CCVars.BabyJailShowReason);
+ var reason = _cfg.GetCVar(CCVars.BabyJailCustomReason);
+ var maxAccountAgeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
+ var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
+
+ // Wait some time to lookup data
+ var record = await _dbManager.GetPlayerRecordByUserId(userId);
+
+ // No player record = new account or the DB is having a skill issue
+ if (record == null)
+ return (false, "");
+
+ var isAccountAgeInvalid = record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
+ if (isAccountAgeInvalid && showReason)
+ {
+ var locAccountReason = reason != string.Empty
+ ? reason
+ : Loc.GetString("baby-jail-account-denied-reason",
+ ("reason",
+ Loc.GetString(
+ "baby-jail-account-reason-account",
+ ("minutes", maxAccountAgeMinutes))));
+
+ return (true, locAccountReason);
+ }
+
+ var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
+ var isTotalPlaytimeInvalid = overallTime == null || overallTime.TimeSpent.TotalMinutes >= maxPlaytimeMinutes;
+
+ if (isTotalPlaytimeInvalid && showReason)
+ {
+ var locPlaytimeReason = reason != string.Empty
+ ? reason
+ : Loc.GetString("baby-jail-account-denied-reason",
+ ("reason",
+ Loc.GetString(
+ "baby-jail-account-reason-overall",
+ ("minutes", maxPlaytimeMinutes))));
+
+ return (true, locPlaytimeReason);
+ }
+
+ if (!showReason && isTotalPlaytimeInvalid || isAccountAgeInvalid)
+ return (true, Loc.GetString("baby-jail-account-denied"));
+
+ return (false, "");
+ }
+
private bool HasTemporaryBypass(NetUserId user)
{
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs
index 62d1f61a35b..96ca8d3614a 100644
--- a/Content.Server/Dragon/DragonSystem.cs
+++ b/Content.Server/Dragon/DragonSystem.cs
@@ -146,7 +146,7 @@ private void OnSpawnRift(EntityUid uid, DragonComponent component, DragonSpawnRi
// cant stack rifts near eachother
foreach (var (_, riftXform) in EntityQuery(true))
{
- if (riftXform.Coordinates.InRange(EntityManager, _transform, xform.Coordinates, RiftRange))
+ if (_transform.InRange(riftXform.Coordinates, xform.Coordinates, RiftRange))
{
_popup.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid);
return;
diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs
index 281bbc47211..8391e8faada 100644
--- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs
+++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs
@@ -65,8 +65,8 @@ bool IsTileClear()
EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid));
- if (component.RemoveOnInteract && stackComp == null && !((!EntityManager.EntityExists(uid) ? EntityLifeStage.Deleted : EntityManager.GetComponent(component.Owner).EntityLifeStage) >= EntityLifeStage.Deleted))
- EntityManager.DeleteEntity(uid);
+ if (component.RemoveOnInteract && stackComp == null)
+ TryQueueDel(uid);
}
}
}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs
index 29b81deb32c..ea10b7c69b3 100644
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs
+++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Content.Server.Explosion.Components;
using Content.Server.Explosion.EntitySystems;
using Robust.Shared.Physics.Dynamics;
@@ -42,14 +42,15 @@ private void OnComponentRemove(EntityUid uid, TriggerOnTimedCollideComponent com
private void UpdateTimedCollide(float frameTime)
{
- foreach (var (activeTrigger, triggerOnTimedCollide) in EntityQuery())
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
{
foreach (var (collidingEntity, collidingTimer) in triggerOnTimedCollide.Colliding)
{
triggerOnTimedCollide.Colliding[collidingEntity] += frameTime;
if (collidingTimer > triggerOnTimedCollide.Threshold)
{
- RaiseLocalEvent(activeTrigger.Owner, new TriggerEvent(activeTrigger.Owner, collidingEntity), true);
+ RaiseLocalEvent(uid, new TriggerEvent(uid, collidingEntity), true);
triggerOnTimedCollide.Colliding[collidingEntity] -= triggerOnTimedCollide.Threshold;
}
}
diff --git a/Content.Server/Extinguisher/FireExtinguisherComponent.cs b/Content.Server/Extinguisher/FireExtinguisherComponent.cs
deleted file mode 100644
index 991fc76c62e..00000000000
--- a/Content.Server/Extinguisher/FireExtinguisherComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Extinguisher;
-using Robust.Shared.GameStates;
-
-namespace Content.Server.Extinguisher;
-
-[RegisterComponent]
-[Access(typeof(FireExtinguisherSystem))]
-public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent
-{
-}
diff --git a/Content.Server/Extinguisher/FireExtinguisherSystem.cs b/Content.Server/Extinguisher/FireExtinguisherSystem.cs
deleted file mode 100644
index b33a1af157f..00000000000
--- a/Content.Server/Extinguisher/FireExtinguisherSystem.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using Content.Server.Chemistry.Containers.EntitySystems;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Popups;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Extinguisher;
-using Content.Shared.FixedPoint;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Verbs;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-
-namespace Content.Server.Extinguisher;
-
-public sealed class FireExtinguisherSystem : EntitySystem
-{
- [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnFireExtinguisherInit);
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnAfterInteract);
- SubscribeLocalEvent>(OnGetInteractionVerbs);
- SubscribeLocalEvent(OnSprayAttempt);
- }
-
- private void OnFireExtinguisherInit(Entity entity, ref ComponentInit args)
- {
- if (entity.Comp.HasSafety)
- {
- UpdateAppearance((entity.Owner, entity.Comp));
- }
- }
-
- private void OnUseInHand(Entity entity, ref UseInHandEvent args)
- {
- if (args.Handled)
- return;
-
- ToggleSafety((entity.Owner, entity.Comp), args.User);
-
- args.Handled = true;
- }
-
- private void OnAfterInteract(Entity entity, ref AfterInteractEvent args)
- {
- if (args.Target == null || !args.CanReach)
- {
- return;
- }
-
- if (args.Handled)
- return;
-
- if (entity.Comp.HasSafety && entity.Comp.Safety)
- {
- _popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), entity.Owner, args.User);
- return;
- }
-
- if (args.Target is not { Valid: true } target ||
- !_solutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution) ||
- !_solutionContainerSystem.TryGetRefillableSolution(entity.Owner, out var containerSoln, out var containerSolution))
- {
- return;
- }
-
- args.Handled = true;
-
- // TODO: why is this copy paste shit here just have fire extinguisher cancel transfer when safety is on
- var transfer = containerSolution.AvailableVolume;
- if (TryComp(entity.Owner, out var solTrans))
- {
- transfer = solTrans.TransferAmount;
- }
- transfer = FixedPoint2.Min(transfer, targetSolution.Volume);
-
- if (transfer > 0)
- {
- var drained = _solutionContainerSystem.Drain(target, targetSoln.Value, transfer);
- _solutionContainerSystem.TryAddSolution(containerSoln.Value, drained);
-
- _audio.PlayPvs(entity.Comp.RefillSound, entity.Owner);
- _popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-after-interact-refilled-message", ("owner", entity.Owner)),
- entity.Owner, args.Target.Value);
- }
- }
-
- private void OnGetInteractionVerbs(Entity entity, ref GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- var user = args.User;
- var verb = new InteractionVerb
- {
- Act = () => ToggleSafety((entity.Owner, entity.Comp), user),
- Text = Loc.GetString("fire-extinguisher-component-verb-text"),
- };
-
- args.Verbs.Add(verb);
- }
-
- private void OnSprayAttempt(Entity entity, ref SprayAttemptEvent args)
- {
- if (entity.Comp.HasSafety && entity.Comp.Safety)
- {
- _popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), entity, args.User);
- args.Cancel();
- }
- }
-
- private void UpdateAppearance(Entity entity)
- {
- if (!Resolve(entity, ref entity.Comp2, false))
- return;
-
- if (entity.Comp1.HasSafety)
- {
- _appearance.SetData(entity, FireExtinguisherVisuals.Safety, entity.Comp1.Safety, entity.Comp2);
- }
- }
-
- public void ToggleSafety(Entity extinguisher, EntityUid user)
- {
- if (!Resolve(extinguisher, ref extinguisher.Comp))
- return;
-
- extinguisher.Comp.Safety = !extinguisher.Comp.Safety;
- _audio.PlayPvs(extinguisher.Comp.SafetySound, extinguisher, AudioParams.Default.WithVariation(0.125f).WithVolume(-4f));
- UpdateAppearance((extinguisher.Owner, extinguisher.Comp));
- }
-}
diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs
index 5499070738f..215ed7c33ff 100644
--- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs
+++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs
@@ -1,11 +1,11 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.Extinguisher;
using Content.Server.Fluids.Components;
using Content.Server.Gravity;
using Content.Server.Popups;
using Content.Shared.FixedPoint;
+using Content.Shared.Fluids;
using Content.Shared.Interaction;
using Content.Shared.Timing;
using Content.Shared.Vapor;
@@ -34,7 +34,7 @@ public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnAfterInteract, after: new[] { typeof(FireExtinguisherSystem) });
+ SubscribeLocalEvent(OnAfterInteract);
}
private void OnAfterInteract(Entity entity, ref AfterInteractEvent args)
@@ -48,7 +48,7 @@ private void OnAfterInteract(Entity entity, ref AfterInteractEve
return;
var ev = new SprayAttemptEvent(args.User);
- RaiseLocalEvent(entity, ev);
+ RaiseLocalEvent(entity, ref ev);
if (ev.Cancelled)
return;
@@ -148,13 +148,3 @@ private void OnAfterInteract(Entity entity, ref AfterInteractEve
_useDelay.TryResetDelay((entity, useDelay));
}
}
-
-public sealed class SprayAttemptEvent : CancellableEntityEventArgs
-{
- public EntityUid User;
-
- public SprayAttemptEvent(EntityUid user)
- {
- User = user;
- }
-}
diff --git a/Content.Server/GameTicking/GameTicker.StatusShell.cs b/Content.Server/GameTicking/GameTicker.StatusShell.cs
index 5009b127b53..370248bb730 100644
--- a/Content.Server/GameTicking/GameTicker.StatusShell.cs
+++ b/Content.Server/GameTicking/GameTicker.StatusShell.cs
@@ -53,6 +53,12 @@ private void GetStatusResponse(JsonNode jObject)
jObject["players"] = players; // Corvax-Queue
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
+
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ jObject["baby_jail"] = _cfg.GetCVar(CCVars.BabyJailEnabled);
jObject["run_level"] = (int) _runLevel;
if (preset != null)
jObject["preset"] = Loc.GetString(preset.ModeTitle);
diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
deleted file mode 100644
index fa352eb320b..00000000000
--- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Server.Ninja.Systems;
-using Content.Shared.Communications;
-using Content.Shared.Random;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.GameTicking.Rules.Components;
-
-///
-/// Stores some configuration used by the ninja system.
-/// Objectives and roundend summary are handled by .
-///
-[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
-public sealed partial class NinjaRuleComponent : Component
-{
- ///
- /// List of threats that can be called in. Copied onto when gloves are enabled.
- ///
- [DataField(required: true)]
- public ProtoId Threats = string.Empty;
-
- ///
- /// Sound played when making the player a ninja via antag control or ghost role
- ///
- [DataField]
- public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg");
-}
diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs
index 203882ed9ef..ae4d0ca2b8c 100644
--- a/Content.Server/Guardian/GuardianSystem.cs
+++ b/Content.Server/Guardian/GuardianSystem.cs
@@ -325,7 +325,7 @@ private void CheckGuardianMove(
if (!guardianComponent.GuardianLoose)
return;
- if (!guardianXform.Coordinates.InRange(EntityManager, _transform, hostXform.Coordinates, guardianComponent.DistanceAllowed))
+ if (!_transform.InRange(guardianXform.Coordinates, hostXform.Coordinates, guardianComponent.DistanceAllowed))
RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
}
diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs
index 582bf7fa67b..6814b596dc5 100644
--- a/Content.Server/Instruments/InstrumentSystem.cs
+++ b/Content.Server/Instruments/InstrumentSystem.cs
@@ -402,7 +402,8 @@ public override void Update(float frameTime)
var trans = transformQuery.GetComponent(uid);
var masterTrans = transformQuery.GetComponent(master);
- if (!masterTrans.Coordinates.InRange(EntityManager, _transform, trans.Coordinates, 10f))
+ if (!_transform.InRange(masterTrans.Coordinates, trans.Coordinates, 10f)
+)
{
Clean(uid, instrument);
}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
deleted file mode 100644
index 30fa84ed90b..00000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item applies a disarm malus when active.
-///
-[RegisterComponent]
-public sealed partial class ItemToggleDisarmMalusComponent : Component
-{
- ///
- /// Item has this modifier to the chance to disarm when activated.
- /// If null, the value will be inferred from the current malus just before the malus is first deactivated.
- ///
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public float? ActivatedDisarmMalus = null;
-
- ///
- /// Item has this modifier to the chance to disarm when deactivated. If none is mentioned, it uses the item's default disarm modifier.
- /// If null, the value will be inferred from the current malus just before the malus is first activated.
- ///
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public float? DeactivatedDisarmMalus = null;
-}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
deleted file mode 100644
index 227491b16c2..00000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item is sharp when toggled on.
-///
-[RegisterComponent]
-public sealed partial class ItemToggleSharpComponent : Component
-{
-}
diff --git a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs b/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
deleted file mode 100644
index f98415eb08f..00000000000
--- a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Server.CombatMode.Disarm;
-using Content.Server.Kitchen.Components;
-using Content.Shared.Item.ItemToggle;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Item;
-
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(ToggleSharp);
- SubscribeLocalEvent(ToggleMalus);
- }
-
- private void ToggleSharp(Entity ent, ref ItemToggledEvent args)
- {
- // TODO generalize this into a "ToggleComponentComponent", though probably with a better name
- if (args.Activated)
- EnsureComp(ent);
- else
- RemCompDeferred(ent);
- }
-
- private void ToggleMalus(Entity ent, ref ItemToggledEvent args)
- {
- if (!TryComp(ent, out var malus))
- return;
-
- if (args.Activated)
- {
- ent.Comp.DeactivatedDisarmMalus ??= malus.Malus;
- if (ent.Comp.ActivatedDisarmMalus is {} activatedMalus)
- malus.Malus = activatedMalus;
- return;
- }
-
- ent.Comp.ActivatedDisarmMalus ??= malus.Malus;
- if (ent.Comp.DeactivatedDisarmMalus is {} deactivatedMalus)
- malus.Malus = deactivatedMalus;
- }
-}
diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
index 6380b71c8a0..de40e98e182 100644
--- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
+++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
///
/// After scanning, retrieves the target Uid to use with its related UI.
///
+///
+/// Requires ItemToggleComponent.
+///
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
public sealed partial class HealthAnalyzerComponent : Component
diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs
index 4373532f018..1896f51eddc 100644
--- a/Content.Server/Medical/DefibrillatorSystem.cs
+++ b/Content.Server/Medical/DefibrillatorSystem.cs
@@ -12,6 +12,7 @@
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Medical;
using Content.Shared.Mind;
using Content.Shared.Mobs;
@@ -37,6 +38,7 @@ public sealed class DefibrillatorSystem : EntitySystem
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly RottingSystem _rotting = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
@@ -50,30 +52,10 @@ public sealed class DefibrillatorSystem : EntitySystem
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
}
- private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
- {
- if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
- return;
-
- if (!TryToggle(uid, component, args.User))
- return;
-
- args.Handled = true;
- _useDelay.TryResetDelay((uid, useDelay));
- }
-
- private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
- {
- if (!TerminatingOrDeleted(uid))
- TryDisable(uid, component);
- }
-
private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target is not { } target)
@@ -96,54 +78,12 @@ private void OnDoAfter(EntityUid uid, DefibrillatorComponent component, Defibril
Zap(uid, target, args.User, component);
}
- public bool TryToggle(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
- {
- if (!Resolve(uid, ref component))
- return false;
-
- return component.Enabled
- ? TryDisable(uid, component)
- : TryEnable(uid, component, user);
- }
-
- public bool TryEnable(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
- {
- if (!Resolve(uid, ref component))
- return false;
-
- if (component.Enabled)
- return false;
-
- if (!_powerCell.HasActivatableCharge(uid))
- return false;
-
- component.Enabled = true;
- _appearance.SetData(uid, ToggleVisuals.Toggled, true);
- _audio.PlayPvs(component.PowerOnSound, uid);
- return true;
- }
-
- public bool TryDisable(EntityUid uid, DefibrillatorComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return false;
-
- if (!component.Enabled)
- return false;
-
- component.Enabled = false;
- _appearance.SetData(uid, ToggleVisuals.Toggled, false);
-
- _audio.PlayPvs(component.PowerOffSound, uid);
- return true;
- }
-
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
- if (!component.Enabled)
+ if (!_toggle.IsActivated(uid))
{
if (user != null)
_popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
@@ -257,7 +197,7 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo
// if we don't have enough power left for another shot, turn it off
if (!_powerCell.HasActivatableCharge(uid))
- TryDisable(uid, component);
+ _toggle.TryDeactivate(uid);
// TODO clean up this clown show above
var ev = new TargetDefibrillatedEvent(user, (uid, component));
diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs
index c72cd2ddf6f..98f4f00d899 100644
--- a/Content.Server/Medical/HealthAnalyzerSystem.cs
+++ b/Content.Server/Medical/HealthAnalyzerSystem.cs
@@ -8,6 +8,8 @@
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
@@ -26,6 +28,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
@@ -36,7 +39,7 @@ public override void Initialize()
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
SubscribeLocalEvent(OnInsertedIntoContainer);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnDropped);
}
@@ -62,7 +65,7 @@ public override void Update(float frameTime)
//Get distance between health analyzer and the scanned entity
var patientCoordinates = Transform(patient).Coordinates;
- if (!patientCoordinates.InRange(EntityManager, _transformSystem, transform.Coordinates, component.MaxScanRange))
+ if (!_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange))
{
//Range too far, disable updates
StopAnalyzingEntity((uid, component), patient);
@@ -111,16 +114,16 @@ private void OnDoAfter(Entity uid, ref HealthAnalyzerDo
private void OnInsertedIntoContainer(Entity uid, ref EntGotInsertedIntoContainerMessage args)
{
if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ _toggle.TryDeactivate(uid.Owner);
}
///
- /// Disable continuous updates once battery is dead
+ /// Disable continuous updates once turned off
///
- private void OnPowerCellSlotEmpty(Entity uid, ref PowerCellSlotEmptyEvent args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
+ StopAnalyzingEntity(ent, patient);
}
///
@@ -129,7 +132,7 @@ private void OnPowerCellSlotEmpty(Entity uid, ref Power
private void OnDropped(Entity uid, ref DroppedEvent args)
{
if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ _toggle.TryDeactivate(uid.Owner);
}
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
@@ -150,7 +153,7 @@ private void BeginAnalyzingEntity(Entity healthAnalyzer
//Link the health analyzer to the scanned entity
healthAnalyzer.Comp.ScannedEntity = target;
- _cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
+ _toggle.TryActivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, true);
}
@@ -165,7 +168,7 @@ private void StopAnalyzingEntity(Entity healthAnalyzer,
//Unlink the analyzer
healthAnalyzer.Comp.ScannedEntity = null;
- _cell.SetPowerCellDrawEnabled(target, false);
+ _toggle.TryDeactivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, false);
}
diff --git a/Content.Server/Movement/Systems/PullController.cs b/Content.Server/Movement/Systems/PullController.cs
index 340dc5654e8..4bd4b603714 100644
--- a/Content.Server/Movement/Systems/PullController.cs
+++ b/Content.Server/Movement/Systems/PullController.cs
@@ -58,6 +58,7 @@ public sealed class PullController : VirtualController
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
///
/// If distance between puller and pulled entity lower that this threshold,
@@ -133,8 +134,8 @@ private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinate
var range = 2f;
var fromUserCoords = coords.WithEntityId(player, EntityManager);
var userCoords = new EntityCoordinates(player, Vector2.Zero);
-
- if (!coords.InRange(EntityManager, TransformSystem, userCoords, range))
+
+ if (!_transformSystem.InRange(coords, userCoords, range))
{
var direction = fromUserCoords.Position - userCoords.Position;
diff --git a/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs
index 3485bd2a18c..452bf327f25 100644
--- a/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs
+++ b/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs
@@ -8,12 +8,19 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class CoordinatesInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
+ private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _transformSystem = sysManager.GetEntitySystem();
+ }
+
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager))
@@ -22,6 +29,6 @@ public override bool IsMet(NPCBlackboard blackboard)
if (!blackboard.TryGetValue(TargetKey, out var target, _entManager))
return false;
- return coordinates.InRange(_entManager, _entManager.System(), target, blackboard.GetValueOrDefault(RangeKey, _entManager));
+ return _transformSystem.InRange(coordinates, target, blackboard.GetValueOrDefault(RangeKey, _entManager));
}
}
diff --git a/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs
index 9d000ca2eb8..901831679e8 100644
--- a/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs
+++ b/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs
@@ -8,12 +8,19 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class CoordinatesNotInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
+ private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _transformSystem = sysManager.GetEntitySystem();
+ }
+
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager))
@@ -22,7 +29,7 @@ public override bool IsMet(NPCBlackboard blackboard)
if (!blackboard.TryGetValue(TargetKey, out var target, _entManager))
return false;
- return !coordinates.InRange(_entManager, _entManager.System(), target, blackboard.GetValueOrDefault(RangeKey, _entManager));
+ return !_transformSystem.InRange(coordinates, target, blackboard.GetValueOrDefault(RangeKey, _entManager));
}
}
diff --git a/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs
index aaccb426d71..921b5ffa226 100644
--- a/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs
+++ b/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs
@@ -8,11 +8,17 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class TargetInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
+ private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _transformSystem = sysManager.GetEntitySystem();
+ }
public override bool IsMet(NPCBlackboard blackboard)
{
@@ -23,6 +29,7 @@ public override bool IsMet(NPCBlackboard blackboard)
!_entManager.TryGetComponent(target, out var targetXform))
return false;
- return coordinates.InRange(_entManager, _entManager.System(), targetXform.Coordinates, blackboard.GetValueOrDefault(RangeKey, _entManager));
+ var transformSystem = _entManager.System;
+ return _transformSystem.InRange(coordinates, targetXform.Coordinates, blackboard.GetValueOrDefault(RangeKey, _entManager));
}
}
diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs
index 32be027ec43..5a02b86201b 100644
--- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs
+++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs
@@ -89,7 +89,8 @@ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTi
HTNOperatorStatus status;
if (_entManager.TryGetComponent(owner, out var combat) &&
- blackboard.TryGetValue(TargetKey, out var target, _entManager))
+ blackboard.TryGetValue(TargetKey, out var target, _entManager) &&
+ target != EntityUid.Invalid)
{
combat.Target = target;
diff --git a/Content.Server/NPC/Queries/Considerations/TargetHealthCon.cs b/Content.Server/NPC/Queries/Considerations/TargetHealthCon.cs
index d4e8a277ea6..cc9c6df83f3 100644
--- a/Content.Server/NPC/Queries/Considerations/TargetHealthCon.cs
+++ b/Content.Server/NPC/Queries/Considerations/TargetHealthCon.cs
@@ -1,6 +1,16 @@
+using Content.Shared.Mobs;
+
namespace Content.Server.NPC.Queries.Considerations;
+///
+/// Goes linearly from 1f to 0f, with 0 damage returning 1f and damage returning 0f
+///
public sealed partial class TargetHealthCon : UtilityConsideration
{
+ ///
+ /// Which MobState the consideration returns 0f at, defaults to choosing earliest incapacitating MobState
+ ///
+ [DataField("targetState")]
+ public MobState TargetState = MobState.Invalid;
}
diff --git a/Content.Server/NPC/Systems/NPCJukeSystem.cs b/Content.Server/NPC/Systems/NPCJukeSystem.cs
index da9fa1f7615..94a30feb0cb 100644
--- a/Content.Server/NPC/Systems/NPCJukeSystem.cs
+++ b/Content.Server/NPC/Systems/NPCJukeSystem.cs
@@ -143,6 +143,9 @@ private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSt
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon))
return;
+ if (!HasComp(melee.Target))
+ return;
+
var cdRemaining = weapon.NextAttack - _timing.CurTime;
var attackCooldown = TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon));
diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs
index ca74d713357..72833fc35ef 100644
--- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs
+++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs
@@ -7,10 +7,13 @@
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Storage.Components;
+using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
@@ -48,6 +51,7 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly WeldableSystem _weldable = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+ [Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
private EntityQuery _puddleQuery;
private EntityQuery _xformQuery;
@@ -293,8 +297,14 @@ private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityCon
return (float) ev.Count / ev.Capacity;
}
- case TargetHealthCon:
+ case TargetHealthCon con:
{
+ if (!TryComp(targetUid, out DamageableComponent? damage))
+ return 0f;
+ if (con.TargetState != MobState.Invalid && _thresholdSystem.TryGetPercentageForState(targetUid, con.TargetState, damage.TotalDamage, out var percentage))
+ return Math.Clamp((float)(1 - percentage), 0f, 1f);
+ if (_thresholdSystem.TryGetIncapPercentage(targetUid, damage.TotalDamage, out var incapPercentage))
+ return Math.Clamp((float)(1 - incapPercentage), 0f, 1f);
return 0f;
}
case TargetInLOSCon:
diff --git a/Content.Server/Ninja/Events/BatteryChangedEvent.cs b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
index 45bfedfee76..1848e881868 100644
--- a/Content.Server/Ninja/Events/BatteryChangedEvent.cs
+++ b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
@@ -1,7 +1,7 @@
namespace Content.Server.Ninja.Events;
///
-/// Raised on the ninja when the suit has its powercell changed.
+/// Raised on the ninja and suit when the suit has its powercell changed.
///
[ByRefEvent]
public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);
diff --git a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
index 59dec556fa0..4baf0913cec 100644
--- a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
+++ b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
@@ -33,16 +33,17 @@ public override void Initialize()
/// Start do after for draining a power source.
/// Can't predict PNBC existing so only done on server.
///
- private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity ent, ref BeforeInteractHandEvent args)
{
+ var (uid, comp) = ent;
var target = args.Target;
- if (args.Handled || comp.BatteryUid == null || !HasComp(target))
+ if (args.Handled || comp.BatteryUid is not {} battery || !HasComp(target))
return;
// handles even if battery is full so you can actually see the poup
args.Handled = true;
- if (_battery.IsFull(comp.BatteryUid.Value))
+ if (_battery.IsFull(battery))
{
_popup.PopupEntity(Loc.GetString("battery-drainer-full"), uid, uid, PopupType.Medium);
return;
@@ -59,23 +60,24 @@ private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, B
_doAfter.TryStartDoAfter(doAfterArgs);
}
- private void OnBatteryChanged(EntityUid uid, BatteryDrainerComponent comp, ref NinjaBatteryChangedEvent args)
+ private void OnBatteryChanged(Entity ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
///
- protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent args)
+ protected override void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent args)
{
- base.OnDoAfterAttempt(uid, comp, args);
+ base.OnDoAfterAttempt(ent, ref args);
- if (comp.BatteryUid == null || _battery.IsFull(comp.BatteryUid.Value))
+ if (ent.Comp.BatteryUid is not {} battery || _battery.IsFull(battery))
args.Cancel();
}
///
- protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+ protected override bool TryDrainPower(Entity ent, EntityUid target)
{
+ var (uid, comp) = ent;
if (comp.BatteryUid == null || !TryComp(comp.BatteryUid.Value, out var battery))
return false;
@@ -98,6 +100,7 @@ protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent com
var output = input * comp.DrainEfficiency;
_battery.SetCharge(comp.BatteryUid.Value, battery.CurrentCharge + output, battery);
+ // TODO: create effect message or something
Spawn("EffectSparks", Transform(target).Coordinates);
_audio.PlayPvs(comp.SparkSound, target);
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
diff --git a/Content.Server/Ninja/Systems/ItemCreatorSystem.cs b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..d7a7be995db
--- /dev/null
+++ b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,57 @@
+using Content.Server.Ninja.Events;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
+using Content.Shared.Popups;
+
+namespace Content.Server.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem
+{
+ [Dependency] private readonly BatterySystem _battery = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnCreateItem);
+ SubscribeLocalEvent(OnBatteryChanged);
+ }
+
+ private void OnCreateItem(Entity ent, ref CreateItemEvent args)
+ {
+ var (uid, comp) = ent;
+ if (comp.Battery is not {} battery)
+ return;
+
+ args.Handled = true;
+
+ var user = args.Performer;
+ if (!_battery.TryUseCharge(battery, comp.Charge))
+ {
+ _popup.PopupEntity(Loc.GetString(comp.NoPowerPopup), user, user);
+ return;
+ }
+
+ var ev = new CreateItemAttemptEvent(user);
+ RaiseLocalEvent(uid, ref ev);
+ if (ev.Cancelled)
+ return;
+
+ // try to put throwing star in hand, otherwise it goes on the ground
+ var star = Spawn(comp.SpawnedPrototype, Transform(user).Coordinates);
+ _hands.TryPickupAnyHand(user, star);
+ }
+
+ private void OnBatteryChanged(Entity ent, ref NinjaBatteryChangedEvent args)
+ {
+ if (ent.Comp.Battery == args.Battery)
+ return;
+
+ ent.Comp.Battery = args.Battery;
+ Dirty(ent, ent.Comp);
+ }
+}
diff --git a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
index ac76ae6b771..3aaf7c5d58e 100644
--- a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
@@ -1,13 +1,8 @@
-using Content.Server.Communications;
-using Content.Server.Mind;
using Content.Server.Ninja.Events;
-using Content.Server.Objectives.Systems;
-using Content.Shared.Communications;
-using Content.Shared.CriminalRecords.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Systems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
-using Content.Shared.Research.Components;
-using Content.Shared.Toggleable;
namespace Content.Server.Ninja.Systems;
@@ -16,89 +11,44 @@ namespace Content.Server.Ninja.Systems;
///
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
{
- [Dependency] private readonly EmagProviderSystem _emagProvider = default!;
- [Dependency] private readonly CodeConditionSystem _codeCondition = default!;
- [Dependency] private readonly CommsHackerSystem _commsHacker = default!;
- [Dependency] private readonly SharedStunProviderSystem _stunProvider = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly SpaceNinjaSystem _ninja = default!;
- public override void Initialize()
+ protected override void EnableGloves(Entity ent, Entity user)
{
- base.Initialize();
+ base.EnableGloves(ent, user);
- SubscribeLocalEvent(OnToggleAction);
- }
-
- ///
- /// Toggle gloves, if the user is a ninja wearing a ninja suit.
- ///
- private void OnToggleAction(EntityUid uid, NinjaGlovesComponent comp, ToggleActionEvent args)
- {
- if (args.Handled)
+ // can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
+ if (user.Comp.Suit is not {} suit)
return;
- args.Handled = true;
-
- var user = args.Performer;
- // need to wear suit to enable gloves
- if (!TryComp(user, out var ninja)
- || ninja.Suit == null
- || !HasComp(ninja.Suit.Value))
- {
- Popup.PopupEntity(Loc.GetString("ninja-gloves-not-wearing-suit"), user, user);
+ if (!_mind.TryGetMind(user, out var mindId, out var mind))
return;
- }
-
- // show its state to the user
- var enabling = comp.User == null;
- Appearance.SetData(uid, ToggleVisuals.Toggled, enabling);
- var message = Loc.GetString(enabling ? "ninja-gloves-on" : "ninja-gloves-off");
- Popup.PopupEntity(message, user, user);
- if (enabling)
+ foreach (var ability in ent.Comp.Abilities)
{
- EnableGloves(uid, comp, user, ninja);
+ // non-objective abilities are added in shared already
+ if (ability.Objective is not {} objId)
+ continue;
+
+ // prevent doing an objective multiple times by toggling gloves after doing them
+ // if it's not tied to an objective always add them anyway
+ if (!_mind.TryFindObjective((mindId, mind), objId, out var obj))
+ {
+ Log.Error($"Ninja glove ability of {ent} referenced missing objective {ability.Objective} of {_mind.MindOwnerLoggingString(mind)}");
+ continue;
+ }
+
+ if (!_objectives.IsCompleted(obj.Value, (mindId, mind)))
+ EntityManager.AddComponents(user, ability.Components);
}
- else
- {
- DisableGloves(uid, comp);
- }
- }
- private void EnableGloves(EntityUid uid, NinjaGlovesComponent comp, EntityUid user, SpaceNinjaComponent ninja)
- {
- // can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
- if (ninja.Suit == null)
- return;
-
- comp.User = user;
- Dirty(uid, comp);
- _ninja.AssignGloves(user, uid, ninja);
-
- var drainer = EnsureComp(user);
- var stun = EnsureComp(user);
- _stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
+ // let abilities that use battery power work
if (_ninja.GetNinjaBattery(user, out var battery, out var _))
{
- var ev = new NinjaBatteryChangedEvent(battery.Value, ninja.Suit.Value);
+ var ev = new NinjaBatteryChangedEvent(battery.Value, suit);
RaiseLocalEvent(user, ref ev);
}
-
- var emag = EnsureComp(user);
- _emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
-
- EnsureComp(user);
- // prevent calling in multiple threats by toggling gloves after
- if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
- {
- var hacker = EnsureComp(user);
- var rule = _ninja.NinjaRule(user);
- if (rule != null)
- _commsHacker.SetThreats(user, rule.Threats, hacker);
- }
- if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
- {
- EnsureComp(user);
- }
}
}
diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
index 04095b549c6..63054eaad50 100644
--- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
@@ -2,7 +2,6 @@
using Content.Server.Ninja.Events;
using Content.Server.Power.Components;
using Content.Server.PowerCell;
-using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
@@ -29,15 +28,13 @@ public override void Initialize()
SubscribeLocalEvent(OnSuitInsertAttempt);
SubscribeLocalEvent(OnEmpAttempt);
- SubscribeLocalEvent(OnAttemptStealth);
- SubscribeLocalEvent(OnCreateThrowingStar);
SubscribeLocalEvent(OnRecallKatana);
SubscribeLocalEvent(OnEmp);
}
- protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+ protected override void NinjaEquipped(Entity ent, Entity user)
{
- base.NinjaEquippedSuit(uid, comp, user, ninja);
+ base.NinjaEquipped(ent, user);
_ninja.SetSuitPowerAlert(user);
}
@@ -57,16 +54,15 @@ private void OnSuitInsertAttempt(EntityUid uid, NinjaSuitComponent comp, Contain
// can only upgrade power cell, not swap to recharge instantly otherwise ninja could just swap batteries with flashlights in maints for easy power
if (!TryComp(args.EntityUid, out var inserting) || inserting.MaxCharge <= battery.MaxCharge)
- {
args.Cancel();
- }
// tell ninja abilities that use battery to update it so they don't use charge from the old one
var user = Transform(uid).ParentUid;
- if (!HasComp(user))
+ if (!_ninja.IsNinja(user))
return;
var ev = new NinjaBatteryChangedEvent(args.EntityUid, uid);
+ RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(user, ref ev);
}
@@ -77,64 +73,22 @@ private void OnEmpAttempt(EntityUid uid, NinjaSuitComponent comp, EmpAttemptEven
args.Cancel();
}
- protected override void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+ protected override void UserUnequippedSuit(Entity ent, Entity user)
{
- base.UserUnequippedSuit(uid, comp, user);
+ base.UserUnequippedSuit(ent, user);
// remove power indicator
_ninja.SetSuitPowerAlert(user);
}
- private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
+ private void OnRecallKatana(Entity ent, ref RecallKatanaEvent args)
{
- var user = args.User;
- // need 1 second of charge to turn on stealth
- var chargeNeeded = SuitWattage(uid, comp);
- // being attacked while cloaked gives no power message since it overloads the power supply or something
- if (!_ninja.GetNinjaBattery(user, out _, out var battery) || battery.CurrentCharge < chargeNeeded)
- {
- Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
- args.Cancel();
- return;
- }
-
- if (comp.DisableCooldown > GameTiming.CurTime)
- {
- Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
- args.Cancel();
- return;
- }
-
- StealthClothing.SetEnabled(uid, user, true);
- }
-
- private void OnCreateThrowingStar(EntityUid uid, NinjaSuitComponent comp, CreateThrowingStarEvent args)
- {
- args.Handled = true;
+ var (uid, comp) = ent;
var user = args.Performer;
- if (!_ninja.TryUseCharge(user, comp.ThrowingStarCharge))
- {
- Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
+ if (!_ninja.NinjaQuery.TryComp(user, out var ninja) || ninja.Katana == null)
return;
- }
-
- if (comp.DisableCooldown > GameTiming.CurTime)
- {
- Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
- return;
- }
-
- // try to put throwing star in hand, otherwise it goes on the ground
- var star = Spawn(comp.ThrowingStarPrototype, Transform(user).Coordinates);
- _hands.TryPickupAnyHand(user, star);
- }
- private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatanaEvent args)
- {
args.Handled = true;
- var user = args.Performer;
- if (!TryComp(user, out var ninja) || ninja.Katana == null)
- return;
var katana = ninja.Katana.Value;
var coords = _transform.GetWorldPosition(katana);
@@ -146,11 +100,8 @@ private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatana
return;
}
- if (comp.DisableCooldown > GameTiming.CurTime)
- {
- Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+ if (CheckDisabled(ent, user))
return;
- }
// TODO: teleporting into belt slot
var message = _hands.TryPickupAnyHand(user, katana)
@@ -159,9 +110,11 @@ private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatana
Popup.PopupEntity(Loc.GetString(message), user, user);
}
- private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
+ private void OnEmp(Entity ent, ref NinjaEmpEvent args)
{
+ var (uid, comp) = ent;
args.Handled = true;
+
var user = args.Performer;
if (!_ninja.TryUseCharge(user, comp.EmpCharge))
{
@@ -169,13 +122,9 @@ private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
return;
}
- if (comp.DisableCooldown > GameTiming.CurTime)
- {
- Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+ if (CheckDisabled(ent, user))
return;
- }
- // I don't think this affects the suit battery, but if it ever does in the future add a blacklist for it
var coords = _transform.GetMapCoordinates(user);
_emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
}
diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
index 0c1e88653fa..28ab6332276 100644
--- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
+++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
@@ -2,7 +2,6 @@
using Content.Server.Chat.Managers;
using Content.Server.CriminalRecords.Systems;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.GenericAntag;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Power.Components;
@@ -11,7 +10,6 @@
using Content.Server.Research.Systems;
using Content.Server.Roles;
using Content.Shared.Alert;
-using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Doors.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
@@ -26,11 +24,6 @@
namespace Content.Server.Ninja.Systems;
-// TODO: when syndiborgs are a thing have a borg converter with 6 second doafter
-// engi -> saboteur
-// medi -> idk reskin it
-// other -> assault
-
///
/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
///
@@ -44,13 +37,11 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly StealthClothingSystem _stealthClothing = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnNinjaCreated);
SubscribeLocalEvent(OnDoorjack);
SubscribeLocalEvent(OnResearchStolen);
SubscribeLocalEvent(OnThreatCalledIn);
@@ -62,7 +53,7 @@ public override void Update(float frameTime)
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var ninja))
{
- UpdateNinja(uid, ninja, frameTime);
+ SetSuitPowerAlert((uid, ninja));
}
}
@@ -80,31 +71,13 @@ private int Download(EntityUid uid, List ids)
return newCount - oldCount;
}
- ///
- /// Returns a ninja's gamerule config data.
- /// If the gamerule was not started then it will be started automatically.
- ///
- public NinjaRuleComponent? NinjaRule(EntityUid uid, GenericAntagComponent? comp = null)
- {
- if (!Resolve(uid, ref comp))
- return null;
-
- // mind not added yet so no rule
- if (comp.RuleEntity == null)
- return null;
-
- return CompOrNull(comp.RuleEntity);
- }
-
// TODO: can probably copy paste borg code here
///
/// Update the alert for the ninja's suit power indicator.
///
- public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
+ public void SetSuitPowerAlert(Entity ent)
{
- if (!Resolve(uid, ref comp, false))
- return;
-
+ var (uid, comp) = ent;
if (comp.Deleted || comp.Suit == null)
{
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
@@ -145,53 +118,6 @@ public override bool TryUseCharge(EntityUid user, float charge)
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
}
- ///
- /// Set up everything for ninja to work and send the greeting message/sound.
- /// Objectives are added by .
- ///
- private void OnNinjaCreated(EntityUid uid, SpaceNinjaComponent comp, ref GenericAntagCreatedEvent args)
- {
- var mindId = args.MindId;
- var mind = args.Mind;
-
- if (mind.Session == null)
- return;
-
- var config = NinjaRule(uid);
- if (config == null)
- return;
-
- var role = new NinjaRoleComponent
- {
- PrototypeId = "SpaceNinja"
- };
- _role.MindAddRole(mindId, role, mind);
- _role.MindPlaySound(mindId, config.GreetingSound, mind);
-
- var session = mind.Session;
- _audio.PlayGlobal(config.GreetingSound, Filter.Empty().AddPlayer(session), false, AudioParams.Default);
- _chatMan.DispatchServerMessage(session, Loc.GetString("ninja-role-greeting"));
- }
-
- // TODO: PowerCellDraw, modify when cloak enabled
- ///
- /// Handle constant power drains from passive usage and cloak.
- ///
- private void UpdateNinja(EntityUid uid, SpaceNinjaComponent ninja, float frameTime)
- {
- if (ninja.Suit == null)
- return;
-
- float wattage = Suit.SuitWattage(ninja.Suit.Value);
-
- SetSuitPowerAlert(uid, ninja);
- if (!TryUseCharge(uid, wattage * frameTime))
- {
- // ran out of power, uncloak ninja
- _stealthClothing.SetEnabled(ninja.Suit.Value, uid, false);
- }
- }
-
///
/// Increment greentext when emagging a door.
///
diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
index 64c958d6f1a..c262651f27a 100644
--- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
+++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
@@ -7,6 +7,7 @@
using Content.Server.Sticky.Events;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
using Robust.Shared.GameObjects;
namespace Content.Server.Ninja.Systems;
@@ -14,7 +15,7 @@ namespace Content.Server.Ninja.Systems;
///
/// Prevents planting a spider charge outside of its location and handles greentext.
///
-public sealed class SpiderChargeSystem : EntitySystem
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
{
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly PopupSystem _popup = default!;
diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs
index 1768606ad20..822486cff52 100644
--- a/Content.Server/Ninja/Systems/StunProviderSystem.cs
+++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs
@@ -6,10 +6,11 @@
using Content.Shared.Ninja.Systems;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
-using Robust.Shared.Prototypes;
+using Content.Shared.Timing;
+using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
-using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
namespace Content.Server.Ninja.Systems;
@@ -20,12 +21,12 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
{
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize()
{
@@ -38,16 +39,18 @@ public override void Initialize()
///
/// Stun clicked mobs on the whitelist, if there is enough power.
///
- private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity ent, ref BeforeInteractHandEvent args)
{
// TODO: generic check
+ var (uid, comp) = ent;
if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target))
return;
- if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
+ if (target == uid || _whitelist.IsWhitelistFail(comp.Whitelist, target))
return;
- if (_timing.CurTime < comp.NextStun)
+ var useDelay = EnsureComp(uid);
+ if (_useDelay.IsDelayed((uid, useDelay), id: comp.DelayId))
return;
// take charge from battery
@@ -63,13 +66,14 @@ private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, Bef
_stun.TryParalyze(target, comp.StunTime, refresh: false);
// short cooldown to prevent instant stunlocking
- comp.NextStun = _timing.CurTime + comp.Cooldown;
+ _useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
+ _useDelay.TryResetDelay((uid, useDelay), id: comp.DelayId);
args.Handled = true;
}
- private void OnBatteryChanged(EntityUid uid, StunProviderComponent comp, ref NinjaBatteryChangedEvent args)
+ private void OnBatteryChanged(Entity ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
}
diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.SmokingPipe.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.SmokingPipe.cs
index 3950c73eb43..26d86acd545 100644
--- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.SmokingPipe.cs
+++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.SmokingPipe.cs
@@ -42,7 +42,7 @@ private void OnPipeInteractUsingEvent(Entity entity, ref I
if (!isHotEvent.IsHot)
return;
- if (TryTransferReagents(entity.Comp, smokable))
+ if (TryTransferReagents(entity, (entity.Owner, smokable)))
SetSmokableState(entity, SmokableState.Lit, smokable);
args.Handled = true;
}
@@ -62,7 +62,7 @@ public void OnPipeAfterInteract(Entity entity, ref AfterIn
if (!isHotEvent.IsHot)
return;
- if (TryTransferReagents(entity.Comp, smokable))
+ if (TryTransferReagents(entity, (entity.Owner, smokable)))
SetSmokableState(entity, SmokableState.Lit, smokable);
args.Handled = true;
}
@@ -74,15 +74,15 @@ private void OnPipeSolutionEmptyEvent(Entity entity, ref S
}
// Convert smokable item into reagents to be smoked
- private bool TryTransferReagents(SmokingPipeComponent component, SmokableComponent smokable)
+ private bool TryTransferReagents(Entity entity, Entity smokable)
{
- if (component.BowlSlot.Item == null)
+ if (entity.Comp.BowlSlot.Item == null)
return false;
- EntityUid contents = component.BowlSlot.Item.Value;
+ EntityUid contents = entity.Comp.BowlSlot.Item.Value;
if (!TryComp(contents, out var reagents) ||
- !_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Solution, out var pipeSolution, out _))
+ !_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Comp.Solution, out var pipeSolution, out _))
return false;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((contents, reagents)))
@@ -93,7 +93,7 @@ private bool TryTransferReagents(SmokingPipeComponent component, SmokableCompone
EntityManager.DeleteEntity(contents);
- _itemSlotsSystem.SetLock(component.Owner, component.BowlSlot, true); //no inserting more until current runs out
+ _itemSlotsSystem.SetLock(entity.Owner, entity.Comp.BowlSlot, true); //no inserting more until current runs out
return true;
}
diff --git a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs
index 43087214a45..d40288183fc 100644
--- a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs
+++ b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs
@@ -31,24 +31,24 @@ public override void Initialize()
///
/// Clicked with utensil
///
- private void OnAfterInteract(EntityUid uid, UtensilComponent component, AfterInteractEvent ev)
+ private void OnAfterInteract(Entity entity, ref AfterInteractEvent ev)
{
if (ev.Handled || ev.Target == null || !ev.CanReach)
return;
- var result = TryUseUtensil(ev.User, ev.Target.Value, component);
+ var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
ev.Handled = result.Handled;
}
- public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, UtensilComponent component)
+ public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity utensil)
{
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
return (false, true);
//Prevents food usage with a wrong utensil
- if ((food.Utensil & component.Types) == 0)
+ if ((food.Utensil & utensil.Comp.Types) == 0)
{
- _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", component.Owner)), user, user);
+ _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
return (false, true);
}
diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs
index 18077b413ad..c9cdf244e66 100644
--- a/Content.Server/Objectives/ObjectivesSystem.cs
+++ b/Content.Server/Objectives/ObjectivesSystem.cs
@@ -12,6 +12,7 @@
using System.Linq;
using System.Text;
using Robust.Server.Player;
+using Robust.Shared.Utility;
namespace Content.Server.Objectives;
@@ -180,33 +181,32 @@ private void AddSummary(StringBuilder result, string agent, List<(EntityUid, str
}
}
- public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
+ public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, ProtoId objectiveGroupProto, float maxDifficulty)
{
- if (!_prototypeManager.TryIndex(objectiveGroupProto, out var groups))
+ if (!_prototypeManager.TryIndex(objectiveGroupProto, out var groupsProto))
{
Log.Error($"Tried to get a random objective, but can't index WeightedRandomPrototype {objectiveGroupProto}");
return null;
}
- // TODO replace whatever the fuck this is with a proper objective selection system
- // yeah the old 'preventing infinite loops' thing wasn't super elegant either and it mislead people on what exactly it did
- var tries = 0;
- while (tries < 20)
- {
- var groupName = groups.Pick(_random);
+ // Make a copy of the weights so we don't trash the prototype by removing entries
+ var groups = groupsProto.Weights.ShallowClone();
+ while (_random.TryPickAndTake(groups, out var groupName))
+ {
if (!_prototypeManager.TryIndex(groupName, out var group))
{
Log.Error($"Couldn't index objective group prototype {groupName}");
return null;
}
- var proto = group.Pick(_random);
- var objective = TryCreateObjective(mindId, mind, proto);
- if (objective != null)
- return objective;
-
- tries++;
+ var objectives = group.Weights.ShallowClone();
+ while (_random.TryPickAndTake(objectives, out var objectiveProto))
+ {
+ if (TryCreateObjective((mindId, mind), objectiveProto, out var objective)
+ && Comp(objective.Value).Difficulty <= maxDifficulty)
+ return objective;
+ }
}
return null;
diff --git a/Content.Server/Objectives/Systems/CodeConditionSystem.cs b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
index 7ba312f4bb9..fbc58dafe82 100644
--- a/Content.Server/Objectives/Systems/CodeConditionSystem.cs
+++ b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
@@ -35,20 +35,6 @@ public bool IsCompleted(Entity ent)
return ent.Comp.Completed;
}
- ///
- /// Returns true if a mob's objective with a certain prototype is completed.
- ///
- public bool IsCompleted(Entity mob, string prototype)
- {
- if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
- return false;
-
- if (!_mind.TryFindObjective(mindId, prototype, out var obj))
- return false;
-
- return IsCompleted(obj.Value);
- }
-
///
/// Sets an objective's completed field.
///
diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs
index 7e854e8bbfb..d3919347f76 100644
--- a/Content.Server/Parallax/BiomeSystem.cs
+++ b/Content.Server/Parallax/BiomeSystem.cs
@@ -126,20 +126,18 @@ private void OnBiomeMapInit(EntityUid uid, BiomeComponent component, MapInitEven
var xform = Transform(uid);
var mapId = xform.MapID;
- if (mapId != MapId.Nullspace && TryComp(uid, out MapGridComponent? mapGrid))
+ if (mapId != MapId.Nullspace && HasComp(uid))
{
var setTiles = new List<(Vector2i Index, Tile tile)>();
- foreach (var grid in _mapManager.GetAllMapGrids(mapId))
+ foreach (var grid in _mapManager.GetAllGrids(mapId))
{
- var gridUid = grid.Owner;
-
- if (!_fixturesQuery.TryGetComponent(gridUid, out var fixtures))
+ if (!_fixturesQuery.TryGetComponent(grid.Owner, out var fixtures))
continue;
// Don't want shuttles flying around now do we.
- _shuttles.Disable(gridUid);
- var pTransform = _physics.GetPhysicsTransform(gridUid);
+ _shuttles.Disable(grid.Owner);
+ var pTransform = _physics.GetPhysicsTransform(grid.Owner);
foreach (var fixture in fixtures.Fixtures.Values)
{
diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs
index 9dc3b5e1e67..4b7f50fb86c 100644
--- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs
+++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs
@@ -101,7 +101,7 @@ public bool InRange(EntityUid pointer, EntityCoordinates coordinates)
{
if (HasComp(pointer))
{
- return Transform(pointer).Coordinates.InRange(EntityManager, _transform, coordinates, 15);
+ return _transform.InRange(Transform(pointer).Coordinates, coordinates, 15);
}
else
{
@@ -145,8 +145,7 @@ public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, E
_popup.PopupEntity(Loc.GetString("pointing-system-try-point-cannot-reach"), player, player);
return false;
}
-
- var mapCoordsPointed = coordsPointed.ToMap(EntityManager, _transform);
+ var mapCoordsPointed = _transform.ToMapCoordinates(coordsPointed);
_rotateToFaceSystem.TryFaceCoordinates(player, mapCoordsPointed.Position);
var arrow = EntityManager.SpawnEntity("PointingArrow", coordsPointed);
@@ -154,7 +153,7 @@ public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, E
if (TryComp(arrow, out var pointing))
{
if (TryComp(player, out TransformComponent? xformPlayer))
- pointing.StartPosition = EntityCoordinates.FromMap(arrow, xformPlayer.Coordinates.ToMap(EntityManager, _transform), _transform).Position;
+ pointing.StartPosition = _transform.ToCoordinates((player, xformPlayer), _transform.ToMapCoordinates(xformPlayer.Coordinates)).Position;
pointing.EndTime = _gameTiming.CurTime + PointDuration;
diff --git a/Content.Server/PowerCell/PowerCellSystem.Draw.cs b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
index 4155a4f6bec..9ebd677f473 100644
--- a/Content.Server/PowerCell/PowerCellSystem.Draw.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
@@ -1,4 +1,5 @@
using Content.Server.Power.Components;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
@@ -10,22 +11,20 @@ public sealed partial class PowerCellSystem
* Handles PowerCellDraw
*/
- private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
-
public override void Update(float frameTime)
{
base.Update(frameTime);
- var query = EntityQueryEnumerator();
+ var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var comp, out var slot))
+ while (query.MoveNext(out var uid, out var comp, out var slot, out var toggle))
{
- if (!comp.Drawing)
+ if (!comp.Enabled || !toggle.Activated)
continue;
if (Timing.CurTime < comp.NextUpdateTime)
continue;
- comp.NextUpdateTime += Delay;
+ comp.NextUpdateTime += comp.Delay;
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
continue;
@@ -33,7 +32,8 @@ public override void Update(float frameTime)
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
- comp.Drawing = false;
+ Toggle.TryDeactivate((uid, toggle));
+
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
@@ -42,26 +42,9 @@ public override void Update(float frameTime)
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
{
// Update the bools for client prediction.
- bool canDraw;
- bool canUse;
-
- if (component.UseRate > 0f)
- {
- canUse = args.Charge > component.UseRate;
- }
- else
- {
- canUse = true;
- }
+ var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
- if (component.DrawRate > 0f)
- {
- canDraw = args.Charge > 0f;
- }
- else
- {
- canDraw = true;
- }
+ var canDraw = component.DrawRate <= 0f || args.Charge > 0f;
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
@@ -76,6 +59,9 @@ private void OnDrawCellChanged(EntityUid uid, PowerCellDrawComponent component,
var canDraw = !args.Ejected && HasCharge(uid, float.MinValue);
var canUse = !args.Ejected && HasActivatableCharge(uid, component);
+ if (!canDraw)
+ Toggle.TryDeactivate(uid);
+
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
component.CanDraw = canDraw;
diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs
index f45a01b2e1b..0d2d9012bc7 100644
--- a/Content.Server/PowerCell/PowerCellSystem.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.cs
@@ -39,8 +39,8 @@ public override void Initialize()
SubscribeLocalEvent(OnDrawChargeChanged);
SubscribeLocalEvent(OnDrawCellChanged);
- // funny
SubscribeLocalEvent(OnCellSlotExamined);
+ // funny
SubscribeLocalEvent(OnSlotMicrowaved);
}
diff --git a/Content.Server/Salvage/SalvageSystem.Magnet.cs b/Content.Server/Salvage/SalvageSystem.Magnet.cs
index 3fe4baca8b6..5e85fdb573a 100644
--- a/Content.Server/Salvage/SalvageSystem.Magnet.cs
+++ b/Content.Server/Salvage/SalvageSystem.Magnet.cs
@@ -8,6 +8,7 @@
using Content.Shared.Salvage.Magnet;
using Robust.Server.Maps;
using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
namespace Content.Server.Salvage;
@@ -253,7 +254,8 @@ private async Task TakeMagnetOffer(Entity data, int
var seed = data.Comp.Offered[index];
var offering = GetSalvageOffering(seed);
- var salvMap = _mapManager.CreateMap();
+ var salvMap = _mapSystem.CreateMap();
+ var salvMapXform = Transform(salvMap);
// Set values while awaiting asteroid dungeon if relevant so we can't double-take offers.
data.Comp.ActiveSeed = seed;
@@ -264,8 +266,8 @@ private async Task TakeMagnetOffer(Entity data, int
switch (offering)
{
case AsteroidOffering asteroid:
- var grid = _mapManager.CreateGrid(salvMap);
- await _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid.Owner, grid, Vector2i.Zero, seed);
+ var grid = _mapManager.CreateGridEntity(salvMap);
+ await _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid.Owner, grid.Comp, Vector2i.Zero, seed);
break;
case SalvageOffering wreck:
var salvageProto = wreck.SalvageMap;
@@ -275,10 +277,10 @@ private async Task TakeMagnetOffer(Entity data, int
Offset = new Vector2(0, 0)
};
- if (!_map.TryLoad(salvMap, salvageProto.MapPath.ToString(), out var roots, opts))
+ if (!_map.TryLoad(salvMapXform.MapID, salvageProto.MapPath.ToString(), out _, opts))
{
Report(magnet, MagnetChannel, "salvage-system-announcement-spawn-debris-disintegrated");
- _mapManager.DeleteMap(salvMap);
+ _mapManager.DeleteMap(salvMapXform.MapID);
return;
}
@@ -288,15 +290,14 @@ private async Task TakeMagnetOffer(Entity data, int
}
Box2? bounds = null;
- var mapXform = _xformQuery.GetComponent(_mapManager.GetMapEntityId(salvMap));
- if (mapXform.ChildCount == 0)
+ if (salvMapXform.ChildCount == 0)
{
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
return;
}
- var mapChildren = mapXform.ChildEnumerator;
+ var mapChildren = salvMapXform.ChildEnumerator;
while (mapChildren.MoveNext(out var mapChild))
{
@@ -340,19 +341,25 @@ private async Task TakeMagnetOffer(Entity data, int
if (!TryGetSalvagePlacementLocation(mapId, attachedBounds, bounds!.Value, worldAngle, out var spawnLocation, out var spawnAngle))
{
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
- _mapManager.DeleteMap(salvMap);
+ _mapManager.DeleteMap(salvMapXform.MapID);
return;
}
+ // I have no idea if we want to return on failure or not
+ // but I assume trying to set the parent with a null value wouldn't have worked out anyways
+ if (!_mapSystem.TryGetMap(spawnLocation.MapId, out var spawnUid))
+ return;
+
data.Comp.ActiveEntities = null;
- mapChildren = mapXform.ChildEnumerator;
+ mapChildren = salvMapXform.ChildEnumerator;
// It worked, move it into position and cleanup values.
while (mapChildren.MoveNext(out var mapChild))
{
var salvXForm = _xformQuery.GetComponent(mapChild);
var localPos = salvXForm.LocalPosition;
- _transform.SetParent(mapChild, salvXForm, _mapManager.GetMapEntityId(spawnLocation.MapId));
+
+ _transform.SetParent(mapChild, salvXForm, spawnUid.Value);
_transform.SetWorldPositionRotation(mapChild, spawnLocation.Position + localPos, spawnAngle, salvXForm);
data.Comp.ActiveEntities ??= new List();
@@ -371,7 +378,7 @@ private async Task TakeMagnetOffer(Entity data, int
}
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-arrived", ("timeLeft", data.Comp.ActiveTime.TotalSeconds));
- _mapManager.DeleteMap(salvMap);
+ _mapManager.DeleteMap(salvMapXform.MapID);
data.Comp.Announced = false;
diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs
index 161b7910844..23a575413ed 100644
--- a/Content.Server/Salvage/SalvageSystem.Runner.cs
+++ b/Content.Server/Salvage/SalvageSystem.Runner.cs
@@ -9,6 +9,7 @@
using Content.Shared.Mobs.Systems;
using Content.Shared.Salvage.Expeditions;
using Content.Shared.Shuttles.Components;
+using Content.Shared.Localizations;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
@@ -103,8 +104,10 @@ private void OnFTLCompleted(ref FTLCompletedEvent args)
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", (component.EndTime - _timing.CurTime).Minutes)));
+ var directionLocalization = ContentLocalizationManager.FormatDirection(component.DungeonLocation.GetDir()).ToLower();
+
if (component.DungeonLocation != Vector2.Zero)
- Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir())));
+ Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", directionLocalization)));
component.Stage = ExpeditionStage.Running;
Dirty(args.MapUid, component);
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
index 746d75f0d85..f289752b7cf 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
@@ -29,7 +29,7 @@ private void OnModuleGotInserted(EntityUid uid, BorgModuleComponent component, E
if (!TryComp(chassis, out var chassisComp) ||
args.Container != chassisComp.ModuleContainer ||
- !chassisComp.Activated)
+ !Toggle.IsActivated(chassis))
return;
if (!_powerCell.HasDrawCharge(uid))
@@ -143,6 +143,7 @@ public void SelectModule(EntityUid chassis,
var ev = new BorgModuleSelectedEvent(chassis);
RaiseLocalEvent(moduleUid, ref ev);
chassisComp.SelectedModule = moduleUid;
+ Dirty(chassis, chassisComp);
}
///
@@ -162,6 +163,7 @@ public void UnselectModule(EntityUid chassis, BorgChassisComponent? chassisComp
var ev = new BorgModuleUnselectedEvent(chassis);
RaiseLocalEvent(chassisComp.SelectedModule.Value, ref ev);
chassisComp.SelectedModule = null;
+ Dirty(chassis, chassisComp);
}
private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs
index c97ca9cbc0d..1c40e9489eb 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.cs
@@ -10,6 +10,7 @@
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
@@ -73,6 +74,7 @@ public override void Initialize()
SubscribeLocalEvent(OnPowerCellChanged);
SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnGetDeadIC);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnBrainMindAdded);
SubscribeLocalEvent(OnBrainPointAttempt);
@@ -173,11 +175,11 @@ private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, Mo
if (args.NewMobState == MobState.Alive)
{
if (_mind.TryGetMind(uid, out _, out _))
- _powerCell.SetPowerCellDrawEnabled(uid, true);
+ _powerCell.SetDrawEnabled(uid, true);
}
else
{
- _powerCell.SetPowerCellDrawEnabled(uid, false);
+ _powerCell.SetDrawEnabled(uid, false);
}
}
@@ -185,24 +187,10 @@ private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, P
{
UpdateBatteryAlert((uid, component));
- if (!TryComp(uid, out var draw))
- return;
-
- // if we eject the battery or run out of charge, then disable
- if (args.Ejected || !_powerCell.HasDrawCharge(uid))
- {
- DisableBorgAbilities(uid, component);
- return;
- }
-
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
- if (_powerCell.HasDrawCharge(uid, draw))
+ if (_powerCell.HasDrawCharge(uid))
{
- // only reenable the powerdraw if a player has the role.
- if (!draw.Drawing && _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(uid))
- _powerCell.SetPowerCellDrawEnabled(uid, true);
-
- EnableBorgAbilities(uid, component);
+ Toggle.TryActivate(uid);
}
UpdateUI(uid, component);
@@ -210,7 +198,7 @@ private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, P
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
- DisableBorgAbilities(uid, component);
+ Toggle.TryDeactivate(uid);
UpdateUI(uid, component);
}
@@ -219,6 +207,23 @@ private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetC
args.Dead = true;
}
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
+ {
+ var (uid, comp) = ent;
+ if (args.Activated)
+ InstallAllModules(uid, comp);
+ else
+ DisableAllModules(uid, comp);
+
+ // only enable the powerdraw if there is a player in the chassis
+ var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
+ _powerCell.SetDrawEnabled(uid, drawing);
+
+ UpdateUI(uid, comp);
+
+ _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+ }
+
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
{
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
@@ -271,44 +276,14 @@ private void UpdateBatteryAlert(Entity ent, PowerCellSlotC
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
}
- ///
- /// Activates the borg, enabling all of its modules.
- ///
- public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component, PowerCellDrawComponent? powerCell = null)
- {
- if (component.Activated)
- return;
-
- component.Activated = true;
- InstallAllModules(uid, component);
- Dirty(uid, component);
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
- }
-
- ///
- /// Deactivates the borg, disabling all of its modules and decreasing its speed.
- ///
- public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
- {
- if (!component.Activated)
- return;
-
- component.Activated = false;
- DisableAllModules(uid, component);
- Dirty(uid, component);
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
- }
-
///
/// Activates a borg when a player occupies it
///
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
- _powerCell.SetPowerCellDrawEnabled(uid, true);
- _access.SetAccessEnabled(uid, true);
+ Toggle.TryActivate(uid);
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
- Dirty(uid, component);
}
///
@@ -317,10 +292,8 @@ public void BorgActivate(EntityUid uid, BorgChassisComponent component)
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
- _powerCell.SetPowerCellDrawEnabled(uid, false);
- _access.SetAccessEnabled(uid, false);
+ Toggle.TryDeactivate(uid);
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
- Dirty(uid, component);
}
///
diff --git a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
deleted file mode 100644
index d758247eca2..00000000000
--- a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Server.StationEvents.Events;
-
-namespace Content.Server.StationEvents.Components;
-
-///
-/// Configuration component for the Space Ninja antag.
-///
-[RegisterComponent, Access(typeof(NinjaSpawnRule))]
-public sealed partial class NinjaSpawnRuleComponent : Component
-{
- ///
- /// Distance that the ninja spawns from the station's half AABB radius
- ///
- [DataField("spawnDistance")]
- public float SpawnDistance = 20f;
-}
diff --git a/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
new file mode 100644
index 00000000000..a0168077fd6
--- /dev/null
+++ b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
@@ -0,0 +1,25 @@
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.StationEvents.Components;
+
+///
+/// Component for spawning antags in space around a station.
+/// Requires AntagSelectionComponent.
+///
+[RegisterComponent, Access(typeof(SpaceSpawnRule))]
+public sealed partial class SpaceSpawnRuleComponent : Component
+{
+ ///
+ /// Distance that the entity spawns from the station's half AABB radius
+ ///
+ [DataField]
+ public float SpawnDistance = 20f;
+
+ ///
+ /// Location that was picked.
+ ///
+ [DataField]
+ public MapCoordinates? Coords;
+}
diff --git a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
similarity index 53%
rename from Content.Server/StationEvents/Events/NinjaSpawnRule.cs
rename to Content.Server/StationEvents/Events/SpaceSpawnRule.cs
index 9cbc193ce61..6fccaaa5cfc 100644
--- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs
+++ b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
@@ -1,5 +1,5 @@
+using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Ninja.Systems;
using Content.Server.Station.Components;
using Content.Server.StationEvents.Components;
using Content.Shared.GameTicking.Components;
@@ -9,18 +9,28 @@
namespace Content.Server.StationEvents.Events;
///
-/// Event for spawning a Space Ninja mid-game.
+/// Station event component for spawning this rules antags in space around a station.
///
-public sealed class NinjaSpawnRule : StationEventSystem
+public sealed class SpaceSpawnRule : StationEventSystem
{
[Dependency] private readonly SharedTransformSystem _transform = default!;
- protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ public override void Initialize()
{
- base.Started(uid, comp, gameRule, args);
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSelectLocation);
+ }
+
+ protected override void Added(EntityUid uid, SpaceSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args)
+ {
+ base.Added(uid, comp, gameRule, args);
if (!TryGetRandomStation(out var station))
+ {
+ ForceEndSelf(uid, gameRule);
return;
+ }
var stationData = Comp(station.Value);
@@ -28,22 +38,28 @@ protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, Gam
var gridUid = StationSystem.GetLargestGrid(stationData);
if (gridUid == null || !TryComp(gridUid, out var grid))
{
- Sawmill.Warning("Chosen station has no grids, cannot spawn space ninja!");
+ Sawmill.Warning("Chosen station has no grids, cannot pick location for {ToPrettyString(uid):rule}");
+ ForceEndSelf(uid, gameRule);
return;
}
- // figure out its AABB size and use that as a guide to how far ninja should be
+ // figure out its AABB size and use that as a guide to how far the spawner should be
var size = grid.LocalAABB.Size.Length() / 2;
var distance = size + comp.SpawnDistance;
var angle = RobustRandom.NextAngle();
// position relative to station center
var location = angle.ToVec() * distance;
- // create the spawner, the ninja will appear when a ghost has picked the role
+ // create the spawner!
var xform = Transform(gridUid.Value);
var position = _transform.GetWorldPosition(xform) + location;
- var coords = new MapCoordinates(position, xform.MapID);
- Sawmill.Info($"Creating ninja spawnpoint at {coords}");
- Spawn("SpawnPointGhostSpaceNinja", coords);
+ comp.Coords = new MapCoordinates(position, xform.MapID);
+ Sawmill.Info($"Picked location {comp.Coords} for {ToPrettyString(uid):rule}");
+ }
+
+ private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args)
+ {
+ if (ent.Comp.Coords is {} coords)
+ args.Coordinates.Add(coords);
}
}
diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
index c1782efabaf..97dd2c7e735 100644
--- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs
+++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
@@ -19,7 +19,7 @@ public sealed class StunbatonSystem : SharedStunbatonSystem
[Dependency] private readonly RiggableSystem _riggableSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly BatterySystem _battery = default!;
- [Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
+ [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
public override void Initialize()
{
diff --git a/Content.Server/Weapons/Misc/TetherGunSystem.cs b/Content.Server/Weapons/Misc/TetherGunSystem.cs
index f6aafe376d6..2bf53d46f4b 100644
--- a/Content.Server/Weapons/Misc/TetherGunSystem.cs
+++ b/Content.Server/Weapons/Misc/TetherGunSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.PowerCell;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.PowerCell;
using Content.Shared.Weapons.Misc;
using Robust.Shared.Physics.Components;
@@ -8,6 +9,7 @@ namespace Content.Server.Weapons.Misc;
public sealed class TetherGunSystem : SharedTetherGunSystem
{
[Dependency] private readonly PowerCellSystem _cell = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
@@ -36,12 +38,12 @@ protected override void StartTether(EntityUid gunUid, BaseForceGunComponent comp
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
{
base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
- _cell.SetPowerCellDrawEnabled(gunUid, true);
+ _toggle.TryActivate(gunUid);
}
protected override void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
{
base.StopTether(gunUid, component, land, transfer);
- _cell.SetPowerCellDrawEnabled(gunUid, false);
+ _toggle.TryDeactivate(gunUid);
}
}
diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
index 7f7c7ba8557..29f91988182 100644
--- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
+++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
@@ -24,6 +24,7 @@
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
+using Robust.Shared.Containers;
namespace Content.Server.Weapons.Ranged.Systems;
@@ -38,6 +39,7 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StaminaSystem _stamina = default!;
[Dependency] private readonly StunSystem _stun = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
private const float DamagePitchVariation = 0.05f;
public const float GunClumsyChance = 0.5f;
@@ -204,17 +206,21 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid?
var result = rayCastResults[0];
- // Checks if the laser should pass over unless targeted by its user
- foreach (var collide in rayCastResults)
+ // Check if laser is shot from in a container
+ if (!_container.IsEntityOrParentInContainer(lastUser))
{
- if (collide.HitEntity != gun.Target &&
- CompOrNull(collide.HitEntity)?.Active == true)
+ // Checks if the laser should pass over unless targeted by its user
+ foreach (var collide in rayCastResults)
{
- continue;
+ if (collide.HitEntity != gun.Target &&
+ CompOrNull(collide.HitEntity)?.Active == true)
+ {
+ continue;
+ }
+
+ result = collide;
+ break;
}
-
- result = collide;
- break;
}
var hit = result.HitEntity;
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
index c6f56a27508..a585a9ef452 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
@@ -2,6 +2,7 @@
using Content.Server.Salvage;
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Clothing;
+using Content.Shared.Item.ItemToggle.Components;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
@@ -29,11 +30,11 @@ public override void Update(float frameTime)
_toActivate.Clear();
- //assume that there's more instruments than artifacts
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out _, out var magboot, out var magXform))
+ //assume that there's more magboots than artifacts
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
{
- if (!magboot.On)
+ if (!toggle.Activated)
continue;
var artiQuery = EntityQueryEnumerator();
diff --git a/Content.Shared/Access/Components/AccessToggleComponent.cs b/Content.Shared/Access/Components/AccessToggleComponent.cs
new file mode 100644
index 00000000000..60a606ac7ea
--- /dev/null
+++ b/Content.Shared/Access/Components/AccessToggleComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Access.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Access.Components;
+
+///
+/// Toggles an access provider with ItemToggle.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(AccessToggleSystem))]
+public sealed partial class AccessToggleComponent : Component;
diff --git a/Content.Shared/Access/Systems/AccessToggleSystem.cs b/Content.Shared/Access/Systems/AccessToggleSystem.cs
new file mode 100644
index 00000000000..564aca06812
--- /dev/null
+++ b/Content.Shared/Access/Systems/AccessToggleSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Access.Systems;
+
+public sealed class AccessToggleSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAccessSystem _access = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnToggled);
+ }
+
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
+ {
+ _access.SetAccessEnabled(ent, args.Activated);
+ }
+}
diff --git a/Content.Shared/Access/Systems/IdExaminableSystem.cs b/Content.Shared/Access/Systems/IdExaminableSystem.cs
index 333272e27ac..13359adcba2 100644
--- a/Content.Shared/Access/Systems/IdExaminableSystem.cs
+++ b/Content.Shared/Access/Systems/IdExaminableSystem.cs
@@ -27,7 +27,8 @@ private void OnGetExamineVerbs(EntityUid uid, IdExaminableComponent component, G
{
Act = () =>
{
- var markup = FormattedMessage.FromMarkup(info);
+ var markup = FormattedMessage.FromMarkupOrThrow(info);
+
_examineSystem.SendExamineTooltip(args.User, uid, markup, false, false);
},
Text = Loc.GetString("id-examinable-component-verb-text"),
diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs
index 1c5a3ba0d93..1a83cf38e51 100644
--- a/Content.Shared/Actions/ActionContainerSystem.cs
+++ b/Content.Shared/Actions/ActionContainerSystem.cs
@@ -261,7 +261,7 @@ public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
if (action.Container == null)
return;
- _transform.DetachParentToNull(actionId, Transform(actionId));
+ _transform.DetachEntity(actionId, Transform(actionId));
// Container removal events should have removed the action from the action container.
// However, just in case the container was already deleted we will still manually clear the container field
diff --git a/Content.Shared/Actions/EntityTargetActionComponent.cs b/Content.Shared/Actions/EntityTargetActionComponent.cs
index 9024f42e0e7..7217794b23b 100644
--- a/Content.Shared/Actions/EntityTargetActionComponent.cs
+++ b/Content.Shared/Actions/EntityTargetActionComponent.cs
@@ -4,6 +4,9 @@
namespace Content.Shared.Actions;
+///
+/// Used on action entities to define an action that triggers when targeting an entity.
+///
[RegisterComponent, NetworkedComponent]
public sealed partial class EntityTargetActionComponent : BaseTargetActionComponent
{
@@ -16,8 +19,15 @@ public sealed partial class EntityTargetActionComponent : BaseTargetActionCompon
[NonSerialized]
public EntityTargetActionEvent? Event;
+ ///
+ /// Determines which entities are valid targets for this action.
+ ///
+ /// No whitelist check when null.
[DataField("whitelist")] public EntityWhitelist? Whitelist;
+ ///
+ /// Whether this action considers the user as a valid target entity when using this action.
+ ///
[DataField("canTargetSelf")] public bool CanTargetSelf = true;
}
diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs
index 0e302f1e028..013348eb4f7 100644
--- a/Content.Shared/Actions/SharedActionsSystem.cs
+++ b/Content.Shared/Actions/SharedActionsSystem.cs
@@ -427,7 +427,7 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg
}
var entityCoordinatesTarget = GetCoordinates(netCoordinatesTarget);
- _rotateToFaceSystem.TryFaceCoordinates(user, entityCoordinatesTarget.ToMapPos(EntityManager, _transformSystem));
+ _rotateToFaceSystem.TryFaceCoordinates(user, _transformSystem.ToMapCoordinates(entityCoordinatesTarget).Position);
if (!ValidateWorldTarget(user, entityCoordinatesTarget, (actionEnt, worldAction)))
return;
@@ -533,7 +533,7 @@ private bool ValidateWorldTargetBase(EntityUid user, EntityCoordinates coords, W
if (action.Range <= 0)
return true;
- return coords.InRange(EntityManager, _transformSystem, Transform(user).Coordinates, action.Range);
+ return _transformSystem.InRange(coords, Transform(user).Coordinates, action.Range);
}
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
diff --git a/Content.Shared/Actions/WorldTargetActionComponent.cs b/Content.Shared/Actions/WorldTargetActionComponent.cs
index 4974b4478db..8066a640349 100644
--- a/Content.Shared/Actions/WorldTargetActionComponent.cs
+++ b/Content.Shared/Actions/WorldTargetActionComponent.cs
@@ -3,6 +3,9 @@
namespace Content.Shared.Actions;
+///
+/// Used on action entities to define an action that triggers when targeting an entity coordinate.
+///
[RegisterComponent, NetworkedComponent]
public sealed partial class WorldTargetActionComponent : BaseTargetActionComponent
{
diff --git a/Content.Shared/Administration/Events/BabyJailChangedEvent.cs b/Content.Shared/Administration/Events/BabyJailChangedEvent.cs
new file mode 100644
index 00000000000..56d5ce51626
--- /dev/null
+++ b/Content.Shared/Administration/Events/BabyJailChangedEvent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.Serialization;
+
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Shared.Administration.Events;
+
+[Serializable, NetSerializable]
+public sealed class BabyJailStatus
+{
+ public bool Enabled;
+ public bool ShowReason;
+ public int MaxAccountAgeMinutes;
+ public int MaxOverallMinutes;
+}
+
+[Serializable, NetSerializable]
+public sealed class BabyJailChangedEvent(BabyJailStatus status) : EntityEventArgs
+{
+ public BabyJailStatus Status = status;
+}
diff --git a/Content.Shared/Beeper/Components/BeeperComponent.cs b/Content.Shared/Beeper/Components/BeeperComponent.cs
index 54d242709c4..f6efbb10f3e 100644
--- a/Content.Shared/Beeper/Components/BeeperComponent.cs
+++ b/Content.Shared/Beeper/Components/BeeperComponent.cs
@@ -10,15 +10,12 @@ namespace Content.Shared.Beeper.Components;
/// This is used for an item that beeps based on
/// proximity to a specified component.
///
+///
+/// Requires ItemToggleComponent to control it.
+///
[RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState]
public sealed partial class BeeperComponent : Component
{
- ///
- /// Whether or not it's on.
- ///
- [DataField, AutoNetworkedField]
- public bool Enabled = true;
-
///
/// How much to scale the interval by (< 0 = min, > 1 = max)
///
@@ -56,7 +53,7 @@ public sealed partial class BeeperComponent : Component
/// Is the beep muted
///
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool IsMuted = false;
+ public bool IsMuted;
///
/// The sound played when the locator beeps.
diff --git a/Content.Shared/Beeper/Systems/BeeperSystem.cs b/Content.Shared/Beeper/Systems/BeeperSystem.cs
index c51eef4da9d..a52e19f7552 100644
--- a/Content.Shared/Beeper/Systems/BeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/BeeperSystem.cs
@@ -1,5 +1,7 @@
using Content.Shared.Beeper.Components;
using Content.Shared.FixedPoint;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Timing;
@@ -11,34 +13,20 @@ namespace Content.Shared.Beeper.Systems;
public sealed class BeeperSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly INetManager _net = default!;
-
- public override void Initialize()
- {
- }
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Update(float frameTime)
{
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var beeper))
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var beeper, out var toggle))
{
- if (!beeper.Enabled)
- continue;
- RunUpdate_Internal(uid, beeper);
+ if (toggle.Activated)
+ RunUpdate_Internal(uid, beeper);
}
}
- public void SetEnable(EntityUid owner, bool isEnabled, BeeperComponent? beeper = null)
- {
- if (!Resolve(owner, ref beeper) || beeper.Enabled == isEnabled)
- return;
- beeper.Enabled = isEnabled;
-
- RunUpdate_Internal(owner, beeper);
- Dirty(owner, beeper);
- }
-
public void SetIntervalScaling(EntityUid owner, BeeperComponent beeper, FixedPoint2 newScaling)
{
newScaling = FixedPoint2.Clamp(newScaling, 0, 1);
@@ -70,6 +58,7 @@ public void SetMute(EntityUid owner, bool isMuted, BeeperComponent? comp = null)
if (!Resolve(owner, ref comp))
return;
comp.IsMuted = isMuted;
+ Dirty(owner, comp);
}
private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper)
@@ -91,19 +80,17 @@ public void ForceUpdate(EntityUid owner, BeeperComponent? beeper = null)
private void RunUpdate_Internal(EntityUid owner, BeeperComponent beeper)
{
- if (!beeper.Enabled)
- {
+ if (!_toggle.IsActivated(owner))
return;
- }
+
UpdateBeepInterval(owner, beeper);
if (beeper.NextBeep >= _timing.CurTime)
return;
+
var beepEvent = new BeepPlayedEvent(beeper.IsMuted);
RaiseLocalEvent(owner, ref beepEvent);
if (!beeper.IsMuted && _net.IsServer)
- {
_audio.PlayPvs(beeper.BeepSound, owner);
- }
beeper.LastBeepTime = _timing.CurTime;
}
}
diff --git a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
index bd857d4c29b..ed3c6366c13 100644
--- a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
@@ -1,7 +1,6 @@
using Content.Shared.Beeper.Components;
-using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Pinpointer;
-using Content.Shared.PowerCell;
using Content.Shared.ProximityDetection;
using Content.Shared.ProximityDetection.Components;
using Content.Shared.ProximityDetection.Systems;
@@ -9,20 +8,17 @@
namespace Content.Shared.Beeper.Systems;
///
-/// This handles logic for implementing proximity beeper as a handheld tool />
+/// This handles controlling a beeper from proximity detector events.
///
public sealed class ProximityBeeperSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
[Dependency] private readonly ProximityDetectionSystem _proximity = default!;
[Dependency] private readonly BeeperSystem _beeper = default!;
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnNewProximityTarget);
SubscribeLocalEvent(OnProximityTargetUpdate);
}
@@ -33,82 +29,16 @@ private void OnProximityTargetUpdate(EntityUid owner, ProximityBeeperComponent p
return;
if (args.Target == null)
{
- _beeper.SetEnable(owner, false, beeper);
+ _beeper.SetMute(owner, true, beeper);
return;
}
- _beeper.SetIntervalScaling(owner,args.Distance/args.Detector.Range, beeper);
- _beeper.SetEnable(owner, true, beeper);
- }
-
- private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
- {
- _beeper.SetEnable(owner, args.Target != null);
- }
- private void OnUseInHand(EntityUid uid, ProximityBeeperComponent proxBeeper, UseInHandEvent args)
- {
- if (args.Handled)
- return;
- args.Handled = TryToggle(uid, proxBeeper, user: args.User);
+ _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
+ _beeper.SetMute(owner, false, beeper);
}
- private void OnPowerCellSlotEmpty(EntityUid uid, ProximityBeeperComponent beeper, ref PowerCellSlotEmptyEvent args)
- {
- if (_proximity.GetEnable(uid))
- TryDisable(uid);
- }
- public bool TryEnable(EntityUid owner, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
- PowerCellDrawComponent? draw = null,EntityUid? user = null)
- {
- if (!Resolve(owner, ref beeper, ref detector))
- return false;
- if (Resolve(owner, ref draw, false) && !_powerCell.HasActivatableCharge(owner, battery: draw, user: user))
- return false;
- Enable(owner, beeper, detector, draw);
- return true;
- }
- private void Enable(EntityUid owner, BeeperComponent beeper,
- ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
- {
- _proximity.SetEnable(owner, true, detector);
- _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, true);
- _powerCell.SetPowerCellDrawEnabled(owner, true, draw);
- }
-
-
- ///
- /// Disables the proximity beeper
- ///
- public bool TryDisable(EntityUid owner,BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, PowerCellDrawComponent? draw = null)
- {
- if (!Resolve(owner, ref beeper, ref detector))
- return false;
-
- if (!detector.Enabled)
- return false;
- Disable(owner, beeper, detector, draw);
- return true;
- }
- private void Disable(EntityUid owner, BeeperComponent beeper,
- ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
- {
- _proximity.SetEnable(owner, false, detector);
- _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, false);
- _beeper.SetEnable(owner, false, beeper);
- _powerCell.SetPowerCellDrawEnabled(owner, false, draw);
- }
-
- ///
- /// toggles the proximity beeper
- ///
- public bool TryToggle(EntityUid owner, ProximityBeeperComponent? proxBeeper = null, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
- PowerCellDrawComponent? draw = null, EntityUid? user = null)
+ private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
{
- if (!Resolve(owner, ref proxBeeper, ref beeper, ref detector))
- return false;
-
- return detector.Enabled
- ? TryDisable(owner, beeper, detector, draw)
- : TryEnable(owner, beeper, detector, draw,user);
+ _beeper.SetMute(owner, args.Target != null);
}
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index d2204c2efe1..df276a3e504 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -332,6 +332,48 @@ public static readonly CVarDef
public static readonly CVarDef BypassBunkerWhitelist =
CVarDef.Create("game.panic_bunker.whitelisted_can_bypass", true, CVar.SERVERONLY);
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ ///
+ /// Whether the baby jail is currently enabled.
+ ///
+ public static readonly CVarDef BabyJailEnabled =
+ CVarDef.Create("game.baby_jail.enabled", false, CVar.NOTIFY | CVar.REPLICATED | CVar.SERVER);
+
+ ///
+ /// Show reason of disconnect for user or not.
+ ///
+ public static readonly CVarDef BabyJailShowReason =
+ CVarDef.Create("game.baby_jail.show_reason", false, CVar.SERVERONLY);
+
+ ///
+ /// Maximum age of the account (from server's PoV, so from first-seen date) in minutes that can access baby
+ /// jailed servers.
+ ///
+ public static readonly CVarDef BabyJailMaxAccountAge =
+ CVarDef.Create("game.baby_jail.max_account_age", 1440, CVar.SERVERONLY);
+
+ ///
+ /// Maximum overall played time allowed to access baby jailed servers.
+ ///
+ public static readonly CVarDef BabyJailMaxOverallMinutes =
+ CVarDef.Create("game.baby_jail.max_overall_minutes", 120, CVar.SERVERONLY);
+
+ ///
+ /// A custom message that will be used for connections denied due to the baby jail.
+ /// If not empty, then will overwrite
+ ///
+ public static readonly CVarDef BabyJailCustomReason =
+ CVarDef.Create("game.baby_jail.custom_reason", string.Empty, CVar.SERVERONLY);
+
+ ///
+ /// Allow bypassing the baby jail if the user is whitelisted.
+ ///
+ public static readonly CVarDef BypassBabyJailWhitelist =
+ CVarDef.Create("game.baby_jail.whitelisted_can_bypass", true, CVar.SERVERONLY);
+
///
/// Make people bonk when trying to climb certain objects like tables.
///
diff --git a/Content.Shared/Cargo/Components/BankClientComponent.cs b/Content.Shared/Cargo/Components/BankClientComponent.cs
new file mode 100644
index 00000000000..4fd70855034
--- /dev/null
+++ b/Content.Shared/Cargo/Components/BankClientComponent.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Cargo;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Cargo.Components;
+
+///
+/// Makes an entity a client of the station's bank account.
+/// When its balance changes it will have raised on it.
+/// Other systems can then use this for logic or to update ui states.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedCargoSystem))]
+[AutoGenerateComponentState]
+public sealed partial class BankClientComponent : Component
+{
+ ///
+ /// The balance updated for the last station this entity was a part of.
+ ///
+ [DataField, AutoNetworkedField]
+ public int Balance;
+}
+
+///
+/// Raised on an entity with when the bank's balance is updated.
+///
+[ByRefEvent]
+public record struct BankBalanceUpdatedEvent(EntityUid Station, int Balance);
diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
index 5de1383cde0..7f95ef184e4 100644
--- a/Content.Shared/Charges/Systems/SharedChargesSystem.cs
+++ b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
@@ -5,10 +5,14 @@ namespace Content.Shared.Charges.Systems;
public abstract class SharedChargesSystem : EntitySystem
{
+ protected EntityQuery Query;
+
public override void Initialize()
{
base.Initialize();
+ Query = GetEntityQuery();
+
SubscribeLocalEvent(OnExamine);
}
@@ -30,9 +34,9 @@ protected virtual void OnExamine(EntityUid uid, LimitedChargesComponent comp, Ex
///
/// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
///
- public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
+ public virtual void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
{
- if (!Resolve(uid, ref comp, false))
+ if (!Query.Resolve(uid, ref comp, false))
return;
var old = comp.Charges;
@@ -47,7 +51,7 @@ public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp
public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
{
// can't be empty if there are no limited charges
- if (!Resolve(uid, ref comp, false))
+ if (!Query.Resolve(uid, ref comp, false))
return false;
return comp.Charges <= 0;
@@ -56,10 +60,24 @@ public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
///
/// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
///
- public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ {
+ AddCharges(uid, -1, comp);
+ }
+
+ ///
+ /// Checks IsEmpty and uses a charge if it isn't empty.
+ ///
+ public bool TryUseCharge(Entity ent)
{
- if (Resolve(uid, ref comp, false))
- AddCharges(uid, -1, comp);
+ if (!Query.Resolve(ent, ref ent.Comp, false))
+ return true;
+
+ if (IsEmpty(ent, ent.Comp))
+ return false;
+
+ UseCharge(ent, ent.Comp);
+ return true;
}
///
@@ -80,7 +98,6 @@ public bool HasInsufficientCharges(EntityUid uid, int requiredCharges, LimitedCh
///
public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null)
{
- if (Resolve(uid, ref comp, false))
- AddCharges(uid, -chargesUsed, comp);
+ AddCharges(uid, -chargesUsed, comp);
}
}
diff --git a/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs b/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs
index 3b753925088..e5d8cc5f597 100644
--- a/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs
+++ b/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs
@@ -117,6 +117,7 @@ private void OnAfterInteract(Entity ent, ref AfterInt
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
var transferred = Transfer(args.User, target, targetSoln.Value, uid, ownerSoln.Value, transferAmount);
+ args.Handled = true;
if (transferred > 0)
{
var toTheBrim = ownerRefill.AvailableVolume == 0;
@@ -125,8 +126,6 @@ private void OnAfterInteract(Entity ent, ref AfterInt
: "comp-solution-transfer-fill-normal";
_popup.PopupClient(Loc.GetString(msg, ("owner", args.Target), ("amount", transferred), ("target", uid)), uid, args.User);
-
- args.Handled = true;
return;
}
}
@@ -143,13 +142,11 @@ private void OnAfterInteract(Entity ent, ref AfterInt
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
var transferred = Transfer(args.User, uid, ownerSoln.Value, target, targetSoln.Value, transferAmount);
-
+ args.Handled = true;
if (transferred > 0)
{
var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", target));
_popup.PopupClient(message, uid, args.User);
-
- args.Handled = true;
}
}
}
@@ -202,6 +199,9 @@ public FixedPoint2 Transfer(EntityUid user,
var solution = _solution.SplitSolution(source, actualAmount);
_solution.AddSolution(target, solution);
+ var ev = new SolutionTransferredEvent(sourceEntity, targetEntity, user, actualAmount);
+ RaiseLocalEvent(targetEntity, ref ev);
+
_adminLogger.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(user):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(targetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
@@ -225,3 +225,9 @@ public void Cancel(string reason)
CancelReason = reason;
}
}
+
+///
+/// Raised on the target entity when a non-zero amount of solution gets transferred.
+///
+[ByRefEvent]
+public record struct SolutionTransferredEvent(EntityUid From, EntityUid To, EntityUid User, FixedPoint2 Amount);
diff --git a/Content.Shared/Clock/ClockComponent.cs b/Content.Shared/Clock/ClockComponent.cs
new file mode 100644
index 00000000000..3a1027d8136
--- /dev/null
+++ b/Content.Shared/Clock/ClockComponent.cs
@@ -0,0 +1,42 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Clock;
+
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedClockSystem))]
+[AutoGenerateComponentState]
+public sealed partial class ClockComponent : Component
+{
+ ///
+ /// If not null, this time will be permanently shown.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan? StuckTime;
+
+ ///
+ /// The format in which time is displayed.
+ ///
+ [DataField, AutoNetworkedField]
+ public ClockType ClockType = ClockType.TwelveHour;
+
+ [DataField]
+ public string HoursBase = "hours_";
+
+ [DataField]
+ public string MinutesBase = "minutes_";
+}
+
+[Serializable, NetSerializable]
+public enum ClockType : byte
+{
+ TwelveHour,
+ TwentyFourHour
+}
+
+[Serializable, NetSerializable]
+public enum ClockVisualLayers : byte
+{
+ HourHand,
+ MinuteHand
+}
diff --git a/Content.Shared/Clock/GlobalTimeManagerComponent.cs b/Content.Shared/Clock/GlobalTimeManagerComponent.cs
new file mode 100644
index 00000000000..764b578b250
--- /dev/null
+++ b/Content.Shared/Clock/GlobalTimeManagerComponent.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clock;
+
+///
+/// This is used for globally managing the time on-station
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedClockSystem))]
+public sealed partial class GlobalTimeManagerComponent : Component
+{
+ ///
+ /// A fixed random offset, used to fuzz the time between shifts.
+ ///
+ [DataField, AutoPausedField, AutoNetworkedField]
+ public TimeSpan TimeOffset;
+}
diff --git a/Content.Shared/Clock/SharedClockSystem.cs b/Content.Shared/Clock/SharedClockSystem.cs
new file mode 100644
index 00000000000..0a7c1b01b2c
--- /dev/null
+++ b/Content.Shared/Clock/SharedClockSystem.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using Content.Shared.Examine;
+using Content.Shared.GameTicking;
+
+namespace Content.Shared.Clock;
+
+public abstract class SharedClockSystem : EntitySystem
+{
+ [Dependency] private readonly SharedGameTicker _ticker = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnExamined);
+ }
+
+ private void OnExamined(Entity ent, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup(Loc.GetString("clock-examine", ("time", GetClockTimeText(ent))));
+ }
+
+ public string GetClockTimeText(Entity ent)
+ {
+ var time = GetClockTime(ent);
+ switch (ent.Comp.ClockType)
+ {
+ case ClockType.TwelveHour:
+ return time.ToString(@"h\:mm");
+ case ClockType.TwentyFourHour:
+ return time.ToString(@"hh\:mm");
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private TimeSpan GetGlobalTime()
+ {
+ return (EntityQuery().FirstOrDefault()?.TimeOffset ?? TimeSpan.Zero) + _ticker.RoundDuration();
+ }
+
+ public TimeSpan GetClockTime(Entity ent)
+ {
+ var comp = ent.Comp;
+
+ if (comp.StuckTime != null)
+ return comp.StuckTime.Value;
+
+ var time = GetGlobalTime();
+
+ switch (comp.ClockType)
+ {
+ case ClockType.TwelveHour:
+ var adjustedHours = time.Hours % 12;
+ if (adjustedHours == 0)
+ adjustedHours = 12;
+ return new TimeSpan(adjustedHours, time.Minutes, time.Seconds);
+ case ClockType.TwentyFourHour:
+ return time;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
index c3c4baf19d0..866ce38a572 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
@@ -3,20 +3,18 @@
namespace Content.Shared.Clothing;
+///
+/// Modifies speed when worn and activated.
+/// Supports ItemToggleComponent.
+///
[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem))]
public sealed partial class ClothingSpeedModifierComponent : Component
{
- [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float WalkModifier = 1.0f;
- [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float SprintModifier = 1.0f;
-
- ///
- /// Is this clothing item currently 'actively' slowing you down?
- /// e.g. magboots can be turned on and off.
- ///
- [DataField("enabled")] public bool Enabled = true;
}
[Serializable, NetSerializable]
@@ -25,12 +23,9 @@ public sealed class ClothingSpeedModifierComponentState : ComponentState
public float WalkModifier;
public float SprintModifier;
- public bool Enabled;
-
- public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier, bool enabled)
+ public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier)
{
WalkModifier = walkModifier;
SprintModifier = sprintModifier;
- Enabled = enabled;
}
}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
index 4198c5c165b..c1efe0b3ddd 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
@@ -1,11 +1,10 @@
-using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.Examine;
-using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.PowerCell;
-using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -15,12 +14,12 @@ namespace Content.Shared.Clothing;
public sealed class ClothingSpeedModifierSystem : EntitySystem
{
- [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
public override void Initialize()
@@ -31,39 +30,12 @@ public override void Initialize()
SubscribeLocalEvent(OnHandleState);
SubscribeLocalEvent>(OnRefreshMoveSpeed);
SubscribeLocalEvent>(OnClothingVerbExamine);
-
- SubscribeLocalEvent>(AddToggleVerb);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnToggleSpeed);
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnToggled);
}
- // Public API
-
- public void SetClothingSpeedModifierEnabled(EntityUid uid, bool enabled, ClothingSpeedModifierComponent? component = null)
- {
- if (!Resolve(uid, ref component, false))
- return;
-
- if (component.Enabled != enabled)
- {
- component.Enabled = enabled;
- Dirty(uid, component);
-
- // inventory system will automatically hook into the event raised by this and update accordingly
- if (_container.TryGetContainingContainer(uid, out var container))
- {
- _movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
- }
- }
- }
-
- // Event handlers
-
private void OnGetState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentGetState args)
{
- args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier, component.Enabled);
+ args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier);
}
private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentHandleState args)
@@ -71,13 +43,11 @@ private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent compone
if (args.Current is not ClothingSpeedModifierComponentState state)
return;
- var diff = component.Enabled != state.Enabled ||
- !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
+ var diff = !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
!MathHelper.CloseTo(component.WalkModifier, state.WalkModifier);
component.WalkModifier = state.WalkModifier;
component.SprintModifier = state.SprintModifier;
- component.Enabled = state.Enabled;
// Avoid raising the event for the container if nothing changed.
// We'll still set the values in case they're slightly different but within tolerance.
@@ -89,10 +59,8 @@ private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent compone
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent args)
{
- if (!component.Enabled)
- return;
-
- args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
+ if (_toggle.IsActivated(uid))
+ args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent component, GetVerbsEvent args)
@@ -142,60 +110,15 @@ private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent
_examine.AddDetailedExamineVerb(args, component, msg, Loc.GetString("clothing-speed-examinable-verb-text"), "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png", Loc.GetString("clothing-speed-examinable-verb-message"));
}
- private void OnMapInit(Entity uid, ref MapInitEvent args)
- {
- _actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
-
- private void OnToggleSpeed(Entity uid, ref ToggleClothingSpeedEvent args)
- {
- if (args.Handled)
- return;
-
- args.Handled = true;
- SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, args.Performer);
- }
-
- private void SetSpeedToggleEnabled(Entity uid, bool value, EntityUid? user)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- if (uid.Comp.Enabled == value)
- return;
-
- TryComp(uid, out var draw);
- if (value && !_powerCell.HasDrawCharge(uid, draw, user: user))
- return;
+ // make sentient boots slow or fast too
+ _movementSpeed.RefreshMovementSpeedModifiers(ent);
- uid.Comp.Enabled = value;
-
- _appearance.SetData(uid, ToggleVisuals.Toggled, uid.Comp.Enabled);
- _actions.SetToggled(uid.Comp.ToggleActionEntity, uid.Comp.Enabled);
- _clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid.Owner, uid.Comp.Enabled);
- _powerCell.SetPowerCellDrawEnabled(uid, uid.Comp.Enabled, draw);
- Dirty(uid, uid.Comp);
- }
-
- private void AddToggleVerb(Entity uid, ref GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- var user = args.User;
- ActivationVerb verb = new()
+ if (_container.TryGetContainingContainer(ent.Owner, out var container))
{
- Text = Loc.GetString("toggle-clothing-verb-text",
- ("entity", Identity.Entity(uid, EntityManager))),
- Act = () => SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, user)
- };
- args.Verbs.Add(verb);
- }
-
- private void OnGetActions(Entity