Skip to content

Commit

Permalink
Revert "Revert Paint (#26593)"
Browse files Browse the repository at this point in the history
This reverts commit c91ed96.

(cherry picked from commit e7aeb4d)
  • Loading branch information
metalgearsloth authored and DEATHB4DEFEAT committed Nov 14, 2024
1 parent 8385fb1 commit d237304
Show file tree
Hide file tree
Showing 41 changed files with 1,000 additions and 6 deletions.
116 changes: 116 additions & 0 deletions Content.Client/Paint/PaintVisualizerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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 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)
{
var shader = _protoMan.Index<ShaderPrototype>(component.ShaderName).Instance();

if (args.Sprite == null)
return;

// What is this even doing? It's not even checking what the value is.
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))
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))
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;
var 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;
}
}
}
}
}
178 changes: 178 additions & 0 deletions Content.Server/Paint/PaintSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
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)
{
BreakOnMove = true,
NeedHand = true,
BreakOnHandChange = true
};

_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
}

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 (HasComp<PaintedComponent>(slotEnt.Value) || !entity.Comp.Blacklist?.IsValid(slotEnt.Value,
EntityManager) != true
|| HasComp<RandomSpriteComponent>(slotEnt.Value) ||
HasComp<HumanoidAppearanceComponent>(
slotEnt.Value))
{
continue;
}

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI
if (entityToPlaceInHands != null)
{
_hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
_audio.PlayPvs(component.Sound, entityToPlaceInHands.Value);
}
}
}
Expand Down
60 changes: 60 additions & 0 deletions Content.Shared/Paint/PaintComponent.cs
Original file line number Diff line number Diff line change
@@ -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);
}
9 changes: 9 additions & 0 deletions Content.Shared/Paint/PaintDoAfterEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;

namespace Content.Shared.Paint;

[Serializable, NetSerializable]
public sealed partial class PaintDoAfterEvent : SimpleDoAfterEvent
{
}
24 changes: 24 additions & 0 deletions Content.Shared/Paint/PaintRemoverComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit d237304

Please sign in to comment.