diff --git a/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs b/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs new file mode 100644 index 0000000000..f16774ce24 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.SprayPainter.Prototypes; +using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// Used to change the appearance of gas canisters. +/// +public sealed class GasCanisterAppearanceSystem : VisualizerSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + protected override void OnAppearanceChange(EntityUid uid, GasCanisterComponent component, ref AppearanceChangeEvent args) + { + if (!AppearanceSystem.TryGetData(uid, PaintableVisuals.Prototype, out var protoName, args.Component) || args.Sprite is not { } old) + return; + + if (!_prototypeManager.HasIndex(protoName)) + return; + + // Create the given prototype and get its first layer. + var tempUid = Spawn(protoName); + SpriteSystem.LayerSetRsiState(uid, 0, SpriteSystem.LayerGetRsiState(tempUid, 0)); + QueueDel(tempUid); + } +} diff --git a/Content.Client/Doors/DoorSystem.cs b/Content.Client/Doors/DoorSystem.cs index cb17cfaf21..3d9a3e2a9a 100644 --- a/Content.Client/Doors/DoorSystem.cs +++ b/Content.Client/Doors/DoorSystem.cs @@ -1,16 +1,17 @@ using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Content.Shared.SprayPainter.Prototypes; using Robust.Client.Animations; using Robust.Client.GameObjects; -using Robust.Client.ResourceManagement; -using Robust.Shared.Serialization.TypeSerializers.Implementations; +using Robust.Shared.Prototypes; namespace Content.Client.Doors; public sealed class DoorSystem : SharedDoorSystem { [Dependency] private readonly AnimationPlayerSystem _animationSystem = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SpriteSystem _sprite = default!; public override void Initialize() @@ -85,8 +86,8 @@ public sealed class DoorSystem : SharedDoorSystem if (!AppearanceSystem.TryGetData(entity, DoorVisuals.State, out var state, args.Component)) state = DoorState.Closed; - if (AppearanceSystem.TryGetData(entity, DoorVisuals.BaseRSI, out var baseRsi, args.Component)) - UpdateSpriteLayers((entity.Owner, args.Sprite), baseRsi); + if (AppearanceSystem.TryGetData(entity, PaintableVisuals.Prototype, out var prototype, args.Component)) + UpdateSpriteLayers((entity.Owner, args.Sprite), prototype); if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey)) _animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey); @@ -139,14 +140,14 @@ public sealed class DoorSystem : SharedDoorSystem } } - private void UpdateSpriteLayers(Entity sprite, string baseRsi) + private void UpdateSpriteLayers(Entity sprite, string targetProto) { - if (!_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res)) - { - Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace); + if (!_prototypeManager.TryIndex(targetProto, out var target)) return; - } - _sprite.SetBaseRsi(sprite.AsNullable(), res.RSI); + if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory)) + return; + + _sprite.SetBaseRsi(sprite.AsNullable(), targetSprite.BaseRSI); } } diff --git a/Content.Client/SprayPainter/SprayPainterSystem.cs b/Content.Client/SprayPainter/SprayPainterSystem.cs index 6a1d27e98b..8f7d7f0362 100644 --- a/Content.Client/SprayPainter/SprayPainterSystem.cs +++ b/Content.Client/SprayPainter/SprayPainterSystem.cs @@ -1,56 +1,129 @@ -using Content.Shared.SprayPainter; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.Serialization.TypeSerializers.Implementations; -using Robust.Shared.Utility; using System.Linq; -using Robust.Shared.Graphics; +using Content.Client.Items; +using Content.Client.Message; +using Content.Client.Stylesheets; +using Content.Shared.Decals; +using Content.Shared.SprayPainter; +using Content.Shared.SprayPainter.Components; +using Content.Shared.SprayPainter.Prototypes; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Client.SprayPainter; +/// +/// Client-side spray painter functions. Caches information for spray painter windows and updates the UI to reflect component state. +/// public sealed class SprayPainterSystem : SharedSprayPainterSystem { - [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; - public List Entries { get; private set; } = new(); + public List Decals = []; + public Dictionary> PaintableGroupsByCategory = new(); + public Dictionary> PaintableStylesByGroup = new(); - protected override void CacheStyles() + public override void Initialize() { - base.CacheStyles(); + base.Initialize(); - Entries.Clear(); - foreach (var style in Styles) + Subs.ItemStatus(ent => new StatusControl(ent)); + SubscribeLocalEvent(OnStateUpdate); + SubscribeLocalEvent(OnPrototypesReloaded); + + CachePrototypes(); + } + + private void OnStateUpdate(Entity ent, ref AfterAutoHandleStateEvent args) + { + UpdateUi(ent); + } + + protected override void UpdateUi(Entity ent) + { + if (_ui.TryGetOpenUi(ent.Owner, SprayPainterUiKey.Key, out var bui)) + bui.Update(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.WasModified() || !args.WasModified() || !args.WasModified()) + return; + + CachePrototypes(); + } + + private void CachePrototypes() + { + PaintableGroupsByCategory.Clear(); + PaintableStylesByGroup.Clear(); + foreach (var category in Proto.EnumeratePrototypes().OrderBy(x => x.ID)) { - var name = style.Name; - string? iconPath = Groups - .FindAll(x => x.StylePaths.ContainsKey(name))? - .MaxBy(x => x.IconPriority)?.StylePaths[name]; - if (iconPath == null) + var groupList = new List(); + foreach (var groupId in category.Groups) { - Entries.Add(new SprayPainterEntry(name, null)); - continue; + if (!Proto.TryIndex(groupId, out var group)) + continue; + + groupList.Add(groupId); + PaintableStylesByGroup[groupId] = group.Styles; } - RSIResource doorRsi = _resourceCache.GetResource(SpriteSpecifierSerializer.TextureRoot / new ResPath(iconPath)); - if (!doorRsi.RSI.TryGetState("closed", out var icon)) - { - Entries.Add(new SprayPainterEntry(name, null)); - continue; - } + if (groupList.Count > 0) + PaintableGroupsByCategory[category.ID] = groupList; + } - Entries.Add(new SprayPainterEntry(name, icon.Frame0)); + Decals.Clear(); + foreach (var decalPrototype in Proto.EnumeratePrototypes().OrderBy(x => x.ID)) + { + if (!decalPrototype.Tags.Contains("station") + && !decalPrototype.Tags.Contains("markings") + || decalPrototype.Tags.Contains("dirty")) + continue; + + Decals.Add(new SprayPainterDecalEntry(decalPrototype.ID, decalPrototype.Sprite)); + } + } + + private sealed class StatusControl : Control + { + private readonly RichTextLabel _label; + private readonly Entity _entity; + private DecalPaintMode? _lastPaintingDecals = null; + + public StatusControl(Entity ent) + { + _entity = ent; + _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; + AddChild(_label); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_entity.Comp.DecalMode == _lastPaintingDecals) + return; + + _lastPaintingDecals = _entity.Comp.DecalMode; + + string modeLocString = _entity.Comp.DecalMode switch + { + DecalPaintMode.Add => "spray-painter-item-status-add", + DecalPaintMode.Remove => "spray-painter-item-status-remove", + _ => "spray-painter-item-status-off" + }; + + _label.SetMarkupPermissive(Robust.Shared.Localization.Loc.GetString("spray-painter-item-status-label", + ("mode", Robust.Shared.Localization.Loc.GetString(modeLocString)))); } } } -public sealed class SprayPainterEntry -{ - public string Name; - public Texture? Icon; - - public SprayPainterEntry(string name, Texture? icon) - { - Name = name; - Icon = icon; - } -} +/// +/// A spray paintable decal, mapped by ID. +/// +public sealed record SprayPainterDecalEntry(string Name, SpriteSpecifier Sprite); diff --git a/Content.Client/SprayPainter/UI/SprayPainterBoundUserInterface.cs b/Content.Client/SprayPainter/UI/SprayPainterBoundUserInterface.cs index 7d6a6cf2a5..701ec80bac 100644 --- a/Content.Client/SprayPainter/UI/SprayPainterBoundUserInterface.cs +++ b/Content.Client/SprayPainter/UI/SprayPainterBoundUserInterface.cs @@ -1,42 +1,96 @@ +using Content.Shared.Decals; using Content.Shared.SprayPainter; using Content.Shared.SprayPainter.Components; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; namespace Content.Client.SprayPainter.UI; -public sealed class SprayPainterBoundUserInterface : BoundUserInterface +/// +/// A BUI for a spray painter. Allows selecting pipe colours, decals, and paintable object types sorted by category. +/// +public sealed class SprayPainterBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { [ViewVariables] private SprayPainterWindow? _window; - public SprayPainterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } - protected override void Open() { base.Open(); - _window = this.CreateWindow(); - - _window.OnSpritePicked = OnSpritePicked; - _window.OnColorPicked = OnColorPicked; - - if (EntMan.TryGetComponent(Owner, out SprayPainterComponent? comp)) + if (_window == null) { - _window.Populate(EntMan.System().Entries, comp.Index, comp.PickedColor, comp.ColorPalette); + _window = this.CreateWindow(); + + _window.OnSpritePicked += OnSpritePicked; + _window.OnSetPipeColor += OnSetPipeColor; + _window.OnTabChanged += OnTabChanged; + _window.OnDecalChanged += OnDecalChanged; + _window.OnDecalColorChanged += OnDecalColorChanged; + _window.OnDecalAngleChanged += OnDecalAngleChanged; + _window.OnDecalSnapChanged += OnDecalSnapChanged; } + + var sprayPainter = EntMan.System(); + _window.PopulateCategories(sprayPainter.PaintableStylesByGroup, sprayPainter.PaintableGroupsByCategory, sprayPainter.Decals); + Update(); + + if (EntMan.TryGetComponent(Owner, out SprayPainterComponent? sprayPainterComp)) + _window.SetSelectedTab(sprayPainterComp.SelectedTab); } - private void OnSpritePicked(ItemList.ItemListSelectedEventArgs args) + public override void Update() { - SendMessage(new SprayPainterSpritePickedMessage(args.ItemIndex)); + if (_window == null) + return; + + if (!EntMan.TryGetComponent(Owner, out SprayPainterComponent? sprayPainter)) + return; + + _window.PopulateColors(sprayPainter.ColorPalette); + if (sprayPainter.PickedColor != null) + _window.SelectColor(sprayPainter.PickedColor); + _window.SetSelectedStyles(sprayPainter.StylesByGroup); + _window.SetSelectedDecal(sprayPainter.SelectedDecal); + _window.SetDecalAngle(sprayPainter.SelectedDecalAngle); + _window.SetDecalColor(sprayPainter.SelectedDecalColor); + _window.SetDecalSnap(sprayPainter.SnapDecals); } - private void OnColorPicked(ItemList.ItemListSelectedEventArgs args) + private void OnDecalSnapChanged(bool snap) + { + SendPredictedMessage(new SprayPainterSetDecalSnapMessage(snap)); + } + + private void OnDecalAngleChanged(int angle) + { + SendPredictedMessage(new SprayPainterSetDecalAngleMessage(angle)); + } + + private void OnDecalColorChanged(Color? color) + { + SendPredictedMessage(new SprayPainterSetDecalColorMessage(color)); + } + + private void OnDecalChanged(ProtoId protoId) + { + SendPredictedMessage(new SprayPainterSetDecalMessage(protoId)); + } + + private void OnTabChanged(int index, bool isSelectedTabWithDecals) + { + SendPredictedMessage(new SprayPainterTabChangedMessage(index, isSelectedTabWithDecals)); + } + + private void OnSpritePicked(string group, string style) + { + SendPredictedMessage(new SprayPainterSetPaintableStyleMessage(group, style)); + } + + private void OnSetPipeColor(ItemList.ItemListSelectedEventArgs args) { var key = _window?.IndexToColorKey(args.ItemIndex); - SendMessage(new SprayPainterColorPickedMessage(key)); + SendPredictedMessage(new SprayPainterSetPipeColorMessage(key)); } } diff --git a/Content.Client/SprayPainter/UI/SprayPainterDecals.xaml b/Content.Client/SprayPainter/UI/SprayPainterDecals.xaml new file mode 100644 index 0000000000..0d5c8e4f16 --- /dev/null +++ b/Content.Client/SprayPainter/UI/SprayPainterDecals.xaml @@ -0,0 +1,26 @@ + + +