From 0c4ca53725e0eceb30e30e6e22738f6e4352c5a8 Mon Sep 17 00:00:00 2001 From: SimpleStation14 Date: Mon, 22 Apr 2024 05:42:56 -0400 Subject: [PATCH] Cherry-picked commit e4d5e7f1aebfc37b1bc3453fdb39578f3897b6a1 from space-wizards/space-station-14/master --- Content.Client/Paint/PaintVisualizerSystem.cs | 120 +++++++ Content.Server/Paint/PaintSystem.cs | 180 +++++++++++ .../EntitySystems/SpawnItemsOnUseSystem.cs | 6 +- Content.Shared/Paint/PaintComponent.cs | 60 ++++ Content.Shared/Paint/PaintDoAfterEvent.cs | 9 + Content.Shared/Paint/PaintRemoverComponent.cs | 24 ++ .../Paint/PaintRemoverDoAfterEvent.cs | 9 + Content.Shared/Paint/PaintRemoverSystem.cs | 96 ++++++ Content.Shared/Paint/PaintedComponent.cs | 41 +++ Content.Shared/Paint/SharedPaintSystem.cs | 11 + .../SprayPainter/SharedSprayPainterSystem.cs | 3 + Resources/Locale/en-US/paint/paint.ftl | 8 + .../Prototypes/Catalog/Cargo/cargo_fun.yml | 10 + .../Prototypes/Catalog/Fills/Crates/fun.yml | 48 ++- .../Prototypes/Catalog/Fills/Lockers/misc.yml | 4 + .../Markers/Spawners/Random/crates.yml | 1 + .../Markers/Spawners/Random/maintenance.yml | 5 + .../Prototypes/Entities/Mobs/NPCs/carp.yml | 1 + .../Entities/Mobs/NPCs/revenant.yml | 3 + .../Entities/Mobs/Player/guardian.yml | 2 + .../Entities/Objects/Fun/spray_paint.yml | 292 ++++++++++++++++++ .../Objects/Materials/Sheets/glass.yml | 1 + .../Objects/Materials/Sheets/metal.yml | 1 + .../Objects/Materials/Sheets/other.yml | 3 + .../Entities/Objects/Materials/materials.yml | 1 + .../Entities/Objects/Misc/tiles.yml | 3 + .../Objects/Specific/Janitorial/soap.yml | 1 + .../Objects/Weapons/Melee/e_sword.yml | 4 + .../Structures/Decoration/bonfire.yml | 3 + .../Structures/Holographic/projections.yml | 3 + .../Entities/Structures/hydro_tray.yml | 3 + Resources/Prototypes/tags.yml | 3 + .../Textures/Interface/VerbIcons/paint.svg | 39 +++ .../Interface/VerbIcons/paint.svg.192dpi.png | Bin 0 -> 15653 bytes .../VerbIcons/paint.svg.192dpi.png.yml | 2 + .../Fun/spraycans.rsi/clown-inhand-left.png | Bin 0 -> 20773 bytes .../Fun/spraycans.rsi/clown-inhand-right.png | Bin 0 -> 20783 bytes .../Objects/Fun/spraycans.rsi/meta.json | 19 +- .../Fun/spraycans.rsi/spray-inhand-left.png | Bin 0 -> 21199 bytes .../Fun/spraycans.rsi/spray-inhand-right.png | Bin 0 -> 21213 bytes .../Objects/Fun/spraycans.rsi/spray_cap.png | Bin 246 -> 0 bytes 41 files changed, 1008 insertions(+), 11 deletions(-) create mode 100644 Content.Client/Paint/PaintVisualizerSystem.cs create mode 100644 Content.Server/Paint/PaintSystem.cs create mode 100644 Content.Shared/Paint/PaintComponent.cs create mode 100644 Content.Shared/Paint/PaintDoAfterEvent.cs create mode 100644 Content.Shared/Paint/PaintRemoverComponent.cs create mode 100644 Content.Shared/Paint/PaintRemoverDoAfterEvent.cs create mode 100644 Content.Shared/Paint/PaintRemoverSystem.cs create mode 100644 Content.Shared/Paint/PaintedComponent.cs create mode 100644 Content.Shared/Paint/SharedPaintSystem.cs create mode 100644 Resources/Locale/en-US/paint/paint.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml create mode 100644 Resources/Textures/Interface/VerbIcons/paint.svg create mode 100644 Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png create mode 100644 Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml create mode 100644 Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png create mode 100644 Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png create mode 100644 Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png create mode 100644 Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png delete mode 100644 Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png diff --git a/Content.Client/Paint/PaintVisualizerSystem.cs b/Content.Client/Paint/PaintVisualizerSystem.cs new file mode 100644 index 00000000000..6c99b2d35f0 --- /dev/null +++ b/Content.Client/Paint/PaintVisualizerSystem.cs @@ -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 + { + /// + /// Visualizer for Paint which applies a shader and colors the entity. + /// + + [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(OnHeldVisualsUpdated); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(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(component.ShaderName).Instance(); + + if (args.Sprite == null) + return; + + if (!_appearance.TryGetData(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(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 index 00000000000..c6718aced68 --- /dev/null +++ b/Content.Server/Paint/PaintSystem.cs @@ -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; + +/// +/// Colors target and consumes reagent on each color success. +/// +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(OnInteract); + SubscribeLocalEvent(OnPaint); + SubscribeLocalEvent>(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 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 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(target) || HasComp(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(target) || HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("paint-failure", ("target", args.Target)), args.User, args.User, PopupType.Medium); + return; + } + + + if (TryPaint(entity, target)) + { + EnsureComp(target, out PaintedComponent? paint); + EnsureComp(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(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(slotEnt.Value) || !entity.Comp.Blacklist?.IsValid(slotEnt.Value, EntityManager) != true + || HasComp(slotEnt.Value) || HasComp(slotEnt.Value)) + return; + + EnsureComp(slotEnt.Value, out PaintedComponent? slotpaint); + EnsureComp(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 reagent, EntityUid target) + { + if (HasComp(target) || HasComp(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; + } +} diff --git a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs index c49bfdec931..0b4b13d6e4b 100644 --- a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs +++ b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs @@ -80,11 +80,6 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI _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 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI 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 index 00000000000..ad09f4ca730 --- /dev/null +++ b/Content.Shared/Paint/PaintComponent.cs @@ -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; + +/// +/// Entity when used on another entity will paint target entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedPaintSystem))] +public sealed partial class PaintComponent : Component +{ + /// + /// Noise made when paint applied. + /// + [DataField] + public SoundSpecifier Spray = new SoundPathSpecifier("/Audio/Effects/spray2.ogg"); + + /// + /// Solution on the entity that contains the paint. + /// + [DataField] + public string Solution = "drink"; + + /// + /// How long the doafter will take. + /// + [DataField] + public int Delay = 2; + + /// + /// Reagent that will be used as paint. + /// + [DataField, AutoNetworkedField] + public ProtoId Reagent = "SpaceGlue"; + + /// + /// Color that the painting entity will instruct the painted entity to be. + /// + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#c62121"); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Blacklist; + /// + /// Reagent consumption per use. + /// + [DataField] + public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5); + + /// + /// Duration per unit + /// + [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 index 00000000000..0851f1541b4 --- /dev/null +++ b/Content.Shared/Paint/PaintDoAfterEvent.cs @@ -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 index 00000000000..54d0ed7a71b --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; + +namespace Content.Shared.Paint; + +/// +/// Removes paint from an entity that was painted with spray paint. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(PaintRemoverSystem))] +public sealed partial class PaintRemoverComponent : Component +{ + /// + /// Sound when target is cleaned. + /// + [DataField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg"); + + /// + /// DoAfter wait time. + /// + [DataField] + public float CleanDelay = 2f; +} diff --git a/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs new file mode 100644 index 00000000000..940b1aa513c --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs @@ -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 index 00000000000..ac1cc624cfe --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverSystem.cs @@ -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; + +/// +/// Removes paint from an entity. +/// +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(OnInteract); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent>(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(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(target); + Dirty(target, paint); + + args.Handled = true; + } + + private void OnPaintRemoveVerb(EntityUid uid, PaintRemoverComponent component, GetVerbsEvent 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 index 00000000000..a6ee7377e11 --- /dev/null +++ b/Content.Shared/Paint/PaintedComponent.cs @@ -0,0 +1,41 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +/// +/// Component applied to target entity when painted. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PaintedComponent : Component +{ + /// + /// Color of the paint. + /// + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#2cdbd5"); + + /// + /// Used to remove the color when component removed. + /// + [DataField, AutoNetworkedField] + public Color BeforeColor; + + /// + /// If paint is enabled. + /// + [DataField, AutoNetworkedField] + public bool Enabled; + + /// + /// Name of the shader. + /// + [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 index 00000000000..10185817b86 --- /dev/null +++ b/Content.Shared/Paint/SharedPaintSystem.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Paint; + +/// +/// Colors target and consumes reagent on each color success. +/// +public abstract class SharedPaintSystem : EntitySystem +{ + public virtual void UpdateAppearance(EntityUid uid, PaintedComponent? component = null) + { + } +} diff --git a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs index 529e321f8da..fa04a50f8b0 100644 --- a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs +++ b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs @@ -4,6 +4,7 @@ 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 @@ private void OnAirlockInteract(Entity ent, ref Intera return; } + RemComp(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 index 00000000000..200b1f6e3f3 --- /dev/null +++ b/Resources/Locale/en-US/paint/paint.ftl @@ -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 diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml index 2d5acf5c90f..d84fedd543f 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml @@ -68,6 +68,16 @@ category: 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: diff --git a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml index 1db0c3121ed..cc5e3b1d174 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml @@ -292,14 +292,21 @@ 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 @@ -332,6 +339,41 @@ 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. diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index 81ec76dae6a..a9490ee5d69 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -132,6 +132,10 @@ 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 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml index ae7e5bcf762..883182aae8d 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml @@ -44,6 +44,7 @@ - CrateMaterialPlastic - CrateMaterialWood - CrateMaterialPlasteel + - CrateFunSprayPaints - CrateFunArtSupplies - CrateEngineeringCableLV - CrateEngineeringCableMV diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 879c1689291..10fb034d0d5 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -174,7 +174,12 @@ - MaterialCloth10 - MaterialWoodPlank10 - ResearchDisk + - DeathPaint - Plunger + - SprayPaintBlue + - SprayPaintRed + - SprayPaintGreen + - SprayPaintOrange - TechnologyDisk - PowerCellMedium - PowerCellSmall diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 73082674736..3e6c603626b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -70,6 +70,7 @@ tags: - Carp - DoorBumpOpener + - NoPaint - type: ReplacementAccent accent: genericAggressive - type: Speech diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 28355bb459e..ec1ed3a58f6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -97,3 +97,6 @@ - RevenantTheme - type: Speech speechVerb: Ghost + - type: Tag + tags: + - NoPaint diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index d892b31fac3..c37826666ac 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -104,6 +104,7 @@ - type: Tag tags: - CannotSuicide + - NoPaint # From the uplink injector - type: entity @@ -212,6 +213,7 @@ 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 index 00000000000..1b417f6cde0 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml @@ -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"} diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 59d8ed19220..2e0eec7a658 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -15,6 +15,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Material - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml index 82b9f62837a..3a887848bf5 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml @@ -15,6 +15,7 @@ tags: - Sheet - Metal + - NoPaint - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index dfb51336289..9dc87a9117d 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -12,6 +12,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible @@ -110,6 +111,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: entity parent: SheetPlasma @@ -132,6 +134,7 @@ tags: - Plastic - Sheet + - NoPaint - type: Material - type: PhysicalComposition materialComposition: diff --git a/Resources/Prototypes/Entities/Objects/Materials/materials.yml b/Resources/Prototypes/Entities/Objects/Materials/materials.yml index e71a163b5d0..96fddefa3ec 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/materials.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/materials.yml @@ -12,6 +12,7 @@ - type: Tag tags: - RawMaterial + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml index 8e29c3c27e6..5b57d930b03 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml @@ -15,6 +15,9 @@ Blunt: 5 - type: Stack count: 1 + - type: Tag + tags: + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 5678de6bafc..56786057d57 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -62,6 +62,7 @@ solution: soap - type: DeleteOnSolutionEmpty solution: soap + - type: PaintRemover - type: FlavorProfile flavors: - clean diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index bc376df5eab..ccbbcc2de84 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -78,6 +78,9 @@ malus: 0 - type: Reflect enabled: false + - type: Tag + tags: + - NoPaint - type: IgnitionSource temperature: 700 @@ -156,6 +159,7 @@ - type: Tag tags: - Write + - NoPaint - type: DisarmMalus malus: 0 diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index 7777153bbac..f82fe8b51bb 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -31,6 +31,9 @@ sound: path: /Audio/Ambience/Objects/fireplace.ogg - type: AlwaysHot + - type: Tag + tags: + - NoPaint - type: entity id: LegionnaireBonfire diff --git a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml index d2a5853fcb0..b8717cf6cc9 100644 --- a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml +++ b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml @@ -25,6 +25,9 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag + tags: + - NoPaint - type: entity id: HoloFan diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 1ab1fd5b2fd..43b8bd197a5 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -92,6 +92,9 @@ - type: GuideHelp guides: - Botany + - type: Tag + tags: + - NoPaint - type: entity parent: hydroponicsTray diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 9f06df7bcb3..c062cd0be3a 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -871,6 +871,9 @@ - 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 index 00000000000..78a56e3570a --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/paint.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..b7bd88245f2ab069dbf5a39850a866af0c5dc174 GIT binary patch literal 15653 zcmeI3Ym6J!6@c$%At6fwD^Z{%uuL393Tejkve)auHrd!ob^#~b&7$l^6=XbfZ4a?$ zoSE^icLQO0mqGyXgEvtlDvC%zAkn5$c`8a@KR`fGMTH1e8(?XniJ&}$1l*bN^RD+2 zK_vcM%O3mObI&>Ve&?R!`7^f-ZQQW9Ye^RXVDaEUY8ZX5aX+WcN7pCx9q*v8g{6Tp z8-NRLbw3?<9X$>KxOkO5GU|+`FPBxb7*sSf1A~=f3C#u|c1fkAsN12#WMED=;(`DD z@x=h6Yw^IAaGFn-k}$6iOj&UA)W#8YYP%ZM0+%GZVig%VC_+bJD#e0f%awS*^DCp@ z?l2c%yeZE1c%a`6$c(0kn51bzCLHW#RlYaEM4~|<9EpS?s~M3O!W=Jg63>c~9OC7W z$W&i}L>KxMv$U){oLXNEhopEQ?>Hry<0d91f)i5Ev~rvfjYc_Mg|JMlok4OD+s_f;&_0~vOe9iqrploBTdd9Iz2rq*pr zWvk#VO;b5ofJJCHHnJ1i+LiLAW7>Ihj*xZroPkkJhV@+DvFq{MuG#YDv|R%7kWQ1W zi^aBugabg z<-_PbBJ|3l^fO+NdA`YmKMiw`HZ?svS#uC#dC_&i^Kv*OhoW13^QK9-IjTHOag^_J z&^)clg{I0`(<&-XLN6*g$d!y-jB5hvH6m++G<$_}5;(-dQ z!K_j)IDtem$umRcOhH$flvz}CgGoV6R~4^5tLI5|^Q4bMRC+}d9SOJEOP-D9nQ&9k zGQAYXIJcw=dg~he(blDTdz-b5SiQ{Tq**X6L>nfg7}svNiAcTl%>G|h4MqM=|}ffstSqQ-{9s>Eth6fBd;s;r=9#7L-5vzZbuw8|-Se054j$BE zT+3)#&A8*Rs9&9t~OehxY&!OXSB8|`Y+G>Wo zZ(n&QB@4DP{=;%`VkPxGa?qwn|9Ba2S^Lq|JnFKj3vsoBTN^mBr1`0At*Ba+&aS|i z`?oBk>P3gi>)Ht+#?2aQME2*8x>Z;xdsQYxMIOC4!M#>(oAtQu*mu}A>v7wL=@o|V zB*bbsY`DqFy?gNg3pS%$WtivVor!L$??281k9Tx33&mg2&IkVT4%%m>p_To@3tNO6(*U|Na`140%lE;1iXOL1X9 z$Rfo>=7VV|E({1+q`1g@FfGM}0U?VN7nu*HrMNI4WRc<`^TD(f7Y2kZQe0#{n3m$g zfRIIsi_8bpQd}4ivPf}}`CwX#3j;zHDK0V}OiOWLK*%D+MdpKPDJ~2MS){ngd@wD= zg#jUp6c?Efrlq(rAY_r^BJ;tt6c+}BEK*!#KA4u`!hn!PBCf8+1F+CQPrXi{$6Mb! zmwOC78q25y!)XAfRsb-4BLH81gT7|~DDwb(xD5dLegMuj@44#GdH_1ggQ@`d;nH-J03Zr|Ct&;EP&wwn%Znb)2De;!;OJaeCP>1qFb`z&qb zg)8@-^X`%LZ){q!ck-1BpXgZqbmESl&P`|DK7V@ADIfoI-Z{G~r|l^uJHXQD`dFJrr zYaZS8?4R>VcFlroKUneXvSrhEzG~gOYt5<*A7mc6YOA>XZxXv~Xw}p^pRE1e-k*Ri zrS~5^-uH*Y_pQ)xy78qiuK-iITc3L1-+S`XlSgL0{?D<$Ja+2N$%Dtaf!HnY9TInZ z)bZ|<2bV7FeDvz;xy2v6_Sfs5(R#+um`?6F@bIGFb{so;PtTRY>VL&{pML1-rJp4h p2EWO5KELuJFpt6C3+_AuIgtw=ow{Om$WDZhO1a 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 Lg-7S^{K9_#hzGN8 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..27b68e2cfe5d2b364b379707b887bdfcb115eeb8 GIT binary patch literal 20783 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json index 0f883ee2801..f34820cec45 100644 --- a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json @@ -58,9 +58,6 @@ { "name": "spray" }, - { - "name": "spray_cap" - }, { "name": "spray_cap_colors" }, @@ -70,6 +67,22 @@ { "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 index 0000000000000000000000000000000000000000..ad3ad959de4138922ea09c84316ad9369fedf403 GIT binary patch literal 21199 zcmeI4eT*bU6~G%61ILFZ5K;bdTr`SUJN3~YRm1GS-tL`u35)0667CKYuDYsvuVr_7 zn3=oXJBb1O!)S=2Mx!SZC4MAEi5mFBaK=P}e+c}c5s4o$l3-MVL5+!GqWF4xW~ygy z_O6-RC?q|}?$&m{_p18$>f^mPllj=b8{d1$jw^RKj&sT0J&Vim_xkkbg4e_EZ^pO( z7XDn=+H)H5wvKAci@W>X!GCizN4nisty(^2iX)s2&8CABNV z6sw!ERVA~yI!Q^Zp-$^)d#yJ#t*U0ttea-H1A1hlUh7EGO*%)CExH`u-m+ux$7;) zVJE$_B?Pk>Z=sgn$<%stDsy!mpw$-IMs}0-N|LNKvgx{S;ET$_LT`Fj-gR@_)X7HY z>Uwot#ZDY+%`D2UX;2Jvkzj=B3h`>ptC8RuOllbCjAW8yHkJ|{N5=;?7$Ou=u|oVB zp*6w}W$HPhadlXOT6X2x4CKXfCbX(WNqb#(8}YhaHPu#gwO+LsbCSbJSzAcfk~WOU zG+4bl>GDLa#Y7!VSH*l&nGU$1lXDF?sJHHS%GJ@UgBxpcgY4!zcM6Z~a+%;e7sx!_ zIq!SEhv(;oPj~ZrHC^E#XnXJby?;I1wsfLmU9+RcK9`=N24NF&M2Im+a_S=?IK@n0 z#)LbqX{Ds0U`zQN<_0kug&2M*-(Vt`%dt>Xnq~nDa3lmWzKSx_TzMW+t}vWN+NU&} z(zGk7#*s8gxIm>hY929)A`(hXv1>4%7Kq0~!hMR730!x3M;&OUSx6+9R$iaA0X{0= zfuuB&jL<282(664J_Xh$*xpeKqgfP!)zFBrZ`u%o!a&2bP$nX8S~n`J^x zp;j(3A=1B#?)T*)<$2$}XMFp=>+;Y!2; z+22&*6u?MuDLmM4xsiIx84^)K8HX9-`T)GQsY)CHp9mX|F-3!LYSSKtg9^4M7+`;) zzx+|J#t<82M`N!ETaV<@_Yw2_2)UsP2O{PSaiWcc(~`UV?1umZPqP{hThDi;;=&-G z1tDU-3rDPy9@21@D3?eTndq!s2gbq>he}5T$;b$#JvV}*Is_;AAsFMX;lwL!Eb@pJ zF7^=(xkR3XQwdH_j(qAXjTKjc5XFs24l$r2$OknTUOGnum!ODY8YxTxC&WvWf|Ts) zXz-Jdtj-iVrE%!eQf+Uu)0Gfe?Okl7q+-Ah@r&{`48`|!fUtB!Bp6jFfOs+T!yr)F z??soR>Nb4pn;!HIrnd;8VE5#a2ABALZw7*Yg@!nX7&r6{_&WlM**J5?PV4A>?HCeM zxt5a|cs)?y4CQGQ1rbaf=6Wb}!Syh$m`j*zL?8!UOfYj=S9{_#1U)_)EA~Rr{pR-3 zt2PAfrI0Pz?wB+|nwuG9NW+{ni#PjGXD`vJNtGs_4V9)H2A259RXePqW<3*#Pj_cA z1gEKQ-N(ie(>zn!l9}pBZEd8JMBNS9HuI|x+84>FGOMF%@c> zU_KX~z!8SpNVzD7fk9RvTZ_*j2svk`u|RPG6(&R#WL!LiKwd~6PC*rTjC)}erM0rD zoR(HS-L&e-+!~|hPP&ad=~*_ml2&EFnZJ$Po-{Oxg7t9es1?l$24qTXP}F6Xq;x0$ndL-)f+D@?=pJO;(b@yp{H z`{GTrc3?4vcSAdAama_Dsi!l3W1`~0trT-N`ij}I$$bc-=a}p&e8d7i-BBHWyd&o` zyHjp^%RbSzo3;kaPArQ8trAxONLHl8WmyzxmADE(vLYoe%c4N5#8m*26)AC976n=* zt^$y(NQuj`D9|c#6@X+#N?evjfmVsD03<6?;<79Xv`SnBAX$+Tmt|3)RpKfD$%>S? zEQ)qoTJ!ro;&O~wa+`w70IV>eQY<#zOZ+3er5Al&;RnfmpqCu zUi|6)d*4Fux$?-Tp*^Ug1S?#}!7{0M*M3s<-6 zm)Aji^mTaopZYrfLI}hLeU!MjguK)l5 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..353e47c56fa71b948a6443d4a3a850de2edcb5c0 GIT binary patch literal 21213 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