From d430d31052d9486cff5d283d3e46e697c1fdda55 Mon Sep 17 00:00:00 2001 From: portfiend <109661617+portfiend@users.noreply.github.com> Date: Sat, 13 Dec 2025 05:42:19 -0600 Subject: [PATCH 1/2] recolor appliers and recolored component you can apply a recolor to an entity. visuals current do not work --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 94 +++++++++++++++++++ .../_DEN/Recolor/RecolorSystem.Applier.cs | 53 +++++++++++ Content.Server/_DEN/Recolor/RecolorSystem.cs | 55 +++++++++++ .../_DEN/Recolor/RecolorApplierComponent.cs | 38 ++++++++ .../_DEN/Recolor/RecoloredComponent.cs | 37 ++++++++ .../_DEN/Recolor/SharedRecolorSystem.cs | 31 ++++++ .../_DEN/Entities/Objects/Tools/paint_can.yml | 15 +++ 7 files changed, 323 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/RecolorApplierComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/RecoloredComponent.cs create mode 100644 Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs create mode 100644 Resources/Prototypes/Entities/_DEN/Entities/Objects/Tools/paint_can.yml diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs new file mode 100644 index 0000000000..b449cc2399 --- /dev/null +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -0,0 +1,94 @@ +using Content.Shared._DEN.Recolor; +using Content.Shared.Sound; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +#pragma warning disable IDE1006 // Naming Styles +namespace Content.Client._DEN.Recolor; +#pragma warning restore IDE1006 // Naming Styles + +public sealed partial class RecolorVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentShutdown); + } + + protected override void OnAppearanceChange(EntityUid uid, + RecoloredComponent component, + ref AppearanceChangeEvent args) + { + base.OnAppearanceChange(uid, component, ref args); + + if (args.Sprite == null + || AppearanceSystem.TryGetData(uid, RecolorVisuals.RecolorDirty, out var dirty) + || dirty is not true) + return; + + ApplyRecolor((uid, component)); + AppearanceSystem.SetData(uid, RecolorVisuals.RecolorDirty, false); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + if (TerminatingOrDeleted(ent.Owner)) + return; + + RemoveRecolor(ent); + } + + private void ApplyRecolor(Entity ent) + { + if (!TryComp(ent.Owner, out var sprite)) + return; + + ShaderPrototype? shader = null; + if (ent.Comp.Shader != null + && _prototype.TryIndex(ent.Comp.Shader, out var proto)) + shader = proto; + + foreach (var spriteLayer in sprite.AllLayers) + { + if (spriteLayer is not SpriteComponent.Layer layer + || !ent.Comp.AffectLayersWithShaders && layer.Shader != null) + continue; + + SpriteSystem.LayerSetColor(layer, sprite.Color); + + if (shader != null) + { + var instance = shader.Instance(); + sprite.LayerSetShader(layer, instance); + } + } + } + + private void RemoveRecolor(Entity ent) + { + if (!TryComp(ent.Owner, out var sprite) + || !AppearanceSystem.TryGetData(ent.Owner, RecolorVisuals.RecolorDirty, out _)) + return; + + ShaderPrototype? shader = null; + if (ent.Comp.Shader != null + && _prototype.TryIndex(ent.Comp.Shader, out var proto)) + shader = proto; + + foreach (var spriteLayer in sprite.AllLayers) + { + if (spriteLayer is not SpriteComponent.Layer layer + || shader != null && layer.Shader != shader.Instance()) + continue; + + sprite.LayerSetShader(layer, ""); + + if (layer.Color == ent.Comp.Color && ent.Comp.PreviousColor != null) + SpriteSystem.LayerSetColor(layer, ent.Comp.PreviousColor.Value); + } + } +} diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs new file mode 100644 index 0000000000..b2d868c6e6 --- /dev/null +++ b/Content.Server/_DEN/Recolor/RecolorSystem.Applier.cs @@ -0,0 +1,53 @@ +using Content.Shared._DEN.Recolor; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; + +#pragma warning disable IDE1006 // Naming Styles +namespace Content.Server._DEN.Recolor; +#pragma warning restore IDE1006 // Naming Styles + +public sealed partial class RecolorSystem : SharedRecolorSystem +{ + private void OnRecolorApplierAfterInteract(Entity ent, ref AfterInteractEvent args) + { + if (!args.CanReach || args.Target is not { Valid: true } target || HasComp(target)) + return; + + TryStartApplyRecolorDoAfter(args.User, target, ent); + } + + private void TryStartApplyRecolorDoAfter(EntityUid user, + EntityUid target, + Entity applier) + { + var doAfterEvent = new ApplyRecolorDoAfterEvent + { + Color = applier.Comp.Color, + Shader = applier.Comp.Shader, + AffectLayersWithShaders = applier.Comp.AffectLayersWithShaders, + Removeable = applier.Comp.Removeable + }; + + 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, + shader: args.Shader, + affectLayersWithShaders: args.AffectLayersWithShaders, + removeable: args.Removeable); + } +} diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.cs b/Content.Server/_DEN/Recolor/RecolorSystem.cs new file mode 100644 index 0000000000..e7bfaf4c02 --- /dev/null +++ b/Content.Server/_DEN/Recolor/RecolorSystem.cs @@ -0,0 +1,55 @@ +using Content.Server.DoAfter; +using Content.Shared._DEN.Recolor; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Server.GameObjects; + +#pragma warning disable IDE1006 // Naming Styles +namespace Content.Server._DEN.Recolor; +#pragma warning restore IDE1006 // Naming Styles + +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(OnRecolorApplierAfterInteract); + SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); + } + + [PublicAPI] + public void Recolor(EntityUid uid, + Color color, + string? shader = null, + bool affectLayersWithShaders = false, + bool removeable = true) + { + if (HasComp(uid)) + return; + + EnsureComp(uid); + var recoloredComponent = new RecoloredComponent + { + Color = color, + Shader = shader, + AffectLayersWithShaders = affectLayersWithShaders, + Removeable = removeable + }; + + AddComp(uid, recoloredComponent); + _appearance.SetData(uid, RecolorVisuals.RecolorDirty, true); + } + + [PublicAPI] + public void RemoveRecolor(Entity ent) + { + if (!Resolve(ent.Owner, ref ent.Comp, logMissing: false)) + return; + + RemComp(ent); + } +} diff --git a/Content.Shared/_DEN/Recolor/RecolorApplierComponent.cs b/Content.Shared/_DEN/Recolor/RecolorApplierComponent.cs new file mode 100644 index 0000000000..0e15210690 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/RecolorApplierComponent.cs @@ -0,0 +1,38 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Recolor; + +[RegisterComponent] +public sealed partial class RecolorApplierComponent : Component +{ + /// + /// The color to change the sprite to. + /// + [DataField] + public Color Color = Color.White; + + /// + /// Whether or not this component can be removed by an entity with RecolorRemoverComponent. + /// + [DataField] + public bool Removeable = true; + + /// + /// Whether or not the recolor should apply to layers that already have shaders. + /// + [DataField] + public bool AffectLayersWithShaders = false; + + /// + /// The shader to apply to the recolored entity. + /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. + /// + [DataField] + public string? Shader = "Greyscale"; + + /// + /// 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/RecoloredComponent.cs b/Content.Shared/_DEN/Recolor/RecoloredComponent.cs new file mode 100644 index 0000000000..613c499e73 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/RecoloredComponent.cs @@ -0,0 +1,37 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Recolor; + +[RegisterComponent] +public sealed partial class RecoloredComponent : Component +{ + /// + /// The color to change the sprite to. + /// + [DataField] + public Color Color = Color.White; + + /// + /// Whether or not this component can be removed by an entity with RecolorRemoverComponent. + /// + [DataField] + public bool Removeable = true; + + /// + /// Whether or not the recolor should apply to layers that already have shaders. + /// + [DataField] + public bool AffectLayersWithShaders = false; + + /// + /// The shader to apply to the recolored entity. + /// Sorry, we don't have ShaderPrototype in Shared, because ShaderPrototype is clientside. + /// + [DataField] + public string? Shader = null; + + /// + /// The previous color of the sprite, before recoloring. + /// + public Color? PreviousColor; +} diff --git a/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs new file mode 100644 index 0000000000..cb17dfed57 --- /dev/null +++ b/Content.Shared/_DEN/Recolor/SharedRecolorSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +#pragma warning disable IDE1006 // Naming Styles +namespace Content.Shared._DEN.Recolor; +#pragma warning restore IDE1006 // Naming Styles + +public abstract partial class SharedRecolorSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + } +} + +[Serializable, NetSerializable] +public enum RecolorVisuals : byte +{ + RecolorDirty +} + +[Serializable, NetSerializable] +public sealed partial class ApplyRecolorDoAfterEvent : DoAfterEvent +{ + public Color Color; + public string? Shader = null; + public bool AffectLayersWithShaders = false; + public bool Removeable = true; + + public override DoAfterEvent Clone() => this; +} diff --git a/Resources/Prototypes/Entities/_DEN/Entities/Objects/Tools/paint_can.yml b/Resources/Prototypes/Entities/_DEN/Entities/Objects/Tools/paint_can.yml new file mode 100644 index 0000000000..1a81dce6fc --- /dev/null +++ b/Resources/Prototypes/Entities/_DEN/Entities/Objects/Tools/paint_can.yml @@ -0,0 +1,15 @@ +- type: entity + parent: BaseItem + id: SprayPaintCan + name: yellow spray paint + 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: "#ffff00" From 5988f2e3cf609d1caeb3e9a143519d7640c9f6c3 Mon Sep 17 00:00:00 2001 From: portfiend <109661617+portfiend@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:41:08 -0600 Subject: [PATCH 2/2] something --- .../_DEN/Recolor/RecolorVisualizerSystem.cs | 9 +++------ Content.Server/_DEN/Recolor/RecolorSystem.cs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs index b449cc2399..770b0e9e02 100644 --- a/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs +++ b/Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs @@ -26,11 +26,11 @@ protected override void OnAppearanceChange(EntityUid uid, base.OnAppearanceChange(uid, component, ref args); if (args.Sprite == null - || AppearanceSystem.TryGetData(uid, RecolorVisuals.RecolorDirty, out var dirty) + || !AppearanceSystem.TryGetData(uid, RecolorVisuals.RecolorDirty, out var dirty) || dirty is not true) return; - ApplyRecolor((uid, component)); + ApplyRecolor((uid, component), args.Sprite); AppearanceSystem.SetData(uid, RecolorVisuals.RecolorDirty, false); } @@ -42,11 +42,8 @@ private void OnComponentShutdown(Entity ent, ref ComponentSh RemoveRecolor(ent); } - private void ApplyRecolor(Entity ent) + private void ApplyRecolor(Entity ent, SpriteComponent sprite) { - if (!TryComp(ent.Owner, out var sprite)) - return; - ShaderPrototype? shader = null; if (ent.Comp.Shader != null && _prototype.TryIndex(ent.Comp.Shader, out var proto)) diff --git a/Content.Server/_DEN/Recolor/RecolorSystem.cs b/Content.Server/_DEN/Recolor/RecolorSystem.cs index e7bfaf4c02..3b4393a450 100644 --- a/Content.Server/_DEN/Recolor/RecolorSystem.cs +++ b/Content.Server/_DEN/Recolor/RecolorSystem.cs @@ -19,6 +19,19 @@ public override void Initialize() SubscribeLocalEvent(OnRecolorApplierAfterInteract); SubscribeLocalEvent(OnApplyRecolorDoAfterEvent); + + SubscribeLocalEvent(OnRecoloredStartup); + SubscribeLocalEvent(OnRecoloredShutdown); + } + + private void OnRecoloredStartup(Entity ent, ref ComponentStartup args) + { + _appearance.SetData(ent, RecolorVisuals.RecolorDirty, true); + } + + private void OnRecoloredShutdown(Entity ent, ref ComponentShutdown args) + { + _appearance.SetData(ent, RecolorVisuals.RecolorDirty, true); } [PublicAPI] @@ -41,7 +54,6 @@ public void Recolor(EntityUid uid, }; AddComp(uid, recoloredComponent); - _appearance.SetData(uid, RecolorVisuals.RecolorDirty, true); } [PublicAPI]