diff --git a/.github/workflows/publish-testing.yml b/.github/workflows/publish-testing.yml index aa3b35dea1..6dacef1324 100644 --- a/.github/workflows/publish-testing.yml +++ b/.github/workflows/publish-testing.yml @@ -2,6 +2,7 @@ concurrency: group: publish-testing + cancel-in-progress: true on: workflow_dispatch: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 02fd9d4046..0294395632 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,6 +2,7 @@ name: Publish concurrency: group: publish + cancel-in-progress: true on: workflow_dispatch: @@ -48,12 +49,14 @@ jobs: GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} - name: Publish changelog (Discord) + continue-on-error: true run: Tools/actions_changelogs_since_last_run.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }} - name: Publish changelog (RSS) + continue-on-error: true run: Tools/actions_changelog_rss.py env: CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }} diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py index becd4506e8..96a7bdae2a 100644 --- a/BuildChecker/git_helper.py +++ b/BuildChecker/git_helper.py @@ -5,6 +5,7 @@ import subprocess import sys import os import shutil +import time from pathlib import Path from typing import List @@ -104,7 +105,21 @@ def reset_solution(): with SOLUTION_PATH.open("w") as f: f.write(content) +def check_for_zip_download(): + # Check if .git exists, + cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + if not os.path.isdir(os.path.join(cur_dir, ".git")): + print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n" + "When downloading straight from GitHub, it leaves out important information that git needs to function. " + "Such as information to download the engine or even the ability to even be able to create contributions. \n" + "Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n" + "If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:" + "https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n" + "Closing automatically in 30 seconds.") + time.sleep(30) + exit(1) if __name__ == '__main__': + check_for_zip_download() install_hooks() update_submodules() diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 0302739816..31350a6a5d 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -202,6 +202,7 @@ namespace Content.Client.Actions return; OnActionAdded?.Invoke(actionId); + ActionsUpdated?.Invoke(); } protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action) @@ -210,6 +211,7 @@ namespace Content.Client.Actions return; OnActionRemoved?.Invoke(actionId); + ActionsUpdated?.Invoke(); } public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions() diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index b77bc9e4bc..43c74adc5d 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Alert; using JetBrains.Annotations; using Robust.Client.Player; +using Robust.Client.UserInterface; using Robust.Shared.GameStates; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -15,6 +16,7 @@ public sealed class ClientAlertsSystem : AlertsSystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IUserInterfaceManager _ui = default!; public event EventHandler? ClearAlerts; public event EventHandler>? SyncAlerts; @@ -27,6 +29,12 @@ public sealed class ClientAlertsSystem : AlertsSystem SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnHandleState); } + + protected override void HandledAlert() + { + _ui.ClickSound(); + } + protected override void LoadPrototypes() { base.LoadPrototypes(); @@ -52,8 +60,24 @@ public sealed class ClientAlertsSystem : AlertsSystem if (args.Current is not AlertComponentState cast) return; + // Save all client-sided alerts to later put back in + var clientAlerts = new Dictionary(); + foreach (var alert in alerts.Comp.Alerts) + { + if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto)) + { + if (alertProto.ClientHandled) + clientAlerts[alert.Key] = alert.Value; + } + } + alerts.Comp.Alerts = new(cast.Alerts); + foreach (var alert in clientAlerts) + { + alerts.Comp.Alerts[alert.Key] = alert.Value; + } + UpdateHud(alerts); } diff --git a/Content.Client/Atmos/EntitySystems/GasTankSystem.cs b/Content.Client/Atmos/EntitySystems/GasTankSystem.cs new file mode 100644 index 0000000000..696e7939f6 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasTankSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; + +namespace Content.Client.Atmos.EntitySystems; + +public sealed class GasTankSystem : SharedGasTankSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnGasTankState); + } + + private void OnGasTankState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui)) + { + bui.Update(); + } + } + + public override void UpdateUserInterface(Entity ent) + { + if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui)) + { + bui.Update(); + } + } +} diff --git a/Content.Client/Atmos/Piping/Unary/Systems/GasCanisterSystem.cs b/Content.Client/Atmos/Piping/Unary/Systems/GasCanisterSystem.cs new file mode 100644 index 0000000000..cae184edbb --- /dev/null +++ b/Content.Client/Atmos/Piping/Unary/Systems/GasCanisterSystem.cs @@ -0,0 +1,32 @@ +using Content.Client.Atmos.UI; +using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.Atmos.Piping.Unary.Systems; +using Content.Shared.NodeContainer; + +namespace Content.Client.Atmos.Piping.Unary.Systems; + +public sealed class GasCanisterSystem : SharedGasCanisterSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnGasState); + } + + private void OnGasState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (UI.TryGetOpenUi(ent.Owner, GasCanisterUiKey.Key, out var bui)) + { + bui.Update(); + } + } + + protected override void DirtyUI(EntityUid uid, GasCanisterComponent? component = null, NodeContainerComponent? nodes = null) + { + if (UI.TryGetOpenUi(uid, GasCanisterUiKey.Key, out var bui)) + { + bui.Update(); + } + } +} diff --git a/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs b/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs index 7bf9b396d5..0456426b1f 100644 --- a/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs +++ b/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs @@ -1,4 +1,7 @@ -using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.IdentityManagement; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.UserInterface; @@ -32,22 +35,22 @@ namespace Content.Client.Atmos.UI private void OnTankEjectPressed() { - SendMessage(new GasCanisterHoldingTankEjectMessage()); + SendPredictedMessage(new GasCanisterHoldingTankEjectMessage()); } private void OnReleasePressureSet(float value) { - SendMessage(new GasCanisterChangeReleasePressureMessage(value)); + SendPredictedMessage(new GasCanisterChangeReleasePressureMessage(value)); } private void OnReleaseValveOpenPressed() { - SendMessage(new GasCanisterChangeReleaseValveMessage(true)); + SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(true)); } private void OnReleaseValveClosePressed() { - SendMessage(new GasCanisterChangeReleaseValveMessage(false)); + SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(false)); } /// @@ -57,17 +60,21 @@ namespace Content.Client.Atmos.UI protected override void UpdateState(BoundUserInterfaceState state) { base.UpdateState(state); - if (_window == null || state is not GasCanisterBoundUserInterfaceState cast) + if (_window == null || state is not GasCanisterBoundUserInterfaceState cast || !EntMan.TryGetComponent(Owner, out GasCanisterComponent? component)) return; - _window.SetCanisterLabel(cast.CanisterLabel); + var canisterLabel = Identity.Name(Owner, EntMan); + var tankLabel = component.GasTankSlot.Item != null ? Identity.Name(component.GasTankSlot.Item.Value, EntMan) : null; + + _window.SetCanisterLabel(canisterLabel); _window.SetCanisterPressure(cast.CanisterPressure); _window.SetPortStatus(cast.PortStatus); - _window.SetTankLabel(cast.TankLabel); + + _window.SetTankLabel(tankLabel); _window.SetTankPressure(cast.TankPressure); - _window.SetReleasePressureRange(cast.ReleasePressureMin, cast.ReleasePressureMax); - _window.SetReleasePressure(cast.ReleasePressure); - _window.SetReleaseValve(cast.ReleaseValve); + _window.SetReleasePressureRange(component.MinReleasePressure, component.MaxReleasePressure); + _window.SetReleasePressure(component.ReleasePressure); + _window.SetReleaseValve(component.ReleaseValve); } protected override void Dispose(bool disposing) diff --git a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs index 3c3d8f1509..b959bc966b 100644 --- a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs +++ b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs @@ -13,9 +13,6 @@ namespace Content.Client.Atmos.UI; [UsedImplicitly] public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { - [ViewVariables] - private const float MaxPressure = Atmospherics.MaxOutputPressure; - [ViewVariables] private GasPressurePumpWindow? _window; diff --git a/Content.Client/Body/Systems/InternalsSystem.cs b/Content.Client/Body/Systems/InternalsSystem.cs new file mode 100644 index 0000000000..87daac3722 --- /dev/null +++ b/Content.Client/Body/Systems/InternalsSystem.cs @@ -0,0 +1,24 @@ +using Content.Shared.Atmos.Components; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; + +namespace Content.Client.Body.Systems; + +public sealed class InternalsSystem : SharedInternalsSystem +{ + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInternalsAfterState); + } + + private void OnInternalsAfterState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (ent.Comp.GasTankEntity != null && _ui.TryGetOpenUi(ent.Comp.GasTankEntity.Value, SharedGasTankUiKey.Key, out var bui)) + { + bui.Update(); + } + } +} diff --git a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml index 72d8cf7daf..940352dc48 100644 --- a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml +++ b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml @@ -38,9 +38,10 @@ - + + SizeFlagsStretchRatio="1" + Name="Orders"> diff --git a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs index b837d59855..4c729b795b 100644 --- a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs +++ b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs @@ -208,6 +208,7 @@ namespace Content.Client.Cargo.UI var product = _protoManager.Index(order.ProductId); var productName = product.Name; + var account = _protoManager.Index(order.Account); var row = new CargoOrderRow { @@ -219,7 +220,9 @@ namespace Content.Client.Cargo.UI "cargo-console-menu-populate-orders-cargo-order-row-product-name-text", ("productName", productName), ("orderAmount", order.OrderQuantity), - ("orderRequester", order.Requester)) + ("orderRequester", order.Requester), + ("accountColor", account.Color), + ("account", Loc.GetString(account.Code))) }, Description = { @@ -282,6 +285,9 @@ namespace Content.Client.Cargo.UI AccountActionButton.Disabled = TransferSpinBox.Value <= 0 || TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit || _timing.CurTime < orderConsole.NextAccountActionTime; + + OrdersSpacer.Visible = !orderConsole.SlipPrinter; + Orders.Visible = !orderConsole.SlipPrinter; } } } diff --git a/Content.Client/Cargo/UI/CargoOrderRow.xaml b/Content.Client/Cargo/UI/CargoOrderRow.xaml index 22bd2291ae..e4ec95d981 100644 --- a/Content.Client/Cargo/UI/CargoOrderRow.xaml +++ b/Content.Client/Cargo/UI/CargoOrderRow.xaml @@ -11,11 +11,10 @@ - + + + diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs index 6471edb6f3..61f9f5f64b 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs +++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs @@ -23,6 +23,9 @@ namespace Content.Client.Lobby.UI LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect"); OptionsButton.OnPressed += _ => UserInterfaceManager.GetUIController().ToggleWindow(); + + CollapseButton.OnPressed += _ => TogglePanel(false); + ExpandButton.OnPressed += _ => TogglePanel(true); } public void SwitchState(LobbyGuiState state) @@ -53,6 +56,12 @@ namespace Content.Client.Lobby.UI } } + private void TogglePanel(bool value) + { + RightSide.Visible = value; + ExpandPanel.Visible = !value; + } + public enum LobbyGuiState : byte { /// diff --git a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs index a9f3c76b48..4e92468471 100644 --- a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs +++ b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs @@ -3,11 +3,10 @@ using Content.Shared.Emag.Systems; using Content.Shared.Medical.Cryogenics; using Content.Shared.Verbs; using Robust.Client.GameObjects; -using DrawDepth = Content.Shared.DrawDepth.DrawDepth; namespace Content.Client.Medical.Cryogenics; -public sealed class CryoPodSystem: SharedCryoPodSystem +public sealed class CryoPodSystem : SharedCryoPodSystem { [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -63,11 +62,9 @@ public sealed class CryoPodSystem: SharedCryoPodSystem { args.Sprite.LayerSetState(CryoPodVisualLayers.Base, "pod-open"); args.Sprite.LayerSetVisible(CryoPodVisualLayers.Cover, false); - args.Sprite.DrawDepth = (int) DrawDepth.Objects; } else { - args.Sprite.DrawDepth = (int) DrawDepth.Mobs; args.Sprite.LayerSetState(CryoPodVisualLayers.Base, isOn ? "pod-on" : "pod-off"); args.Sprite.LayerSetState(CryoPodVisualLayers.Cover, isOn ? "cover-on" : "cover-off"); args.Sprite.LayerSetVisible(CryoPodVisualLayers.Cover, true); diff --git a/Content.Client/NPC/NPCSteeringSystem.cs b/Content.Client/NPC/NPCSteeringSystem.cs index eda3d74cf7..9ca3ec1a7e 100644 --- a/Content.Client/NPC/NPCSteeringSystem.cs +++ b/Content.Client/NPC/NPCSteeringSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Client.Physics.Controllers; +using Content.Client.PhysicsSystem.Controllers; using Content.Shared.Movement.Components; using Content.Shared.NPC; using Content.Shared.NPC.Events; diff --git a/Content.Client/Overlays/BlackAndWhiteOverlay.cs b/Content.Client/Overlays/BlackAndWhiteOverlay.cs new file mode 100644 index 0000000000..aae2b63acf --- /dev/null +++ b/Content.Client/Overlays/BlackAndWhiteOverlay.cs @@ -0,0 +1,47 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays; + +public sealed partial class BlackAndWhiteOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + private readonly ShaderInstance _greyscaleShader; + + public BlackAndWhiteOverlay() + { + IoCManager.InjectDependencies(this); + _greyscaleShader = _prototypeManager.Index("GreyscaleFullscreen").InstanceUnique(); + ZIndex = 10; // draw this over the DamageOverlay, RainbowOverlay etc. + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + return true; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _greyscaleShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + handle.UseShader(_greyscaleShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs b/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs new file mode 100644 index 0000000000..09c282d10a --- /dev/null +++ b/Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays; +using Robust.Client.Graphics; +using Robust.Client.Player; + +namespace Content.Client.Overlays; + +public sealed partial class BlackAndWhiteOverlaySystem : EquipmentHudSystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private BlackAndWhiteOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent component) + { + base.UpdateInternal(component); + + _overlayMan.AddOverlay(_overlay); + } + + protected override void DeactivateInternal() + { + base.DeactivateInternal(); + + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Client/Paper/UI/PaperVisualsComponent.cs b/Content.Client/Paper/UI/PaperVisualsComponent.cs index f0dee3cf90..735c5a7e24 100644 --- a/Content.Client/Paper/UI/PaperVisualsComponent.cs +++ b/Content.Client/Paper/UI/PaperVisualsComponent.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Robust.Shared.Utility; namespace Content.Client.Paper.UI; @@ -55,6 +56,24 @@ public sealed partial class PaperVisualsComponent : Component [DataField("headerMargin")] public Box2 HeaderMargin = default; + /// + /// A path to an image which will be used as a footer on the paper + /// + [DataField] + public ResPath? FooterImagePath; + + /// + /// Modulate the footer image by this color + /// + [DataField] + public Color FooterImageModulate = Color.White; + + /// + /// Any additional margin to add around the footer + /// + [DataField] + public Box2 FooterMargin = default; + /// /// Path to an image to use as the background to the "content" of the paper /// The header and actual written text will use this as a background. The diff --git a/Content.Client/Paper/UI/PaperWindow.xaml b/Content.Client/Paper/UI/PaperWindow.xaml index 503ae928a3..5e882b29e6 100644 --- a/Content.Client/Paper/UI/PaperWindow.xaml +++ b/Content.Client/Paper/UI/PaperWindow.xaml @@ -22,6 +22,7 @@