From 9766c38f6a262f684c68d72b50521b45aa9a6d78 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Sat, 21 Mar 2026 14:43:06 -0400 Subject: [PATCH 01/13] basis up --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 46 +++++++++++ .../_DEN/Recolor/RecolorSystem.Applier.cs | 53 +++++++++++++ Content.Server/_DEN/Recolor/RecolorSystem.cs | 78 +++++++++++++++++++ .../Components/RecolorApplierComponent.cs | 37 +++++++++ .../Recolor/Components/RecoloredComponent.cs | 32 ++++++++ .../_DEN/Recolor/SharedRecolorSystem.cs | 25 ++++++ .../Entities/Objects/Tools/paint_cans.yml | 29 +++++++ .../Prototypes/_DEN/Shaders/desaturated.yml | 4 + .../Textures/_DEN/Shaders/desaturated.swsl | 5 ++ 9 files changed, 309 insertions(+) create mode 100644 Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs create mode 100644 Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs create mode 100644 Content.Server/_DEN/Recolor/RecolorSystem.cs create mode 100644 Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs create mode 100644 Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml create mode 100644 Resources/Prototypes/_DEN/Shaders/desaturated.yml create mode 100644 Resources/Textures/_DEN/Shaders/desaturated.swsl diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs new file mode 100644 index 0000000000..c08ed3afb5 --- /dev/null +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -0,0 +1,46 @@ +using System.Linq; +using Content.Shared._DEN.Recolor.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client._DEN.Recolor; + +public sealed class RecolorVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + protected override void OnAppearanceChange(EntityUid uid, RecoloredComponent component, ref AppearanceChangeEvent args) + { + base.OnAppearanceChange(uid, component, ref args); + + if (args.Sprite == null) + return; + + ApplyRecolor((uid, component), args.Sprite); + } + + private void ApplyRecolor(Entity ent, SpriteComponent sprite) + { + ShaderPrototype? shader = null; + + if (ent.Comp.Shader != null && _prototype.TryIndex(ent.Comp.Shader, out var proto)) + shader = proto; + + for (var i = 0; i < sprite.AllLayers.Count(); i++) + { + if (!SpriteSystem.TryGetLayer((ent, sprite), i, out var layer, false)) + continue; + + if (!ent.Comp.AffectLayersWithShaders && layer.ShaderPrototype != null) + continue; + + if (shader != null) + { + var instance = shader.Instance(); + sprite.LayerSetShader(i, instance, ent.Comp.Shader); + } + + SpriteSystem.LayerSetColor(layer, ent.Comp.Color); + } + } +} diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs new file mode 100644 index 0000000000..337d301055 --- /dev/null +++ b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs @@ -0,0 +1,53 @@ +using Content.Shared._DEN.Recolor; +using Content.Shared._DEN.Recolor.Components; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; + +namespace Content.Server._DEN.Recolor; + +public sealed partial class RecolorSystem +{ + private void OnRecolorApplierAfterInteract(Entity ent, ref AfterInteractEvent args) + { + if (!args.CanReach || args.Target == null) + return; + + TryStartApplyRecolorDoAfter(args.User, args.Target.Value, ent); + } + + private void TryStartApplyRecolorDoAfter(EntityUid user, + EntityUid target, + Entity applier) + { + var doAfterEvent = new ApplyRecolorDoAfterEvent + { + Color = applier.Comp.Color, + Shader = applier.Comp.Shader, + Removable = applier.Comp.Removable, + AffectLayersWithShaders = applier.Comp.AffectLayersWithShaders, + }; + + var doAfterArgs = new DoAfterArgs(EntityManager, + user: user, + seconds: (float)applier.Comp.DoAfterDuration.TotalSeconds, + @event: doAfterEvent, + eventTarget: applier, + target: target, + used: applier); + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + + private void OnApplyRecolorDoAfterEvent(Entity ent, ref ApplyRecolorDoAfterEvent args) + { + if (args.Target is null) + return; + + Recolor( + uid: args.Target.Value, + color: args.Color, + affectLayersWithShaders: args.AffectLayersWithShaders, + removable: args.Removable, + shader: args.Shader); + } +} diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.cs b/Content.Server/_DEN/Recolor/RecolorSystem.cs new file mode 100644 index 0000000000..58d7208dab --- /dev/null +++ b/Content.Server/_DEN/Recolor/RecolorSystem.cs @@ -0,0 +1,78 @@ +using Content.Server.DoAfter; +using Content.Shared._DEN.Recolor; +using Content.Shared._DEN.Recolor.Components; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Server.GameObjects; + +namespace Content.Server._DEN.Recolor; + +public sealed partial class RecolorSystem : SharedRecolorSystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnComponentShutdown); + + SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); + SubscribeLocalEvent(OnRecolorApplierAfterInteract); + } + + private void OnComponentStartup(Entity ent, ref ComponentStartup args) + { + DirtyVisuals(ent); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + DirtyVisuals(ent); + } + + private void DirtyVisuals(Entity ent) + { + if (!TryComp(ent, out AppearanceComponent? appearance)) + return; + + _appearance.SetData(ent, RecolorVisuals.Color, true, appearance); + } + + [PublicAPI] + public void Recolor(EntityUid uid, + Color color, + bool affectLayersWithShaders, + bool removable, + string? shader = null) + { + if (HasComp(uid)) + { + //Replace old recolored component. you can spray things with paint twice.. right? + RemComp(uid); + } + + EnsureComp(uid); + + var comp = new RecoloredComponent + { + Color = color, + Shader = shader, + AffectLayersWithShaders = affectLayersWithShaders, + Removable = removable, + }; + + AddComp(uid, comp); + } + + [PublicAPI] + public void RemoveRecolor(Entity ent) + { + if (!Resolve(ent.Owner, ref ent.Comp, logMissing: false)) + return; + + RemComp(ent, ent.Comp); + } +} diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs new file mode 100644 index 0000000000..8997592c6e --- /dev/null +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -0,0 +1,37 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DEN.Recolor.Components; + +[RegisterComponent] +public sealed partial class RecolorApplierComponent : Component +{ + /// + /// The color to apply to the object being recolored. + /// + [DataField] + public Color Color { get; set; } + + /// + /// Whether or not the color applied can be removed via normal means. + /// + public bool Removable { get; set; } + + /// + /// Whether or not the recolor should apply to layers that already have shaders. + /// + [DataField] + public bool AffectLayersWithShaders; + + /// + /// The shader to apply to the recolored entity. + /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. + /// + [DataField] + public string? Shader = "Desaturated"; + + /// + /// How long it takes for this object to apply the recolor to the target. + /// + [DataField] + public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3.0f); +} diff --git a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs new file mode 100644 index 0000000000..383e6d8142 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs @@ -0,0 +1,32 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DEN.Recolor.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RecoloredComponent : Component +{ + /// + /// The color to change the sprite to. + /// + [DataField, AutoNetworkedField] + public Color Color = Color.White; + + /// + /// Whether or not this component can be removed by an entity with RecolorRemoverComponent. + /// + public bool Removable { get; set; } + + /// + /// Whether or not the recolor should apply to layers that already have shaders. + /// + [DataField] + public bool AffectLayersWithShaders; + + /// + /// The shader to apply to the recolored entity. + /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. + /// + [DataField, AutoNetworkedField] + public string? Shader { get; set; } + +} diff --git a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs new file mode 100644 index 0000000000..c1f438b7f0 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs @@ -0,0 +1,25 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._DEN.Recolor; + +public abstract class SharedRecolorSystem : EntitySystem; + +[Serializable, NetSerializable] +public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent +{ + public Color Color; + public bool AffectLayersWithShaders; + public bool Removable; + public string? Shader; + public override DoAfterEvent Clone() + { + return this; + } +} + +[Serializable, NetSerializable] +public enum RecolorVisuals +{ + Color, +} diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml new file mode 100644 index 0000000000..a009859ea1 --- /dev/null +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -0,0 +1,29 @@ +- type: entity + parent: BaseItem + id: SprayPaintCan + abstract: true + description: it's spray paint + components: + - type: Sprite + sprite: Objects/Tools/crowbar.rsi + state: icon + - type: Item + storedSprite: + sprite: Objects/Tools/crowbar.rsi + state: storage + - type: RecolorApplier + color: "#ffffff" + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanYellow + name: yellow spray paint + components: + - type: RecolorApplier + color: "#ffff00" + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanWhite + name: white spray paint + diff --git a/Resources/Prototypes/_DEN/Shaders/desaturated.yml b/Resources/Prototypes/_DEN/Shaders/desaturated.yml new file mode 100644 index 0000000000..44489cc7fc --- /dev/null +++ b/Resources/Prototypes/_DEN/Shaders/desaturated.yml @@ -0,0 +1,4 @@ +- type: shader + id: Desaturated + kind: source + path: "/Textures/_Den/Shaders/desaturated.swsl" diff --git a/Resources/Textures/_DEN/Shaders/desaturated.swsl b/Resources/Textures/_DEN/Shaders/desaturated.swsl new file mode 100644 index 0000000000..cffe13fad1 --- /dev/null +++ b/Resources/Textures/_DEN/Shaders/desaturated.swsl @@ -0,0 +1,5 @@ +void fragment() { + highp vec4 color = zTexture(UV); + COLOR.rgb = mix(vec3(dot(color.rgb, vec3(0.399, 0.687, 0.214))), color.rgb, 0); + COLOR.a = color.a; +} From 17ef36c9b88f7916257dcfff9ad605099b5449a6 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Sun, 22 Mar 2026 00:56:05 -0400 Subject: [PATCH 02/13] clean and fix so much code its so much better it works on inhands and its happy --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 128 ++++++++++++++++-- .../_DEN/Recolor/RecolorSystem.Applier.cs | 9 +- Content.Server/_DEN/Recolor/RecolorSystem.cs | 36 +++-- .../Components/RecolorApplierComponent.cs | 10 +- .../Recolor/Components/RecoloredComponent.cs | 15 +- .../_DEN/Recolor/SharedRecolorSystem.cs | 6 +- .../Entities/Objects/Tools/paint_cans.yml | 3 + 7 files changed, 179 insertions(+), 28 deletions(-) diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index c08ed3afb5..7dab591fdd 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -1,5 +1,11 @@ using System.Linq; +using Content.Client.Clothing; +using Content.Client.Items.Systems; +using Content.Shared._DEN.Recolor; using Content.Shared._DEN.Recolor.Components; +using Content.Shared.Clothing; +using Content.Shared.Hands; +using Content.Shared.Item; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Prototypes; @@ -9,6 +15,21 @@ namespace Content.Client._DEN.Recolor; public sealed class RecolorVisualizerSystem : VisualizerSystem { [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly ItemSystem _item = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentShutdown); + + SubscribeLocalEvent(ApplyRecolorInHands, + after: [typeof(ItemSystem)]); + + SubscribeLocalEvent(ApplyRecolorEquipment, + after: [typeof(ClientClothingSystem)]); + } + protected override void OnAppearanceChange(EntityUid uid, RecoloredComponent component, ref AppearanceChangeEvent args) { base.OnAppearanceChange(uid, component, ref args); @@ -16,31 +37,114 @@ protected override void OnAppearanceChange(EntityUid uid, RecoloredComponent com if (args.Sprite == null) return; - ApplyRecolor((uid, component), args.Sprite); + ApplyRecolorSprite((uid, component), args.Sprite); + _item.VisualsChanged(uid); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + if (TerminatingOrDeleted(ent.Owner)) + return; + + if (!TryComp(ent, out SpriteComponent? sprite)) + return; + + RemoveRecolor(ent,sprite); + _item.VisualsChanged(ent); + } + + private void ApplyRecolorInHands(Entity ent, ref GetInhandVisualsEvent args) + { + ApplyRecolorLayers(ent,args.Layers); + } + + private void ApplyRecolorEquipment(Entity ent, ref GetEquipmentVisualsEvent args) + { + ApplyRecolorLayers(ent,args.Layers); } - private void ApplyRecolor(Entity ent, SpriteComponent sprite) + private void ApplyRecolorLayers(Entity ent, List<(string, PrototypeLayerData)> layers) { - ShaderPrototype? shader = null; + var appearanceData = GetRecolorAppearanceData(ent); + foreach (var (_, layerData) in layers) + { + // Apply Color + layerData.Color = appearanceData.Color; + + //Test shader whitelists and blacklists + if (!AllowedShader(layerData.Shader, appearanceData)) + continue; + + // Apply shaders + layerData.Shader = appearanceData.Shader; + } + } - if (ent.Comp.Shader != null && _prototype.TryIndex(ent.Comp.Shader, out var proto)) - shader = proto; + private void ApplyRecolorSprite(Entity ent, SpriteComponent sprite) + { + var appearanceData = GetRecolorAppearanceData(ent); for (var i = 0; i < sprite.AllLayers.Count(); i++) { if (!SpriteSystem.TryGetLayer((ent, sprite), i, out var layer, false)) continue; - if (!ent.Comp.AffectLayersWithShaders && layer.ShaderPrototype != null) + // Apply color + SpriteSystem.LayerSetColor(layer, appearanceData.Color); + + var layerShader = layer.ShaderPrototype; + + if (!AllowedShader(layerShader?.Id, appearanceData)) + continue; + + // Apply shaders + if (appearanceData.Shader != null) + sprite.LayerSetShader(i, appearanceData.Shader); + } + } + + private void RemoveRecolor(Entity ent, SpriteComponent sprite) + { + var appearanceData = GetRecolorAppearanceData(ent); + + for (var i = 0; i < sprite.AllLayers.Count(); i++) + { + // TODO: Make it possible to get the previous color and shaders, currently impossible due to sprite system being fully clientside + + if (!SpriteSystem.TryGetLayer((ent, sprite), i, out var layer, false)) continue; - if (shader != null) - { - var instance = shader.Instance(); - sprite.LayerSetShader(i, instance, ent.Comp.Shader); - } + // Remove colors + SpriteSystem.LayerSetColor(layer, Color.White); + + // Remove shaders + var layerShader = layer.ShaderPrototype; + + if (!AllowedShader(layerShader?.Id, appearanceData)) + continue; - SpriteSystem.LayerSetColor(layer, ent.Comp.Color); + sprite.LayerSetShader(i, ""); } } + + private record RecolorAppearanceData(Color Color, string? Shader,List? ShaderBlacklist, List? ShaderWhitelist); + + private RecolorAppearanceData GetRecolorAppearanceData(Entity ent) + { + AppearanceSystem.TryGetData(ent, RecolorVisuals.Color, out Color color); + AppearanceSystem.TryGetData(ent, RecolorVisuals.Shader, out string? shader); + AppearanceSystem.TryGetData(ent, RecolorVisuals.ShaderBlacklist, out List? shaderBlacklist); + AppearanceSystem.TryGetData(ent, RecolorVisuals.ShaderWhitelist, out List? shaderWhitelist); + + return new RecolorAppearanceData(color, shader, shaderBlacklist, shaderWhitelist); + } + + private static bool AllowedShader(string? shader, RecolorAppearanceData appearanceData) + { + if (shader == null) + return true; + + return (appearanceData.ShaderBlacklist == null || !appearanceData.ShaderBlacklist.Contains(shader)) + && (appearanceData.ShaderWhitelist == null || appearanceData.ShaderWhitelist.Contains(shader)); + } } diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs index 337d301055..55db199f48 100644 --- a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs @@ -23,8 +23,9 @@ private void TryStartApplyRecolorDoAfter(EntityUid user, { Color = applier.Comp.Color, Shader = applier.Comp.Shader, + ShaderBlacklist = applier.Comp.ShaderBlacklist, + ShaderWhitelist = applier.Comp.ShaderWhitelist, Removable = applier.Comp.Removable, - AffectLayersWithShaders = applier.Comp.AffectLayersWithShaders, }; var doAfterArgs = new DoAfterArgs(EntityManager, @@ -46,8 +47,10 @@ private void OnApplyRecolorDoAfterEvent(Entity ent, ref Recolor( uid: args.Target.Value, color: args.Color, - affectLayersWithShaders: args.AffectLayersWithShaders, removable: args.Removable, - shader: args.Shader); + shader: args.Shader, + shaderWhitelist: args.ShaderWhitelist, + shaderBlacklist: args.ShaderBlacklist + ); } } diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.cs b/Content.Server/_DEN/Recolor/RecolorSystem.cs index 58d7208dab..527d7a75ff 100644 --- a/Content.Server/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Server/_DEN/Recolor/RecolorSystem.cs @@ -25,28 +25,47 @@ public override void Initialize() private void OnComponentStartup(Entity ent, ref ComponentStartup args) { - DirtyVisuals(ent); + RefreshVisuals(ent); } private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) { - DirtyVisuals(ent); + RemoveVisuals(ent); } - private void DirtyVisuals(Entity ent) + public void RefreshVisuals(Entity ent) { if (!TryComp(ent, out AppearanceComponent? appearance)) return; - _appearance.SetData(ent, RecolorVisuals.Color, true, appearance); + _appearance.SetData(ent, RecolorVisuals.Color, ent.Comp.Color, appearance); + + if (ent.Comp.Shader != null) + _appearance.SetData(ent, RecolorVisuals.Shader, ent.Comp.Shader, appearance); + if (ent.Comp.ShaderWhitelist != null) + _appearance.SetData(ent, RecolorVisuals.ShaderWhitelist, ent.Comp.ShaderWhitelist, appearance); + if (ent.Comp.ShaderBlacklist != null) + _appearance.SetData(ent, RecolorVisuals.ShaderBlacklist, ent.Comp.ShaderBlacklist, appearance); + } + + public void RemoveVisuals(Entity ent) + { + if (!TryComp(ent, out AppearanceComponent? appearance)) + return; + + _appearance.RemoveData(ent, RecolorVisuals.Color); + _appearance.RemoveData(ent, RecolorVisuals.Shader); + _appearance.RemoveData(ent, RecolorVisuals.ShaderBlacklist); + _appearance.RemoveData(ent, RecolorVisuals.ShaderWhitelist); } [PublicAPI] public void Recolor(EntityUid uid, Color color, - bool affectLayersWithShaders, bool removable, - string? shader = null) + string? shader = null, + List? shaderWhitelist = null, + List? shaderBlacklist = null) { if (HasComp(uid)) { @@ -60,10 +79,11 @@ public void Recolor(EntityUid uid, { Color = color, Shader = shader, - AffectLayersWithShaders = affectLayersWithShaders, + ShaderBlacklist = shaderBlacklist, + ShaderWhitelist = shaderWhitelist, Removable = removable, }; - + AddComp(uid, comp); } diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 8997592c6e..0180948d0d 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -17,10 +17,16 @@ public sealed partial class RecolorApplierComponent : Component public bool Removable { get; set; } /// - /// Whether or not the recolor should apply to layers that already have shaders. + /// Don't apply shader to layers with these shaders. (Sorry about the lack of shader prototype) /// [DataField] - public bool AffectLayersWithShaders; + public List? ShaderBlacklist; + + /// + /// Only apply shader to layers with these shaders. + /// + [DataField] + public List? ShaderWhitelist; /// /// The shader to apply to the recolored entity. diff --git a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs index 383e6d8142..4d628cb808 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs @@ -17,10 +17,16 @@ public sealed partial class RecoloredComponent : Component public bool Removable { get; set; } /// - /// Whether or not the recolor should apply to layers that already have shaders. + /// Don't apply to layers with these shaders. (Sorry about the lack of shader prototype) /// [DataField] - public bool AffectLayersWithShaders; + public List? ShaderBlacklist; + + /// + /// Only apply to layers with these shaders. + /// + [DataField] + public List? ShaderWhitelist; /// /// The shader to apply to the recolored entity. @@ -29,4 +35,9 @@ public sealed partial class RecoloredComponent : Component [DataField, AutoNetworkedField] public string? Shader { get; set; } + [DataField, AutoNetworkedField] + public Color? PreviousColor { get; set; } + + [DataField, AutoNetworkedField] + public Dictionary? PreviousShaders { get; set; } } diff --git a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs index c1f438b7f0..9847dba461 100644 --- a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs @@ -9,8 +9,9 @@ public abstract class SharedRecolorSystem : EntitySystem; public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent { public Color Color; - public bool AffectLayersWithShaders; public bool Removable; + public List? ShaderBlacklist; + public List? ShaderWhitelist; public string? Shader; public override DoAfterEvent Clone() { @@ -22,4 +23,7 @@ public override DoAfterEvent Clone() public enum RecolorVisuals { Color, + Shader, + ShaderWhitelist, + ShaderBlacklist, } diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index a009859ea1..a080c6a0a4 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -13,6 +13,9 @@ state: storage - type: RecolorApplier color: "#ffffff" + shaderBlacklist: + - unshaded + - DisplacedDraw - type: entity parent: SprayPaintCan From 7e2104442baf53a6b7f1834fcf3b3241ea7d4e64 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Mon, 23 Mar 2026 19:27:19 -0400 Subject: [PATCH 03/13] what the fuck? --- .../_DEN/Recolor/RecolorSystem.Applier.cs | 56 --- .../Components/RecolorApplierComponent.cs | 64 +++- .../_DEN/Recolor/RecolorSystem.Applier.cs | 112 ++++++ .../_DEN/Recolor/RecolorSystem.cs | 100 +++-- .../_DEN/Recolor/SharedRecolorSystem.cs | 29 -- .../Locale/en-US/_DEN/recolor/popups.ftl | 1 + Resources/Locale/en-US/_DEN/recolor/verbs.ftl | 1 + .../Entities/Objects/Tools/paint_cans.yml | 349 +++++++++++++++++- .../_DEN/Interface/VerbIcons/attributions.txt | 1 + .../Interface/VerbIcons/paint-spray-can.svg | 1 + .../VerbIcons/paint-spray-can.svg.192dpi.png | Bin 0 -> 4865 bytes .../paint-spray-can.svg.192dpi.png.yml | 2 + .../Objects/Tools/spray-paint.rsi/meta.json | 20 + .../Tools/spray-paint.rsi/spray-can-base.png | Bin 0 -> 254 bytes .../spray-can-color-closed.png | Bin 0 -> 207 bytes .../spray-paint.rsi/spray-can-color-open.png | Bin 0 -> 196 bytes 16 files changed, 601 insertions(+), 135 deletions(-) delete mode 100644 Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs create mode 100644 Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs rename {Content.Server => Content.Shared}/_DEN/Recolor/RecolorSystem.cs (63%) delete mode 100644 Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs create mode 100644 Resources/Locale/en-US/_DEN/recolor/popups.ftl create mode 100644 Resources/Locale/en-US/_DEN/recolor/verbs.ftl create mode 100644 Resources/Textures/_DEN/Interface/VerbIcons/attributions.txt create mode 100644 Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg create mode 100644 Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png create mode 100644 Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png.yml create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-base.png create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-color-closed.png create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-color-open.png diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs deleted file mode 100644 index 55db199f48..0000000000 --- a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Shared._DEN.Recolor; -using Content.Shared._DEN.Recolor.Components; -using Content.Shared.DoAfter; -using Content.Shared.Interaction; - -namespace Content.Server._DEN.Recolor; - -public sealed partial class RecolorSystem -{ - private void OnRecolorApplierAfterInteract(Entity ent, ref AfterInteractEvent args) - { - if (!args.CanReach || args.Target == null) - return; - - TryStartApplyRecolorDoAfter(args.User, args.Target.Value, ent); - } - - private void TryStartApplyRecolorDoAfter(EntityUid user, - EntityUid target, - Entity applier) - { - var doAfterEvent = new ApplyRecolorDoAfterEvent - { - Color = applier.Comp.Color, - Shader = applier.Comp.Shader, - ShaderBlacklist = applier.Comp.ShaderBlacklist, - ShaderWhitelist = applier.Comp.ShaderWhitelist, - Removable = applier.Comp.Removable, - }; - - var doAfterArgs = new DoAfterArgs(EntityManager, - user: user, - seconds: (float)applier.Comp.DoAfterDuration.TotalSeconds, - @event: doAfterEvent, - eventTarget: applier, - target: target, - used: applier); - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - } - - private void OnApplyRecolorDoAfterEvent(Entity ent, ref ApplyRecolorDoAfterEvent args) - { - if (args.Target is null) - return; - - Recolor( - uid: args.Target.Value, - color: args.Color, - removable: args.Removable, - shader: args.Shader, - shaderWhitelist: args.ShaderWhitelist, - shaderBlacklist: args.ShaderBlacklist - ); - } -} diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 0180948d0d..5d1778b834 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -1,10 +1,66 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Utility; namespace Content.Shared._DEN.Recolor.Components; -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class RecolorApplierComponent : Component { + + //Recolor Applier Specific Datafields + + /// + /// How long it takes for this object to apply the recolor to the target. + /// + [DataField] + public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(2.0f); + + /// + /// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely. + /// + [DataField, AutoNetworkedField] + public int? MaxUses; + + /// + /// Current amount of uses the applier can spray. + /// + [DataField, AutoNetworkedField] + public int UsesLeft; + + /// + /// LocId used for the "you're outta paint" popup. + /// + [DataField, AutoNetworkedField] + public LocId NoMoreUsesPopup = "spray-paint-empty"; + + /// + /// Sound to play when the doafter is over. + /// + [DataField, AutoNetworkedField] + public SoundSpecifier? DoafterSound = new SoundPathSpecifier("/Audio/Effects/Spray2.ogg"); + + /// + /// Entity Whitelist to determine what items can be repainted. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? EntityWhitelist; + + /// + /// LocId used for the apply recolor verb. + /// + [DataField, AutoNetworkedField] + public LocId VerbText = "verb-spray-paint"; + + /// + /// Icon used for the apply recolor verb. + /// + [DataField, AutoNetworkedField] + public SpriteSpecifier VerbIcon = new SpriteSpecifier.Texture(new ResPath("/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png")); + + // Recolor Datafields + /// /// The color to apply to the object being recolored. /// @@ -34,10 +90,4 @@ public sealed partial class RecolorApplierComponent : Component /// [DataField] public string? Shader = "Desaturated"; - - /// - /// How long it takes for this object to apply the recolor to the target. - /// - [DataField] - public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3.0f); } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs new file mode 100644 index 0000000000..dab4ebd99b --- /dev/null +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -0,0 +1,112 @@ +using Content.Shared._DEN.Recolor.Components; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Verbs; + +namespace Content.Shared._DEN.Recolor; + +public abstract partial class RecolorSystem +{ + private static void OnComponentStartup(Entity ent, ref ComponentStartup args) + { + if (ent.Comp.MaxUses != null) + ent.Comp.UsesLeft = ent.Comp.MaxUses.Value; + } + + private void OnRecolorApplierAfterInteract(Entity ent, ref AfterInteractEvent args) + { + if (!args.CanReach + || args.Target == null + || CanRecolor(ent, args.User, args.Target.Value)) + return; + + TryStartApplyRecolorDoAfter(args.User, args.Target.Value, ent); + } + + private void TryStartApplyRecolorDoAfter( + EntityUid user, + EntityUid target, + Entity applier) + { + var doAfterEvent = new ApplyRecolorDoAfterEvent + { + Color = applier.Comp.Color, + Shader = applier.Comp.Shader, + ShaderBlacklist = applier.Comp.ShaderBlacklist, + ShaderWhitelist = applier.Comp.ShaderWhitelist, + Removable = applier.Comp.Removable, + }; + + var doAfterArgs = new DoAfterArgs(EntityManager, + user: user, + seconds: (float)applier.Comp.DoAfterDuration.TotalSeconds, + @event: doAfterEvent, + eventTarget: applier, + target: target, + used: applier); + + _doAfter.TryStartDoAfter(doAfterArgs); + } + + private void OnApplyRecolorDoAfterEvent(Entity ent, ref ApplyRecolorDoAfterEvent args) + { + if (args.Target is null || args.Handled || args.Cancelled) + return; + + Recolor( + uid: args.Target.Value, + color: args.Color, + removable: args.Removable, + shader: args.Shader, + shaderWhitelist: args.ShaderWhitelist, + shaderBlacklist: args.ShaderBlacklist + ); + + PlayUseSound(ent, args.User); + + ent.Comp.UsesLeft--; + + args.Handled = true; + } + + private bool CanRecolor(Entity applier, EntityUid user, EntityUid target) + { + if (_whitelist.IsWhitelistPass(applier.Comp.EntityWhitelist, target) + || !_openable.IsClosed(applier, user) + || applier.Comp is not { UsesLeft: <= 0, MaxUses: not null }) + return true; + + PopupNoMoreUses(applier,user); + return false; + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess + || !args.CanInteract + || CanRecolor(ent, args.User, args.Target)) + return; + + var user = args.User; + var target = args.Target; + + var verb = new UtilityVerb + { + Act = () => TryStartApplyRecolorDoAfter(user, target, ent), + Text = Loc.GetString(ent.Comp.VerbText), + Icon = ent.Comp.VerbIcon, + }; + + args.Verbs.Add(verb); + } + + private void PopupNoMoreUses(Entity ent, EntityUid user) + { + _popup.PopupPredicted(Loc.GetString(ent.Comp.NoMoreUsesPopup, ("name", ent)),ent, user); + } + + private void PlayUseSound(Entity ent, EntityUid user) + { + _audio.PlayPredicted(ent.Comp.DoafterSound, ent, user); + } +} diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs similarity index 63% rename from Content.Server/_DEN/Recolor/RecolorSystem.cs rename to Content.Shared/_DEN/Recolor/RecolorSystem.cs index 527d7a75ff..264bd55a9d 100644 --- a/Content.Server/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -1,16 +1,24 @@ -using Content.Server.DoAfter; -using Content.Shared._DEN.Recolor; using Content.Shared._DEN.Recolor.Components; +using Content.Shared.DoAfter; using Content.Shared.Interaction; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Content.Shared.Whitelist; using JetBrains.Annotations; -using Robust.Server.GameObjects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Serialization; -namespace Content.Server._DEN.Recolor; +namespace Content.Shared._DEN.Recolor; -public sealed partial class RecolorSystem : SharedRecolorSystem +public abstract partial class RecolorSystem : EntitySystem { - [Dependency] private readonly AppearanceSystem _appearance = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { @@ -19,8 +27,10 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnComponentShutdown); - SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); SubscribeLocalEvent(OnRecolorApplierAfterInteract); + SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent>(OnGetVerbs); } private void OnComponentStartup(Entity ent, ref ComponentStartup args) @@ -33,32 +43,6 @@ private void OnComponentShutdown(Entity ent, ref ComponentSh RemoveVisuals(ent); } - public void RefreshVisuals(Entity ent) - { - if (!TryComp(ent, out AppearanceComponent? appearance)) - return; - - _appearance.SetData(ent, RecolorVisuals.Color, ent.Comp.Color, appearance); - - if (ent.Comp.Shader != null) - _appearance.SetData(ent, RecolorVisuals.Shader, ent.Comp.Shader, appearance); - if (ent.Comp.ShaderWhitelist != null) - _appearance.SetData(ent, RecolorVisuals.ShaderWhitelist, ent.Comp.ShaderWhitelist, appearance); - if (ent.Comp.ShaderBlacklist != null) - _appearance.SetData(ent, RecolorVisuals.ShaderBlacklist, ent.Comp.ShaderBlacklist, appearance); - } - - public void RemoveVisuals(Entity ent) - { - if (!TryComp(ent, out AppearanceComponent? appearance)) - return; - - _appearance.RemoveData(ent, RecolorVisuals.Color); - _appearance.RemoveData(ent, RecolorVisuals.Shader); - _appearance.RemoveData(ent, RecolorVisuals.ShaderBlacklist); - _appearance.RemoveData(ent, RecolorVisuals.ShaderWhitelist); - } - [PublicAPI] public void Recolor(EntityUid uid, Color color, @@ -83,7 +67,7 @@ public void Recolor(EntityUid uid, ShaderWhitelist = shaderWhitelist, Removable = removable, }; - + AddComp(uid, comp); } @@ -95,4 +79,50 @@ public void RemoveRecolor(Entity ent) RemComp(ent, ent.Comp); } + + private void RefreshVisuals(Entity ent) + { + if (!TryComp(ent, out AppearanceComponent? appearance)) + return; + + _appearance.SetData(ent, RecolorVisuals.Color, ent.Comp.Color, appearance); + + if (ent.Comp.Shader != null) + _appearance.SetData(ent, RecolorVisuals.Shader, ent.Comp.Shader, appearance); + if (ent.Comp.ShaderWhitelist != null) + _appearance.SetData(ent, RecolorVisuals.ShaderWhitelist, ent.Comp.ShaderWhitelist, appearance); + if (ent.Comp.ShaderBlacklist != null) + _appearance.SetData(ent, RecolorVisuals.ShaderBlacklist, ent.Comp.ShaderBlacklist, appearance); + } + + private void RemoveVisuals(Entity ent) + { + _appearance.RemoveData(ent, RecolorVisuals.Color); + _appearance.RemoveData(ent, RecolorVisuals.Shader); + _appearance.RemoveData(ent, RecolorVisuals.ShaderBlacklist); + _appearance.RemoveData(ent, RecolorVisuals.ShaderWhitelist); + } +} + +[Serializable, NetSerializable] +public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent +{ + public Color Color; + public bool Removable; + public List? ShaderBlacklist; + public List? ShaderWhitelist; + public string? Shader; + public override DoAfterEvent Clone() + { + return this; + } +} + +[Serializable, NetSerializable] +public enum RecolorVisuals +{ + Color, + Shader, + ShaderWhitelist, + ShaderBlacklist, } diff --git a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs deleted file mode 100644 index 9847dba461..0000000000 --- a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Serialization; - -namespace Content.Shared._DEN.Recolor; - -public abstract class SharedRecolorSystem : EntitySystem; - -[Serializable, NetSerializable] -public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent -{ - public Color Color; - public bool Removable; - public List? ShaderBlacklist; - public List? ShaderWhitelist; - public string? Shader; - public override DoAfterEvent Clone() - { - return this; - } -} - -[Serializable, NetSerializable] -public enum RecolorVisuals -{ - Color, - Shader, - ShaderWhitelist, - ShaderBlacklist, -} diff --git a/Resources/Locale/en-US/_DEN/recolor/popups.ftl b/Resources/Locale/en-US/_DEN/recolor/popups.ftl new file mode 100644 index 0000000000..8f61036b37 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/recolor/popups.ftl @@ -0,0 +1 @@ +spray-paint-empty = {$name} is out of paint! diff --git a/Resources/Locale/en-US/_DEN/recolor/verbs.ftl b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl new file mode 100644 index 0000000000..44282e2c86 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl @@ -0,0 +1 @@ +verb-spray-paint = Spray Paint diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index a080c6a0a4..d36b982c40 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -1,21 +1,70 @@ + + - type: entity parent: BaseItem id: SprayPaintCan abstract: true description: it's spray paint components: + - type: Appearance - type: Sprite - sprite: Objects/Tools/crowbar.rsi - state: icon - - type: Item - storedSprite: - sprite: Objects/Tools/crowbar.rsi - state: storage + sprite: _DEN/Objects/Tools/spray-paint.rsi + state: spray-can-base + - type: Openable + closeable: true + sound: + path: /Audio/Effects/pop_high.ogg + closeSound: + path: /Audio/Effects/pop_high.ogg - type: RecolorApplier - color: "#ffffff" shaderBlacklist: - unshaded - DisplacedDraw + maxUses: 64 + +# Rainbow colors + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanRed + name: red spray paint + components: + - type: RecolorApplier + color: red + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: red + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: red} + False: {state: "spray-can-color-closed" , color: red} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanOrange + name: orange spray paint + components: + - type: RecolorApplier + color: orange + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: orange + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: orange} + False: {state: "spray-can-color-closed" , color: orange} - type: entity parent: SprayPaintCan @@ -23,10 +72,294 @@ name: yellow spray paint components: - type: RecolorApplier - color: "#ffff00" + color: yellow + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: yellow + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: yellow} + False: {state: "spray-can-color-closed" , color: yellow} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanLimeGreen + name: lime green spray paint + components: + - type: RecolorApplier + color: greenyellow + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: greenyellow + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: greenyellow} + False: {state: "spray-can-color-closed" , color: greenyellow} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanGreen + name: green spray paint + components: + - type: RecolorApplier + color: green + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: green + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: green} + False: {state: "spray-can-color-closed" , color: green} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanCyan + name: cyan spray paint + components: + - type: RecolorApplier + color: cyan + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: cyan + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: cyan} + False: {state: "spray-can-color-closed" , color: cyan} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanBlue + name: blue spray paint + components: + - type: RecolorApplier + color: blue + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: blue + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: blue} + False: {state: "spray-can-color-closed" , color: blue} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanPurple + name: purple spray paint + components: + - type: RecolorApplier + color: purple + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: purple + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: purple} + False: {state: "spray-can-color-closed" , color: purple} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanPink + name: pink spray paint + components: + - type: RecolorApplier + color: hotpink + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: hotpink + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: hotpink} + False: {state: "spray-can-color-closed" , color: hotpink} + +# Metallics + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanGold + name: gold spray paint + components: + - type: RecolorApplier + color: gold + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: gold + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: gold} + False: {state: "spray-can-color-closed" , color: gold} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanSilver + name: silver spray paint + components: + - type: RecolorApplier + color: silver + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: silver + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: silver} + False: {state: "spray-can-color-closed" , color: silver} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanCopper + name: copper spray paint + components: + - type: RecolorApplier + color: chocolate + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: chocolate + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: chocolate} + False: {state: "spray-can-color-closed" , color: chocolate} + +# Shades and Neutrals - type: entity parent: SprayPaintCan id: SprayPaintCanWhite name: white spray paint + components: + - type: RecolorApplier + color: white + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: white + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: white} + False: {state: "spray-can-color-closed" , color: white} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanGray + name: gray spray paint + components: + - type: RecolorApplier + color: gray + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: gray + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: gray} + False: {state: "spray-can-color-closed" , color: gray} +- type: entity + parent: SprayPaintCan + id: SprayPaintCanBlack + name: black spray paint + components: + - type: RecolorApplier + color: &black "#333333" + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: *black + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: *black} + False: {state: "spray-can-color-closed" , color: *black} + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanBrown + name: brown spray paint + components: + - type: RecolorApplier + color: saddlebrown + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: saddlebrown + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: saddlebrown} + False: {state: "spray-can-color-closed" , color: saddlebrown} diff --git a/Resources/Textures/_DEN/Interface/VerbIcons/attributions.txt b/Resources/Textures/_DEN/Interface/VerbIcons/attributions.txt new file mode 100644 index 0000000000..9109b52bcd --- /dev/null +++ b/Resources/Textures/_DEN/Interface/VerbIcons/attributions.txt @@ -0,0 +1 @@ +Paint Spray Can Outline SVG Vector from https://www.svgrepo.com/svg/5941/paint-spray-can-outline, license cc0 diff --git a/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg b/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg new file mode 100644 index 0000000000..1b40bd3134 --- /dev/null +++ b/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png b/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..545a6730bd343ccd175a2390aa7a69ed4e577b62 GIT binary patch literal 4865 zcmeHLX!2K&+2dAS{ABAYm~qfh2RW^PBn2Z$8bp`Ox>}y?4%e=bU@ax&L$S zyLQ6Wd9B7~4FJH}qeq-h0iXaM6@Z!wynT#)D}gr*`IPf_fZd862Vi~GQKy4$u~}1n zaoj1Y_v@L3iW3={^@*VxdgeI%Ten@a5zU&LVvAK>oS2^*ncUGZT4%WDo1+D(NzE6& ziNCn_#ZQGvZ%^)y2q;NDx8ub+>g%mpr}%4fPrdoq?kimXYZ(uFY2z}bm8 zO3S1Jky9!pwXU>EYCB`3G@d*3=zqPq4>W_cK~_t9dwb7Ug!V*DpYue#clkU3{CT^` zDGabOG3vWj;`qo61Nc8r%{reXFCbl@<>O|>4jVX1fCmL8iuzW00tu;K761# zkUl;^12t2hB`5*lV!oKpu6;=H6AMui)7*CAMu~|b2CR8BeOtB`Av5&|D@%pw78jYS zXAS_H&et81-$SOn&rXXTvVGC!ZY>N5zq^qzZSIES2!l%n4jjtJDO{_K65z*s#(K#+ z%xW6s?@-;_-$!&90WklzUQ_1RYcju3^O8P$9oH&c2LSc_ptAh=KCdN6l&5ab&FX`a z(W{aTmGYk@kWaWLK;w=nNRN5QlwIKXNSYu@eATKWH$w9V*Wm7){#GjZy#t z%&NV&_hS}gJkq^pn+G2$fnEL3`^FeK{MviA`JAx=*hGSh{3)dMEibH{PHBBh(pCe+ ztIB%P36^c;M=kk$_3e^m>?44W@z%CKEl+YcN>Ek<4Ate2W+CGaJjzGr?|f_Mr}(Lm z{y^3;k)}9?1K_mXeaRd$=8p5eys@itqQd|Xw~JV{1(Fz5!A#qQng`? zc0CS2)@x%&K|kv2;rnj%s|1JBHPpd&!7u875fqu^9q$`3Cso9;gb!=WBuHZ86vyY>qtffzA2+i$QRmXT){^uOOb?s%q` zHvN)VF(mZpniLqhv5_@Z3WgzMgf9vtZ>S6ECAfnWsN5?&X;Umcb6I_Vg+v-6vga_2}w*uf10e#Hn$PQpqCc+L-4r%~q9_duHeZhT^9juxl$SizJ1p$bWlAyp zBIz0i!!?6clcBSI2SDM=0eg^Q6R4*4zoI+DW=n!66<8@bJ39xZ^BbRp*SUOwZRD+g za(Lsibeo1iwvxio<$f?UILMEbDf2gYekAEA9DCoCtm+vsNDmf|pwH1^rn8%+Tc_#F z$Q6Be!y^00nK3*wmrX0FoG8SZsoZ(muBDe9F-Q*(r=s16K9cXA`}RURi7G6R;l;y9 z6*I;|eow;7zr1G)Unwo(x$U!h`3Lk6V#pK*&k1w-$ZpaAaSalKmrtFQan%H`t?NIZ z*sLd;U^<+LJ0q@Q4MZAPTw=owoQw=`XfwajkEn$|#CE4fu1%5WG0lr=t1HFtRL*D8 ziYP8($v#`9#`PHNR_AYrv%+V1wG@AG6mopbMkmOcS(`^WF4i?#Qv8nLI23RpH7K@I zejVkwYB-L4`n{$r7_#JwkWA*Qu4j%lJ;j9 ztU~8D^0e!dMS7rEXd=7OM~K5gobY4MAF#YOq|@Z7)uZn7FzjLNeaiR}uEF54uUM5O zk0?{^Rww3mV+6!(;tc&SVo~Yh9C1Pa3-hX;E0apxHYK1su4MkIEr6W=gp?)UJ6Z4r z(-Kb~zo@+)Gr4makNwTh=cYqG#Vd39W5dzvdy-%FWoY@_PFL)*O!L%g-JK%W>@ks9 z29q{xHI;jq^LiAW!17aPfW7IrQQpfezc4cvDf_GV??Kuam!DYJR7k~z&aJ&<$jc72 zB666rj$5=qJx1K%k@rNxb`V-fdqP3y9}F_|%a-;vm3xn4wld-bUjeQ9-69#!G{xa% z#fdm-ri=2qFm*8B5ACy6D?BcDXypo2q7I zYUozn$)0tcsN5G5v7FtTnG`Q%hl(`H*F>9vs#jVd8NGF8#hR{NBs(bnu}n2SXgjVu z2;YTSEJCO(=g^qVD$BEIrul+q6`1_S7EBhgg5`AYjr7M8=A$a7CqKvV?^WEVJnM1X z)!GtbA5GE0D{*1pppM^(k!$x3eI1^Q$7}mSPmD4AEfvL-aVXhw9LXrOQ_8vt1V2_6 zQl3ImkG}(Lb~mVM8ZVg4W>w#*&6k9DVU^9s(oj{yMSA6ChPR(8!*Qn2L^i2n=vb_$ z1d{8evhEG$FNEJv4}kEml|a?dOh~Bq_YSR!#7;G!RhRFx67M~e2&b;bbpV^tc7_UK zLE|=C90*slr%qu6n4Ip>lmsG49caY^V(0#9$*Zzt2J7TQY(l4dEBAZ`0UsAWwMB4| zz^w1TwC~&H7cGpg?e+?Q5f2BhHQW9ZB#5L6(+$+RKDpIip+-{=Hh$71#a>qp(=pWK zXLM%e=fi5;kTk34za{22JAvMj06!9;VMzW1nuug0y0EQhM(i)OwI@j5KFc*MsAW;) zM`d@_h+f#h$<@ul%cDezm63+Fs4qSHwPAi3A=S?iz3p2c$mih4;k1f^)KeI!83uNdmgD-(qSlTX+s8>U9sGC9> zLr9yuDtX><$Ss<&f)%c_$PN(d@<}se({cXdwL#i-$7Zj)d!JJk3?fxMP5@g;Wewn^ z{P(L>Ja=A|fe`$ciidf87;s&3 zxKO;rtHXd{4KqHn8)kQBRUp*m2$^hOD)8)X_=EW=LBuaH04S=%ZgT_<+6UmlLIqAE zyM6@!&f%XTf5zos8~V>;__G-PuZtnN+3pkM!T$9L@@TXv4AI{K{zrJV1$1q9sDbdf zN3PPf8N)ZbJkX=af6-s{PoCV^P;pmpZ|6UuvmXtC)5*@I6Jkz>LBcAUj`qT1r^l>5 z!YQ&-BCn3Kqc%1+m2Wt*Gva#FG=-q$BVuDjP7|1$5vKRFEO8b27umD*^C-&x#C>2^ zy4Y}eJ(&De?o$&1G2Es9WL(30+F+l|&RHy5EHzEr8{KNW z70l-(=7x};7METHr#)BBT**tz-l1Ui*NwD}kq>^N9~9E35` literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png.yml b/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json new file mode 100644 index 0000000000..b66995521a --- /dev/null +++ b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprites by honeyed-lemons (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "spray-can-base" + }, + { + "name": "spray-can-color-closed" + }, + { + "name": "spray-can-color-open" + } + ] +} diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-base.png b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-base.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc007a4684ec37068189eca6afbebf0babb5cd7 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}J3L(+Ln2z= zPQA$KtjOb<@2y_IS${{(+G2{eMN6`UON`>*Ai1}{UvAwKbb|4rmdZ{ym4?R_=j@Zy zmaW=0`SCfnX+dv%;+-^)hFlG~8kN0lRoTqW$UR%rK65^kOm3=b5?~7Ql1OG?aA8qo zQ4^K(xgha)?E{nI&c?Y->o4qIaIGuq^(CDW_M{#UP2ICjtne2)+0rP#9?3vqTcyIa#2jMF5OY;Rd6!kYR;Ll!Lwr_gS zgO~|BR+Sx>t_rzxhK)ImZRQOD;k8=@l4X)OCNyd{&HFk{XS3kMg={V?T24Dk_avOF zjFT4nl;0Qo;PK`1M)Qw1V;TJQL*LdZEIs1sal-s2HPpc99pLfSO{&*=}})wS-b zu2VB?U+<-%G;!w5DCv7yYZt9r|J{*k`H>AA9O^9&Ga}EaYZT2<4RVoI5)|An*TIn@ ss Date: Tue, 24 Mar 2026 19:34:25 -0400 Subject: [PATCH 04/13] add recolor remover, etc etc etc etc --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 21 +++-- .../Components/RecolorApplierComponent.cs | 61 +++++++++++--- .../Components/RecolorRemoverComponent.cs | 21 +++++ .../Recolor/Components/RecoloredComponent.cs | 17 ++-- .../_DEN/Recolor/RecolorSystem.Applier.cs | 76 ++++++++++++----- .../_DEN/Recolor/RecolorSystem.Remover.cs | 83 +++++++++++++++++++ Content.Shared/_DEN/Recolor/RecolorSystem.cs | 31 +++++-- .../Locale/en-US/_DEN/recolor/examine.ftl | 5 ++ .../Locale/en-US/_DEN/recolor/popups.ftl | 4 + Resources/Locale/en-US/_DEN/recolor/verbs.ftl | 1 + .../Objects/Specific/Janitorial/soap.yml | 1 + .../Entities/Objects/Tools/paint_cans.yml | 22 ++++- 12 files changed, 287 insertions(+), 56 deletions(-) create mode 100644 Content.Shared/_DEN/Recolor/Components/RecolorRemoverComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs create mode 100644 Resources/Locale/en-US/_DEN/recolor/examine.ftl diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index 7dab591fdd..79f3fef776 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -14,7 +14,6 @@ namespace Content.Client._DEN.Recolor; public sealed class RecolorVisualizerSystem : VisualizerSystem { - [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ItemSystem _item = default!; public override void Initialize() @@ -65,7 +64,11 @@ private void ApplyRecolorEquipment(Entity ent, ref GetEquipm private void ApplyRecolorLayers(Entity ent, List<(string, PrototypeLayerData)> layers) { - var appearanceData = GetRecolorAppearanceData(ent); + if(!TryComp(ent, out var appearance)) + return; + + var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); + foreach (var (_, layerData) in layers) { // Apply Color @@ -82,7 +85,10 @@ private void ApplyRecolorLayers(Entity ent, List<(string, Pr private void ApplyRecolorSprite(Entity ent, SpriteComponent sprite) { - var appearanceData = GetRecolorAppearanceData(ent); + if(!TryComp(ent, out var appearance)) + return; + + var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); for (var i = 0; i < sprite.AllLayers.Count(); i++) { @@ -105,7 +111,10 @@ private void ApplyRecolorSprite(Entity ent, SpriteComponent private void RemoveRecolor(Entity ent, SpriteComponent sprite) { - var appearanceData = GetRecolorAppearanceData(ent); + if(!TryComp(ent, out var appearance)) + return; + + var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); for (var i = 0; i < sprite.AllLayers.Count(); i++) { @@ -123,13 +132,13 @@ private void RemoveRecolor(Entity ent, SpriteComponent sprit if (!AllowedShader(layerShader?.Id, appearanceData)) continue; - sprite.LayerSetShader(i, ""); + sprite.LayerSetShader(i, null, null); } } private record RecolorAppearanceData(Color Color, string? Shader,List? ShaderBlacklist, List? ShaderWhitelist); - private RecolorAppearanceData GetRecolorAppearanceData(Entity ent) + private RecolorAppearanceData GetRecolorAppearanceData(Entity ent) { AppearanceSystem.TryGetData(ent, RecolorVisuals.Color, out Color color); AppearanceSystem.TryGetData(ent, RecolorVisuals.Shader, out string? shader); diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 5d1778b834..23d709666c 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -11,16 +11,34 @@ public sealed partial class RecolorApplierComponent : Component //Recolor Applier Specific Datafields + /// + /// The color to apply to the object being recolored. + /// + [DataField] + public string? ColorName; + + /// + /// Type of paint used, purely for flavor. + /// + [DataField] + public string? PaintType; + /// /// How long it takes for this object to apply the recolor to the target. /// [DataField] public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(2.0f); + /// + /// Sound to play when the doafter is over. + /// + [DataField] + public SoundSpecifier? DoafterSound = new SoundPathSpecifier("/Audio/Effects/Spray2.ogg"); + /// /// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely. /// - [DataField, AutoNetworkedField] + [DataField] public int? MaxUses; /// @@ -30,33 +48,51 @@ public sealed partial class RecolorApplierComponent : Component public int UsesLeft; /// - /// LocId used for the "you're outta paint" popup. + /// LocId used for the "you're outta paint!" popup. /// - [DataField, AutoNetworkedField] + [DataField] public LocId NoMoreUsesPopup = "spray-paint-empty"; /// - /// Sound to play when the doafter is over. + /// LocId used for the "you can't paint that!" popup. /// - [DataField, AutoNetworkedField] - public SoundSpecifier? DoafterSound = new SoundPathSpecifier("/Audio/Effects/Spray2.ogg"); + [DataField] + public LocId CantRecolorPopup = "spray-paint-fail"; + + /// + /// LocId used for the "you can't paint that!" popup. + /// + [DataField] + public LocId ColorShowcaseExamine = "spray-paint-examine-color"; + + /// + /// LocId used for the "you can't paint that!" popup. + /// + [DataField] + public LocId UsesExamine = "spray-paint-examine-uses"; /// /// Entity Whitelist to determine what items can be repainted. /// - [DataField, AutoNetworkedField] + [DataField] public EntityWhitelist? EntityWhitelist; + /// + /// Entity Blacklist to determine what items can't be repainted. + /// + [DataField] + public EntityWhitelist? EntityBlacklist; + /// /// LocId used for the apply recolor verb. /// - [DataField, AutoNetworkedField] + [DataField] public LocId VerbText = "verb-spray-paint"; /// /// Icon used for the apply recolor verb. /// - [DataField, AutoNetworkedField] + [DataField] public SpriteSpecifier VerbIcon = new SpriteSpecifier.Texture(new ResPath("/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png")); // Recolor Datafields @@ -64,13 +100,14 @@ public sealed partial class RecolorApplierComponent : Component /// /// The color to apply to the object being recolored. /// - [DataField] + [DataField, AutoNetworkedField] public Color Color { get; set; } /// - /// Whether or not the color applied can be removed via normal means. + /// Whether the color applied can be removed via normal means. /// - public bool Removable { get; set; } + [DataField,AutoNetworkedField] + public bool Removable = true; /// /// Don't apply shader to layers with these shaders. (Sorry about the lack of shader prototype) diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorRemoverComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorRemoverComponent.cs new file mode 100644 index 0000000000..061aa2b548 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/Components/RecolorRemoverComponent.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DEN.Recolor.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RecolorRemoverComponent : Component +{ + /// + /// How long it takes for this object to remove the recolor on the target. + /// + [DataField] + public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(2.0f); + + /// + /// Sound to play when the doafter is over. + /// + [DataField] + public SoundSpecifier? DoafterSound; + +} diff --git a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs index 4d628cb808..31a9503917 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs @@ -11,21 +11,28 @@ public sealed partial class RecoloredComponent : Component [DataField, AutoNetworkedField] public Color Color = Color.White; + /// + /// Type of paint used, purely for flavor. + /// + [DataField, AutoNetworkedField] + public string? PaintType; + /// /// Whether or not this component can be removed by an entity with RecolorRemoverComponent. /// + [DataField, AutoNetworkedField] public bool Removable { get; set; } /// /// Don't apply to layers with these shaders. (Sorry about the lack of shader prototype) /// - [DataField] + [DataField, AutoNetworkedField] public List? ShaderBlacklist; /// /// Only apply to layers with these shaders. /// - [DataField] + [DataField, AutoNetworkedField] public List? ShaderWhitelist; /// @@ -34,10 +41,4 @@ public sealed partial class RecoloredComponent : Component /// [DataField, AutoNetworkedField] public string? Shader { get; set; } - - [DataField, AutoNetworkedField] - public Color? PreviousColor { get; set; } - - [DataField, AutoNetworkedField] - public Dictionary? PreviousShaders { get; set; } } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs index dab4ebd99b..ee21bbc33b 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -1,11 +1,13 @@ using Content.Shared._DEN.Recolor.Components; using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Verbs; +using Robust.Shared.ColorNaming; namespace Content.Shared._DEN.Recolor; -public abstract partial class RecolorSystem +public sealed partial class RecolorSystem { private static void OnComponentStartup(Entity ent, ref ComponentStartup args) { @@ -17,13 +19,16 @@ private void OnRecolorApplierAfterInteract(Entity ent, { if (!args.CanReach || args.Target == null - || CanRecolor(ent, args.User, args.Target.Value)) + || args.Handled + || !CanRecolor(ent, args.User, args.Target.Value)) + { return; + } - TryStartApplyRecolorDoAfter(args.User, args.Target.Value, ent); + args.Handled = TryStartApplyRecolorDoAfter(args.User, args.Target.Value, ent); } - private void TryStartApplyRecolorDoAfter( + private bool TryStartApplyRecolorDoAfter( EntityUid user, EntityUid target, Entity applier) @@ -31,6 +36,7 @@ private void TryStartApplyRecolorDoAfter( var doAfterEvent = new ApplyRecolorDoAfterEvent { Color = applier.Comp.Color, + PaintType = applier.Comp.PaintType, Shader = applier.Comp.Shader, ShaderBlacklist = applier.Comp.ShaderBlacklist, ShaderWhitelist = applier.Comp.ShaderWhitelist, @@ -43,9 +49,14 @@ private void TryStartApplyRecolorDoAfter( @event: doAfterEvent, eventTarget: applier, target: target, - used: applier); + used: applier) + { + BreakOnDropItem = true, + BreakOnMove = true, + BreakOnHandChange = true, + }; - _doAfter.TryStartDoAfter(doAfterArgs); + return _doAfter.TryStartDoAfter(doAfterArgs); } private void OnApplyRecolorDoAfterEvent(Entity ent, ref ApplyRecolorDoAfterEvent args) @@ -57,34 +68,49 @@ private void OnApplyRecolorDoAfterEvent(Entity ent, ref uid: args.Target.Value, color: args.Color, removable: args.Removable, + paintType: args.PaintType, shader: args.Shader, shaderWhitelist: args.ShaderWhitelist, shaderBlacklist: args.ShaderBlacklist ); - PlayUseSound(ent, args.User); + _audio.PlayPredicted(ent.Comp.DoafterSound, ent, args.User); + + ent.Comp.UsesLeft -= 1; - ent.Comp.UsesLeft--; + Dirty(ent); args.Handled = true; } private bool CanRecolor(Entity applier, EntityUid user, EntityUid target) { - if (_whitelist.IsWhitelistPass(applier.Comp.EntityWhitelist, target) - || !_openable.IsClosed(applier, user) - || applier.Comp is not { UsesLeft: <= 0, MaxUses: not null }) - return true; + // Check if the applier is opened + if (_openable.IsClosed(applier, user, predicted: true)) + return false; - PopupNoMoreUses(applier,user); - return false; + // Check whitelist and blacklist + if (!_whitelist.CheckBoth(target, applier.Comp.EntityBlacklist, applier.Comp.EntityWhitelist)) + { + _popup.PopupClient(Loc.GetString(applier.Comp.CantRecolorPopup, ("target", target)),applier, user); + return false; + } + + // Check if there's enough uses left + if (applier.Comp is { UsesLeft: <= 0, MaxUses: not null }) + { + _popup.PopupClient(Loc.GetString(applier.Comp.NoMoreUsesPopup, ("name", applier)),applier, user); + return false; + } + + return true; } - private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + private void OnGetApplierVerbs(Entity ent, ref GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract - || CanRecolor(ent, args.User, args.Target)) + || !CanRecolor(ent, args.User, args.Target)) return; var user = args.User; @@ -100,13 +126,19 @@ private void OnGetVerbs(Entity ent, ref GetVerbsEvent ent, EntityUid user) + private void OnExamined(Entity ent, ref ExaminedEvent args) { - _popup.PopupPredicted(Loc.GetString(ent.Comp.NoMoreUsesPopup, ("name", ent)),ent, user); - } + if (!args.IsInDetailsRange) + return; + // Get the color's name. If the color itself has a color defined, use it + var colorName = ent.Comp.ColorName ?? + // Otherwise, use what colornaming THINKS it is. + ColorNaming.Describe(ent.Comp.Color, _localizationManager); - private void PlayUseSound(Entity ent, EntityUid user) - { - _audio.PlayPredicted(ent.Comp.DoafterSound, ent, user); + args.PushMarkup(Loc.GetString(ent.Comp.ColorShowcaseExamine, ("color", ent.Comp.Color), ("colorName", colorName))); + + // If max uses isn't null (signifying this item has infinite uses), show uses count + if (ent.Comp.MaxUses != null) + args.PushMarkup(Loc.GetString(ent.Comp.UsesExamine, ("uses", ent.Comp.UsesLeft))); } } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs new file mode 100644 index 0000000000..9762142581 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs @@ -0,0 +1,83 @@ +using Content.Shared._DEN.Recolor.Components; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared._DEN.Recolor; + +public sealed partial class RecolorSystem +{ + private void OnRecolorRemoverAfterInteract(Entity ent, ref AfterInteractEvent args) + { + if (args.Target == null || args.Handled || !args.CanReach) + return; + + if (!TryComp(args.Target, out var recolored) || !recolored.Removable) + return; + + if (recolored.PaintType != null) + _popup.PopupClient(Loc.GetString("recolor-remover-start-popup", ("name", args.Target), ("paintType", recolored.PaintType)),ent,args.User); + + args.Handled = TryStartRemoveRecolorDoAfter(args.User, (args.Target.Value, recolored), ent); + } + + private bool TryStartRemoveRecolorDoAfter( + EntityUid user, + Entity target, + Entity remover) + { + var doAfterEvent = new RemoveRecolorDoAfterEvent(); + + var doAfterArgs = new DoAfterArgs(EntityManager, + user: user, + seconds: (float)remover.Comp.DoAfterDuration.TotalSeconds, + @event: doAfterEvent, + eventTarget: remover, + target: target, + used: remover) + { + BreakOnDropItem = true, + BreakOnMove = true, + BreakOnHandChange = true, + }; + + return _doAfter.TryStartDoAfter(doAfterArgs); + } + + private void OnRemoveRecolorDoAfterEvent(Entity ent, ref RemoveRecolorDoAfterEvent args) + { + if (args.Handled || args.Cancelled || !TryComp(args.Target, out var recolored)) + return; + + RemoveRecolor((args.Target.Value, recolored)); + + _audio.PlayPredicted(ent.Comp.DoafterSound, ent, args.User); + + if (recolored.PaintType != null) + _popup.PopupClient(Loc.GetString("recolor-remover-finish-popup", ("name", args.Target), ("paintType", recolored.PaintType)), ent, args.User); + + args.Handled = true; + } + + private void OnGetRemoverVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (!TryComp(args.Target, out var recolored) || !recolored.Removable) + return; + + var user = args.User; + var target = args.Target; + + var verb = new UtilityVerb + { + Act = () => TryStartRemoveRecolorDoAfter(user, (target, recolored), ent), + Text = Loc.GetString("verb-remove-recolor"), + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")), + }; + + args.Verbs.Add(verb); + } +} diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs index 264bd55a9d..b16d82d29c 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -1,5 +1,6 @@ using Content.Shared._DEN.Recolor.Components; using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Popups; @@ -11,8 +12,10 @@ namespace Content.Shared._DEN.Recolor; -public abstract partial class RecolorSystem : EntitySystem +public sealed partial class RecolorSystem : EntitySystem { + [Dependency] private readonly ILocalizationManager _localizationManager = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly OpenableSystem _openable = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -23,14 +26,19 @@ public abstract partial class RecolorSystem : EntitySystem public override void Initialize() { base.Initialize(); - + // Recolored events SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnComponentShutdown); - + SubscribeLocalEvent(OnComponentRemove); + // Recolor Applier Events SubscribeLocalEvent(OnRecolorApplierAfterInteract); SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent>(OnGetApplierVerbs); + SubscribeLocalEvent(OnExamined); + // Recolor Remover Events + SubscribeLocalEvent(OnRecolorRemoverAfterInteract); + SubscribeLocalEvent>(OnGetRemoverVerbs); + SubscribeLocalEvent(OnRemoveRecolorDoAfterEvent); } private void OnComponentStartup(Entity ent, ref ComponentStartup args) @@ -38,7 +46,7 @@ private void OnComponentStartup(Entity ent, ref ComponentSta RefreshVisuals(ent); } - private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + private void OnComponentRemove(Entity ent, ref ComponentRemove args) { RemoveVisuals(ent); } @@ -48,6 +56,7 @@ public void Recolor(EntityUid uid, Color color, bool removable, string? shader = null, + string? paintType = null, List? shaderWhitelist = null, List? shaderBlacklist = null) { @@ -62,13 +71,15 @@ public void Recolor(EntityUid uid, var comp = new RecoloredComponent { Color = color, + Removable = removable, Shader = shader, + PaintType = paintType, ShaderBlacklist = shaderBlacklist, ShaderWhitelist = shaderWhitelist, - Removable = removable, }; AddComp(uid, comp); + Dirty((uid, comp)); } [PublicAPI] @@ -112,12 +123,18 @@ public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent public List? ShaderBlacklist; public List? ShaderWhitelist; public string? Shader; + public string? PaintType; + public override DoAfterEvent Clone() { return this; } } +[Serializable, NetSerializable] +public sealed partial class RemoveRecolorDoAfterEvent : SimpleDoAfterEvent; + + [Serializable, NetSerializable] public enum RecolorVisuals { diff --git a/Resources/Locale/en-US/_DEN/recolor/examine.ftl b/Resources/Locale/en-US/_DEN/recolor/examine.ftl new file mode 100644 index 0000000000..42cc4d42e1 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/recolor/examine.ftl @@ -0,0 +1,5 @@ +spray-paint-examine-color = It contains [color={$color}]{$colorName}[/color] spray paint. +spray-paint-examine-uses = { $uses -> +[one] There is [color=yellow]{$uses}[/color] spray left. +*[other] There are [color=yellow]{$uses}[/color] sprays left. +}. diff --git a/Resources/Locale/en-US/_DEN/recolor/popups.ftl b/Resources/Locale/en-US/_DEN/recolor/popups.ftl index 8f61036b37..9b9c797e19 100644 --- a/Resources/Locale/en-US/_DEN/recolor/popups.ftl +++ b/Resources/Locale/en-US/_DEN/recolor/popups.ftl @@ -1 +1,5 @@ spray-paint-empty = {$name} is out of paint! +spray-paint-fail = {$target} can't be spray painted! + +recolor-remover-start-popup = You start cleaning the {$paintType} off of the {$name}... +recolor-remover-finish-popup = You clean the {$paintType} off of the {$name}! diff --git a/Resources/Locale/en-US/_DEN/recolor/verbs.ftl b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl index 44282e2c86..fa08253072 100644 --- a/Resources/Locale/en-US/_DEN/recolor/verbs.ftl +++ b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl @@ -1 +1,2 @@ verb-spray-paint = Spray Paint +verb-remove-recolor = Remove recolor diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 69eff77aeb..461c621735 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -68,6 +68,7 @@ - type: CleansForensics - type: Residue residueAdjective: residue-slippery + - type: RecolorRemover # DEN - type: entity parent: BaseSoap diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index d36b982c40..538bc23b6c 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -4,7 +4,7 @@ parent: BaseItem id: SprayPaintCan abstract: true - description: it's spray paint + description: A can of high quality spray paint with patented anti-skin technology. components: - type: Appearance - type: Sprite @@ -17,9 +17,15 @@ closeSound: path: /Audio/Effects/pop_high.ogg - type: RecolorApplier + removable: true + paintType: spray paint shaderBlacklist: - unshaded - DisplacedDraw + entityBlacklist: + components: + - HumanoidProfile + - Puddle maxUses: 64 # Rainbow colors @@ -31,6 +37,7 @@ components: - type: RecolorApplier color: red + colorName: red - type: Sprite layers: - state: spray-can-base @@ -52,6 +59,7 @@ components: - type: RecolorApplier color: orange + colorName: orange - type: Sprite layers: - state: spray-can-base @@ -73,6 +81,7 @@ components: - type: RecolorApplier color: yellow + colorName: yellow - type: Sprite layers: - state: spray-can-base @@ -94,6 +103,7 @@ components: - type: RecolorApplier color: greenyellow + colorName: lime green - type: Sprite layers: - state: spray-can-base @@ -115,6 +125,7 @@ components: - type: RecolorApplier color: green + colorName: green - type: Sprite layers: - state: spray-can-base @@ -136,6 +147,7 @@ components: - type: RecolorApplier color: cyan + colorName: cyan - type: Sprite layers: - state: spray-can-base @@ -157,6 +169,7 @@ components: - type: RecolorApplier color: blue + colorName: blue - type: Sprite layers: - state: spray-can-base @@ -178,6 +191,7 @@ components: - type: RecolorApplier color: purple + colorName: purple - type: Sprite layers: - state: spray-can-base @@ -199,6 +213,7 @@ components: - type: RecolorApplier color: hotpink + colorName: pink - type: Sprite layers: - state: spray-can-base @@ -222,6 +237,7 @@ components: - type: RecolorApplier color: gold + colorName: gold - type: Sprite layers: - state: spray-can-base @@ -243,6 +259,7 @@ components: - type: RecolorApplier color: silver + colorName: silver - type: Sprite layers: - state: spray-can-base @@ -264,6 +281,7 @@ components: - type: RecolorApplier color: chocolate + colorName: copper - type: Sprite layers: - state: spray-can-base @@ -329,6 +347,7 @@ components: - type: RecolorApplier color: &black "#333333" + colorName: black - type: Sprite layers: - state: spray-can-base @@ -350,6 +369,7 @@ components: - type: RecolorApplier color: saddlebrown + colorName: brown - type: Sprite layers: - state: spray-can-base From c16a917fc2937b80310f92cf086422faedd5411f Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Tue, 24 Mar 2026 20:03:19 -0400 Subject: [PATCH 05/13] add trash and update ftl --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 2 ++ Content.Shared/_DEN/Recolor/RecolorSystem.cs | 11 +++++++++++ .../Locale/en-US/_DEN/recolor/examine.ftl | 11 +++++++---- .../Entities/Objects/Tools/paint_cans.yml | 19 +++++++++++++------ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index 79f3fef776..626df6b7d4 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -4,6 +4,7 @@ using Content.Shared._DEN.Recolor; using Content.Shared._DEN.Recolor.Components; using Content.Shared.Clothing; +using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Item; using Robust.Client.GameObjects; @@ -29,6 +30,7 @@ public override void Initialize() after: [typeof(ClientClothingSystem)]); } + protected override void OnAppearanceChange(EntityUid uid, RecoloredComponent component, ref AppearanceChangeEvent args) { base.OnAppearanceChange(uid, component, ref args); diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs index b16d82d29c..74570eff62 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -29,6 +29,8 @@ public override void Initialize() // Recolored events SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnComponentRemove); + SubscribeLocalEvent(OnExamined); + // Recolor Applier Events SubscribeLocalEvent(OnRecolorApplierAfterInteract); SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); @@ -51,6 +53,15 @@ private void OnComponentRemove(Entity ent, ref ComponentRemo RemoveVisuals(ent); } + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + if (ent.Comp.PaintType != null) + args.PushMarkup(Loc.GetString("recolored-examine", ("color", ent.Comp.Color), ("paintType", ent.Comp.PaintType))); + } + [PublicAPI] public void Recolor(EntityUid uid, Color color, diff --git a/Resources/Locale/en-US/_DEN/recolor/examine.ftl b/Resources/Locale/en-US/_DEN/recolor/examine.ftl index 42cc4d42e1..f7dc140f81 100644 --- a/Resources/Locale/en-US/_DEN/recolor/examine.ftl +++ b/Resources/Locale/en-US/_DEN/recolor/examine.ftl @@ -1,5 +1,8 @@ spray-paint-examine-color = It contains [color={$color}]{$colorName}[/color] spray paint. -spray-paint-examine-uses = { $uses -> -[one] There is [color=yellow]{$uses}[/color] spray left. -*[other] There are [color=yellow]{$uses}[/color] sprays left. -}. + +spray-paint-examine-uses = There { $uses -> +[one] is [color=yellow]{$uses}[/color] spray +*[other] are [color=yellow]{$uses}[/color] sprays +} left. + +recolored-examine = It is covered in [color={$color}]{$paintType}[/color]. diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index 538bc23b6c..d2ea97ec84 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -16,17 +16,24 @@ path: /Audio/Effects/pop_high.ogg closeSound: path: /Audio/Effects/pop_high.ogg - - type: RecolorApplier + - type: PhysicalComposition + materialComposition: + Steel: 100 + - type: RecolorApplier # The important one removable: true paintType: spray paint shaderBlacklist: - - unshaded - - DisplacedDraw + - unshaded # I don't want things becoming unshaded!!! + - DisplacedDraw # I don't want things becoming undisplaced!!! entityBlacklist: components: - - HumanoidProfile - - Puddle - maxUses: 64 + - HumanoidProfile # Prevent spraying on humans + - Puddle # It's a liquid lol + - Cable # For accessibility reasons. + - CableVisualizer # See above + - AtmosPipeColor # It doesn't work currently, unsure why? Possibly because gaspipecolor visualizer runs first. + - PipeColorVisuals # See above + maxUses: 64 # Might be too much? Unsure. # Rainbow colors From 3ea4c34261186eecd28a303182ab6633d78e8687 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Tue, 24 Mar 2026 21:59:57 -0400 Subject: [PATCH 06/13] magic spray paint --- .../UI/RecolorApplierColorSelectorBui.cs | 34 +++++++++ .../UI/RecolorApplierColorSelectorMenu.xaml | 9 +++ .../RecolorApplierColorSelectorMenu.xaml.cs | 29 ++++++++ .../RecolorApplierColorSelectorComponent.cs | 4 ++ .../RecolorSystem.Applier.ColorSelector.cs | 43 ++++++++++++ .../_DEN/Recolor/RecolorSystem.Applier.cs | 13 ++++ .../_DEN/Recolor/RecolorSystem.Remover.cs | 6 +- Content.Shared/_DEN/Recolor/RecolorSystem.cs | 5 ++ Resources/Locale/en-US/_DEN/recolor/ui.ftl | 1 + Resources/Locale/en-US/_DEN/recolor/verbs.ftl | 2 + .../Entities/Objects/Tools/paint_cans.yml | 65 ++++++++++++++++++ .../Objects/Tools/spray-paint.rsi/meta.json | 6 ++ .../spray-paint.rsi/spray-can-rainbow-cap.png | Bin 0 -> 181 bytes .../spray-paint.rsi/spray-can-rainbow.png | Bin 0 -> 416 bytes 14 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs create mode 100644 Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml create mode 100644 Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml.cs create mode 100644 Content.Shared/_DEN/Recolor/Components/RecolorApplierColorSelectorComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs create mode 100644 Resources/Locale/en-US/_DEN/recolor/ui.ftl create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-rainbow-cap.png create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-rainbow.png diff --git a/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs new file mode 100644 index 0000000000..a222ff69ad --- /dev/null +++ b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs @@ -0,0 +1,34 @@ +using Content.Shared._DEN.Recolor; +using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; + +namespace Content.Client._DEN.Recolor.UI; + +public sealed class RecolorApplierColorSelectorBui(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + [ViewVariables] + private RecolorApplierColorSelectorMenu? _menu; + + protected override void Open() + { + base.Open(); + + _menu = this.CreateWindow(); + _menu.OnColorChanged += SelectColor; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + // Set the color of the color selector to the same color as the spray paint currently is + if (state is RecolorSystem.RecolorApplierColorState recolorApplierColorState) + { + _menu?.SelectColor(recolorApplierColorState.Color); + } + } + // Sent out when a new color is chosen + private void SelectColor(Color color) + { + SendMessage(new RecolorSystem.RecolorApplierColorMessage(color)); + } +} diff --git a/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml new file mode 100644 index 0000000000..f6ebeb438f --- /dev/null +++ b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml.cs b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml.cs new file mode 100644 index 0000000000..c9048ae970 --- /dev/null +++ b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorMenu.xaml.cs @@ -0,0 +1,29 @@ +using Content.Shared._DEN.Recolor; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._DEN.Recolor.UI; + +[GenerateTypedNameReferences] +public sealed partial class RecolorApplierColorSelectorMenu: DefaultWindow +{ + public Action? OnColorChanged; + + public RecolorApplierColorSelectorMenu() + { + RobustXamlLoader.Load(this); + + ColorSelector.OnColorChanged += color => + { + OnColorChanged?.Invoke(color); + }; + } + + // Set the color selector's color. + public void SelectColor(Color color) + { + ColorSelector.Color = color; + } +} diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierColorSelectorComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierColorSelectorComponent.cs new file mode 100644 index 0000000000..431e905d80 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierColorSelectorComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared._DEN.Recolor.Components; + +[RegisterComponent] +public sealed partial class RecolorApplierColorSelectorComponent : Component; diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs new file mode 100644 index 0000000000..9a555e723a --- /dev/null +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs @@ -0,0 +1,43 @@ +using Content.Shared._DEN.Recolor.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared._DEN.Recolor; + +public sealed partial class RecolorSystem +{ + private void OnBoundUIOpened(Entity ent, ref BoundUIOpenedEvent args) + { + if (args.UiKey is not RecolorApplierColorSelectorKey key + || !TryComp(ent.Owner, out var recolorApplier)) + return; + + var state = new RecolorApplierColorState(recolorApplier.Color); + _ui.SetUiState(ent.Owner, key, state); + } + + private void OnRecolorApplierColorChanged(Entity ent, ref RecolorApplierColorMessage args) + { + if (!TryComp(ent, out var recolorApplier)) + return; + + ChangeColor((ent.Owner,recolorApplier),args.Color); + } + + [Serializable, NetSerializable] + public enum RecolorApplierColorSelectorKey : byte + { + Key, + } + + [Serializable, NetSerializable] + public sealed class RecolorApplierColorMessage(Color color) : BoundUserInterfaceMessage + { + public readonly Color Color = color; + } + + [Serializable, NetSerializable] + public sealed class RecolorApplierColorState(Color color) : BoundUserInterfaceState + { + public readonly Color Color = color; + } +} diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs index ee21bbc33b..67691dd5fc 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -3,12 +3,25 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Verbs; +using JetBrains.Annotations; using Robust.Shared.ColorNaming; namespace Content.Shared._DEN.Recolor; public sealed partial class RecolorSystem { + [PublicAPI] + public void ChangeColor(Entity ent, Color color, string? colorName = null) + { + if (color == ent.Comp.Color) + return; + + ent.Comp.Color = color; + ent.Comp.ColorName = colorName ?? null; + + Dirty(ent); + } + private static void OnComponentStartup(Entity ent, ref ComponentStartup args) { if (ent.Comp.MaxUses != null) diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs index 9762142581..92a3969eba 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs @@ -16,9 +16,6 @@ private void OnRecolorRemoverAfterInteract(Entity ent, if (!TryComp(args.Target, out var recolored) || !recolored.Removable) return; - if (recolored.PaintType != null) - _popup.PopupClient(Loc.GetString("recolor-remover-start-popup", ("name", args.Target), ("paintType", recolored.PaintType)),ent,args.User); - args.Handled = TryStartRemoveRecolorDoAfter(args.User, (args.Target.Value, recolored), ent); } @@ -42,6 +39,9 @@ private bool TryStartRemoveRecolorDoAfter( BreakOnHandChange = true, }; + if (target.Comp.PaintType != null) + _popup.PopupClient(Loc.GetString("recolor-remover-start-popup", ("name", target), ("paintType", target.Comp.PaintType)),remover,user); + return _doAfter.TryStartDoAfter(doAfterArgs); } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs index 74570eff62..780a297562 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -22,6 +22,7 @@ public sealed partial class RecolorSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; public override void Initialize() { @@ -37,6 +38,10 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent>(OnGetApplierVerbs); SubscribeLocalEvent(OnExamined); + // Recolor Applier Color Selector Events + SubscribeLocalEvent(OnBoundUIOpened); + SubscribeLocalEvent(OnRecolorApplierColorChanged); + // Recolor Remover Events SubscribeLocalEvent(OnRecolorRemoverAfterInteract); SubscribeLocalEvent>(OnGetRemoverVerbs); diff --git a/Resources/Locale/en-US/_DEN/recolor/ui.ftl b/Resources/Locale/en-US/_DEN/recolor/ui.ftl new file mode 100644 index 0000000000..6704e264b1 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/recolor/ui.ftl @@ -0,0 +1 @@ +recolor-applier-color-selector-window-title = Magic Spray Paint diff --git a/Resources/Locale/en-US/_DEN/recolor/verbs.ftl b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl index fa08253072..d7ea9ac214 100644 --- a/Resources/Locale/en-US/_DEN/recolor/verbs.ftl +++ b/Resources/Locale/en-US/_DEN/recolor/verbs.ftl @@ -1,2 +1,4 @@ verb-spray-paint = Spray Paint verb-remove-recolor = Remove recolor + +verb-recolor-applier-color-selector-openui = Change color diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index d2ea97ec84..7d8a55ff15 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -390,3 +390,68 @@ enum.OpenableVisuals.Layer: True: {state: "spray-can-color-open" , color: saddlebrown} False: {state: "spray-can-color-closed" , color: saddlebrown} + +# Magic spray paints + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanMagic + name: magic spray paint + components: + - type: RecolorApplier + color: white + - type: RecolorApplierColorSelector + - type: ActivatableUI + blockSpectators: true + inHandsOnly: true + singleUser: true + verbText: verb-recolor-applier-color-selector-openui #fuck you i can make it as long as i want + key: enum.RecolorApplierColorSelectorKey.Key + - type: UserInterface + interfaces: + enum.RecolorApplierColorSelectorKey.Key: + type: RecolorApplierColorSelectorBui + - type: Sprite + layers: + - state: spray-can-rainbow + map: ["Base"] + - state: spray-can-rainbow-cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {visible: false} + False: {visible: true} + +- type: entity + parent: SprayPaintCanMagic + id: SprayPaintCanMagicMapping + suffix: do not map + name: infinite magic spray paint + components: + - type: RecolorApplier + maxUses: + +- type: entity + parent: SprayPaintCan + id: SprayPaintCanInvisible + suffix: do not map + name: invisible spray paint + components: + - type: RecolorApplier + color: "#FFFFFF00" + colorName: invisible + - type: Sprite + layers: + - state: spray-can-base + map: ["Base"] + - state: spray-can-color-closed + map: ["enum.OpenableVisuals.Layer"] + color: white + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray-can-color-open" , color: white} + False: {state: "spray-can-color-closed" , color: white} diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json index b66995521a..8e23f24934 100644 --- a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json +++ b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/meta.json @@ -15,6 +15,12 @@ }, { "name": "spray-can-color-open" + }, + { + "name": "spray-can-rainbow" + }, + { + "name": "spray-can-rainbow-cap" } ] } diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-rainbow-cap.png b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/spray-can-rainbow-cap.png new file mode 100644 index 0000000000000000000000000000000000000000..98dbe1a26d3becea08972e86cc9c493ece05d82b GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}`JOJ0ArY-_ zFK^^Mpuof8pni%)=ZG24Q8PJ?^&c{YU&uMlvhQS4d840WEco;0!MZhDA64ovy|8t~ zv$b2VzhthxC38T4fkE_9l9Nlz(kX4`Y@!Lv65q!JrJ9$^ryaPvQgKQ6^86Xa_qMkw c0fF}2IlIGWewNg32U^YG>FVdQ&MBb@0B-d`!TPx$T1iAfR9J=Wl|4&BVHAKLGbGmx2minYU3AT`rJ*gXe-H@aB4KL|wHDD*pwQG9 z{(=ZESW^*#2p58uxPzceLlNa^P)X%3=M|&iKGVJD<(%_y&iw!%A0MCpjEG35(~)Uv z%$X+EqjB-N{uP0GG%o2_h;Fw_v)Lpa3r!`VZvL4_h;7?4TtT^97D*<>L}Ff>5M-U_ z)nhV+6ab4r4tQFABzRQ>TC)`Zc)ja4oQ#>E7TDQ>z#&|$jmNjILH2?384C=#&sP9A z+su)#R_HH6k6K_Od`U2JgJqpkNMr!8EQ@R;#aGy+p{!^89dI0ne6>P0lEQHu(=r6K z32(bmSvZPuFri-R!TX*|_@u3>Y5WY7dZwA<=vVLp)I8#~!I=`{33y5vpq{{ky#z&% zgW}*!iTMP)Bn(i$1@QAX9Oc&iV5Qws*E13rN{rr{H23lO6MO@GNN`e0O;a%d0000< KMNUMnLSTZPy|aV> literal 0 HcmV?d00001 From a92f8dbd8efc71d68bf638f1b607c1d7cbacf9d6 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Wed, 25 Mar 2026 22:13:26 -0400 Subject: [PATCH 07/13] clean code drastically --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 39 ++---- .../Components/RecolorApplierComponent.cs | 47 +------ .../Recolor/Components/RecoloredComponent.cs | 35 +---- .../RecolorSystem.Applier.ColorSelector.cs | 2 +- .../_DEN/Recolor/RecolorSystem.Applier.cs | 31 ++--- .../_DEN/Recolor/RecolorSystem.Remover.cs | 17 ++- Content.Shared/_DEN/Recolor/RecolorSystem.cs | 107 +++++++++----- .../Locale/en-US/_DEN/recolor/examine.ftl | 2 +- .../Entities/Objects/Tools/paint_cans.yml | 131 +++++++++++++----- 9 files changed, 207 insertions(+), 204 deletions(-) diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index 626df6b7d4..7a81578257 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -69,19 +69,20 @@ private void ApplyRecolorLayers(Entity ent, List<(string, Pr if(!TryComp(ent, out var appearance)) return; - var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + return; foreach (var (_, layerData) in layers) { // Apply Color - layerData.Color = appearanceData.Color; + layerData.Color = recolorData.Color; //Test shader whitelists and blacklists - if (!AllowedShader(layerData.Shader, appearanceData)) + if (!AllowedShader(layerData.Shader, recolorData)) continue; // Apply shaders - layerData.Shader = appearanceData.Shader; + layerData.Shader = recolorData.Shader; } } @@ -90,7 +91,8 @@ private void ApplyRecolorSprite(Entity ent, SpriteComponent if(!TryComp(ent, out var appearance)) return; - var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + return; for (var i = 0; i < sprite.AllLayers.Count(); i++) { @@ -98,16 +100,16 @@ private void ApplyRecolorSprite(Entity ent, SpriteComponent continue; // Apply color - SpriteSystem.LayerSetColor(layer, appearanceData.Color); + SpriteSystem.LayerSetColor(layer, recolorData.Color); var layerShader = layer.ShaderPrototype; - if (!AllowedShader(layerShader?.Id, appearanceData)) + if (!AllowedShader(layerShader?.Id, recolorData)) continue; // Apply shaders - if (appearanceData.Shader != null) - sprite.LayerSetShader(i, appearanceData.Shader); + if (recolorData.Shader != null) + sprite.LayerSetShader(i, recolorData.Shader); } } @@ -116,7 +118,8 @@ private void RemoveRecolor(Entity ent, SpriteComponent sprit if(!TryComp(ent, out var appearance)) return; - var appearanceData = GetRecolorAppearanceData((ent.Owner,appearance)); + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + return; for (var i = 0; i < sprite.AllLayers.Count(); i++) { @@ -131,26 +134,14 @@ private void RemoveRecolor(Entity ent, SpriteComponent sprit // Remove shaders var layerShader = layer.ShaderPrototype; - if (!AllowedShader(layerShader?.Id, appearanceData)) + if (!AllowedShader(layerShader?.Id, recolorData)) continue; sprite.LayerSetShader(i, null, null); } } - private record RecolorAppearanceData(Color Color, string? Shader,List? ShaderBlacklist, List? ShaderWhitelist); - - private RecolorAppearanceData GetRecolorAppearanceData(Entity ent) - { - AppearanceSystem.TryGetData(ent, RecolorVisuals.Color, out Color color); - AppearanceSystem.TryGetData(ent, RecolorVisuals.Shader, out string? shader); - AppearanceSystem.TryGetData(ent, RecolorVisuals.ShaderBlacklist, out List? shaderBlacklist); - AppearanceSystem.TryGetData(ent, RecolorVisuals.ShaderWhitelist, out List? shaderWhitelist); - - return new RecolorAppearanceData(color, shader, shaderBlacklist, shaderWhitelist); - } - - private static bool AllowedShader(string? shader, RecolorAppearanceData appearanceData) + private static bool AllowedShader(string? shader, RecolorData appearanceData) { if (shader == null) return true; diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 23d709666c..c71275cb2d 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -9,19 +9,11 @@ namespace Content.Shared._DEN.Recolor.Components; public sealed partial class RecolorApplierComponent : Component { - //Recolor Applier Specific Datafields - /// - /// The color to apply to the object being recolored. + /// RecolorData to recolor items with. /// - [DataField] - public string? ColorName; - - /// - /// Type of paint used, purely for flavor. - /// - [DataField] - public string? PaintType; + [DataField, AutoNetworkedField] + public RecolorData RecolorData; /// /// How long it takes for this object to apply the recolor to the target. @@ -94,37 +86,4 @@ public sealed partial class RecolorApplierComponent : Component /// [DataField] public SpriteSpecifier VerbIcon = new SpriteSpecifier.Texture(new ResPath("/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png")); - - // Recolor Datafields - - /// - /// The color to apply to the object being recolored. - /// - [DataField, AutoNetworkedField] - public Color Color { get; set; } - - /// - /// Whether the color applied can be removed via normal means. - /// - [DataField,AutoNetworkedField] - public bool Removable = true; - - /// - /// Don't apply shader to layers with these shaders. (Sorry about the lack of shader prototype) - /// - [DataField] - public List? ShaderBlacklist; - - /// - /// Only apply shader to layers with these shaders. - /// - [DataField] - public List? ShaderWhitelist; - - /// - /// The shader to apply to the recolored entity. - /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. - /// - [DataField] - public string? Shader = "Desaturated"; } diff --git a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs index 31a9503917..9d1c0e5b7e 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs @@ -6,39 +6,8 @@ namespace Content.Shared._DEN.Recolor.Components; public sealed partial class RecoloredComponent : Component { /// - /// The color to change the sprite to. + /// RecolorData this component is storing. /// [DataField, AutoNetworkedField] - public Color Color = Color.White; - - /// - /// Type of paint used, purely for flavor. - /// - [DataField, AutoNetworkedField] - public string? PaintType; - - /// - /// Whether or not this component can be removed by an entity with RecolorRemoverComponent. - /// - [DataField, AutoNetworkedField] - public bool Removable { get; set; } - - /// - /// Don't apply to layers with these shaders. (Sorry about the lack of shader prototype) - /// - [DataField, AutoNetworkedField] - public List? ShaderBlacklist; - - /// - /// Only apply to layers with these shaders. - /// - [DataField, AutoNetworkedField] - public List? ShaderWhitelist; - - /// - /// The shader to apply to the recolored entity. - /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. - /// - [DataField, AutoNetworkedField] - public string? Shader { get; set; } + public RecolorData RecolorData; } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs index 9a555e723a..fabb3df3b1 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.ColorSelector.cs @@ -11,7 +11,7 @@ private void OnBoundUIOpened(Entity ent, r || !TryComp(ent.Owner, out var recolorApplier)) return; - var state = new RecolorApplierColorState(recolorApplier.Color); + var state = new RecolorApplierColorState(recolorApplier.RecolorData.Color); _ui.SetUiState(ent.Owner, key, state); } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs index 67691dd5fc..b29a30c96d 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -13,11 +13,13 @@ public sealed partial class RecolorSystem [PublicAPI] public void ChangeColor(Entity ent, Color color, string? colorName = null) { - if (color == ent.Comp.Color) + var recolorData = ent.Comp.RecolorData; + + if (color == recolorData.Color) return; - ent.Comp.Color = color; - ent.Comp.ColorName = colorName ?? null; + recolorData.Color = color; + recolorData.ColorName = colorName ?? null; Dirty(ent); } @@ -48,12 +50,7 @@ private bool TryStartApplyRecolorDoAfter( { var doAfterEvent = new ApplyRecolorDoAfterEvent { - Color = applier.Comp.Color, - PaintType = applier.Comp.PaintType, - Shader = applier.Comp.Shader, - ShaderBlacklist = applier.Comp.ShaderBlacklist, - ShaderWhitelist = applier.Comp.ShaderWhitelist, - Removable = applier.Comp.Removable, + RecolorData = applier.Comp.RecolorData, }; var doAfterArgs = new DoAfterArgs(EntityManager, @@ -79,12 +76,7 @@ private void OnApplyRecolorDoAfterEvent(Entity ent, ref Recolor( uid: args.Target.Value, - color: args.Color, - removable: args.Removable, - paintType: args.PaintType, - shader: args.Shader, - shaderWhitelist: args.ShaderWhitelist, - shaderBlacklist: args.ShaderBlacklist + recolorData: args.RecolorData ); _audio.PlayPredicted(ent.Comp.DoafterSound, ent, args.User); @@ -143,12 +135,11 @@ private void OnExamined(Entity ent, ref ExaminedEvent a { if (!args.IsInDetailsRange) return; - // Get the color's name. If the color itself has a color defined, use it - var colorName = ent.Comp.ColorName ?? - // Otherwise, use what colornaming THINKS it is. - ColorNaming.Describe(ent.Comp.Color, _localizationManager); + var recolorData = ent.Comp.RecolorData; + + var colorName = GetColorName(recolorData); - args.PushMarkup(Loc.GetString(ent.Comp.ColorShowcaseExamine, ("color", ent.Comp.Color), ("colorName", colorName))); + args.PushMarkup(Loc.GetString(ent.Comp.ColorShowcaseExamine, ("color", recolorData.Color), ("colorName", colorName))); // If max uses isn't null (signifying this item has infinite uses), show uses count if (ent.Comp.MaxUses != null) diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs index 92a3969eba..548c86a2fd 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Remover.cs @@ -12,8 +12,7 @@ private void OnRecolorRemoverAfterInteract(Entity ent, { if (args.Target == null || args.Handled || !args.CanReach) return; - - if (!TryComp(args.Target, out var recolored) || !recolored.Removable) + if (!TryComp(args.Target, out var recolored) || !recolored.RecolorData.Removable) return; args.Handled = TryStartRemoveRecolorDoAfter(args.User, (args.Target.Value, recolored), ent); @@ -39,8 +38,10 @@ private bool TryStartRemoveRecolorDoAfter( BreakOnHandChange = true, }; - if (target.Comp.PaintType != null) - _popup.PopupClient(Loc.GetString("recolor-remover-start-popup", ("name", target), ("paintType", target.Comp.PaintType)),remover,user); + var recolorData = target.Comp.RecolorData; + + if (recolorData.PaintType != null) + _popup.PopupClient(Loc.GetString("recolor-remover-start-popup", ("name", target), ("paintType", recolorData.PaintType)),remover,user); return _doAfter.TryStartDoAfter(doAfterArgs); } @@ -50,12 +51,14 @@ private void OnRemoveRecolorDoAfterEvent(Entity ent, re if (args.Handled || args.Cancelled || !TryComp(args.Target, out var recolored)) return; + var recolorData = recolored.RecolorData; + RemoveRecolor((args.Target.Value, recolored)); _audio.PlayPredicted(ent.Comp.DoafterSound, ent, args.User); - if (recolored.PaintType != null) - _popup.PopupClient(Loc.GetString("recolor-remover-finish-popup", ("name", args.Target), ("paintType", recolored.PaintType)), ent, args.User); + if (recolorData.PaintType != null) + _popup.PopupClient(Loc.GetString("recolor-remover-finish-popup", ("name", args.Target), ("paintType", recolorData.PaintType)), ent, args.User); args.Handled = true; } @@ -65,7 +68,7 @@ private void OnGetRemoverVerbs(Entity ent, ref GetVerbs if (!args.CanInteract || !args.CanAccess) return; - if (!TryComp(args.Target, out var recolored) || !recolored.Removable) + if (!TryComp(args.Target, out var recolored) || !recolored.RecolorData.Removable) return; var user = args.User; diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs index 780a297562..36afc04bf0 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Audio.Systems; +using Robust.Shared.ColorNaming; using Robust.Shared.Serialization; namespace Content.Shared._DEN.Recolor; @@ -63,18 +64,24 @@ private void OnExamined(Entity ent, ref ExaminedEvent args) if (!args.IsInDetailsRange) return; - if (ent.Comp.PaintType != null) - args.PushMarkup(Loc.GetString("recolored-examine", ("color", ent.Comp.Color), ("paintType", ent.Comp.PaintType))); + var recolorData = ent.Comp.RecolorData; + + if (recolorData is { PaintType: not null}) + { + var colorName = GetColorName(recolorData); + + args.PushMarkup(Loc.GetString( + "recolored-examine", + ("color", recolorData.Color), + ("colorName", colorName), + ("paintType", recolorData.PaintType))); + } } [PublicAPI] - public void Recolor(EntityUid uid, - Color color, - bool removable, - string? shader = null, - string? paintType = null, - List? shaderWhitelist = null, - List? shaderBlacklist = null) + public void Recolor( + EntityUid uid, + RecolorData recolorData) { if (HasComp(uid)) { @@ -86,12 +93,7 @@ public void Recolor(EntityUid uid, var comp = new RecoloredComponent { - Color = color, - Removable = removable, - Shader = shader, - PaintType = paintType, - ShaderBlacklist = shaderBlacklist, - ShaderWhitelist = shaderWhitelist, + RecolorData = recolorData, }; AddComp(uid, comp); @@ -107,39 +109,75 @@ public void RemoveRecolor(Entity ent) RemComp(ent, ent.Comp); } + private string GetColorName(RecolorData recolorData) + { + // Get the color's name. If the color itself has a color defined, use it + return recolorData.ColorName ?? + // Otherwise, use what colornaming THINKS it is. + ColorNaming.Describe(recolorData.Color, _localizationManager); + } + private void RefreshVisuals(Entity ent) { if (!TryComp(ent, out AppearanceComponent? appearance)) return; - _appearance.SetData(ent, RecolorVisuals.Color, ent.Comp.Color, appearance); + var recolorData = ent.Comp.RecolorData; - if (ent.Comp.Shader != null) - _appearance.SetData(ent, RecolorVisuals.Shader, ent.Comp.Shader, appearance); - if (ent.Comp.ShaderWhitelist != null) - _appearance.SetData(ent, RecolorVisuals.ShaderWhitelist, ent.Comp.ShaderWhitelist, appearance); - if (ent.Comp.ShaderBlacklist != null) - _appearance.SetData(ent, RecolorVisuals.ShaderBlacklist, ent.Comp.ShaderBlacklist, appearance); + _appearance.SetData(ent, RecolorVisuals.RecolorData, recolorData, appearance); } private void RemoveVisuals(Entity ent) { - _appearance.RemoveData(ent, RecolorVisuals.Color); - _appearance.RemoveData(ent, RecolorVisuals.Shader); - _appearance.RemoveData(ent, RecolorVisuals.ShaderBlacklist); - _appearance.RemoveData(ent, RecolorVisuals.ShaderWhitelist); + _appearance.RemoveData(ent, RecolorVisuals.RecolorData); } } +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class RecolorData +{ + /// + /// Color to recolor with. + /// + [DataField] + public Color Color { get; set; } = Color.White; + /// + /// Whether the Recolor can be removed by the RecolorRemoverSystem. + /// + [DataField] + public bool Removable { get; set; } = true; + /// + /// Name of the color, purely for flavor. + /// + [DataField] + public string? ColorName { get; set; } + /// + /// Purely for flavor, used for locale information. + /// + [DataField] + public string? PaintType { get; set; } + /// + /// Replaces layers shader with this shader. + /// + [DataField] + public string? Shader { get; set; } = "Desaturated"; + /// + /// If used, these will be the only shaders replaced. + /// + [DataField] + public List? ShaderBlacklist { get; set; } + /// + /// If used, these shaders will never be replaced. + /// + [DataField] + public List? ShaderWhitelist { get; set; } +} + [Serializable, NetSerializable] public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent { - public Color Color; - public bool Removable; - public List? ShaderBlacklist; - public List? ShaderWhitelist; - public string? Shader; - public string? PaintType; + public RecolorData RecolorData; public override DoAfterEvent Clone() { @@ -154,8 +192,5 @@ public sealed partial class RemoveRecolorDoAfterEvent : SimpleDoAfterEvent; [Serializable, NetSerializable] public enum RecolorVisuals { - Color, - Shader, - ShaderWhitelist, - ShaderBlacklist, + RecolorData } diff --git a/Resources/Locale/en-US/_DEN/recolor/examine.ftl b/Resources/Locale/en-US/_DEN/recolor/examine.ftl index f7dc140f81..1d318f9a39 100644 --- a/Resources/Locale/en-US/_DEN/recolor/examine.ftl +++ b/Resources/Locale/en-US/_DEN/recolor/examine.ftl @@ -5,4 +5,4 @@ spray-paint-examine-uses = There { $uses -> *[other] are [color=yellow]{$uses}[/color] sprays } left. -recolored-examine = It is covered in [color={$color}]{$paintType}[/color]. +recolored-examine = It is covered in [color={$color}]{$colorName}[/color] {$paintType}. diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index 7d8a55ff15..795a83f8f0 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -19,12 +19,13 @@ - type: PhysicalComposition materialComposition: Steel: 100 - - type: RecolorApplier # The important one - removable: true - paintType: spray paint - shaderBlacklist: - - unshaded # I don't want things becoming unshaded!!! - - DisplacedDraw # I don't want things becoming undisplaced!!! + - type: RecolorApplier # The important one\ + recolorData: + color: white + paintType: &paintType spray paint + shaderBlacklist: &blacklist + - unshaded + - DisplacedDraw entityBlacklist: components: - HumanoidProfile # Prevent spraying on humans @@ -43,8 +44,11 @@ name: red spray paint components: - type: RecolorApplier - color: red - colorName: red + recolorData: + color: red + colorName: red + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -65,8 +69,11 @@ name: orange spray paint components: - type: RecolorApplier - color: orange - colorName: orange + recolorData: + color: orange + colorName: orange + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -87,8 +94,11 @@ name: yellow spray paint components: - type: RecolorApplier - color: yellow - colorName: yellow + recolorData: + color: yellow + colorName: yellow + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -109,8 +119,11 @@ name: lime green spray paint components: - type: RecolorApplier - color: greenyellow - colorName: lime green + recolorData: + color: greenyellow + colorName: lime green + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -131,8 +144,11 @@ name: green spray paint components: - type: RecolorApplier - color: green - colorName: green + recolorData: + color: green + colorName: green + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -153,8 +169,11 @@ name: cyan spray paint components: - type: RecolorApplier - color: cyan - colorName: cyan + recolorData: + color: cyan + colorName: cyan + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -175,8 +194,11 @@ name: blue spray paint components: - type: RecolorApplier - color: blue - colorName: blue + recolorData: + color: blue + colorName: blue + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -197,8 +219,11 @@ name: purple spray paint components: - type: RecolorApplier - color: purple - colorName: purple + recolorData: + color: purple + colorName: purple + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -219,8 +244,11 @@ name: pink spray paint components: - type: RecolorApplier - color: hotpink - colorName: pink + recolorData: + color: hotpink + colorName: pink + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -243,8 +271,11 @@ name: gold spray paint components: - type: RecolorApplier - color: gold - colorName: gold + recolorData: + color: gold + colorName: gold + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -265,8 +296,11 @@ name: silver spray paint components: - type: RecolorApplier - color: silver - colorName: silver + recolorData: + color: silver + colorName: silver + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -287,8 +321,11 @@ name: copper spray paint components: - type: RecolorApplier - color: chocolate - colorName: copper + recolorData: + color: chocolate + colorName: copper + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -311,7 +348,10 @@ name: white spray paint components: - type: RecolorApplier - color: white + recolorData: + color: white + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -332,7 +372,10 @@ name: gray spray paint components: - type: RecolorApplier - color: gray + recolorData: + color: gray + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -353,7 +396,10 @@ name: black spray paint components: - type: RecolorApplier - color: &black "#333333" + recolorData: + color: &black "#333333" + paintType: *paintType + shaderBlacklist: *blacklist colorName: black - type: Sprite layers: @@ -375,8 +421,11 @@ name: brown spray paint components: - type: RecolorApplier - color: saddlebrown - colorName: brown + recolorData: + color: saddlebrown + colorName: brown + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base @@ -399,7 +448,10 @@ name: magic spray paint components: - type: RecolorApplier - color: white + recolorData: + color: white + paintType: *paintType + shaderBlacklist: *blacklist - type: RecolorApplierColorSelector - type: ActivatableUI blockSpectators: true @@ -440,8 +492,11 @@ name: invisible spray paint components: - type: RecolorApplier - color: "#FFFFFF00" - colorName: invisible + recolorData: + color: "#FFFFFF00" + colorName: invisible + paintType: *paintType + shaderBlacklist: *blacklist - type: Sprite layers: - state: spray-can-base From c4e9bf7e75e1062caa86336c1a24d3e1a6b32a8e Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Thu, 26 Mar 2026 01:45:29 -0400 Subject: [PATCH 08/13] comments, polish, organization. --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 10 +- .../UI/RecolorApplierColorSelectorBui.cs | 1 - .../Recolor/Components/RecoloredComponent.cs | 6 ++ .../_DEN/Recolor/RecolorSystem.Applier.cs | 7 +- Content.Shared/_DEN/Recolor/RecolorSystem.cs | 101 +++++++++++++----- 5 files changed, 92 insertions(+), 33 deletions(-) diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index 7a81578257..99d1e01b13 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -4,12 +4,8 @@ using Content.Shared._DEN.Recolor; using Content.Shared._DEN.Recolor.Components; using Content.Shared.Clothing; -using Content.Shared.Examine; using Content.Shared.Hands; -using Content.Shared.Item; using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Shared.Prototypes; namespace Content.Client._DEN.Recolor; @@ -69,7 +65,7 @@ private void ApplyRecolorLayers(Entity ent, List<(string, Pr if(!TryComp(ent, out var appearance)) return; - if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData,appearance)) return; foreach (var (_, layerData) in layers) @@ -91,7 +87,7 @@ private void ApplyRecolorSprite(Entity ent, SpriteComponent if(!TryComp(ent, out var appearance)) return; - if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData, appearance)) return; for (var i = 0; i < sprite.AllLayers.Count(); i++) @@ -118,7 +114,7 @@ private void RemoveRecolor(Entity ent, SpriteComponent sprit if(!TryComp(ent, out var appearance)) return; - if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData)) + if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData, appearance)) return; for (var i = 0; i < sprite.AllLayers.Count(); i++) diff --git a/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs index a222ff69ad..9814dfc3c9 100644 --- a/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs +++ b/Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs @@ -1,6 +1,5 @@ using Content.Shared._DEN.Recolor; using Robust.Client.UserInterface; -using Robust.Shared.Prototypes; namespace Content.Client._DEN.Recolor.UI; diff --git a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs index 9d1c0e5b7e..277ac10967 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs @@ -10,4 +10,10 @@ public sealed partial class RecoloredComponent : Component /// [DataField, AutoNetworkedField] public RecolorData RecolorData; + + /// + /// Examine text as a locid. + /// + [DataField, AutoNetworkedField] + public LocId ExamineText = "recolored-examine"; } diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs index b29a30c96d..3d48282872 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -4,12 +4,17 @@ using Content.Shared.Interaction; using Content.Shared.Verbs; using JetBrains.Annotations; -using Robust.Shared.ColorNaming; namespace Content.Shared._DEN.Recolor; public sealed partial class RecolorSystem { + /// + /// Change the color any given recolor applier applies. + /// + /// Recolor applier to change the color of. + /// Color to change to. + /// Name of the color you're changing to, purely for flavor. [PublicAPI] public void ChangeColor(Entity ent, Color color, string? colorName = null) { diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.cs index 36afc04bf0..d483455312 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.cs @@ -39,6 +39,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent>(OnGetApplierVerbs); SubscribeLocalEvent(OnExamined); + // Recolor Applier Color Selector Events SubscribeLocalEvent(OnBoundUIOpened); SubscribeLocalEvent(OnRecolorApplierColorChanged); @@ -49,39 +50,50 @@ public override void Initialize() SubscribeLocalEvent(OnRemoveRecolorDoAfterEvent); } - private void OnComponentStartup(Entity ent, ref ComponentStartup args) - { - RefreshVisuals(ent); - } - - private void OnComponentRemove(Entity ent, ref ComponentRemove args) - { - RemoveVisuals(ent); - } - - private void OnExamined(Entity ent, ref ExaminedEvent args) + /// + /// Recolor an entity using provided recolorData. + /// + /// Entity to recolor. + /// Recolor data to use when recoloring. + [PublicAPI] + public void Recolor( + EntityUid uid, + RecolorData recolorData) { - if (!args.IsInDetailsRange) - return; + if (HasComp(uid)) + { + //Replace old recolored component. you can spray things with paint twice.. right? + RemComp(uid); + } - var recolorData = ent.Comp.RecolorData; + EnsureComp(uid); - if (recolorData is { PaintType: not null}) + var comp = new RecoloredComponent { - var colorName = GetColorName(recolorData); + RecolorData = recolorData, + }; - args.PushMarkup(Loc.GetString( - "recolored-examine", - ("color", recolorData.Color), - ("colorName", colorName), - ("paintType", recolorData.PaintType))); - } + AddComp(uid, comp); + Dirty((uid, comp)); } + /// + /// Recolor an entity with simple parameters. + /// + /// Entity to recolor. + /// Color to recolor to. + /// If the recoloring can be removed by regular means. + /// Shader to replace default shaders with. + /// Paint type to use, purely for flavor. + /// Examine text LocId to use. [PublicAPI] public void Recolor( EntityUid uid, - RecolorData recolorData) + Color color, + bool removable, + string? shader, + string? paintType, + string examineText = "recolored-examine") { if (HasComp(uid)) { @@ -93,13 +105,21 @@ public void Recolor( var comp = new RecoloredComponent { - RecolorData = recolorData, + RecolorData = new RecolorData + { + Color = color, + Removable = removable, + Shader = shader, + PaintType = paintType, + }, + ExamineText = examineText, }; AddComp(uid, comp); Dirty((uid, comp)); } + /// Entity to remove the recolor of. [PublicAPI] public void RemoveRecolor(Entity ent) { @@ -109,6 +129,36 @@ public void RemoveRecolor(Entity ent) RemComp(ent, ent.Comp); } + private void OnComponentStartup(Entity ent, ref ComponentStartup args) + { + RefreshVisuals(ent); + } + + private void OnComponentRemove(Entity ent, ref ComponentRemove args) + { + RemoveVisuals(ent); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var recolorData = ent.Comp.RecolorData; + + if (recolorData is { PaintType: not null}) + { + var colorName = GetColorName(recolorData); + + args.PushMarkup(Loc.GetString( + ent.Comp.ExamineText, + ("color", recolorData.Color), + ("colorName", colorName), + ("paintType", recolorData.PaintType))); + } + } + + private string GetColorName(RecolorData recolorData) { // Get the color's name. If the color itself has a color defined, use it @@ -133,6 +183,9 @@ private void RemoveVisuals(Entity ent) } } +/// +/// Stores information regarding recolored objects. +/// [Serializable, NetSerializable] [DataDefinition] public sealed partial class RecolorData From 7e1863779c8c496e35097d465d481978a8936ddd Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Thu, 26 Mar 2026 02:31:15 -0400 Subject: [PATCH 09/13] add orders --- .../_DEN/Recolor/RecolorSystem.Applier.cs | 13 +++- .../_DEN/Catalog/Cargo/cargo_fun.yml | 19 +++++ .../_DEN/Catalog/Fills/Boxes/general.yml | 71 ++++++++++++++++++ .../_DEN/Catalog/Fills/Crates/fun.yml | 25 ++++++ .../Tools/spray-paint.rsi/icon-rainbow.png | Bin 0 -> 413 bytes .../Objects/Tools/spray-paint.rsi/icon.png | Bin 0 -> 276 bytes .../Objects/Tools/spray-paint.rsi/meta.json | 6 ++ 7 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 Resources/Prototypes/_DEN/Catalog/Cargo/cargo_fun.yml create mode 100644 Resources/Prototypes/_DEN/Catalog/Fills/Boxes/general.yml create mode 100644 Resources/Prototypes/_DEN/Catalog/Fills/Crates/fun.yml create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon-rainbow.png create mode 100644 Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon.png diff --git a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs index 3d48282872..32a46840d4 100644 --- a/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs +++ b/Content.Shared/_DEN/Recolor/RecolorSystem.Applier.cs @@ -93,10 +93,17 @@ private void OnApplyRecolorDoAfterEvent(Entity ent, ref args.Handled = true; } - private bool CanRecolor(Entity applier, EntityUid user, EntityUid target) + private bool CanRecolor(Entity applier, EntityUid user, EntityUid target, bool? verb = false) { // Check if the applier is opened - if (_openable.IsClosed(applier, user, predicted: true)) + + // All this code is to make sure not to send a popup if this is done with a verb. sigh + EntityUid? closedUser = user; + + if (verb != null && verb.Value) + closedUser = null; + + if (_openable.IsClosed(applier, closedUser, predicted: true)) return false; // Check whitelist and blacklist @@ -120,7 +127,7 @@ private void OnGetApplierVerbs(Entity ent, ref GetVerbs { if (!args.CanAccess || !args.CanInteract - || !CanRecolor(ent, args.User, args.Target)) + || !CanRecolor(ent, args.User, args.Target, true)) return; var user = args.User; diff --git a/Resources/Prototypes/_DEN/Catalog/Cargo/cargo_fun.yml b/Resources/Prototypes/_DEN/Catalog/Cargo/cargo_fun.yml new file mode 100644 index 0000000000..2fea64e2b2 --- /dev/null +++ b/Resources/Prototypes/_DEN/Catalog/Cargo/cargo_fun.yml @@ -0,0 +1,19 @@ +- type: cargoProduct + id: FunSprayPaintBundle + icon: + sprite: _DEN/Objects/Tools/spray-paint.rsi + state: icon + product: CrateFunSprayPaintVariety + cost: 2000 + category: cargoproduct-category-name-fun + group: market + +- type: cargoProduct + id: FunSprayPaintMagic + icon: + sprite: _DEN/Objects/Tools/spray-paint.rsi + state: icon-rainbow + product: CrateFunSprayPaintMagic + cost: 2000 + category: cargoproduct-category-name-fun + group: market diff --git a/Resources/Prototypes/_DEN/Catalog/Fills/Boxes/general.yml b/Resources/Prototypes/_DEN/Catalog/Fills/Boxes/general.yml new file mode 100644 index 0000000000..277204d83c --- /dev/null +++ b/Resources/Prototypes/_DEN/Catalog/Fills/Boxes/general.yml @@ -0,0 +1,71 @@ +- type: entity + parent: BoxCardboard + id: BoxSprayPaintBase + abstract: true + components: + - type: Sprite + layers: + - state: box + - state: clown # placeholder + - type: Storage + grid: + - 0,0,5,3 + whitelist: + components: + - RecolorApplier + +- type: entity + parent: BoxSprayPaintBase + id: BoxSprayPaintColors + name: colored spray paint + description: Box filled with a rainbow assortment of spray paints! + components: + - type: EntityTableContainerFill + containers: + storagebase: !type:AllSelector + children: + - id: SprayPaintCanRed + - id: SprayPaintCanOrange + - id: SprayPaintCanYellow + - id: SprayPaintCanLimeGreen + - id: SprayPaintCanGreen + - id: SprayPaintCanCyan + - id: SprayPaintCanBlue + - id: SprayPaintCanPurple + - id: SprayPaintCanPink + - id: SprayPaintCanBrown + +- type: entity + parent: BoxSprayPaintBase + id: BoxSprayPaintShades + name: greyscale spray paint + description: Box filled with greyscale spray paints! + components: + - type: EntityTableContainerFill + containers: + storagebase: !type:AllSelector + children: + - id: SprayPaintCanWhite + amount: 4 + - id: SprayPaintCanGray + amount: 4 + - id: SprayPaintCanBlack + amount: 4 + +- type: entity + parent: BoxSprayPaintBase + id: BoxSprayPaintMetals + name: metallic spray paint + description: Box filled with metallic spray paints! + components: + - type: EntityTableContainerFill + containers: + storagebase: !type:AllSelector + children: + - id: SprayPaintCanGold + amount: 4 + - id: SprayPaintCanSilver + amount: 4 + - id: SprayPaintCanCopper + amount: 4 + diff --git a/Resources/Prototypes/_DEN/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/_DEN/Catalog/Fills/Crates/fun.yml new file mode 100644 index 0000000000..b94e91f392 --- /dev/null +++ b/Resources/Prototypes/_DEN/Catalog/Fills/Crates/fun.yml @@ -0,0 +1,25 @@ +- type: entity + parent: CratePlastic + id: CrateFunSprayPaintVariety + name: bulk spray paint crate + description: A crate filled with a variety of spray paints! + components: + - type: EntityTableContainerFill + containers: + entity_storage: !type:AllSelector + children: + - id: BoxSprayPaintColors + - id: BoxSprayPaintShades + - id: BoxSprayPaintMetals + +- type: entity + parent: CratePlastic + id: CrateFunSprayPaintMagic + name: magic spray paint crate + description: A crate filled with.. A singular can of magic spray paint! + components: + - type: EntityTableContainerFill + containers: + entity_storage: !type:AllSelector + children: + - id: SprayPaintCanMagic diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon-rainbow.png b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon-rainbow.png new file mode 100644 index 0000000000000000000000000000000000000000..672bc36d660f1fb9e3eb3081410191ecea6a1452 GIT binary patch literal 413 zcmV;O0b>4%P)Px$S4l)cR9J=WmOo3wKp2OARS`3|B$Av(m|3s^36pkSs;&SUcz{NEd|! zoSX~pB30_9gK$W1mkfnu^$*x{u!5IlxmTkO_dtfcM}F_adkHzfU@-hKCF+Ag5Xi6h zp68LXNX_FoR<5*Aq0Oa~838FJNn(y7kt7Lh+s@P|kOaQdFwM-5r2M8pwKD?eJX7bK zquLp13y`*8u~i4}U1w@9I4@pcdlM)f0HxTe<8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon.png b/Resources/Textures/_DEN/Objects/Tools/spray-paint.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c3646aa75685124965f6602e7c0d001b70859043 GIT binary patch literal 276 zcmV+v0qg#WP)Px#&PhZ;R9J=Wmcb2!FbqY1t4>gc;F4|FAw&HgvJICEVOF4*HYy>9@`0dAd6K1I zDe=d$Yy*u(<6>^=MFR8px8L!W6x=e)1)UmvE(n-K5f4Sg1mIclDM$kL1kAY~^iEv? zehuT;2yZlV*Y0!s^e0u3PyO{%{tMK&c6dolt`0ss>0(3yHpxWdXhS^;WV* a<49gCK6K+H)G1p40000 Date: Thu, 26 Mar 2026 11:04:07 -0400 Subject: [PATCH 10/13] fix tests --- Resources/Prototypes/_DEN/Shaders/desaturated.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/_DEN/Shaders/desaturated.yml b/Resources/Prototypes/_DEN/Shaders/desaturated.yml index 44489cc7fc..064bab5f14 100644 --- a/Resources/Prototypes/_DEN/Shaders/desaturated.yml +++ b/Resources/Prototypes/_DEN/Shaders/desaturated.yml @@ -1,4 +1,4 @@ - type: shader id: Desaturated kind: source - path: "/Textures/_Den/Shaders/desaturated.swsl" + path: "/Textures/_DEN/Shaders/desaturated.swsl" From 52a785ed506bab46f0b6985b1243b2dbf7e60e6b Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Thu, 26 Mar 2026 13:10:33 -0400 Subject: [PATCH 11/13] fix more tests --- .../_DEN/Recolor/Components/RecolorApplierComponent.cs | 2 +- Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index c71275cb2d..7fcf9dc1f3 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -25,7 +25,7 @@ public sealed partial class RecolorApplierComponent : Component /// Sound to play when the doafter is over. /// [DataField] - public SoundSpecifier? DoafterSound = new SoundPathSpecifier("/Audio/Effects/Spray2.ogg"); + public SoundSpecifier? DoafterSound = new SoundPathSpecifier("/Audio/Effects/spray2.ogg"); /// /// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely. diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml index 795a83f8f0..499614d511 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Tools/paint_cans.yml @@ -400,7 +400,7 @@ color: &black "#333333" paintType: *paintType shaderBlacklist: *blacklist - colorName: black + colorName: black - type: Sprite layers: - state: spray-can-base From a9ff88f6897e94a2102ff2c677c2d23b5a8cb7b8 Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Thu, 26 Mar 2026 13:38:01 -0400 Subject: [PATCH 12/13] PLEASE TELL ME THIS IS THE LAST TEST FAIL. --- .../_DEN/Recolor/Components/RecolorApplierComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 7fcf9dc1f3..1bd0d9f38d 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -30,7 +30,7 @@ public sealed partial class RecolorApplierComponent : Component /// /// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely. /// - [DataField] + [ViewVariables(VVAccess.ReadWrite)] public int? MaxUses; /// From b0c89df5f41be4abbf3ba731061737148aa3596b Mon Sep 17 00:00:00 2001 From: honeyed-lemons Date: Thu, 26 Mar 2026 13:40:01 -0400 Subject: [PATCH 13/13] i did the wrong one oops --- .../_DEN/Recolor/Components/RecolorApplierComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs index 1bd0d9f38d..9b4a96e0a4 100644 --- a/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs +++ b/Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs @@ -30,13 +30,13 @@ public sealed partial class RecolorApplierComponent : Component /// /// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely. /// - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public int? MaxUses; /// /// Current amount of uses the applier can spray. /// - [DataField, AutoNetworkedField] + [ViewVariables(VVAccess.ReadWrite)] public int UsesLeft; ///