diff --git a/Content.Client/Body/Systems/BodySystem.cs b/Content.Client/Body/Systems/BodySystem.cs index a36384e0f91..f0ec65faafc 100644 --- a/Content.Client/Body/Systems/BodySystem.cs +++ b/Content.Client/Body/Systems/BodySystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Body.Systems; +using Content.Shared.Body.Systems; // Shitmed Change Start using Content.Shared._Shitmed.Body.Part; using Content.Shared.Humanoid; diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index d4177f76453..7899adf43f2 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -81,7 +81,7 @@ private void UpdateLayers(Entity e foreach (var (key, info) in component.CustomBaseLayers) { oldLayers.Remove(key); - SetLayerData(entity, key, info.Id, sexMorph: false, color: info.Color); + SetLayerData(entity, key, info.Id, sexMorph: false, color: info.Color, overrideSkin: true); } // hide old layers @@ -98,7 +98,8 @@ private void SetLayerData( HumanoidVisualLayers key, string? protoId, bool sexMorph = false, - Color? color = null) + Color? color = null, + bool overrideSkin = false) { var component = entity.Comp1; var sprite = entity.Comp2; @@ -119,7 +120,7 @@ private void SetLayerData( var proto = _prototypeManager.Index(protoId); component.BaseLayers[key] = proto; - if (proto.MatchSkin) + if (proto.MatchSkin && !overrideSkin) layer.Color = component.SkinColor.WithAlpha(proto.LayerAlpha); if (proto.BaseSprite != null) diff --git a/Content.Client/Weapons/Ranged/Components/MagazineVisualsComponent.cs b/Content.Client/Weapons/Ranged/Components/MagazineVisualsComponent.cs index 373be94a384..7a21ab37bb5 100644 --- a/Content.Client/Weapons/Ranged/Components/MagazineVisualsComponent.cs +++ b/Content.Client/Weapons/Ranged/Components/MagazineVisualsComponent.cs @@ -22,6 +22,13 @@ public sealed partial class MagazineVisualsComponent : Component /// Should we hide when the count is 0 /// [DataField("zeroVisible")] public bool ZeroVisible; + + /// + /// Goobstation / Mono. + /// Whether should only set zero step when there is no ammo left. + /// + [DataField] + public bool ZeroNoAmmo; } public enum GunVisualLayers : byte diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.MagazineVisuals.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.MagazineVisuals.cs index fc3cf4de4a2..306ccc2d90c 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.MagazineVisuals.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.MagazineVisuals.cs @@ -82,6 +82,9 @@ private void OnMagazineVisualsChange(EntityUid uid, MagazineVisualsComponent com var step = ContentHelpers.RoundToLevels((int)current, (int)capacity, component.MagSteps); + if (component.ZeroNoAmmo && step == 0 && (int)current > 0) // Goobstation / Mono + step = Math.Min(1, component.MagSteps - 1); + if (step == 0 && !component.ZeroVisible) { if (_sprite.LayerMapTryGet((uid, sprite), GunVisualLayers.Mag, out _, false)) diff --git a/Content.Client/_Goobstation/Wizard/Trail/TrailOverlay.cs b/Content.Client/_Goobstation/Wizard/Trail/TrailOverlay.cs new file mode 100644 index 00000000000..afbd91c6b3e --- /dev/null +++ b/Content.Client/_Goobstation/Wizard/Trail/TrailOverlay.cs @@ -0,0 +1,187 @@ +using System.Numerics; +using Content.Shared._Goobstation.Wizard.Projectiles; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using DrawDepth = Content.Shared.DrawDepth.DrawDepth; + +namespace Content.Client._Goobstation.Wizard.Trail; + +public sealed class TrailOverlay : Overlay +{ + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; + + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _protoMan; + private readonly IGameTiming _timing; + + private readonly SpriteSystem _sprite; + private readonly TransformSystem _transform; + + public TrailOverlay(IEntityManager entManager, IPrototypeManager protoMan, IGameTiming timing) + { + ZIndex = (int) DrawDepth.Effects; + + _entManager = entManager; + _protoMan = protoMan; + _timing = timing; + _sprite = _entManager.System(); + _transform = _entManager.System(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var eye = args.Viewport.Eye; + + if (eye == null) + return; + + var eyeRot = eye.Rotation; + var handle = args.WorldHandle; + var bounds = args.WorldAABB; + + var xformQuery = _entManager.GetEntityQuery(); + var spriteQuery = _entManager.GetEntityQuery(); + + var query = _entManager.EntityQueryEnumerator(); + while (query.MoveNext(out _, out var trail, out var xform)) + { + if (trail.TrailData.Count == 0) + continue; + + var (position, rotation) = _transform.GetWorldPositionRotation(xform, xformQuery); + + if (trail.Shader != null && _protoMan.TryIndex(trail.Shader, out var shaderProto)) + { + var shader = shaderProto.InstanceUnique(); + foreach (var (key, data) in trail.ShaderData) + { + switch (data) + { + case GetShaderLocalPositionData: + shader.SetParameter(key, args.Viewport.WorldToLocal(position)); + break; + case GetShaderFloatParam f: + if (float.TryParse(f.Param, out var fValue)) + shader.SetParameter(key, fValue); + break; + } + } + handle.UseShader(shader); + } + else + handle.UseShader(null); + + if (trail.RenderedEntity != null) + { + Direction? direction = null; + var rot = rotation; + if (trail.RenderedEntityRotationStrategy == RenderedEntityRotationStrategy.Trail) + { + var dirRot = rotation + eyeRot; + direction = dirRot.GetCardinalDir(); + } + else if (trail.RenderedEntityRotationStrategy == RenderedEntityRotationStrategy.RenderedEntity) + rot = _transform.GetWorldRotation(trail.RenderedEntity.Value); + + if (spriteQuery.TryComp(trail.RenderedEntity.Value, out var sprite)) + { + handle.SetTransform(Matrix3x2.Identity); + foreach (var data in trail.TrailData) + { + if (data.Color.A <= 0.01f || data.Scale <= 0.01f || data.MapId != args.MapId) + continue; + + var worldPosition = data.Position; + if (!bounds.Contains(worldPosition)) + continue; + + if (trail.RenderedEntityRotationStrategy == RenderedEntityRotationStrategy.Particle) + { + rot = data.Angle; + direction = (rot + eyeRot).GetCardinalDir(); + } + + var originalColor = sprite.Color; + var originalScale = sprite.Scale; + sprite.Color = data.Color; + sprite.Scale *= data.Scale; + sprite.Render(handle, eyeRot, rot, direction, worldPosition); + sprite.Color = originalColor; + sprite.Scale = originalScale; + } + } + continue; + } + + if (trail.Sprite == null) + { + handle.SetTransform(Matrix3x2.Identity); + if (xform.MapID == args.MapId) + { + var start = trail.TrailData[^1].Position; + DrawTrailLine(start, position, trail.Color, trail.Scale, bounds, handle); + } + + for (var i = 1; i < trail.TrailData.Count; i++) + { + var data = trail.TrailData[i]; + var prevData = trail.TrailData[i - 1]; + + if (data.MapId == args.MapId && prevData.MapId == args.MapId) + DrawTrailLine(prevData.Position, data.Position, data.Color, data.Scale, bounds, handle); + } + + continue; + } + + var textureSize = _sprite.Frame0(trail.Sprite).Size; + var pos = -(Vector2) textureSize / 2f / EyeManager.PixelsPerMeter; + foreach (var data in trail.TrailData) + { + if (data.Color.A <= 0.01f || data.Scale <= 0.01f || data.MapId != args.MapId) + continue; + + var worldPosition = data.Position; + if (!bounds.Contains(worldPosition)) + continue; + + var scaleMatrix = Matrix3x2.CreateScale(new Vector2(data.Scale, data.Scale)); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); + + var time = _timing.CurTime > data.SpawnTime ? _timing.CurTime - data.SpawnTime : TimeSpan.Zero; + var texture = _sprite.GetFrame(trail.Sprite, time); + + handle.SetTransform(Matrix3x2.Multiply(scaleMatrix, worldMatrix)); + handle.DrawTexture(texture, pos, data.Angle, data.Color); + } + } + + handle.UseShader(null); + handle.SetTransform(Matrix3x2.Identity); + } + + private void DrawTrailLine(Vector2 start, + Vector2 end, + Color color, + float scale, + Box2 bounds, + DrawingHandleWorld handle) + { + if (color.A <= 0.01f || scale <= 0.01f) + return; + + if (!bounds.Contains(start) || !bounds.Contains(end)) + return; + + var halfScale = scale * 0.5f; + var direction = end - start; + var angle = direction.ToAngle(); + var box = new Box2(start - new Vector2(0f, halfScale), + start + new Vector2(direction.Length(), halfScale)); + var boxRotated = new Box2Rotated(box, angle, start); + handle.DrawRect(boxRotated, color); + } +} diff --git a/Content.Client/_Goobstation/Wizard/Trail/TrailSystem.cs b/Content.Client/_Goobstation/Wizard/Trail/TrailSystem.cs new file mode 100644 index 00000000000..33f04e48822 --- /dev/null +++ b/Content.Client/_Goobstation/Wizard/Trail/TrailSystem.cs @@ -0,0 +1,307 @@ +using System.Linq; +using System.Numerics; +using Content.Shared._Goobstation.Wizard.Projectiles; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Animations; +using Robust.Shared.Map; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Spawners; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client._Goobstation.Wizard.Trail; + +public sealed class TrailSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IEyeManager _eye = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + private EntityQuery _xformQuery; + private EntityQuery _physicsQuery; + + public override void Initialize() + { + base.Initialize(); + _overlay.AddOverlay(new TrailOverlay(EntityManager, _protoMan, _timing)); + + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnStartup); + + _xformQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + + UpdatesOutsidePrediction = true; + } + + private void OnStartup(Entity ent, ref ComponentStartup args) + { + ent.Comp.Accumulator = ent.Comp.Frequency; + ent.Comp.LerpAccumulator = ent.Comp.LerpTime; + } + + private void OnRemove(Entity ent, ref ComponentRemove args) + { + var (_, comp) = ent; + + if (!comp.SpawnRemainingTrail || comp.TrailData.Count == 0 || comp.Frequency <= 0f || comp.Lifetime <= 0f) + return; + + if (comp.LastCoords.MapId != _eye.CurrentEye.Position.MapId) + return; + + if (comp.RenderedEntity != null && TerminatingOrDeleted(comp.RenderedEntity.Value)) + return; + + var remainingTrail = Spawn(null, comp.LastCoords); + EnsureComp(remainingTrail).Lifetime = comp.Lifetime; + var trail = EnsureComp(remainingTrail); + trail.SpawnRemainingTrail = false; + trail.Frequency = 0f; + trail.Lifetime = comp.Lifetime; + trail.AlphaLerpAmount = comp.AlphaLerpAmount; + trail.ScaleLerpAmount = comp.ScaleLerpAmount; + trail.VelocityLerpAmount = comp.VelocityLerpAmount; + trail.PositionLerpAmount = comp.PositionLerpAmount; + trail.AlphaLerpTarget = comp.AlphaLerpTarget; + trail.ScaleLerpTarget = comp.ScaleLerpTarget; + trail.Sprite = comp.Sprite; + trail.Color = comp.Color; + trail.Scale = comp.Scale; + trail.TrailData = comp.TrailData; + trail.Shader = comp.Shader; + trail.ParticleAmount = comp.ParticleAmount; + trail.StartAngle = comp.StartAngle; + trail.EndAngle = comp.EndAngle; + trail.LerpTime = comp.LerpTime; + trail.LerpAccumulator = comp.LerpAccumulator; + trail.RenderedEntity = comp.RenderedEntity; + trail.Velocity = comp.Velocity; + trail.Radius = comp.Radius; + trail.MaxParticleAmount = comp.MaxParticleAmount; + trail.ParticleCount = comp.ParticleCount; + trail.SpawnPosition = comp.SpawnPosition; + trail.SpawnEntityPosition = comp.SpawnEntityPosition; + trail.RenderedEntityRotationStrategy = comp.RenderedEntityRotationStrategy; + trail.AdditionalLerpData = comp.AdditionalLerpData; + trail.TrailData.Sort((x, y) => x.SpawnTime.CompareTo(y.SpawnTime)); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlay.RemoveOverlay(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var trail, out var xform)) + { + if (trail.Lifetime <= 0f) + continue; + + var (position, rotation) = _transform.GetWorldPositionRotation(xform, _xformQuery); + trail.LastCoords = new MapCoordinates(position, xform.MapID); + + Lerp(trail, position, frameTime); + + trail.Accumulator += frameTime; + + // Assuming that lifetime and frequency don't change + if (trail.Accumulator > trail.Lifetime && trail.Lifetime < trail.Frequency && trail.TrailData.Count > 0) + trail.TrailData.Clear(); + + if (trail.Frequency <= 0f || trail.ParticleAmount < 1 || + trail.MaxParticleAmount > 0 && trail.ParticleCount >= trail.MaxParticleAmount) + { + if (trail.Accumulator <= trail.Lifetime) + continue; + + trail.Accumulator = 0f; + + for (var i = 0; i < Math.Max(1, trail.ParticleAmount); i++) + { + if (trail.TrailData.Count == 0) + { + if (IsClientSide(uid) && trail.Frequency <= 0f) + QueueDel(uid); + break; + } + + trail.TrailData.RemoveAt(0); + } + + continue; + } + + if (trail.Accumulator <= trail.Frequency) + continue; + + trail.Accumulator = 0f; + + if (trail.SpawnEntityPosition != null && !Exists(trail.SpawnEntityPosition.Value)) + continue; + + Angle angle; + if (_physicsQuery.TryComp(uid, out var physics) && physics.LinearVelocity.LengthSquared() > 0) + angle = physics.LinearVelocity.ToAngle(); + else + angle = xform.LocalRotation; + + var start = trail.StartAngle + angle; + var end = trail.EndAngle + angle; + + // It would break if we try to do this with line based trails + if (trail.ParticleAmount == 1 || trail is { ParticleAmount: > 1, RenderedEntity: null, Sprite: null }) + { + var direction = new Angle((end.Theta + start.Theta) * 0.5).ToVec(); + SpawnParticle(trail, position, rotation, direction, xform.MapID); + continue; + } + + if (trail.ParticleAmount < 1) // Impossible + continue; + + var angles = LinearSpread(start, end, trail.ParticleAmount); + for (var i = 0; i < trail.ParticleAmount; i++) + { + SpawnParticle(trail, position, rotation, angles[i].ToVec(), xform.MapID); + if (trail.MaxParticleAmount > 0 && trail.ParticleCount >= trail.MaxParticleAmount) + break; + } + } + } + + private Angle[] LinearSpread(Angle start, Angle end, int intervals) + { + DebugTools.Assert(intervals > 1); + var angles = new Angle[intervals]; + + for (var i = 0; i <= intervals - 1; i++) + { + angles[i] = new Angle(start + (end - start) * i / (intervals - 1)); + } + + return angles; + } + + private void SpawnParticle(TrailComponent trail, Vector2 position, Angle rotation, Vector2 direction, MapId mapId) + { + DebugTools.Assert(trail is { ParticleAmount: > 0, Frequency: > 0f }); + trail.ParticleCount++; + + if (trail.SpawnEntityPosition != null && Exists(trail.SpawnEntityPosition.Value)) + { + position = _transform.GetWorldPosition(trail.SpawnEntityPosition.Value, _xformQuery); + if (trail.SpawnPosition != null) + position += trail.SpawnPosition.Value; + } + else if (trail.SpawnPosition != null) + position = trail.SpawnPosition.Value; + + var targetPos = position + direction * trail.Radius; + if (trail.TrailData.Count < + MathF.Max(trail.ParticleAmount, trail.ParticleAmount * trail.Lifetime / trail.Frequency)) + { + trail.TrailData.Add(new TrailData(targetPos, + trail.Velocity, + mapId, + direction, + rotation, + trail.Color, + trail.Scale, + _timing.CurTime)); + } + else if (trail.TrailData.Count > 0) + { + if (trail.CurIndex >= trail.TrailData.Count || trail.Sprite == null) + trail.CurIndex = 0; + + var data = trail.TrailData[trail.CurIndex]; + + data.Color = trail.Color; + data.Position = targetPos; + data.Velocity = trail.Velocity; + data.MapId = mapId; + data.Direction = direction; + data.Angle = rotation; + data.Scale = trail.Scale; + data.SpawnTime = _timing.CurTime; + + if (trail.Sprite == null) + { + if (trail is + { + AlphaLerpAmount: <= 0f, ScaleLerpAmount: <= 0f, VelocityLerpAmount: <= 0f, Velocity: 0f, + PositionLerpAmount: <= 0f, + }) + return; + + trail.TrailData.RemoveAt(0); + trail.TrailData.Add(data); + } + else + trail.CurIndex++; + } + } + + private void Lerp(TrailComponent trail, Vector2 position, float frameTime) + { + if (trail is + { + AlphaLerpAmount: <= 0f, ScaleLerpAmount: <= 0f, Velocity: 0f, VelocityLerpAmount: <= 0f, + PositionLerpAmount: <= 0f, + }) + return; + + trail.LerpAccumulator += frameTime; + + if (trail.LerpAccumulator <= trail.LerpTime) + return; + + trail.LerpAccumulator = 0; + + foreach (var data in trail.TrailData) + { + if (trail.LerpDelay > _timing.CurTime - data.SpawnTime) + return; + + if (trail.AlphaLerpAmount > 0f) + { + var alphaTarget = trail.AlphaLerpTarget is >= 0f and <= 1f ? trail.AlphaLerpTarget : 0f; + data.Color.A = float.Lerp(data.Color.A, alphaTarget, trail.AlphaLerpAmount); + } + + if (trail.ScaleLerpAmount > 0f) + { + var scaleTarget = trail.ScaleLerpTarget >= 0f ? trail.ScaleLerpTarget : 0f; + data.Scale = float.Lerp(data.Scale, scaleTarget, trail.ScaleLerpAmount); + } + + data.Position += data.Direction * data.Velocity; + + if (trail.PositionLerpAmount > 0f) + data.Position = Vector2.Lerp(data.Position, position, trail.PositionLerpAmount); + + if (trail.VelocityLerpAmount > 0f) + data.Velocity = float.Lerp(data.Velocity, trail.VelocityLerpTarget, trail.VelocityLerpAmount); + } + + foreach (var lerpData in trail.AdditionalLerpData.Where(x => x.LerpAmount > 0f)) + { + lerpData.Value = float.Lerp(lerpData.Value, lerpData.LerpTarget, lerpData.LerpAmount); + + AnimationHelper.SetAnimatableProperty(trail, lerpData.Property, lerpData.Value); + } + } +} diff --git a/Content.Client/_Lua/Starmap/StarmapControl.cs b/Content.Client/_Lua/Starmap/StarmapControl.cs index b5d34aff9a6..63464ed2534 100644 --- a/Content.Client/_Lua/Starmap/StarmapControl.cs +++ b/Content.Client/_Lua/Starmap/StarmapControl.cs @@ -3,6 +3,7 @@ // See AGPLv3.txt for details. using Content.Shared._Lua.Starmap; +using Content.Shared._Lua.Starmap.Components; using Content.Shared._Mono.Company; using Content.Shared.Lua.CLVar; using Robust.Client.Graphics; @@ -369,17 +370,21 @@ private bool IsSectorVisibleForLocalCompany(MapId mapId) if (!_sectorIdByMap.TryGetValue(mapId, out var sectorId) || string.IsNullOrWhiteSpace(sectorId)) return true; var company = SectorVisibility.NoneCompany; + IReadOnlyCollection? learned = null; var local = _player.LocalEntity; if (local != null && _ent.TryGetComponent(local.Value, out CompanyComponent? companyComp) && !string.IsNullOrWhiteSpace(companyComp.CompanyName)) company = companyComp.CompanyName; + if (local != null && + _ent.TryGetComponent(local.Value, out KnownSectorsComponent? known)) + learned = known.LearnedSectorIds; try { var dataId = _cfg.GetCVar(CLVars.StarmapDataId); if (!_proto.TryIndex(dataId, out var data)) return true; - return SectorVisibility.IsSectorVisible(data, sectorId, company, _sectorsGloballyUnlocked); + return SectorVisibility.IsSectorVisible(data, sectorId, company, _sectorsGloballyUnlocked, learned); } catch { diff --git a/Content.Client/_Lua/Starmap/UI/ShareSectorBoundUserInterface.cs b/Content.Client/_Lua/Starmap/UI/ShareSectorBoundUserInterface.cs new file mode 100644 index 00000000000..de2b6bc30f9 --- /dev/null +++ b/Content.Client/_Lua/Starmap/UI/ShareSectorBoundUserInterface.cs @@ -0,0 +1,43 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using Content.Shared._Lua.Starmap; +using Robust.Client.GameObjects; + +namespace Content.Client._Lua.Starmap.UI; + +public sealed class ShareSectorBoundUserInterface : BoundUserInterface +{ + private ShareSectorWindow? _window; + + public ShareSectorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new ShareSectorWindow(); + _window.OnShareSector += sectorId => SendMessage(new ShareSectorSelectedMessage(sectorId)); + _window.OnClose += Close; + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is ShareSectorBoundUserInterfaceState shareState) + _window?.UpdateState(shareState); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _window?.Dispose(); + } +} diff --git a/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml b/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml new file mode 100644 index 00000000000..6053dd04b5a --- /dev/null +++ b/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml.cs b/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml.cs new file mode 100644 index 00000000000..5938b0fcc25 --- /dev/null +++ b/Content.Client/_Lua/Starmap/UI/ShareSectorWindow.xaml.cs @@ -0,0 +1,146 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using Content.Shared._Lua.Starmap; +using Content.Shared._Mono.Company; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using System.Numerics; + +namespace Content.Client._Lua.Starmap.UI; + +[GenerateTypedNameReferences] +public sealed partial class ShareSectorWindow : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + public event Action? OnShareSector; + + public ShareSectorWindow() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + } + + protected override Vector2 ContentsMinimumSize => new(360, 420); + + public void UpdateState(ShareSectorBoundUserInterfaceState state) + { + StatusLabel.Text = Loc.GetString("share-sector-window-count", ("count", state.Sectors.Count)); + SectorList.RemoveAllChildren(); + + foreach (var sector in state.Sectors) + { + var button = new ContainerButton + { + HorizontalExpand = true, + ToolTip = GetSectorName(sector), + }; + button.AddStyleClass(ContainerButton.StyleClassButton); + + var layout = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + HorizontalExpand = true, + SeparationOverride = 6, + RectClipContent = true, + }; + + AddCompanyFlag(layout, sector.CompanyIds); + + var textLayout = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + HorizontalExpand = true, + MinWidth = 0, + SeparationOverride = 1, + }; + textLayout.AddChild(new Label + { + Text = GetSectorName(sector), + MinWidth = 0, + HorizontalExpand = true, + FontColorOverride = GetPrimaryCompanyColor(sector.CompanyIds), + ClipText = true, + }); + textLayout.AddChild(new Label + { + Text = Loc.GetString("share-sector-window-entry", ("x", sector.X), ("y", sector.Y)), + MinWidth = 0, + HorizontalExpand = true, + ClipText = true, + StyleClasses = { "LabelSubText" }, + }); + layout.AddChild(textLayout); + + button.AddChild(layout); + button.OnPressed += _ => OnShareSector?.Invoke(sector.SectorId); + + SectorList.AddChild(button); + } + } + + private void AddCompanyFlag(BoxContainer layout, IReadOnlyList companyIds) + { + foreach (var companyId in companyIds) + { + if (!TryGetCompanyIcon(companyId, out var texture)) + continue; + + layout.AddChild(new TextureRect + { + Texture = texture, + MinSize = new Vector2(32, 32), + MaxSize = new Vector2(32, 32), + Stretch = TextureRect.StretchMode.KeepAspectCentered, + VerticalAlignment = VAlignment.Center, + }); + return; + } + + layout.AddChild(new Label + { + Text = "*", + MinWidth = 32, + Align = Label.AlignMode.Center, + VerticalAlignment = VAlignment.Center, + StyleClasses = { "LabelSubText" }, + }); + } + + private Color? GetPrimaryCompanyColor(IReadOnlyList companyIds) + { + foreach (var companyId in companyIds) + { + if (_prototypeManager.TryIndex(companyId, out var company)) + return company.Color; + } + + return null; + } + + private bool TryGetCompanyIcon(string companyId, out Texture? texture) + { + texture = null; + if (!_prototypeManager.TryIndex(companyId, out var company) || string.IsNullOrWhiteSpace(company.IconPath)) + return false; + + texture = _resourceCache.GetResource(company.IconPath!).Texture; + return true; + } + + private static string GetSectorName(ShareSectorEntry sector) + { + return Loc.TryGetString(sector.NameLocKey, out var localized) + ? localized + : sector.SectorId; + } +} diff --git a/Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml b/Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml new file mode 100644 index 00000000000..2ae514e373e --- /dev/null +++ b/Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml @@ -0,0 +1,14 @@ + + +