Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
148 changes: 148 additions & 0 deletions Content.Client/_DEN/Recolor/RecolorVisualizerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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, ComponentShutdown>(OnComponentShutdown);

SubscribeLocalEvent<RecoloredComponent, GetInhandVisualsEvent>(ApplyRecolorInHands,
after: [typeof(ItemSystem)]);

SubscribeLocalEvent<RecoloredComponent, GetEquipmentVisualsEvent>(ApplyRecolorEquipment,
after: [typeof(ClientClothingSystem)]);
}


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 OnComponentShutdown(Entity<RecoloredComponent> ent, ref ComponentShutdown args)
{
if (TerminatingOrDeleted(ent.Owner))
return;

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
@@ -0,0 +1,4 @@
namespace Content.Shared._DEN.Recolor.Components;

[RegisterComponent]
public sealed partial class RecolorApplierColorSelectorComponent : Component;
89 changes: 89 additions & 0 deletions Content.Shared/_DEN/Recolor/Components/RecolorApplierComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared._DEN.Recolor.Components;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RecolorApplierComponent : Component
{

/// <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)]
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"));
}
21 changes: 21 additions & 0 deletions Content.Shared/_DEN/Recolor/Components/RecolorRemoverComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;

namespace Content.Shared._DEN.Recolor.Components;

[RegisterComponent, NetworkedComponent]
public sealed partial class RecolorRemoverComponent : Component
{
/// <summary>
/// How long it takes for this object to remove the recolor on 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;

}
19 changes: 19 additions & 0 deletions Content.Shared/_DEN/Recolor/Components/RecoloredComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Robust.Shared.GameStates;

namespace Content.Shared._DEN.Recolor.Components;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RecoloredComponent : Component
{
/// <summary>
/// RecolorData this component is storing.
/// </summary>
[DataField, AutoNetworkedField]
public RecolorData RecolorData;

/// <summary>
/// Examine text as a locid.
/// </summary>
[DataField, AutoNetworkedField]
public LocId ExamineText = "recolored-examine";
}
Loading
Loading