]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Spray Paint (Review Ready) (#23003)
authorbrainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Mon, 18 Mar 2024 21:29:48 +0000 (21:29 +0000)
committerGitHub <noreply@github.com>
Mon, 18 Mar 2024 21:29:48 +0000 (15:29 -0600)
* Spray Paint (Draft)

* paint colors, paints in maints loot, cargo crate of paints.

* fix

* remove paint (sort of)

* moved paintcleaner into own system

* Moved paint to server (had to unfortunately)

* doafter now breaks when moving away.

* cant paint mobstatecomp

* loads of fixes

* fixes

* fixes

* nopaintshadercomp

* fixes

* fix

* use locale for paint remove string

* remove nopaintshadercomponent and use blacklist

* remove enabled.true from visualizer

* paint doafter event.

* add verbs for paint and remove paint and icon for paint verb.

* fixes

* no longer replaces shader when shader exists.

* replace forloop with foreach, check shader before adding and removing.

* paint doafter now separate so no copy paste code

* Entities in sprayed targets item slots are also now correctly sprayed.

* fix

* fix

* fix airlock psray painter now removes painted before painting door.

* spray paints now use openablecomponent.

* fix

* fix damn accesstypes.

* fix

* fix

41 files changed:
Content.Client/Paint/PaintVisualizerSystem.cs [new file with mode: 0644]
Content.Server/Paint/PaintSystem.cs [new file with mode: 0644]
Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs
Content.Shared/Paint/PaintComponent.cs [new file with mode: 0644]
Content.Shared/Paint/PaintDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Paint/PaintRemoverComponent.cs [new file with mode: 0644]
Content.Shared/Paint/PaintRemoverDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Paint/PaintRemoverSystem.cs [new file with mode: 0644]
Content.Shared/Paint/PaintedComponent.cs [new file with mode: 0644]
Content.Shared/Paint/SharedPaintSystem.cs [new file with mode: 0644]
Content.Shared/SprayPainter/SharedSprayPainterSystem.cs
Resources/Locale/en-US/paint/paint.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Cargo/cargo_fun.yml
Resources/Prototypes/Catalog/Fills/Crates/fun.yml
Resources/Prototypes/Catalog/Fills/Lockers/misc.yml
Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml
Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml
Resources/Prototypes/Entities/Mobs/NPCs/carp.yml
Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml
Resources/Prototypes/Entities/Mobs/Player/guardian.yml
Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml
Resources/Prototypes/Entities/Objects/Materials/materials.yml
Resources/Prototypes/Entities/Objects/Misc/tiles.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml
Resources/Prototypes/Entities/Structures/Holographic/projections.yml
Resources/Prototypes/Entities/Structures/hydro_tray.yml
Resources/Prototypes/tags.yml
Resources/Textures/Interface/VerbIcons/paint.svg [new file with mode: 0644]
Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png [new file with mode: 0644]
Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml [new file with mode: 0644]
Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Fun/spraycans.rsi/meta.json
Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png [deleted file]

diff --git a/Content.Client/Paint/PaintVisualizerSystem.cs b/Content.Client/Paint/PaintVisualizerSystem.cs
new file mode 100644 (file)
index 0000000..6c99b2d
--- /dev/null
@@ -0,0 +1,120 @@
+using System.Linq;
+using Robust.Client.GameObjects;
+using static Robust.Client.GameObjects.SpriteComponent;
+using Content.Shared.Clothing;
+using Content.Shared.Hands;
+using Content.Shared.Paint;
+using Robust.Client.Graphics;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Paint
+{
+    public sealed class PaintedVisualizerSystem : VisualizerSystem<PaintedComponent>
+    {
+        /// <summary>
+        /// Visualizer for Paint which applies a shader and colors the entity.
+        /// </summary>
+
+        [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+        [Dependency] private readonly IPrototypeManager _protoMan = default!;
+
+        public ShaderInstance? Shader; // in Robust.Client.Graphics so cannot move to shared component.
+
+        public override void Initialize()
+        {
+            base.Initialize();
+
+            SubscribeLocalEvent<PaintedComponent, HeldVisualsUpdatedEvent>(OnHeldVisualsUpdated);
+            SubscribeLocalEvent<PaintedComponent, ComponentShutdown>(OnShutdown);
+            SubscribeLocalEvent<PaintedComponent, EquipmentVisualsUpdatedEvent>(OnEquipmentVisualsUpdated);
+        }
+
+        protected override void OnAppearanceChange(EntityUid uid, PaintedComponent component, ref AppearanceChangeEvent args)
+        {
+            // ShaderPrototype sadly in Robust.Client, cannot move to shared component.
+            Shader = _protoMan.Index<ShaderPrototype>(component.ShaderName).Instance();
+
+            if (args.Sprite == null)
+                return;
+
+            if (!_appearance.TryGetData<bool>(uid, PaintVisuals.Painted, out bool isPainted))
+                return;
+
+            var sprite = args.Sprite;
+
+
+            foreach (var spriteLayer in sprite.AllLayers)
+            {
+                if (spriteLayer is not Layer layer)
+                    continue;
+
+                if (layer.Shader == null) // If shader isn't null we dont want to replace the original shader.
+                {
+                    layer.Shader = Shader;
+                    layer.Color = component.Color;
+                }
+            }
+        }
+
+        private void OnHeldVisualsUpdated(EntityUid uid, PaintedComponent component, HeldVisualsUpdatedEvent args)
+        {
+            if (args.RevealedLayers.Count == 0)
+                return;
+
+            if (!TryComp(args.User, out SpriteComponent? sprite))
+                return;
+
+            foreach (var revealed in args.RevealedLayers)
+            {
+                if (!sprite.LayerMapTryGet(revealed, out var layer) || sprite[layer] is not Layer notlayer)
+                    continue;
+
+                sprite.LayerSetShader(layer, component.ShaderName);
+                sprite.LayerSetColor(layer, component.Color);
+            }
+        }
+
+        private void OnEquipmentVisualsUpdated(EntityUid uid, PaintedComponent component, EquipmentVisualsUpdatedEvent args)
+        {
+            if (args.RevealedLayers.Count == 0)
+                return;
+
+            if (!TryComp(args.Equipee, out SpriteComponent? sprite))
+                return;
+
+            foreach (var revealed in args.RevealedLayers)
+            {
+                if (!sprite.LayerMapTryGet(revealed, out var layer) || sprite[layer] is not Layer notlayer)
+                    continue;
+
+                sprite.LayerSetShader(layer, component.ShaderName);
+                sprite.LayerSetColor(layer, component.Color);
+            }
+        }
+
+        private void OnShutdown(EntityUid uid, PaintedComponent component, ref ComponentShutdown args)
+        {
+            if (!TryComp(uid, out SpriteComponent? sprite))
+                return;
+
+            component.BeforeColor = sprite.Color;
+            Shader = _protoMan.Index<ShaderPrototype>(component.ShaderName).Instance();
+
+            if (!Terminating(uid))
+            {
+                foreach (var spriteLayer in sprite.AllLayers)
+                {
+                    if (spriteLayer is not Layer layer)
+                        continue;
+
+                    if (layer.Shader == Shader) // If shader isn't same as one in component we need to ignore it.
+                    {
+                        layer.Shader = null;
+                        if (layer.Color == component.Color) // If color isn't the same as one in component we don't want to change it.
+                            layer.Color = component.BeforeColor;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Content.Server/Paint/PaintSystem.cs b/Content.Server/Paint/PaintSystem.cs
new file mode 100644 (file)
index 0000000..c6718ac
--- /dev/null
@@ -0,0 +1,180 @@
+using Content.Shared.Popups;
+using Content.Shared.Paint;
+using Content.Shared.Sprite;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Server.Chemistry.Containers.EntitySystems;
+using Robust.Shared.Audio.Systems;
+using Content.Shared.Humanoid;
+using Robust.Shared.Utility;
+using Content.Shared.Verbs;
+using Content.Shared.SubFloor;
+using Content.Server.Nutrition.Components;
+using Content.Shared.Inventory;
+using Content.Server.Nutrition.EntitySystems;
+
+namespace Content.Server.Paint;
+
+/// <summary>
+/// Colors target and consumes reagent on each color success.
+/// </summary>
+public sealed class PaintSystem : SharedPaintSystem
+{
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly InventorySystem _inventory = default!;
+    [Dependency] private readonly OpenableSystem _openable = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<PaintComponent, AfterInteractEvent>(OnInteract);
+        SubscribeLocalEvent<PaintComponent, PaintDoAfterEvent>(OnPaint);
+        SubscribeLocalEvent<PaintComponent, GetVerbsEvent<UtilityVerb>>(OnPaintVerb);
+    }
+
+    private void OnInteract(EntityUid uid, PaintComponent component, AfterInteractEvent args)
+    {
+        if (!args.CanReach)
+            return;
+
+        if (args.Target is not { Valid: true } target)
+            return;
+
+        PrepPaint(uid, component, target, args.User);
+    }
+
+    private void OnPaintVerb(EntityUid uid, PaintComponent component, GetVerbsEvent<UtilityVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess)
+            return;
+
+        var paintText = Loc.GetString("paint-verb");
+
+        var verb = new UtilityVerb()
+        {
+            Act = () =>
+            {
+                PrepPaint(uid, component, args.Target, args.User);
+            },
+
+            Text = paintText,
+            Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/paint.svg.192dpi.png"))
+        };
+        args.Verbs.Add(verb);
+    }
+    private void PrepPaint(EntityUid uid, PaintComponent component, EntityUid target, EntityUid user)
+    {
+
+        var doAfterEventArgs = new DoAfterArgs(EntityManager, user, component.Delay, new PaintDoAfterEvent(), uid, target: target, used: uid)
+        {
+            BreakOnTargetMove = true,
+            BreakOnUserMove = true,
+            BreakOnDamage = true,
+            NeedHand = true,
+            BreakOnHandChange = true
+        };
+
+        if (!_doAfterSystem.TryStartDoAfter(doAfterEventArgs))
+            return;
+    }
+
+    private void OnPaint(Entity<PaintComponent> entity, ref PaintDoAfterEvent args)
+    {
+        if (args.Target == null || args.Used == null)
+            return;
+
+        if (args.Handled || args.Cancelled)
+            return;
+
+        if (args.Target is not { Valid: true } target)
+            return;
+
+        if (!_openable.IsOpen(entity))
+        {
+            _popup.PopupEntity(Loc.GetString("paint-closed", ("used", args.Used)), args.User, args.User, PopupType.Medium);
+            return;
+        }
+
+        if (HasComp<PaintedComponent>(target) || HasComp<RandomSpriteComponent>(target))
+        {
+            _popup.PopupEntity(Loc.GetString("paint-failure-painted", ("target", args.Target)), args.User, args.User, PopupType.Medium);
+            return;
+        }
+
+        if (!entity.Comp.Blacklist?.IsValid(target, EntityManager) != true || HasComp<HumanoidAppearanceComponent>(target) || HasComp<SubFloorHideComponent>(target))
+        {
+            _popup.PopupEntity(Loc.GetString("paint-failure", ("target", args.Target)), args.User, args.User, PopupType.Medium);
+            return;
+        }
+
+
+        if (TryPaint(entity, target))
+        {
+            EnsureComp<PaintedComponent>(target, out PaintedComponent? paint);
+            EnsureComp<AppearanceComponent>(target);
+
+            paint.Color = entity.Comp.Color; // set the target color to the color specified in the spray paint yml.
+            _audio.PlayPvs(entity.Comp.Spray, entity);
+            paint.Enabled = true;
+
+            if (HasComp<InventoryComponent>(target)) // Paint any clothing the target is wearing.
+            {
+                if (_inventory.TryGetSlots(target, out var slotDefinitions))
+                {
+                    foreach (var slot in slotDefinitions)
+                    {
+                        if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt))
+                            continue;
+
+                        if (slotEnt == null)
+                            return;
+
+                        if (HasComp<PaintedComponent>(slotEnt.Value) || !entity.Comp.Blacklist?.IsValid(slotEnt.Value, EntityManager) != true
+                            || HasComp<RandomSpriteComponent>(slotEnt.Value) || HasComp<HumanoidAppearanceComponent>(slotEnt.Value))
+                            return;
+
+                        EnsureComp<PaintedComponent>(slotEnt.Value, out PaintedComponent? slotpaint);
+                        EnsureComp<AppearanceComponent>(slotEnt.Value);
+                        slotpaint.Color = entity.Comp.Color;
+                        _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true);
+                        Dirty(slotEnt.Value, slotpaint);
+                    }
+                }
+            }
+
+            _popup.PopupEntity(Loc.GetString("paint-success", ("target", args.Target)), args.User, args.User, PopupType.Medium);
+            _appearanceSystem.SetData(target, PaintVisuals.Painted, true);
+            Dirty(target, paint);
+            args.Handled = true;
+            return;
+        }
+
+        if (!TryPaint(entity, target))
+        {
+            _popup.PopupEntity(Loc.GetString("paint-empty", ("used", args.Used)), args.User, args.User, PopupType.Medium);
+            return;
+        }
+    }
+
+    private bool TryPaint(Entity<PaintComponent> reagent, EntityUid target)
+    {
+        if (HasComp<HumanoidAppearanceComponent>(target) || HasComp<SubFloorHideComponent>(target))
+            return false;
+
+        if (_solutionContainer.TryGetSolution(reagent.Owner, reagent.Comp.Solution, out _, out var solution))
+        {
+            var quantity = solution.RemoveReagent(reagent.Comp.Reagent, reagent.Comp.ConsumptionUnit);
+            if (quantity > 0)// checks quantity of solution is more than 0.
+                return true;
+
+            if (quantity < 1)
+                return false;
+        }
+        return false;
+    }
+}
index c49bfdec9317459e24edbd46fcea487e223307c4..0b4b13d6e4b5b5bc6ea184007abd3ede7d534844 100644 (file)
@@ -80,11 +80,6 @@ namespace Content.Server.Storage.EntitySystems
                 _adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(args.User)} used {ToPrettyString(uid)} which spawned {ToPrettyString(entityToPlaceInHands.Value)}");
             }
 
-            if (component.Sound != null)
-            {
-                _audio.PlayPvs(component.Sound, uid);
-            }
-
             component.Uses--;
 
             // Delete entity only if component was successfully used
@@ -97,6 +92,7 @@ namespace Content.Server.Storage.EntitySystems
             if (entityToPlaceInHands != null)
             {
                 _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
+                _audio.PlayPvs(component.Sound, entityToPlaceInHands.Value);
             }
         }
     }
diff --git a/Content.Shared/Paint/PaintComponent.cs b/Content.Shared/Paint/PaintComponent.cs
new file mode 100644 (file)
index 0000000..ad09f4c
--- /dev/null
@@ -0,0 +1,60 @@
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Audio;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Paint;
+
+/// <summary>
+/// Entity when used on another entity will paint target entity.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedPaintSystem))]
+public sealed partial class PaintComponent : Component
+{
+    /// <summary>
+    /// Noise made when paint applied.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier Spray = new SoundPathSpecifier("/Audio/Effects/spray2.ogg");
+
+    /// <summary>
+    /// Solution on the entity that contains the paint.
+    /// </summary>
+    [DataField]
+    public string Solution = "drink";
+
+    /// <summary>
+    /// How long the doafter will take.
+    /// </summary>
+    [DataField]
+    public int Delay = 2;
+
+    /// <summary>
+    /// Reagent that will be used as paint.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public ProtoId<ReagentPrototype> Reagent = "SpaceGlue";
+
+    /// <summary>
+    /// Color that the painting entity will instruct the painted entity to be.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Color Color = Color.FromHex("#c62121");
+
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public EntityWhitelist? Blacklist;
+    /// <summary>
+    /// Reagent consumption per use.
+    /// </summary>
+    [DataField]
+    public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5);
+
+    /// <summary>
+    /// Duration per unit
+    /// </summary>
+    [DataField]
+    public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6);
+}
diff --git a/Content.Shared/Paint/PaintDoAfterEvent.cs b/Content.Shared/Paint/PaintDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..0851f15
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Paint;
+
+[Serializable, NetSerializable]
+public sealed partial class PaintDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/Paint/PaintRemoverComponent.cs b/Content.Shared/Paint/PaintRemoverComponent.cs
new file mode 100644 (file)
index 0000000..54d0ed7
--- /dev/null
@@ -0,0 +1,24 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Audio;
+
+namespace Content.Shared.Paint;
+
+/// <summary>
+///  Removes paint from an entity that was painted with spray paint.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(PaintRemoverSystem))]
+public sealed partial class PaintRemoverComponent : Component
+{
+    /// <summary>
+    /// Sound when target is cleaned.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg");
+
+    /// <summary>
+    /// DoAfter wait time.
+    /// </summary>
+    [DataField]
+    public float CleanDelay = 2f;
+}
diff --git a/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..940b1aa
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Paint;
+
+[Serializable, NetSerializable]
+public sealed partial class PaintRemoverDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/Paint/PaintRemoverSystem.cs b/Content.Shared/Paint/PaintRemoverSystem.cs
new file mode 100644 (file)
index 0000000..ac1cc62
--- /dev/null
@@ -0,0 +1,96 @@
+using Content.Shared.Popups;
+using Content.Shared.Interaction;
+using Content.Shared.DoAfter;
+using Content.Shared.Verbs;
+using Content.Shared.Sprite;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Paint;
+
+/// <summary>
+/// Removes paint from an entity.
+/// </summary>
+public sealed class PaintRemoverSystem : SharedPaintSystem
+{
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<PaintRemoverComponent, AfterInteractEvent>(OnInteract);
+        SubscribeLocalEvent<PaintRemoverComponent, PaintRemoverDoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<PaintRemoverComponent, GetVerbsEvent<UtilityVerb>>(OnPaintRemoveVerb);
+    }
+
+    // When entity is painted, remove paint from that entity.
+    private void OnInteract(EntityUid uid, PaintRemoverComponent component, AfterInteractEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        if (!args.CanReach || args.Target is not { Valid: true } target || !HasComp<PaintedComponent>(target))
+            return;
+
+        _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new PaintRemoverDoAfterEvent(), uid, args.Target, uid)
+        {
+            BreakOnUserMove = true,
+            BreakOnDamage = true,
+            BreakOnTargetMove = true,
+            MovementThreshold = 1.0f,
+        });
+        args.Handled = true;
+    }
+
+    private void OnDoAfter(EntityUid uid, PaintRemoverComponent component, DoAfterEvent args)
+    {
+        if (args.Cancelled || args.Handled || args.Args.Target == null)
+            return;
+
+        if (args.Target is not { Valid: true } target)
+            return;
+
+        if (!TryComp(target, out PaintedComponent? paint))
+            return;
+
+        paint.Enabled = false;
+        _audio.PlayPredicted(component.Sound, target, args.User);
+        _popup.PopupClient(Loc.GetString("paint-removed", ("target", target)), args.User, args.User, PopupType.Medium);
+        _appearanceSystem.SetData(target, PaintVisuals.Painted, false);
+        RemComp<PaintedComponent>(target);
+        Dirty(target, paint);
+
+        args.Handled = true;
+    }
+
+    private void OnPaintRemoveVerb(EntityUid uid, PaintRemoverComponent component, GetVerbsEvent<UtilityVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess)
+            return;
+
+        var paintremovalText = Loc.GetString("paint-remove-verb");
+
+        var verb = new UtilityVerb()
+        {
+            Act = () => {
+
+                _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new PaintRemoverDoAfterEvent(), uid, args.Target, uid)
+                {
+                    BreakOnUserMove = true,
+                    BreakOnDamage = true,
+                    BreakOnTargetMove = true,
+                    MovementThreshold = 1.0f,
+                });
+            },
+
+            Text = paintremovalText
+        };
+
+        args.Verbs.Add(verb);
+    }
+}
diff --git a/Content.Shared/Paint/PaintedComponent.cs b/Content.Shared/Paint/PaintedComponent.cs
new file mode 100644 (file)
index 0000000..a6ee737
--- /dev/null
@@ -0,0 +1,41 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Paint;
+
+/// <summary>
+/// Component applied to target entity when painted.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class PaintedComponent : Component
+{
+    /// <summary>
+    ///  Color of the paint.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Color Color = Color.FromHex("#2cdbd5");
+
+    /// <summary>
+    ///  Used to remove the color when component removed.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Color BeforeColor;
+
+    /// <summary>
+    /// If paint is enabled.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled;
+
+    /// <summary>
+    /// Name of the shader.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string ShaderName = "Greyscale";
+}
+
+[Serializable, NetSerializable]
+public enum PaintVisuals : byte
+{
+    Painted,
+}
diff --git a/Content.Shared/Paint/SharedPaintSystem.cs b/Content.Shared/Paint/SharedPaintSystem.cs
new file mode 100644 (file)
index 0000000..1018581
--- /dev/null
@@ -0,0 +1,11 @@
+namespace Content.Shared.Paint;
+
+/// <summary>
+/// Colors target and consumes reagent on each color success.
+/// </summary>
+public abstract class SharedPaintSystem : EntitySystem
+{
+    public virtual void UpdateAppearance(EntityUid uid, PaintedComponent? component = null)
+    {
+    }
+}
index 529e321f8daa4f9d5a4371bcca08c7b3ac009735..fa04a50f8b096876c50f2b1b0668a393d200931e 100644 (file)
@@ -4,6 +4,7 @@ using Content.Shared.DoAfter;
 using Content.Shared.Doors.Components;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
+using Content.Shared.Paint;
 using Content.Shared.SprayPainter.Components;
 using Content.Shared.SprayPainter.Prototypes;
 using Robust.Shared.Audio.Systems;
@@ -129,6 +130,8 @@ public abstract class SharedSprayPainterSystem : EntitySystem
             return;
         }
 
+        RemComp<PaintedComponent>(ent);
+
         var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.AirlockSprayTime, new SprayPainterDoorDoAfterEvent(sprite, style.Department), args.Used, target: ent, used: args.Used)
         {
             BreakOnTargetMove = true,
diff --git a/Resources/Locale/en-US/paint/paint.ftl b/Resources/Locale/en-US/paint/paint.ftl
new file mode 100644 (file)
index 0000000..200b1f6
--- /dev/null
@@ -0,0 +1,8 @@
+paint-success = {THE($target)} has been covered in paint!
+paint-failure = Can't cover {THE($target)} in paint!
+paint-failure-painted = {THE($target)} is already covered in paint!
+paint-empty = {THE($used)} is empty!
+paint-removed = You clean off the paint!
+paint-closed = You must open {THE($used)}  first!
+paint-verb = Paint
+paint-remove-verb = Remove Paint
index 61085f13b9442e837fdd9697ef3767d52ec8b725..c29458a1ee54b5fc575014625303c6bb33d4ffc1 100644 (file)
   category: cargoproduct-category-name-fun
   group: market
 
+- type: cargoProduct
+  id: FunSprayPaints
+  icon:
+    sprite: Objects/Fun/spraycans.rsi
+    state: death2_cap
+  product: CrateFunSprayPaints
+  cost: 2000
+  category: Fun
+  group: market
+
 - type: cargoProduct
   id: FunParty
   icon:
index 72cf6c447cc04311cc0b16a79f22d7b2677f1535..17018cb9e60a0cfac00333156bd5e8f75ce6945d 100644 (file)
     contents:
       - id: SnapPopBox
       - id: CrazyGlue
-        amount: 2
       - id: PlasticBanana
+      - id: FunnyPaint
+        orGroup: Paint
+        prob: 0.5
+      - id: FunnyPaintYellow
+        orGroup: Paint
+        prob: 0.5
       - id: WhoopieCushion
       - id: ToyHammer
       - id: MrChips
-        orGroup: GiftPool
+        prob: 0.5
+        orGroup: Dummy
       - id: MrDips
-        orGroup: Giftpool
+        prob: 0.5
+        orGroup: Dummy
       - id: RevolverCapGun
       - id: BalloonNT
       - id: ClothingShoesClownLarge
         amount: 15
         prob: 0.05
 
+- type: entity
+  id: CrateFunSprayPaints
+  name: spray paint crate
+  description: a crate filled with spray paint.
+  parent: CratePlastic
+  suffix: Spray Paint
+  components:
+  - type: StorageFill
+    contents:
+      - id: SprayPaintBlue
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintRed
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintOrange
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintBlack
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintGreen
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintPurple
+        amount: 2
+        prob: 0.33
+      - id: SprayPaintWhite
+        amount: 2
+        prob: 0.33
+      - id: DeathPaint
+        amount: 2
+      - id: DeathPaintTwo
+        amount: 2
+
 - type: entity
   name: dartboard box set
   description: A box with everything you need for a fun game of darts.
index 25078dbe57f51f2f3d2aae24eb7110d18959d9ed..850fb912734d9ac0b80b0a53fdb68c8b02cb6c7d 100644 (file)
           prob: 0.25
         - id: StrangePill
           prob: 0.20
+        - id: DeathPaint
+          prob: 0.05
+        - id: DeathPaintTwo
+          prob: 0.05
         - id: DrinkMopwataBottleRandom
           prob: 0.20
         - id: ModularReceiver
index ae7e5bcf76208d1ee1bcefb7742c6c039c41503e..883182aae8da5de09d432223fd78d1b64db34a23 100644 (file)
@@ -44,6 +44,7 @@
         - CrateMaterialPlastic
         - CrateMaterialWood
         - CrateMaterialPlasteel
+        - CrateFunSprayPaints
         - CrateFunArtSupplies
         - CrateEngineeringCableLV
         - CrateEngineeringCableMV
index 5481ddbda426ce02975f80ed6c403d7e741a33e6..df61726a9963f09c2203408255d0e399fad8accc 100644 (file)
         - MaterialCloth10
         - MaterialWoodPlank10
         - ResearchDisk
+        - DeathPaint
         - Plunger
+        - SprayPaintBlue
+        - SprayPaintRed
+        - SprayPaintGreen
+        - SprayPaintOrange
         - TechnologyDisk
         - PowerCellMedium
         - PowerCellSmall
index 7308267473645284e7f74e5327da4005987ede84..3e6c603626bc64f4b23b2f2aa969cd187e7e9c65 100644 (file)
@@ -70,6 +70,7 @@
       tags:
         - Carp
         - DoorBumpOpener
+        - NoPaint
     - type: ReplacementAccent
       accent: genericAggressive
     - type: Speech
index 68ebf52dc06548eb823a0bbe6bb31434b073c52b..4a35f71ac05efa069a71049844fbbd834faeff10 100644 (file)
@@ -93,3 +93,6 @@
     - RevenantTheme
   - type: Speech
     speechVerb: Ghost
+  - type: Tag
+    tags:
+      - NoPaint
index d892b31fac3a530146a2286c191b2089fc876269..c37826666acd10bb1cee0e017bdfdf85d10b1a64 100644 (file)
     - type: Tag
       tags:
         - CannotSuicide
+        - NoPaint
 
 # From the uplink injector
 - type: entity
       tags:
         - CannotSuicide
         - FootstepSound
+        - NoPaint
     - type: Inventory
       templateId: holoclown
     - type: Hands
diff --git a/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml
new file mode 100644 (file)
index 0000000..1b417f6
--- /dev/null
@@ -0,0 +1,292 @@
+# Base Paints
+- type: entity
+  parent: BaseItem
+  id: PaintBase
+  name: spray paint
+  description: A tin of spray paint.
+  noSpawn: true
+  components:
+  - type: Appearance
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    state: clown_cap
+    layers:
+      - state: clown_cap
+        map: ["enum.OpenableVisuals.Layer"]
+  - type: Paint
+    consumptionUnit: 10
+    blacklist:
+      tags:
+        - NoPaint
+  - type: Item
+    sprite: Objects/Fun/spraycans.rsi
+    heldPrefix: spray
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 50
+        reagents:
+        - ReagentId: SpaceGlue
+          Quantity: 50
+  - type: TrashOnSolutionEmpty
+    solution: drink
+  - type: Sealable
+  - type: Openable
+    sound:
+      path: /Audio/Effects/pop_high.ogg
+    closeable: true
+    closeSound:
+      path: /Audio/Effects/pop_high.ogg
+
+# Paints
+
+# funnypaint
+- type: entity
+  parent: PaintBase
+  id: FunnyPaint
+  name: funny paint
+  description:  A tin of funny paint, manufactured by Honk! Co.
+  components:
+  - type: Paint
+    color: "#fa74df"
+  - type: Item
+    sprite: Objects/Fun/spraycans.rsi
+    heldPrefix: clown
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "clown"}
+          False: {state: "clown_cap"}
+
+- type: entity
+  parent: PaintBase
+  id: FunnyPaintYellow
+  name: funny paint
+  description:  A tin of funny paint, manufactured by Honk! Co.
+  components:
+  - type: Paint
+    color: "#d5e028"
+  - type: Item
+    sprite: Objects/Fun/spraycans.rsi
+    heldPrefix: clown
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    state: clown2_cap
+    layers:
+      - state: clown2_cap
+        map: ["enum.OpenableVisuals.Layer"]
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "clown2"}
+          False: {state: "clown2_cap"}
+
+#death paint
+- type: entity
+  parent: PaintBase
+  id: DeathPaint
+  components:
+  - type: Paint
+    color: "#ff20c8"
+  - type: Item
+    sprite: Objects/Fun/spraycans.rsi
+    heldPrefix: spray
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    state: death_cap
+    layers:
+      - state: death_cap
+        map: ["enum.OpenableVisuals.Layer"]
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "death"}
+          False: {state: "death_cap"}
+
+- type: entity
+  parent: PaintBase
+  id: DeathPaintTwo
+  components:
+  - type: Paint
+    color: "#ff2020"
+  - type: Item
+    sprite: Objects/Fun/spraycans.rsi
+    heldPrefix: spray
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    state: death2_cap
+    layers:
+      - state: death2_cap
+        map: ["enum.OpenableVisuals.Layer"]
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "death2"}
+          False: {state: "death2_cap"}
+
+#Sprays
+
+#Blue
+- type: entity
+  parent: PaintBase
+  id: SprayPaintBlue
+  suffix: Blue
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#5890f7"
+  - type: Paint
+    color: "#5890f7"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#5890f7"}
+          False: {state: "spray_cap_colors" , color: "#5890f7"}
+
+#Red
+- type: entity
+  parent: PaintBase
+  id: SprayPaintRed
+  suffix: Red
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#ff3b3b"
+  - type: Paint
+    color: "#ff3b3b"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#ff3b3b"}
+          False: {state: "spray_cap_colors" , color: "#ff3b3b"}
+
+#Green
+- type: entity
+  parent: PaintBase
+  id: SprayPaintGreen
+  suffix: Green
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#73f170"
+  - type: Paint
+    color: "#73f170"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#73f170"}
+          False: {state: "spray_cap_colors" , color: "#73f170"}
+
+#Black
+- type: entity
+  parent: PaintBase
+  id: SprayPaintBlack
+  suffix: Black
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#3a3a3a"
+  - type: Paint
+    color: "#3a3a3a"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#3a3a3a"}
+          False: {state: "spray_cap_colors" , color: "#3a3a3a"}
+
+#Orange
+- type: entity
+  parent: PaintBase
+  id: SprayPaintOrange
+  suffix: Orange
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#f6a44b"
+  - type: Paint
+    color: "#f6a44b"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#f6a44b"}
+          False: {state: "spray_cap_colors" , color: "#f6a44b"}
+
+#Purple
+- type: entity
+  parent: PaintBase
+  id: SprayPaintPurple
+  suffix: Purple
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#c063f5"
+  - type: Paint
+    color: "#c063f5"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#c063f5"}
+          False: {state: "spray_cap_colors" , color: "#c063f5"}
+
+#White
+- type: entity
+  parent: PaintBase
+  id: SprayPaintWhite
+  suffix: White
+  components:
+  - type: Sprite
+    sprite: Objects/Fun/spraycans.rsi
+    layers:
+      - state: spray
+        map: ["Base"]
+      - state: spray_cap_colors
+        map: ["enum.OpenableVisuals.Layer"]
+        color: "#f2f2f2"
+  - type: Paint
+    color: "#f2f2f2"
+  - type: GenericVisualizer
+    visuals:
+      enum.OpenableVisuals.Opened:
+        enum.OpenableVisuals.Layer:
+          True: {state: "spray_colors" , color: "#f2f2f2"}
+          False: {state: "spray_cap_colors" , color: "#f2f2f2"}
index 59d8ed192205e5b2d5dda5da4c3e21bb48f5e2ec..2e0eec7a6584f0b9a525cfa307ec25b0799f46dc 100644 (file)
@@ -15,6 +15,7 @@
   - type: Tag
     tags:
     - Sheet
+    - NoPaint
   - type: Material
   - type: Damageable
     damageContainer: Inorganic
index 82b9f62837a47b9f5bebed9038777625f87f6c8a..3a887848bf57369bbdcdb329f0c4802d10817f35 100644 (file)
@@ -15,6 +15,7 @@
     tags:
     - Sheet
     - Metal
+    - NoPaint
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Metallic
index dfb513362890dd450496d98b63d1adb72594d2e6..9dc87a9117d89d94fa6d57663da4bba493f48f25 100644 (file)
@@ -12,6 +12,7 @@
   - type: Tag
     tags:
     - Sheet
+    - NoPaint
   - type: Damageable
     damageContainer: Inorganic
   - type: Destructible
   - type: Tag
     tags:
     - Sheet
+    - NoPaint
 
 - type: entity
   parent: SheetPlasma
     tags:
     - Plastic
     - Sheet
+    - NoPaint
   - type: Material
   - type: PhysicalComposition
     materialComposition:
index d11df5d94e8a4638cccaa9e1d079c4b580413adf..2bfd409c300431506233f8290ce66bf13d5ec8f1 100644 (file)
@@ -12,6 +12,7 @@
   - type: Tag
     tags:
       - RawMaterial
+      - NoPaint
   - type: Damageable
     damageContainer: Inorganic
   - type: Destructible
index 8e29c3c27e65b3236ee4aa4538c384364ba93991..5b57d930b037a22b7c15bec98050d027d1ec7a41 100644 (file)
@@ -15,6 +15,9 @@
         Blunt: 5
   - type: Stack
     count: 1
+  - type: Tag
+    tags:
+      - NoPaint
   - type: Damageable
     damageContainer: Inorganic
   - type: Destructible
index 5678de6bafc5f49728edfd7047a04174d797ed20..56786057d5740f105a68a01df17569601822c0d7 100644 (file)
@@ -62,6 +62,7 @@
     solution: soap
   - type: DeleteOnSolutionEmpty
     solution: soap
+  - type: PaintRemover
   - type: FlavorProfile
     flavors:
       - clean
index 13c8b9cb25c508a3d5f60831018092cf150bee00..c5ea3595409b0ba28d01442d6f03dc1024af2e1e 100644 (file)
@@ -78,6 +78,9 @@
     malus: 0
   - type: Reflect
     enabled: false
+  - type: Tag
+    tags:
+      - NoPaint
   - type: IgnitionSource
     temperature: 700
 
   - type: Tag
     tags:
     - Write
+    - NoPaint
   - type: DisarmMalus
     malus: 0
 
index 7777153bbac7528a30b87642b07fbfd9eda272bf..f82fe8b51bbe52646a1d31edc0edf2494c5a819f 100644 (file)
@@ -31,6 +31,9 @@
     sound:
       path: /Audio/Ambience/Objects/fireplace.ogg
   - type: AlwaysHot
+  - type: Tag
+    tags:
+      - NoPaint
 
 - type: entity
   id: LegionnaireBonfire
index d2a5853fcb0614ddd311930da8efddeaa49d2b37..b8717cf6cc92f670f02f02bccb695fc946218313 100644 (file)
@@ -25,6 +25,9 @@
         behaviors:\r
           - !type:DoActsBehavior\r
             acts: [ "Destruction" ]\r
+  - type: Tag\r
+    tags:\r
+      - NoPaint\r
 \r
 - type: entity\r
   id: HoloFan\r
index 1ab1fd5b2fdb8eb5add97bed58c35ed8951887a6..43b8bd197a524ef8bf1ff1aec5e01ce849e08e8c 100644 (file)
@@ -92,6 +92,9 @@
   - type: GuideHelp
     guides:
     - Botany
+  - type: Tag
+    tags:
+      - NoPaint
 
 - type: entity
   parent: hydroponicsTray
index 59239efd9a16d238addef2c1a300b8a9ede3b49a..ab154c6b8f5ea73679809ba5f07da38731fea79f 100644 (file)
 - type: Tag
   id: NoBlockAnchoring
 
+- type: Tag
+  id: NoPaint
+
 - type: Tag
   id: NozzleBackTank
 
diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg b/Resources/Textures/Interface/VerbIcons/paint.svg
new file mode 100644 (file)
index 0000000..78a56e3
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   version="1.1"
+   id="svg834"
+   sodipodi:docname="plus.svg"
+   inkscape:version="1.1.2 (b8e25be833, 2022-02-05)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs838" />
+  <sodipodi:namedview
+     id="namedview836"
+     pagecolor="#505050"
+     bordercolor="#eeeeee"
+     borderopacity="1"
+     inkscape:pageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="33.666667"
+     inkscape:cx="11.985149"
+     inkscape:cy="7.2475248"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg834" />
+  <path
+     d="M 22.69307,10.252475 H 13.806931 V 1.3663366 H 10.252475 V 10.252475 H 1.3663366 v 3.554455 h 8.8861384 v 8.886139 h 3.554456 V 13.80693 h 8.886139 z"
+     id="path832"
+     inkscape:label="path832"
+     style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.888614" />
+</svg>
diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png
new file mode 100644 (file)
index 0000000..b7bd882
Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png differ
diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml
new file mode 100644 (file)
index 0000000..5c43e23
--- /dev/null
@@ -0,0 +1,2 @@
+sample:
+  filter: true
diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png
new file mode 100644 (file)
index 0000000..d90d5b2
Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png differ
diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png
new file mode 100644 (file)
index 0000000..27b68e2
Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png differ
index 0f883ee2801a227b7f406048de4c20a26cf88a76..f34820cec45eb2a888912a5a330a0ffa346f6a27 100644 (file)
@@ -58,9 +58,6 @@
     {
       "name": "spray"
     },
-    {
-      "name": "spray_cap"
-    },
     {
       "name": "spray_cap_colors"
     },
     {
       "name": "equipped-BELT",
       "directions": 4
+    },
+    {
+      "name": "clown-inhand-right",
+      "directions": 4
+    },
+    {
+      "name": "clown-inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "spray-inhand-right",
+      "directions": 4
+    },
+    {
+      "name": "spray-inhand-left",
+      "directions": 4
     }
   ]
 }
diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png
new file mode 100644 (file)
index 0000000..ad3ad95
Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png differ
diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png
new file mode 100644 (file)
index 0000000..353e47c
Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png differ
diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png
deleted file mode 100644 (file)
index 01d2d49..0000000
Binary files a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png and /dev/null differ