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
30 changes: 30 additions & 0 deletions Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// Forge-Change-Start
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.SprayPainter.Prototypes;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;

namespace Content.Client.Atmos.EntitySystems;

/// <summary>
/// Used to change the appearance of gas canisters.
/// </summary>
public sealed partial class GasCanisterAppearanceSystem : VisualizerSystem<GasCanisterComponent>
{
[Dependency] private IPrototypeManager _prototypeManager = default!;
protected override void OnAppearanceChange(EntityUid uid, GasCanisterComponent component, ref AppearanceChangeEvent args)
{
if (!AppearanceSystem.TryGetData<string>(uid, PaintableVisuals.Prototype, out var protoName, args.Component) || args.Sprite is not { } old)
return;

if (!_prototypeManager.HasIndex(protoName))
return;

// Create the given prototype and get its first layer.
var tempUid = Spawn(protoName);
SpriteSystem.LayerSetRsiState(uid, 0, SpriteSystem.LayerGetRsiState(tempUid, 0));
QueueDel(tempUid);
}
}
/// Forge-Change-End
///
100 changes: 77 additions & 23 deletions Content.Client/Doors/DoorSystem.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.SprayPainter.Prototypes; /// Forge-Chane
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Prototypes; /// Forge-Chane

namespace Content.Client.Doors;

public sealed partial class DoorSystem : SharedDoorSystem
{
[Dependency] private AnimationPlayerSystem _animationSystem = default!;
[Dependency] private IResourceCache _resourceCache = default!;
// [Dependency] private IResourceCache _resourceCache = default!; /// Forge-Chane-Del
[Dependency] private SpriteSystem _sprite = default!;
[Dependency] private IPrototypeManager _prototypeManager = default!; /// Forge-Chane

public override void Initialize()
{
Expand All @@ -22,15 +23,15 @@ public override void Initialize()
protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args)
{
var comp = ent.Comp;
comp.OpenSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.ClosedSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.OpenSpriteStates = new List<(Enum, string)>(2); /// Forge-Chane
comp.ClosedSpriteStates = new List<(Enum, string)>(2); /// Forge-Chane

comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));

comp.OpeningAnimation = new Animation
{
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
Length = comp.OpeningAnimationTime, /// Forge-Chane
AnimationTracks =
{
new AnimationTrackSpriteFlick
Expand All @@ -46,7 +47,7 @@ protected override void OnComponentInit(Entity<DoorComponent> ent, ref Component

comp.ClosingAnimation = new Animation
{
Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
Length = comp.ClosingAnimationTime, /// Forge-Chane
AnimationTracks =
{
new AnimationTrackSpriteFlick
Expand All @@ -62,7 +63,7 @@ protected override void OnComponentInit(Entity<DoorComponent> ent, ref Component

comp.EmaggingAnimation = new Animation
{
Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
Length = comp.EmaggingAnimationTime, /// Forge-Chane
AnimationTracks =
{
new AnimationTrackSpriteFlick
Expand All @@ -85,11 +86,8 @@ private void OnAppearanceChange(Entity<DoorComponent> entity, ref AppearanceChan
if (!AppearanceSystem.TryGetData<DoorState>(entity, DoorVisuals.State, out var state, args.Component))
state = DoorState.Closed;

if (AppearanceSystem.TryGetData<string>(entity, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
UpdateSpriteLayers((entity.Owner, args.Sprite), baseRsi);

if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
_animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
if (AppearanceSystem.TryGetData<string>(entity, PaintableVisuals.Prototype, out var prototype, args.Component)) /// Forge-Chane
UpdateSpriteLayers((entity.Owner, args.Sprite), prototype); /// Forge-Chane

// We are checking beforehand since some doors may not have an emagging visual layer, and we don't want LayerSetVisible to throw an error.
if (_sprite.TryGetLayer(entity.Owner, DoorVisualLayers.BaseEmagging, out var _, false))
Expand All @@ -105,54 +103,110 @@ private void UpdateAppearanceForDoorState(Entity<DoorComponent> entity, SpriteCo
switch (state)
{
case DoorState.Open:
/// Forge-Chane-Start
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.OpenKey))
return;

if (_animationSystem.HasRunningAnimation(entity, DoorComponent.CloseKey))
{
_animationSystem.Stop(entity, null, DoorComponent.CloseKey);
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.OpenKey);
}

foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
{
// Allow animations to play while it's open (e.g., pinion);
// the animation unsets this so we gotta set it again.
_sprite.LayerSetAutoAnimated((entity.Owner, sprite), layer, true);
/// Forge-Chane-End
_sprite.LayerSetRsiState((entity.Owner, sprite), layer, layerState);
}

return;
case DoorState.Closed:
/// Forge-Chane-Start
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.CloseKey))
return;

if (_animationSystem.HasRunningAnimation(entity, DoorComponent.OpenKey))
{
_animationSystem.Stop(entity, null, DoorComponent.OpenKey);
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.CloseKey);
}
/// Forge-Chane-End
foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
{
_sprite.LayerSetAutoAnimated((entity.Owner, sprite), layer, true); /// Forge-Chane
_sprite.LayerSetRsiState((entity.Owner, sprite), layer, layerState);
}

return;
case DoorState.Opening:
if (entity.Comp.OpeningAnimationTime == 0.0)
/// Forge-Chane-Start
if (entity.Comp.OpeningAnimationTime == TimeSpan.Zero)
return;

_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.OpenKey))
/// Forge-Chane-End
return;

_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.OpenKey); /// Forge-Chane

return;
case DoorState.Closing:
if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
/// Forge-Chane-Start
if (entity.Comp.ClosingAnimationTime == TimeSpan.Zero)
return;

if (_animationSystem.HasRunningAnimation(entity, DoorComponent.CloseKey))
/// Forge-Chane-End
return;

_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.CloseKey); /// Forge-Chane

return;
case DoorState.Denying:
_animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
/// Forge-Chane-Start
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.DenyKey))
return;

_animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.DenyKey);
/// Forge-Chane-End

return;
case DoorState.Emagging:
/// Forge-Chane-Start
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.EmagKey))
return;
/// Forge-Chane-End
// We are checking beforehand since some doors may not have an emagging visual layer.
if (_sprite.TryGetLayer(entity.Owner, DoorVisualLayers.BaseEmagging, out var _, false))
_animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.EmagKey); /// Forge-Chane

return;
}
}

private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string baseRsi)
/// Forge-Chane-Start
private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string targetProto)
{
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
if (!_prototypeManager.HasIndex(targetProto))
return;

// Spawn the target prototype client-side so we can copy its sprite base RSI.
var tempUid = Spawn(targetProto);

if (!TryComp<SpriteComponent>(tempUid, out var targetSprite))
{
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
QueueDel(tempUid);
return;
}

_sprite.SetBaseRsi(sprite.AsNullable(), res.RSI);
if (targetSprite.BaseRSI != null)
_sprite.SetBaseRsi(sprite.AsNullable(), targetSprite.BaseRSI);

QueueDel(tempUid);
}
}
/// Forge-Chane-End
///
141 changes: 109 additions & 32 deletions Content.Client/SprayPainter/SprayPainterSystem.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,133 @@
/// Forge-Chane-Start
using System.Linq;
using Content.Client.Items;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Decals;
using Content.Shared.SprayPainter;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Content.Shared.SprayPainter.Components;
using Content.Shared.SprayPainter.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
using Robust.Shared.Graphics;

namespace Content.Client.SprayPainter;

/// <summary>
/// Client-side spray painter functions. Caches information for spray painter windows and updates the UI to reflect component state.
/// </summary>
public sealed partial class SprayPainterSystem : SharedSprayPainterSystem
{
[Dependency] private IResourceCache _resourceCache = default!;
[Dependency] private UserInterfaceSystem _ui = default!;
[Dependency] private IPrototypeManager _protoMan = default!;

public List<SprayPainterDecalEntry> Decals = [];
public Dictionary<string, List<string>> PaintableGroupsByCategory = new();
public Dictionary<string, Dictionary<string, EntProtoId>> PaintableStylesByGroup = new();

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

public List<SprayPainterEntry> Entries { get; private set; } = new();
Subs.ItemStatus<SprayPainterComponent>(ent => new StatusControl(ent));
SubscribeLocalEvent<SprayPainterComponent, AfterAutoHandleStateEvent>(OnStateUpdate);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);

protected override void CacheStyles()
CachePrototypes();
}

private void OnStateUpdate(Entity<SprayPainterComponent> ent, ref AfterAutoHandleStateEvent args)
{
base.CacheStyles();
UpdateUi(ent);
}

Entries.Clear();
foreach (var style in Styles)
protected override void UpdateUi(Entity<SprayPainterComponent> ent)
{
if (_ui.TryGetOpenUi(ent.Owner, SprayPainterUiKey.Key, out var bui))
bui.Update();
}

private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
{
if (!args.WasModified<PaintableGroupCategoryPrototype>() || !args.WasModified<PaintableGroupPrototype>() || !args.WasModified<DecalPrototype>())
return;

CachePrototypes();
}

private void CachePrototypes()
{
PaintableGroupsByCategory.Clear();
PaintableStylesByGroup.Clear();
foreach (var category in _protoMan.EnumeratePrototypes<PaintableGroupCategoryPrototype>().OrderBy(x => x.ID))
{
var name = style.Name;
string? iconPath = Groups
.FindAll(x => x.StylePaths.ContainsKey(name))?
.MaxBy(x => x.IconPriority)?.StylePaths[name];
if (iconPath == null)
var groupList = new List<string>();
foreach (var groupId in category.Groups)
{
Entries.Add(new SprayPainterEntry(name, null));
continue;
if (!_protoMan.Resolve(groupId, out var group))
continue;

groupList.Add(groupId);
PaintableStylesByGroup[groupId] = group.Styles;
}

RSIResource doorRsi = _resourceCache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / new ResPath(iconPath));
if (!doorRsi.RSI.TryGetState("closed", out var icon))
{
Entries.Add(new SprayPainterEntry(name, null));
if (groupList.Count > 0)
PaintableGroupsByCategory[category.ID] = groupList;
}

Decals.Clear();
foreach (var decalPrototype in _protoMan.EnumeratePrototypes<DecalPrototype>().OrderBy(x => x.ID))
{
if (!decalPrototype.Tags.Contains("station")
&& !decalPrototype.Tags.Contains("markings")
|| decalPrototype.Tags.Contains("dirty"))
continue;
}

Entries.Add(new SprayPainterEntry(name, icon.Frame0));
Decals.Add(new SprayPainterDecalEntry(decalPrototype.ID, decalPrototype.Sprite));
}
}
}

public sealed class SprayPainterEntry
{
public string Name;
public Texture? Icon;

public SprayPainterEntry(string name, Texture? icon)
private sealed class StatusControl : Control
{
Name = name;
Icon = icon;
private readonly RichTextLabel _label;
private readonly Entity<SprayPainterComponent> _entity;
private DecalPaintMode? _lastPaintingDecals = null;

public StatusControl(Entity<SprayPainterComponent> ent)
{
_entity = ent;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
}

protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

if (_entity.Comp.DecalMode == _lastPaintingDecals)
return;

_lastPaintingDecals = _entity.Comp.DecalMode;

string modeLocString = _entity.Comp.DecalMode switch
{
DecalPaintMode.Add => "spray-painter-item-status-add",
DecalPaintMode.Remove => "spray-painter-item-status-remove",
_ => "spray-painter-item-status-off"
};

_label.SetMarkupPermissive(Robust.Shared.Localization.Loc.GetString("spray-painter-item-status-label",
("mode", Robust.Shared.Localization.Loc.GetString(modeLocString))));
}
}
}

/// <summary>
/// A spray paintable decal, mapped by ID.
/// </summary>
public sealed record SprayPainterDecalEntry(string Name, SpriteSpecifier Sprite);
/// Forge-Chane-End
Loading
Loading