Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System.Linq;
using Content.Client.Clothing;
using Content.Client.Items.Systems;
using Content.Shared._DEN.Recolor;
using Content.Shared._DEN.Recolor.Components;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Robust.Client.GameObjects;

namespace Content.Client._DEN.Recolor;

public sealed class RecolorVisualizerSystem : VisualizerSystem<RecoloredComponent>
{
[Dependency] private readonly ItemSystem _item = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<RecoloredComponent, OnRecolorRemovedEvent>(OnRecolorRemoved);

SubscribeLocalEvent<RecoloredComponent, GetInhandVisualsEvent>(ApplyRecolorInHands,
after: [typeof(ItemSystem)]); // Done so ItemSystem can handle sprite layers first, otherwise we apply our changes to nothing.

SubscribeLocalEvent<RecoloredComponent, GetEquipmentVisualsEvent>(ApplyRecolorEquipment,
after: [typeof(ClientClothingSystem)]); // Same as above.
}

protected override void OnAppearanceChange(EntityUid uid, RecoloredComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);

if (args.Sprite == null)
return;

ApplyRecolorSprite((uid, component), args.Sprite);
_item.VisualsChanged(uid);
}

private void OnRecolorRemoved(Entity<RecoloredComponent> ent, ref OnRecolorRemovedEvent args)
{
if (!TryComp(ent, out SpriteComponent? sprite))
return;

RemoveRecolor(ent,sprite);
_item.VisualsChanged(ent);
}

private void ApplyRecolorInHands(Entity<RecoloredComponent> ent, ref GetInhandVisualsEvent args)
{
ApplyRecolorLayers(ent,args.Layers);
}

private void ApplyRecolorEquipment(Entity<RecoloredComponent> ent, ref GetEquipmentVisualsEvent args)
{
ApplyRecolorLayers(ent,args.Layers);
}

private void ApplyRecolorLayers(Entity<RecoloredComponent> ent, List<(string, PrototypeLayerData)> layers)
{
if(!TryComp<AppearanceComponent>(ent, out var appearance))
return;

if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData,appearance))
return;

foreach (var (_, layerData) in layers)
{
// Apply Color
layerData.Color = recolorData.Color;

//Test shader whitelists and blacklists
if (!AllowedShader(layerData.Shader, recolorData))
continue;

// Apply shaders
layerData.Shader = recolorData.Shader;
}
}

private void ApplyRecolorSprite(Entity<RecoloredComponent> ent, SpriteComponent sprite)
{
if(!TryComp<AppearanceComponent>(ent, out var appearance))
return;

if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData, appearance))
return;

for (var i = 0; i < sprite.AllLayers.Count(); i++)
{
if (!SpriteSystem.TryGetLayer((ent, sprite), i, out var layer, false))
continue;

// Apply color
SpriteSystem.LayerSetColor(layer, recolorData.Color);

var layerShader = layer.ShaderPrototype;

if (!AllowedShader(layerShader?.Id, recolorData))
continue;

// Apply shaders
if (recolorData.Shader != null)
sprite.LayerSetShader(i, recolorData.Shader);
}
}

private void RemoveRecolor(Entity<RecoloredComponent> ent, SpriteComponent sprite)
{
if(!TryComp<AppearanceComponent>(ent, out var appearance))
return;

if (!AppearanceSystem.TryGetData(ent, RecolorVisuals.RecolorData, out RecolorData recolorData, appearance))
return;

for (var i = 0; i < sprite.AllLayers.Count(); i++)
{
// TODO: Make it possible to get the previous color and shaders, currently impossible due to sprite system being fully clientside

if (!SpriteSystem.TryGetLayer((ent, sprite), i, out var layer, false))
continue;

// Remove colors
SpriteSystem.LayerSetColor(layer, Color.White);

// Remove shaders
var layerShader = layer.ShaderPrototype;

if (!AllowedShader(layerShader?.Id, recolorData))
continue;

sprite.LayerSetShader(i, null, null);
}
}

private static bool AllowedShader(string? shader, RecolorData appearanceData)
{
if (shader == null)
return true;

return (appearanceData.ShaderBlacklist == null || !appearanceData.ShaderBlacklist.Contains(shader))
&& (appearanceData.ShaderWhitelist == null || appearanceData.ShaderWhitelist.Contains(shader));
}
}
33 changes: 33 additions & 0 deletions Content.Client/_DEN/Recolor/UI/RecolorApplierColorSelectorBui.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Content.Shared._DEN.Recolor;
using Robust.Client.UserInterface;

namespace Content.Client._DEN.Recolor.UI;

public sealed class RecolorApplierColorSelectorBui(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private RecolorApplierColorSelectorMenu? _menu;

protected override void Open()
{
base.Open();

_menu = this.CreateWindow<RecolorApplierColorSelectorMenu>();
_menu.OnColorChanged += SelectColor;
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
// Set the color of the color selector to the same color as the spray paint currently is
if (state is RecolorSystem.RecolorApplierColorState recolorApplierColorState)
{
_menu?.SelectColor(recolorApplierColorState.Color);
}
}
// Sent out when a new color is chosen
private void SelectColor(Color color)
{
SendMessage(new RecolorSystem.RecolorApplierColorMessage(color));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'recolor-applier-color-selector-window-title'}"
MinSize="400 170"
SetSize="400 170"
MaxSize="800 170">
<BoxContainer Orientation="Vertical" SeparationOverride="5">
<ColorSelectorSliders Name="ColorSelector"/>
</BoxContainer>
</DefaultWindow>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Content.Shared._DEN.Recolor;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client._DEN.Recolor.UI;

[GenerateTypedNameReferences]
public sealed partial class RecolorApplierColorSelectorMenu : DefaultWindow
{
public Action<Color>? OnColorChanged;

public RecolorApplierColorSelectorMenu()
{
RobustXamlLoader.Load(this);

ColorSelector.OnColorChanged += color =>
{
OnColorChanged?.Invoke(color);
};
}

// Set the color selector's color.
public void SelectColor(Color color)
{
ColorSelector.Color = color;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared._DEN.Recolor; // DEN (do i need to comment imports here. whatever)

namespace Content.Shared.Clothing.EntitySystems;

public sealed class ToggleableClothingSystem : EntitySystem
public sealed partial class ToggleableClothingSystem : EntitySystem // DEN, made partial.
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netMan = default!;
Expand All @@ -38,6 +39,8 @@ public override void Initialize()
SubscribeLocalEvent<ToggleableClothingComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<ToggleableClothingComponent, ComponentRemove>(OnRemoveToggleable);
SubscribeLocalEvent<ToggleableClothingComponent, GotUnequippedEvent>(OnToggleableUnequip);
SubscribeLocalEvent<ToggleableClothingComponent, OnRecoloredEvent>(OnToggleableRecolored); // DEN, recolor system
SubscribeLocalEvent<ToggleableClothingComponent, OnRecolorRemovedEvent>(OnToggleableRecolorRemoved); // DEN, recolor system

SubscribeLocalEvent<AttachedClothingComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<AttachedClothingComponent, GotUnequippedEvent>(OnAttachedUnequip);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Shared._DEN.Recolor;
using Content.Shared.Clothing.Components;

namespace Content.Shared.Clothing.EntitySystems;

public sealed partial class ToggleableClothingSystem
{
[Dependency] private readonly RecolorSystem _recolor = default!;

// DEN, recolor system start
private void OnToggleableRecolored(Entity<ToggleableClothingComponent> ent, ref OnRecoloredEvent args)
{
var toggled = ent.Comp.ClothingUid;

if (toggled != null)
_recolor.Recolor(toggled.Value,args.RecolorData, args.Recolorer);
}

private void OnToggleableRecolorRemoved(Entity<ToggleableClothingComponent> ent, ref OnRecolorRemovedEvent args)
{
var toggled = ent.Comp.ClothingUid;

if (toggled != null)
_recolor.RemoveRecolor(toggled.Value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Content.Shared._DEN.Recolor.Components;
/// <summary>
/// Component used to designate that a recolor applier can have its color selected via a ui.
/// </summary>
[RegisterComponent]
public sealed partial class RecolorApplierColorSelectorComponent : Component;
Comment thread
honeyed-lemons marked this conversation as resolved.
91 changes: 91 additions & 0 deletions Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared._DEN.Recolor.Components;
/// <summary>
/// Component used to designate something can apply recolors, EG spray paint.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RecolorApplierComponent : Component
Comment thread
honeyed-lemons marked this conversation as resolved.
{

/// <summary>
/// RecolorData to recolor items with.
/// </summary>
[DataField, AutoNetworkedField]
public RecolorData RecolorData;

/// <summary>
/// How long it takes for this object to apply the recolor to the target.
/// </summary>
[DataField]
public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(2.0f);

/// <summary>
/// Sound to play when the doafter is over.
/// </summary>
[DataField]
public SoundSpecifier? DoAfterSound = new SoundPathSpecifier("/Audio/Effects/spray2.ogg");

/// <summary>
/// Maximum amount of uses the applier can spray, if left null the applier can apply infinitely.
/// </summary>
[DataField]
public int? MaxUses;

/// <summary>
/// Current amount of uses the applier can spray.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int UsesLeft;

/// <summary>
/// LocId used for the "you're outta paint!" popup.
/// </summary>
[DataField]
public LocId NoMoreUsesPopup = "spray-paint-empty";

/// <summary>
/// LocId used for the "you can't paint that!" popup.
/// </summary>
[DataField]
public LocId CantRecolorPopup = "spray-paint-fail";

/// <summary>
/// LocId used for the "you can't paint that!" popup.
/// </summary>
[DataField]
public LocId ColorShowcaseExamine = "spray-paint-examine-color";

/// <summary>
/// LocId used for the "you can't paint that!" popup.
/// </summary>
[DataField]
public LocId UsesExamine = "spray-paint-examine-uses";

/// <summary>
/// Entity Whitelist to determine what items can be repainted.
/// </summary>
[DataField]
public EntityWhitelist? EntityWhitelist;

/// <summary>
/// Entity Blacklist to determine what items can't be repainted.
/// </summary>
[DataField]
public EntityWhitelist? EntityBlacklist;

/// <summary>
/// LocId used for the apply recolor verb.
/// </summary>
[DataField]
public LocId VerbText = "verb-spray-paint";

/// <summary>
/// Icon used for the apply recolor verb.
/// </summary>
[DataField]
public SpriteSpecifier VerbIcon = new SpriteSpecifier.Texture(new ResPath("/Textures/_DEN/Interface/VerbIcons/paint-spray-can.svg.192dpi.png"));
}
Loading
Loading