diff --git a/.github/workflows/publish-publish.yml b/.github/workflows/publish-publish.yml index 4ba26382c53..6729f278a18 100644 --- a/.github/workflows/publish-publish.yml +++ b/.github/workflows/publish-publish.yml @@ -1,7 +1,7 @@ name: Publish Public -#concurrency: -# group: publish +concurrency: + group: publish on: workflow_dispatch: @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@v3.6.0 with: submodules: 'recursive' + - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: @@ -41,21 +42,10 @@ jobs: - name: Package client run: dotnet run --project Content.Packaging client --no-wipe-release - - name: Upload build artifact - id: artifact-upload-step - uses: actions/upload-artifact@v4 - with: - name: build - path: release/*.zip - compression-level: 0 - retention-days: 0 - - name: Publish version - run: Tools/publish_github_artifact.py + run: Tools/publish_multi_request.py env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN_PUBLIC }} - ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }} GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} FORK_ID: ${{ vars.FORK_ID_PUBLIC }} @@ -69,8 +59,3 @@ 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/publish.yml b/.github/workflows/publish.yml index 3ec2935621b..543ef30b9b9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,7 @@ name: Publish -#concurrency: -# group: publish +concurrency: + group: publish on: workflow_dispatch: @@ -60,21 +60,10 @@ jobs: - name: Package client run: dotnet run --project Content.Packaging client --no-wipe-release - - name: Upload build artifact - id: artifact-upload-step - uses: actions/upload-artifact@v4 - with: - name: build - path: release/*.zip - compression-level: 0 - retention-days: 0 - - name: Publish version - run: Tools/publish_github_artifact.py + run: Tools/publish_multi_request.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 }} FORK_ID: ${{ vars.FORK_ID }} @@ -88,8 +77,3 @@ 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/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index f805f6643d2..2425cdefb91 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -101,7 +101,7 @@ protected override void FrameUpdate(FrameEventArgs args) { var duration = Cooldown.Value.End - Cooldown.Value.Start; - if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup)) + if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup)) return; _cooldownLabel.SetMessage(markup); diff --git a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs index 7cb32b43df5..615f1434df2 100644 --- a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs +++ b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs @@ -1,14 +1,13 @@ +using System.Linq; using System.Numerics; using Content.Client.UserInterface.Controls; +using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Robust.Client.AutoGenerated; using Robust.Client.Console; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Prototypes; namespace Content.Client.Administration.UI.SetOutfit @@ -65,9 +64,18 @@ private void SearchBarOnOnTextChanged(LineEdit.LineEditEventArgs obj) PopulateByFilter(SearchBar.Text); } + private IEnumerable GetPrototypes() + { + // Filter out any StartingGearPrototypes that belong to loadouts + var loadouts = _prototypeManager.EnumeratePrototypes(); + var loadoutGears = loadouts.Select(l => l.StartingGear); + return _prototypeManager.EnumeratePrototypes() + .Where(p => !loadoutGears.Contains(p.ID)); + } + private void PopulateList() { - foreach (var gear in _prototypeManager.EnumeratePrototypes()) + foreach (var gear in GetPrototypes()) { OutfitList.Add(GetItem(gear, OutfitList)); } @@ -76,7 +84,7 @@ private void PopulateList() private void PopulateByFilter(string filter) { OutfitList.Clear(); - foreach (var gear in _prototypeManager.EnumeratePrototypes()) + foreach (var gear in GetPrototypes()) { if (!string.IsNullOrEmpty(filter) && gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant())) diff --git a/Content.Client/Anomaly/AnomalySystem.cs b/Content.Client/Anomaly/AnomalySystem.cs index c93f0ce9490..28c015f3021 100644 --- a/Content.Client/Anomaly/AnomalySystem.cs +++ b/Content.Client/Anomaly/AnomalySystem.cs @@ -20,8 +20,9 @@ public override void Initialize() SubscribeLocalEvent(OnAppearanceChanged); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnAnimationComplete); - } + SubscribeLocalEvent(OnShutdown); + } private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args) { _floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime); @@ -75,4 +76,13 @@ public override void Update(float frameTime) } } } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + sprite.Scale = Vector2.One; + sprite.Color = sprite.Color.WithAlpha(1f); + } } diff --git a/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs new file mode 100644 index 00000000000..efb1a8d46e8 --- /dev/null +++ b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Anomaly.Components; +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Anomaly.Effects; + +public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(OnAfterHandleState); + SubscribeLocalEvent(OnCompShutdown); + } + + private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(ent, out var sprite)) + return; + + if (ent.Comp.FallbackSprite is null) + return; + + if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index)) + index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap); + + if (TryComp(ent, out var body) && + body.Prototype is not null && + ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite)) + { + sprite.LayerSetSprite(index, speciesSprite); + } + else + { + sprite.LayerSetSprite(index, ent.Comp.FallbackSprite); + } + + sprite.LayerSetVisible(index, true); + sprite.LayerSetShader(index, "unshaded"); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + var index = sprite.LayerMapGet(ent.Comp.LayerMap); + sprite.LayerSetVisible(index, false); + } +} diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index ca6336b91b8..b525747aa9c 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -306,6 +306,9 @@ private void ProcessNearbyAmbience(TransformComponent playerXform) .WithMaxDistance(comp.Range); var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams); + if (stream == null) + continue; + _playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key); playingCount++; diff --git a/Content.Client/Audio/ClientGlobalSoundSystem.cs b/Content.Client/Audio/ClientGlobalSoundSystem.cs index 7c77865f741..50c3971d95a 100644 --- a/Content.Client/Audio/ClientGlobalSoundSystem.cs +++ b/Content.Client/Audio/ClientGlobalSoundSystem.cs @@ -67,7 +67,7 @@ private void PlayAdminSound(AdminSoundEvent soundEvent) if(!_adminAudioEnabled) return; var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); - _adminAudio.Add(stream.Value.Entity); + _adminAudio.Add(stream?.Entity); } private void PlayStationEventMusic(StationEventMusicEvent soundEvent) @@ -76,7 +76,7 @@ private void PlayStationEventMusic(StationEventMusicEvent soundEvent) if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return; var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); - _eventAudio.Add(soundEvent.Type, stream.Value.Entity); + _eventAudio.Add(soundEvent.Type, stream?.Entity); } private void PlayGameSound(GameGlobalSoundEvent soundEvent) diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs index d60c978ccf5..bf7ab26cba2 100644 --- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs @@ -213,9 +213,9 @@ private void UpdateAmbientMusic() false, AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider)); - _ambientMusicStream = strim.Value.Entity; + _ambientMusicStream = strim?.Entity; - if (_musicProto.FadeIn) + if (_musicProto.FadeIn && strim != null) { FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime); } diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs index 92c5b7a4191..9864dbcb2a9 100644 --- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs @@ -185,7 +185,7 @@ private void PlaySoundtrack(string soundtrackFilename) false, _lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) ); - if (playResult.Value.Entity == default) + if (playResult == null) { _sawmill.Warning( $"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!", diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index a941f0acff9..1c1f1984de4 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -1,7 +1,12 @@ +using System.Linq; +using System.Numerics; +using System.Threading; using Content.Client.Verbs; using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Input; +using Content.Shared.Interaction.Events; +using Content.Shared.Item; using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Client.GameObjects; @@ -12,13 +17,8 @@ using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Utility; -using System.Linq; -using System.Numerics; -using System.Threading; using static Content.Shared.Interaction.SharedInteractionSystem; using static Robust.Client.UserInterface.Controls.BoxContainer; -using Content.Shared.Interaction.Events; -using Content.Shared.Item; using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.Examine @@ -35,7 +35,6 @@ public sealed class ExamineSystem : ExamineSystemShared private EntityUid _examinedEntity; private EntityUid _lastExaminedEntity; - private EntityUid _playerEntity; private Popup? _examineTooltipOpen; private ScreenCoordinates _popupPos; private CancellationTokenSource? _requestCancelTokenSource; @@ -74,9 +73,9 @@ private void OnExaminedItemDropped(EntityUid item, ItemComponent comp, DroppedEv public override void Update(float frameTime) { if (_examineTooltipOpen is not {Visible: true}) return; - if (!_examinedEntity.Valid || !_playerEntity.Valid) return; + if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return; - if (!CanExamine(_playerEntity, _examinedEntity)) + if (!CanExamine(player, _examinedEntity)) CloseTooltip(); } @@ -114,9 +113,8 @@ private bool HandleExamine(in PointerInputCmdHandler.PointerInputCmdArgs args) return false; } - _playerEntity = _playerManager.LocalEntity ?? default; - - if (_playerEntity == default || !CanExamine(_playerEntity, entity)) + if (_playerManager.LocalEntity is not { } player || + !CanExamine(player, entity)) { return false; } diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs index a2ed2d50e0d..692782ded4b 100644 --- a/Content.Client/Explosion/ExplosionSystem.cs +++ b/Content.Client/Explosion/ExplosionSystem.cs @@ -2,7 +2,4 @@ namespace Content.Client.Explosion.EntitySystems; -public sealed class ExplosionSystem : SharedExplosionSystem -{ - -} +public sealed class ExplosionSystem : SharedExplosionSystem; diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index 046be2aa621..8e41c382dd7 100644 --- a/Content.Client/Flash/FlashOverlay.cs +++ b/Content.Client/Flash/FlashOverlay.cs @@ -16,6 +16,7 @@ public sealed class FlashOverlay : Overlay [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _timing = default!; + private readonly SharedFlashSystem _flash; private readonly StatusEffectsSystem _statusSys; public override OverlaySpace Space => OverlaySpace.WorldSpace; @@ -27,6 +28,7 @@ public FlashOverlay() { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique(); + _flash = _entityManager.System(); _statusSys = _entityManager.System(); } @@ -41,7 +43,7 @@ protected override void FrameUpdate(FrameEventArgs args) || !_entityManager.TryGetComponent(playerEntity, out var status)) return; - if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status)) + if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status)) return; var curTime = _timing.CurTime; diff --git a/Content.Client/Guidebook/GuidebookDataSystem.cs b/Content.Client/Guidebook/GuidebookDataSystem.cs new file mode 100644 index 00000000000..f47ad6ef1bb --- /dev/null +++ b/Content.Client/Guidebook/GuidebookDataSystem.cs @@ -0,0 +1,45 @@ +using Content.Shared.Guidebook; + +namespace Content.Client.Guidebook; + +/// +/// Client system for storing and retrieving values extracted from entity prototypes +/// for display in the guidebook (). +/// Requests data from the server on . +/// Can also be pushed new data when the server reloads prototypes. +/// +public sealed class GuidebookDataSystem : EntitySystem +{ + private GuidebookData? _data; + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnServerUpdated); + + // Request data from the server + RaiseNetworkEvent(new RequestGuidebookDataEvent()); + } + + private void OnServerUpdated(UpdateGuidebookDataEvent args) + { + // Got new data from the server, either in response to our request, or because prototypes reloaded on the server + _data = args.Data; + _data.Freeze(); + } + + /// + /// Attempts to retrieve a value using the given identifiers. + /// See for more information. + /// + public bool TryGetValue(string prototype, string component, string field, out object? value) + { + if (_data == null) + { + value = null; + return false; + } + return _data.TryGetValue(prototype, component, field, out value); + } +} diff --git a/Content.Client/Guidebook/Richtext/ProtodataTag.cs b/Content.Client/Guidebook/Richtext/ProtodataTag.cs new file mode 100644 index 00000000000..a725fd4e4b5 --- /dev/null +++ b/Content.Client/Guidebook/Richtext/ProtodataTag.cs @@ -0,0 +1,49 @@ +using System.Globalization; +using Robust.Client.UserInterface.RichText; +using Robust.Shared.Utility; + +namespace Content.Client.Guidebook.RichText; + +/// +/// RichText tag that can display values extracted from entity prototypes. +/// In order to be accessed by this tag, the desired field/property must +/// be tagged with . +/// +public sealed class ProtodataTag : IMarkupTag +{ + [Dependency] private readonly ILogManager _logMan = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + + public string Name => "protodata"; + private ISawmill Log => _log ??= _logMan.GetSawmill("protodata_tag"); + private ISawmill? _log; + + public string TextBefore(MarkupNode node) + { + // Do nothing with an empty tag + if (!node.Value.TryGetString(out var prototype)) + return string.Empty; + + if (!node.Attributes.TryGetValue("comp", out var component)) + return string.Empty; + if (!node.Attributes.TryGetValue("member", out var member)) + return string.Empty; + node.Attributes.TryGetValue("format", out var format); + + var guidebookData = _entMan.System(); + + // Try to get the value + if (!guidebookData.TryGetValue(prototype, component.StringValue!, member.StringValue!, out var value)) + { + Log.Error($"Failed to find protodata for {component}.{member} in {prototype}"); + return "???"; + } + + // If we have a format string and a formattable value, format it as requested + if (!string.IsNullOrEmpty(format.StringValue) && value is IFormattable formattable) + return formattable.ToString(format.StringValue, CultureInfo.CurrentCulture); + + // No format string given, so just use default ToString + return value?.ToString() ?? "NULL"; + } +} diff --git a/Content.Client/Mapping/MappingScreen.xaml b/Content.Client/Mapping/MappingScreen.xaml index b6413608478..9cc3e734f0e 100644 --- a/Content.Client/Mapping/MappingScreen.xaml +++ b/Content.Client/Mapping/MappingScreen.xaml @@ -78,6 +78,7 @@ ToolTip="Pick (Hold 5)" /> + diff --git a/Content.Client/Mapping/MappingScreen.xaml.cs b/Content.Client/Mapping/MappingScreen.xaml.cs index b2ad2fd83fb..46c0e51fad6 100644 --- a/Content.Client/Mapping/MappingScreen.xaml.cs +++ b/Content.Client/Mapping/MappingScreen.xaml.cs @@ -96,6 +96,22 @@ public MappingScreen() Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png"; Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png"; + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; + Flip.OnPressed += args => FlipSides(); + } + + public void FlipSides() + { + ScreenContainer.Flip(); + + if (SpawnContainer.GetPositionInParent() == 0) + { + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; + } + else + { + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; + } } private void OnDecalColorPicked(Color color) diff --git a/Content.Client/PDA/PdaBoundUserInterface.cs b/Content.Client/PDA/PdaBoundUserInterface.cs index 37ce9c4280f..2d4033390c3 100644 --- a/Content.Client/PDA/PdaBoundUserInterface.cs +++ b/Content.Client/PDA/PdaBoundUserInterface.cs @@ -4,18 +4,20 @@ using Content.Shared.PDA; using JetBrains.Annotations; using Robust.Client.UserInterface; -using Robust.Shared.Configuration; namespace Content.Client.PDA { [UsedImplicitly] public sealed class PdaBoundUserInterface : CartridgeLoaderBoundUserInterface { + private readonly PdaSystem _pdaSystem; + [ViewVariables] private PdaMenu? _menu; public PdaBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { + _pdaSystem = EntMan.System(); } protected override void Open() @@ -92,7 +94,13 @@ protected override void UpdateState(BoundUserInterfaceState state) if (state is not PdaUpdateState updateState) return; - _menu?.UpdateState(updateState); + if (_menu == null) + { + _pdaSystem.Log.Error("PDA state received before menu was created."); + return; + } + + _menu.UpdateState(updateState); } protected override void AttachCartridgeUI(Control cartridgeUIFragment, string? title) diff --git a/Content.Client/PDA/PdaMenu.xaml b/Content.Client/PDA/PdaMenu.xaml index 8b26860332d..8c9b4ae2ee6 100644 --- a/Content.Client/PDA/PdaMenu.xaml +++ b/Content.Client/PDA/PdaMenu.xaml @@ -67,14 +67,17 @@ Description="{Loc 'comp-pda-ui-ringtone-button-description'}"/> diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 03df383eebc..c97110b208e 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -8,132 +8,131 @@ using Robust.Shared.Player; using Robust.Shared.Timing; -namespace Content.Client.Physics.Controllers +namespace Content.Client.Physics.Controllers; + +public sealed class MoverController : SharedMoverController { - public sealed class MoverController : SharedMoverController - { - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnRelayPlayerAttached); - SubscribeLocalEvent(OnRelayPlayerDetached); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); - - SubscribeLocalEvent(OnUpdatePredicted); - SubscribeLocalEvent(OnUpdateRelayTargetPredicted); - SubscribeLocalEvent(OnUpdatePullablePredicted); - } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRelayPlayerAttached); + SubscribeLocalEvent(OnRelayPlayerDetached); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnUpdatePredicted); + SubscribeLocalEvent(OnUpdateRelayTargetPredicted); + SubscribeLocalEvent(OnUpdatePullablePredicted); + } - private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - // Enable prediction if an entity is controlled by the player - if (entity.Owner == _playerManager.LocalEntity) - args.IsPredicted = true; - } + private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + // Enable prediction if an entity is controlled by the player + if (entity.Owner == _playerManager.LocalEntity) + args.IsPredicted = true; + } - private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - if (entity.Comp.Source == _playerManager.LocalEntity) - args.IsPredicted = true; - } + private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + if (entity.Comp.Source == _playerManager.LocalEntity) + args.IsPredicted = true; + } - private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - // Enable prediction if an entity is being pulled by the player. - // Disable prediction if an entity is being pulled by some non-player entity. + private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + // Enable prediction if an entity is being pulled by the player. + // Disable prediction if an entity is being pulled by some non-player entity. - if (entity.Comp.Puller == _playerManager.LocalEntity) - args.IsPredicted = true; - else if (entity.Comp.Puller != null) - args.BlockPrediction = true; + if (entity.Comp.Puller == _playerManager.LocalEntity) + args.IsPredicted = true; + else if (entity.Comp.Puller != null) + args.BlockPrediction = true; - // TODO recursive pulling checks? - // What if the entity is being pulled by a vehicle controlled by the player? - } + // TODO recursive pulling checks? + // What if the entity is being pulled by a vehicle controlled by the player? + } - private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) - { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) + { + Physics.UpdateIsPredicted(entity.Owner); + Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) - { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) + { + Physics.UpdateIsPredicted(entity.Owner); + Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - public override void UpdateBeforeSolve(bool prediction, float frameTime) - { - base.UpdateBeforeSolve(prediction, frameTime); + public override void UpdateBeforeSolve(bool prediction, float frameTime) + { + base.UpdateBeforeSolve(prediction, frameTime); - if (_playerManager.LocalEntity is not {Valid: true} player) - return; + if (_playerManager.LocalEntity is not {Valid: true} player) + return; - if (RelayQuery.TryGetComponent(player, out var relayMover)) - HandleClientsideMovement(relayMover.RelayEntity, frameTime); + if (RelayQuery.TryGetComponent(player, out var relayMover)) + HandleClientsideMovement(relayMover.RelayEntity, frameTime); - HandleClientsideMovement(player, frameTime); - } + HandleClientsideMovement(player, frameTime); + } - private void HandleClientsideMovement(EntityUid player, float frameTime) + private void HandleClientsideMovement(EntityUid player, float frameTime) + { + if (!MoverQuery.TryGetComponent(player, out var mover) || + !XformQuery.TryGetComponent(player, out var xform)) { - if (!MoverQuery.TryGetComponent(player, out var mover) || - !XformQuery.TryGetComponent(player, out var xform)) - { - return; - } - - var physicsUid = player; - PhysicsComponent? body; - var xformMover = xform; + return; + } - if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) - { - if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || - !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) - { - return; - } + var physicsUid = player; + PhysicsComponent? body; + var xformMover = xform; - physicsUid = xform.ParentUid; - } - else if (!PhysicsQuery.TryGetComponent(player, out body)) + if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) + { + if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || + !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) { return; } - // Server-side should just be handled on its own so we'll just do this shizznit - HandleMobMovement( - player, - mover, - physicsUid, - body, - xformMover, - frameTime); + physicsUid = xform.ParentUid; } - - protected override bool CanSound() + else if (!PhysicsQuery.TryGetComponent(player, out body)) { - return _timing is { IsFirstTimePredicted: true, InSimulation: true }; + return; } + + // Server-side should just be handled on its own so we'll just do this shizznit + HandleMobMovement( + player, + mover, + physicsUid, + body, + xformMover, + frameTime); + } + + protected override bool CanSound() + { + return _timing is { IsFirstTimePredicted: true, InSimulation: true }; } } diff --git a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs index 5a082485a5a..a6a20958f53 100644 --- a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs +++ b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs @@ -18,9 +18,6 @@ protected override void OnActivate(Entity e return; } - if (TryComp(ent.Owner, out var panel) && panel.Open) - return; - _popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User); args.Cancel(); } diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs index f90731bfa75..b96eae44e9d 100644 --- a/Content.Client/Replay/ContentReplayPlaybackManager.cs +++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs @@ -1,10 +1,8 @@ -using System.IO.Compression; using Content.Client.Administration.Managers; using Content.Client.Launcher; using Content.Client.MainMenu; using Content.Client.Replay.Spectator; using Content.Client.Replay.UI.Loading; -using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Chat; using Content.Shared.Chat; using Content.Shared.Effects; @@ -26,8 +24,6 @@ using Robust.Client.State; using Robust.Client.Timing; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; @@ -60,7 +56,7 @@ public sealed class ContentReplayPlaybackManager public bool IsScreenshotMode = false; private bool _initialized; - + /// /// Most recently loaded file, for re-attempting the load with error tolerance. /// Required because the zip reader auto-disposes and I'm too lazy to change it so that @@ -96,32 +92,17 @@ private void OnFinishedLoading(Exception? exception) return; } - ReturnToDefaultState(); - - // Show a popup window with the error message - var text = Loc.GetString("replay-loading-failed", ("reason", exception)); - var box = new BoxContainer - { - Orientation = BoxContainer.LayoutOrientation.Vertical, - Children = {new Label {Text = text}} - }; + if (_client.RunLevel == ClientRunLevel.SinglePlayerGame) + _client.StopSinglePlayer(); - var popup = new DefaultWindow { Title = "Error!" }; - popup.Contents.AddChild(box); + Action? retryAction = null; + Action? cancelAction = null; - // Add button for attempting to re-load the replay while ignoring some errors. - if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is {} last) + if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is { } last) { - var button = new Button - { - Text = Loc.GetString("replay-loading-retry"), - StyleClasses = { StyleBase.ButtonCaution } - }; - - button.OnPressed += _ => + retryAction = () => { _cfg.SetCVar(CVars.ReplayIgnoreErrors, true); - popup.Dispose(); IReplayFileReader reader = last.Zip == null ? new ReplayFileReaderResources(_resMan, last.Folder) @@ -129,11 +110,20 @@ private void OnFinishedLoading(Exception? exception) _loadMan.LoadAndStartReplay(reader); }; - - box.AddChild(button); } - popup.OpenCentered(); + // If we have an explicit menu to get back to (e.g. replay browser UI), show a cancel button. + if (DefaultState != null) + { + cancelAction = () => + { + _stateMan.RequestStateChange(DefaultState); + }; + } + + // Switch to a new game state to present the error and cancel/retry options. + var state = _stateMan.RequestStateChange(); + state.SetData(exception, cancelAction, retryAction); } public void ReturnToDefaultState() diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs new file mode 100644 index 00000000000..223895eb29c --- /dev/null +++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs @@ -0,0 +1,36 @@ +using Content.Client.Stylesheets; +using Robust.Client.State; +using Robust.Client.UserInterface; +using Robust.Shared.Utility; + +namespace Content.Client.Replay.UI.Loading; + +/// +/// State used to display an error message if a replay failed to load. +/// +/// +/// +public sealed class ReplayLoadingFailed : State +{ + [Dependency] private readonly IStylesheetManager _stylesheetManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterface = default!; + + private ReplayLoadingFailedControl? _control; + + public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed) + { + DebugTools.Assert(_control != null); + _control.SetData(exception, cancelPressed, retryPressed); + } + + protected override void Startup() + { + _control = new ReplayLoadingFailedControl(_stylesheetManager); + _userInterface.StateRoot.AddChild(_control); + } + + protected override void Shutdown() + { + _control?.Orphan(); + } +} diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml new file mode 100644 index 00000000000..5f77a66e535 --- /dev/null +++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml @@ -0,0 +1,14 @@ + + + + + + + + +