diff --git a/.editorconfig b/.editorconfig index 3e44d1a2811..370c3f9dee6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -72,7 +72,7 @@ csharp_style_expression_bodied_constructors = false:suggestion #csharp_style_expression_bodied_indexers = true:silent #csharp_style_expression_bodied_lambdas = true:silent #csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_methods = true:suggestion #csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:suggestion diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index 96bbcc54f2a..878d71cac18 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -321,7 +321,8 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId)) continue; - _displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers); + if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers)) + index++; } } diff --git a/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs b/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs new file mode 100644 index 00000000000..41319ea29fb --- /dev/null +++ b/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs @@ -0,0 +1,89 @@ +using System.Numerics; +using Content.Shared.Floofstation.Leash.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; + +namespace Content.Client.Floofstation.Leash; + +public sealed class LeashVisualsOverlay : Overlay +{ + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + + private readonly IEntityManager _entMan; + private readonly SpriteSystem _sprites; + private readonly SharedTransformSystem _xform; + private readonly EntityQuery _xformQuery; + private readonly EntityQuery _spriteQuery; + + public LeashVisualsOverlay(IEntityManager entMan) + { + _entMan = entMan; + _sprites = _entMan.System(); + _xform = _entMan.System(); + _xformQuery = _entMan.GetEntityQuery(); + _spriteQuery = _entMan.GetEntityQuery(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var worldHandle = args.WorldHandle; + worldHandle.SetTransform(Vector2.Zero, Angle.Zero); + + var query = _entMan.EntityQueryEnumerator(); + while (query.MoveNext(out var visualsComp)) + { + if (visualsComp.Source is not {Valid: true} source + || visualsComp.Target is not {Valid: true} target + || !_xformQuery.TryGetComponent(source, out var xformComp) + || !_xformQuery.TryGetComponent(target, out var otherXformComp) + || xformComp.MapID != args.MapId + || otherXformComp.MapID != xformComp.MapID) + continue; + + var texture = _sprites.Frame0(visualsComp.Sprite); + var width = texture.Width / (float) EyeManager.PixelsPerMeter; + + var coordsA = xformComp.Coordinates; + var coordsB = otherXformComp.Coordinates; + + // If both coordinates are in the same spot (e.g. the leash is being held by the leashed), don't render anything + if (coordsA.TryDistance(_entMan, _xform, coordsB, out var dist) && dist < 0.01f) + continue; + + var rotA = xformComp.LocalRotation; + var rotB = otherXformComp.LocalRotation; + var offsetA = visualsComp.OffsetSource; + var offsetB = visualsComp.OffsetTarget; + + // Sprite rotation is the rotation along the Z axis + // Which is different from transform rotation for all mobs that are seen from the side (instead of top-down) + if (_spriteQuery.TryGetComponent(source, out var spriteA)) + { + offsetA *= spriteA.Scale; + rotA = spriteA.Rotation; + } + if (_spriteQuery.TryGetComponent(target, out var spriteB)) + { + offsetB *= spriteB.Scale; + rotB = spriteB.Rotation; + } + + coordsA = coordsA.Offset(rotA.RotateVec(offsetA)); + coordsB = coordsB.Offset(rotB.RotateVec(offsetB)); + + var posA = _xform.ToMapCoordinates(coordsA).Position; + var posB = _xform.ToMapCoordinates(coordsB).Position; + var diff = (posB - posA); + var length = diff.Length(); + + // So basically, we find the midpoint, then create a box that describes the sprite boundaries, then rotate it + var midPoint = diff / 2f + posA; + var angle = (posB - posA).ToWorldAngle(); + var box = new Box2(-width / 2f, -length / 2f, width / 2f, length / 2f); + var rotate = new Box2Rotated(box.Translated(midPoint), angle, midPoint); + + worldHandle.DrawTextureRect(texture, rotate); + } + } +} diff --git a/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs b/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs new file mode 100644 index 00000000000..05d0f6085e2 --- /dev/null +++ b/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs @@ -0,0 +1,21 @@ +using Content.Client.Physics; +using Robust.Client.Graphics; + +namespace Content.Client.Floofstation.Leash; + +public sealed class LeashVisualsSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + _overlay.AddOverlay(new LeashVisualsOverlay(EntityManager)); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlay.RemoveOverlay(); + } +} diff --git a/Content.Client/FootPrint/FootPrintsVisualizerSystem.cs b/Content.Client/FootPrint/FootPrintsVisualizerSystem.cs new file mode 100644 index 00000000000..3998d6b3c17 --- /dev/null +++ b/Content.Client/FootPrint/FootPrintsVisualizerSystem.cs @@ -0,0 +1,65 @@ +using Content.Shared.FootPrint; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Random; + +namespace Content.Client.FootPrint; + +public sealed class FootPrintsVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInitialized); + SubscribeLocalEvent(OnShutdown); + } + + private void OnInitialized(EntityUid uid, FootPrintComponent comp, ComponentInit args) + { + if (!TryComp(uid, out var sprite)) + return; + + sprite.LayerMapReserveBlank(FootPrintVisualLayers.Print); + UpdateAppearance(uid, comp, sprite); + } + + private void OnShutdown(EntityUid uid, FootPrintComponent comp, ComponentShutdown args) + { + if (TryComp(uid, out var sprite) + && sprite.LayerMapTryGet(FootPrintVisualLayers.Print, out var layer)) + sprite.RemoveLayer(layer); + } + + private void UpdateAppearance(EntityUid uid, FootPrintComponent component, SpriteComponent sprite) + { + if (!sprite.LayerMapTryGet(FootPrintVisualLayers.Print, out var layer) + || !TryComp(component.PrintOwner, out var printsComponent) + || !TryComp(uid, out var appearance) + || !_appearance.TryGetData(uid, FootPrintVisualState.State, out var printVisuals, appearance)) + return; + + sprite.LayerSetState(layer, new RSI.StateId(printVisuals switch + { + FootPrintVisuals.BareFootPrint => printsComponent.RightStep ? printsComponent.RightBarePrint : printsComponent.LeftBarePrint, + FootPrintVisuals.ShoesPrint => printsComponent.ShoesPrint, + FootPrintVisuals.SuitPrint => printsComponent.SuitPrint, + FootPrintVisuals.Dragging => _random.Pick(printsComponent.DraggingPrint), + _ => throw new ArgumentOutOfRangeException($"Unknown {printVisuals} parameter.") + }), printsComponent.RsiPath); + + if (_appearance.TryGetData(uid, FootPrintVisualState.Color, out var printColor, appearance)) + sprite.LayerSetColor(layer, printColor); + } + + protected override void OnAppearanceChange (EntityUid uid, FootPrintComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite is not { } sprite) + return; + + UpdateAppearance(uid, component, sprite); + } +} diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml index a096caa4cfd..45999e23779 100644 --- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml +++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml @@ -21,6 +21,7 @@ Orientation="Vertical"> +