spray painter rework (#23287)
* refactor and add Department to PaintableAirlock, move it to server dir since its in namespace * add departments to doors, cleanup * add style -> departments mapping * AirlockDepartmentsPrototype * update shared spray stuff to have department * name file the same as the class name * department optional * refactor spray painter system + send department * fixy * client * no need to rewrite ActivateableUi * pro ops * the reckoning * hiss * . * :trollface: * add standard atmos colors to palette * Update Content.Shared/SprayPainter/SharedSprayPainterSystem.cs --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -14,29 +14,31 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
|
||||
|
||||
public List<SprayPainterEntry> Entries { get; private set; } = new();
|
||||
|
||||
public override void Initialize()
|
||||
protected override void CacheStyles()
|
||||
{
|
||||
base.Initialize();
|
||||
base.CacheStyles();
|
||||
|
||||
foreach (string style in Styles)
|
||||
Entries.Clear();
|
||||
foreach (var style in Styles)
|
||||
{
|
||||
var name = style.Name;
|
||||
string? iconPath = Groups
|
||||
.FindAll(x => x.StylePaths.ContainsKey(style))?
|
||||
.MaxBy(x => x.IconPriority)?.StylePaths[style];
|
||||
.FindAll(x => x.StylePaths.ContainsKey(name))?
|
||||
.MaxBy(x => x.IconPriority)?.StylePaths[name];
|
||||
if (iconPath == null)
|
||||
{
|
||||
Entries.Add(new SprayPainterEntry(style, null));
|
||||
Entries.Add(new SprayPainterEntry(name, null));
|
||||
continue;
|
||||
}
|
||||
|
||||
RSIResource doorRsi = _resourceCache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / new ResPath(iconPath));
|
||||
if (!doorRsi.RSI.TryGetState("closed", out var icon))
|
||||
{
|
||||
Entries.Add(new SprayPainterEntry(style, null));
|
||||
Entries.Add(new SprayPainterEntry(name, null));
|
||||
continue;
|
||||
}
|
||||
|
||||
Entries.Add(new SprayPainterEntry(style, icon.Frame0));
|
||||
Entries.Add(new SprayPainterEntry(name, icon.Frame0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.SprayPainter;
|
||||
using Content.Shared.SprayPainter.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -20,14 +21,20 @@ public sealed class SprayPainterBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if (!EntMan.TryGetComponent<SprayPainterComponent>(Owner, out var comp))
|
||||
return;
|
||||
|
||||
_window = new SprayPainterWindow();
|
||||
|
||||
_painter = EntMan.System<SprayPainterSystem>();
|
||||
|
||||
_window.OpenCentered();
|
||||
_window.OnClose += Close;
|
||||
_window.OnSpritePicked = OnSpritePicked;
|
||||
_window.OnColorPicked = OnColorPicked;
|
||||
|
||||
_window.Populate(_painter.Entries, comp.Index, comp.PickedColor, comp.ColorPalette);
|
||||
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -37,25 +44,6 @@ public sealed class SprayPainterBoundUserInterface : BoundUserInterface
|
||||
_window?.Dispose();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (_painter == null)
|
||||
return;
|
||||
|
||||
if (state is not SprayPainterBoundUserInterfaceState stateCast)
|
||||
return;
|
||||
|
||||
_window.Populate(_painter.Entries,
|
||||
stateCast.SelectedStyle,
|
||||
stateCast.SelectedColorKey,
|
||||
stateCast.Palette);
|
||||
}
|
||||
|
||||
private void OnSpritePicked(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
SendMessage(new SprayPainterSpritePickedMessage(args.ItemIndex));
|
||||
|
||||
@@ -1,186 +1,69 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.SprayPainter;
|
||||
using Content.Shared.SprayPainter.Components;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.SprayPainter;
|
||||
|
||||
/// <summary>
|
||||
/// A system for painting airlocks and pipes using enginner painter
|
||||
/// Handles spraying pipes using a spray painter.
|
||||
/// Airlocks are handled in shared.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class SprayPainterSystem : SharedSprayPainterSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AtmosPipeColorSystem _pipeColorSystem = default!;
|
||||
[Dependency] private readonly AtmosPipeColorSystem _pipeColor = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SprayPainterComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SprayPainterComponent, AfterInteractEvent>(AfterInteractOn);
|
||||
SubscribeLocalEvent<SprayPainterComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<SprayPainterComponent, SprayPainterSpritePickedMessage>(OnSpritePicked);
|
||||
SubscribeLocalEvent<SprayPainterComponent, SprayPainterColorPickedMessage>(OnColorPicked);
|
||||
SubscribeLocalEvent<SprayPainterComponent, SprayPainterDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<SprayPainterComponent, SprayPainterPipeDoAfterEvent>(OnPipeDoAfter);
|
||||
|
||||
SubscribeLocalEvent<AtmosPipeColorComponent, InteractUsingEvent>(OnPipeInteract);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, SprayPainterComponent component, ComponentInit args)
|
||||
private void OnPipeDoAfter(Entity<SprayPainterComponent> ent, ref SprayPainterPipeDoAfterEvent args)
|
||||
{
|
||||
if (component.ColorPalette.Count == 0)
|
||||
return;
|
||||
|
||||
SetColor(uid, component, component.ColorPalette.First().Key);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, SprayPainterComponent component, SprayPainterDoAfterEvent args)
|
||||
{
|
||||
component.IsSpraying = false;
|
||||
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (args.Args.Target == null)
|
||||
if (args.Args.Target is not {} target)
|
||||
return;
|
||||
|
||||
EntityUid target = (EntityUid) args.Args.Target;
|
||||
if (!TryComp<AtmosPipeColorComponent>(target, out var color))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(component.SpraySound, uid);
|
||||
Audio.PlayPvs(ent.Comp.SpraySound, ent);
|
||||
|
||||
if (TryComp<AtmosPipeColorComponent>(target, out var atmosPipeColorComp))
|
||||
{
|
||||
_pipeColorSystem.SetColor(target, atmosPipeColorComp, args.Color ?? Color.White);
|
||||
} else { // Target is an airlock
|
||||
if (args.Sprite != null)
|
||||
{
|
||||
_appearance.SetData(target, DoorVisuals.BaseRSI, args.Sprite);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
|
||||
}
|
||||
}
|
||||
_pipeColor.SetColor(target, color, args.Color);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, SprayPainterComponent component, ActivateInWorldEvent args)
|
||||
private void OnPipeInteract(Entity<AtmosPipeColorComponent> ent, ref InteractUsingEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
||||
return;
|
||||
DirtyUI(uid, component);
|
||||
|
||||
_userInterfaceSystem.TryOpen(uid, SprayPainterUiKey.Key, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AfterInteractOn(EntityUid uid, SprayPainterComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.IsSpraying || args.Target is not { Valid: true } target || !args.CanReach)
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (EntityManager.TryGetComponent<PaintableAirlockComponent>(target, out var airlock))
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<AirlockGroupPrototype>(airlock.Group, out var grp))
|
||||
{
|
||||
Log.Error("Group not defined: %s", airlock.Group);
|
||||
return;
|
||||
}
|
||||
|
||||
string style = Styles[component.Index];
|
||||
if (!grp.StylePaths.TryGetValue(style, out var sprite))
|
||||
{
|
||||
string msg = Loc.GetString("spray-painter-style-not-available");
|
||||
_popupSystem.PopupEntity(msg, args.User, args.User);
|
||||
return;
|
||||
}
|
||||
component.IsSpraying = true;
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.AirlockSprayTime, new SprayPainterDoAfterEvent(sprite, null), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true,
|
||||
};
|
||||
_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
|
||||
|
||||
// Log attempt
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}");
|
||||
} else { // Painting pipes
|
||||
if(component.PickedColor is null)
|
||||
if (!TryComp<SprayPainterComponent>(args.Used, out var painter) || painter.PickedColor is not {} colorName)
|
||||
return;
|
||||
|
||||
if (!EntityManager.HasComponent<AtmosPipeColorComponent>(target))
|
||||
if (!painter.ColorPalette.TryGetValue(colorName, out var color))
|
||||
return;
|
||||
|
||||
if(!component.ColorPalette.TryGetValue(component.PickedColor, out var color))
|
||||
return;
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.PipeSprayTime, new SprayPainterDoAfterEvent(null, color), uid, target, uid)
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.PipeSprayTime, new SprayPainterPipeDoAfterEvent(color), args.Used, target: ent, used: args.Used)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
CancelDuplicate = true,
|
||||
// multiple pipes can be sprayed at once just not the same one
|
||||
DuplicateCondition = DuplicateConditions.SameTarget,
|
||||
NeedHand = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnColorPicked(EntityUid uid, SprayPainterComponent component, SprayPainterColorPickedMessage args)
|
||||
{
|
||||
SetColor(uid, component, args.Key);
|
||||
}
|
||||
|
||||
private void OnSpritePicked(EntityUid uid, SprayPainterComponent component, SprayPainterSpritePickedMessage args)
|
||||
{
|
||||
component.Index = args.Index;
|
||||
DirtyUI(uid, component);
|
||||
}
|
||||
|
||||
private void SetColor(EntityUid uid, SprayPainterComponent component, string? paletteKey)
|
||||
{
|
||||
if (paletteKey == null)
|
||||
return;
|
||||
|
||||
if (!component.ColorPalette.ContainsKey(paletteKey) || paletteKey == component.PickedColor)
|
||||
return;
|
||||
|
||||
component.PickedColor = paletteKey;
|
||||
DirtyUI(uid, component);
|
||||
}
|
||||
|
||||
private void DirtyUI(EntityUid uid, SprayPainterComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
_userInterfaceSystem.TrySetUiState(
|
||||
uid,
|
||||
SprayPainterUiKey.Key,
|
||||
new SprayPainterBoundUserInterfaceState(
|
||||
component.Index,
|
||||
component.PickedColor,
|
||||
component.ColorPalette));
|
||||
args.Handled = DoAfter.TryStartDoAfter(doAfterEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.SprayPainter;
|
||||
namespace Content.Shared.SprayPainter.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class PaintableAirlockComponent : Component
|
||||
{
|
||||
[DataField("group", customTypeSerializer:typeof(PrototypeIdSerializer<AirlockGroupPrototype>))]
|
||||
public string Group = default!;
|
||||
/// <summary>
|
||||
/// Group of styles this airlock can be painted with, e.g. glass, standard or external.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public ProtoId<AirlockGroupPrototype> Group = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Department this airlock is painted as, or none.
|
||||
/// Must be specified in prototypes for turf war to work.
|
||||
/// To better catch any mistakes, you need to explicitly state a non-styled airlock has a null department.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public ProtoId<DepartmentPrototype>? Department;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.SprayPainter.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class SprayPainterComponent : Component
|
||||
{
|
||||
[DataField("spraySound")]
|
||||
[DataField]
|
||||
public SoundSpecifier SpraySound = new SoundPathSpecifier("/Audio/Effects/spray2.ogg");
|
||||
|
||||
[DataField("airlockSprayTime")]
|
||||
public float AirlockSprayTime = 3.0f;
|
||||
[DataField]
|
||||
public TimeSpan AirlockSprayTime = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField("pipeSprayTime")]
|
||||
public float PipeSprayTime = 1.0f;
|
||||
[DataField]
|
||||
public TimeSpan PipeSprayTime = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField("isSpraying")]
|
||||
public bool IsSpraying = false;
|
||||
/// <summary>
|
||||
/// DoAfterId for airlock spraying.
|
||||
/// Pipes do not track doafters so you can spray multiple at once.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DoAfterId? AirlockDoAfter;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
/// <summary>
|
||||
/// Pipe color chosen to spray with.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? PickedColor;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("colorPalette")]
|
||||
/// <summary>
|
||||
/// Pipe colors that can be selected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, Color> ColorPalette = new();
|
||||
|
||||
public int Index = default!;
|
||||
/// <summary>
|
||||
/// Airlock style index selected.
|
||||
/// After prototype reload this might not be the same style but it will never be out of bounds.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int Index;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.SprayPainter.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Maps airlock style names to department ids.
|
||||
/// </summary>
|
||||
[Prototype("airlockDepartments")]
|
||||
public sealed class AirlockDepartmentsPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of style names to department ids.
|
||||
/// If a style does not have a department (e.g. external) it is set to null.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public Dictionary<string, ProtoId<DepartmentPrototype>> Departments = new();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.SprayPainter;
|
||||
|
||||
public abstract class SharedSprayPainterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public List<string> Styles { get; private set; } = new();
|
||||
public List<AirlockGroupPrototype> Groups { get; private set; } = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SortedSet<string> styles = new();
|
||||
foreach (AirlockGroupPrototype grp in _prototypeManager.EnumeratePrototypes<AirlockGroupPrototype>())
|
||||
{
|
||||
Groups.Add(grp);
|
||||
foreach (string style in grp.StylePaths.Keys)
|
||||
{
|
||||
styles.Add(style);
|
||||
}
|
||||
}
|
||||
Styles = styles.ToList();
|
||||
}
|
||||
}
|
||||
202
Content.Shared/SprayPainter/SharedSprayPainterSystem.cs
Normal file
202
Content.Shared/SprayPainter/SharedSprayPainterSystem.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.SprayPainter.Components;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.SprayPainter;
|
||||
|
||||
/// <summary>
|
||||
/// System for painting airlocks using a spray painter.
|
||||
/// Pipes are handled serverside since AtmosPipeColorSystem is server only.
|
||||
/// </summary>
|
||||
public abstract class SharedSprayPainterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager Proto = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] protected readonly SharedDoAfterSystem DoAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
public List<AirlockStyle> Styles { get; private set; } = new();
|
||||
public List<AirlockGroupPrototype> Groups { get; private set; } = new();
|
||||
|
||||
[ValidatePrototypeId<AirlockDepartmentsPrototype>]
|
||||
private const string Departments = "Departments";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
CacheStyles();
|
||||
|
||||
SubscribeLocalEvent<SprayPainterComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SprayPainterComponent, SprayPainterDoorDoAfterEvent>(OnDoorDoAfter);
|
||||
Subs.BuiEvents<SprayPainterComponent>(SprayPainterUiKey.Key, subs =>
|
||||
{
|
||||
subs.Event<SprayPainterSpritePickedMessage>(OnSpritePicked);
|
||||
subs.Event<SprayPainterColorPickedMessage>(OnColorPicked);
|
||||
});
|
||||
|
||||
SubscribeLocalEvent<PaintableAirlockComponent, InteractUsingEvent>(OnAirlockInteract);
|
||||
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<SprayPainterComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.ColorPalette.Count == 0)
|
||||
return;
|
||||
|
||||
SetColor(ent, ent.Comp.ColorPalette.First().Key);
|
||||
}
|
||||
|
||||
private void OnDoorDoAfter(Entity<SprayPainterComponent> ent, ref SprayPainterDoorDoAfterEvent args)
|
||||
{
|
||||
ent.Comp.AirlockDoAfter = null;
|
||||
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (args.Args.Target is not {} target)
|
||||
return;
|
||||
|
||||
if (!TryComp<PaintableAirlockComponent>(target, out var airlock))
|
||||
return;
|
||||
|
||||
airlock.Department = args.Department;
|
||||
Dirty(target, airlock);
|
||||
|
||||
Audio.PlayPredicted(ent.Comp.SpraySound, ent, args.Args.User);
|
||||
Appearance.SetData(target, DoorVisuals.BaseRSI, args.Sprite);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#region UI messages
|
||||
|
||||
private void OnColorPicked(Entity<SprayPainterComponent> ent, ref SprayPainterColorPickedMessage args)
|
||||
{
|
||||
SetColor(ent, args.Key);
|
||||
}
|
||||
|
||||
private void OnSpritePicked(Entity<SprayPainterComponent> ent, ref SprayPainterSpritePickedMessage args)
|
||||
{
|
||||
if (args.Index >= Styles.Count)
|
||||
return;
|
||||
|
||||
ent.Comp.Index = args.Index;
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
|
||||
private void SetColor(Entity<SprayPainterComponent> ent, string? paletteKey)
|
||||
{
|
||||
if (paletteKey == null || paletteKey == ent.Comp.PickedColor)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.ColorPalette.ContainsKey(paletteKey))
|
||||
return;
|
||||
|
||||
ent.Comp.PickedColor = paletteKey;
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnAirlockInteract(Entity<PaintableAirlockComponent> ent, ref InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<SprayPainterComponent>(args.Used, out var painter) || painter.AirlockDoAfter != null)
|
||||
return;
|
||||
|
||||
var group = Proto.Index<AirlockGroupPrototype>(ent.Comp.Group);
|
||||
|
||||
var style = Styles[painter.Index];
|
||||
if (!group.StylePaths.TryGetValue(style.Name, out var sprite))
|
||||
{
|
||||
string msg = Loc.GetString("spray-painter-style-not-available");
|
||||
_popup.PopupEntity(msg, args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.AirlockSprayTime, new SprayPainterDoorDoAfterEvent(sprite, style.Department), args.Used, target: ent, used: args.Used)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
};
|
||||
if (!DoAfter.TryStartDoAfter(doAfterEventArgs, out var id))
|
||||
return;
|
||||
|
||||
// since we are now spraying an airlock prevent spraying more at the same time
|
||||
// pipes ignore this
|
||||
painter.AirlockDoAfter = id;
|
||||
args.Handled = true;
|
||||
|
||||
// Log the attempt
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(ent):target} to '{style.Name}' at {Transform(ent).Coordinates:targetlocation}");
|
||||
}
|
||||
|
||||
#region Style caching
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if (!args.WasModified<AirlockGroupPrototype>() && !args.WasModified<AirlockDepartmentsPrototype>())
|
||||
return;
|
||||
|
||||
Styles.Clear();
|
||||
Groups.Clear();
|
||||
CacheStyles();
|
||||
|
||||
// style index might be invalid now so check them all
|
||||
var max = Styles.Count - 1;
|
||||
var query = AllEntityQuery<SprayPainterComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Index > max)
|
||||
{
|
||||
comp.Index = max;
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CacheStyles()
|
||||
{
|
||||
// collect every style's name
|
||||
var names = new SortedSet<string>();
|
||||
foreach (var group in Proto.EnumeratePrototypes<AirlockGroupPrototype>())
|
||||
{
|
||||
Groups.Add(group);
|
||||
foreach (var style in group.StylePaths.Keys)
|
||||
{
|
||||
names.Add(style);
|
||||
}
|
||||
}
|
||||
|
||||
// get their department ids too for the final style list
|
||||
var departments = Proto.Index<AirlockDepartmentsPrototype>(Departments);
|
||||
Styles.Capacity = names.Count;
|
||||
foreach (var name in names)
|
||||
{
|
||||
departments.Departments.TryGetValue(name, out var department);
|
||||
Styles.Add(new AirlockStyle(name, department));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record struct AirlockStyle(string Name, string? Department);
|
||||
@@ -12,7 +12,7 @@ public enum SprayPainterUiKey
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SprayPainterSpritePickedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int Index { get; }
|
||||
public readonly int Index;
|
||||
|
||||
public SprayPainterSpritePickedMessage(int index)
|
||||
{
|
||||
@@ -23,7 +23,7 @@ public sealed class SprayPainterSpritePickedMessage : BoundUserInterfaceMessage
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SprayPainterColorPickedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string? Key { get; }
|
||||
public readonly string? Key;
|
||||
|
||||
public SprayPainterColorPickedMessage(string? key)
|
||||
{
|
||||
@@ -32,36 +32,40 @@ public sealed class SprayPainterColorPickedMessage : BoundUserInterfaceMessage
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SprayPainterBoundUserInterfaceState : BoundUserInterfaceState
|
||||
public sealed partial class SprayPainterDoorDoAfterEvent : DoAfterEvent
|
||||
{
|
||||
public int SelectedStyle { get; }
|
||||
public string? SelectedColorKey { get; }
|
||||
public Dictionary<string, Color> Palette { get; }
|
||||
/// <summary>
|
||||
/// Base RSI path to set for the door sprite.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Sprite;
|
||||
|
||||
public SprayPainterBoundUserInterfaceState(int selectedStyle, string? selectedColorKey, Dictionary<string, Color> palette)
|
||||
/// <summary>
|
||||
/// Department id to set for the door, if the style has one.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? Department;
|
||||
|
||||
public SprayPainterDoorDoAfterEvent(string sprite, string? department)
|
||||
{
|
||||
SelectedStyle = selectedStyle;
|
||||
SelectedColorKey = selectedColorKey;
|
||||
Palette = palette;
|
||||
Sprite = sprite;
|
||||
Department = department;
|
||||
}
|
||||
|
||||
public override DoAfterEvent Clone() => this;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SprayPainterDoAfterEvent : DoAfterEvent
|
||||
public sealed partial class SprayPainterPipeDoAfterEvent : DoAfterEvent
|
||||
{
|
||||
[DataField("sprite")]
|
||||
public string? Sprite = null;
|
||||
/// <summary>
|
||||
/// Color of the pipe to set.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Color Color;
|
||||
|
||||
[DataField("color")]
|
||||
public Color? Color = null;
|
||||
|
||||
private SprayPainterDoAfterEvent()
|
||||
public SprayPainterPipeDoAfterEvent(Color color)
|
||||
{
|
||||
}
|
||||
|
||||
public SprayPainterDoAfterEvent(string? sprite, Color? color)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
state: spray_painter
|
||||
- type: Item
|
||||
sprite: Objects/Tools/spray_painter.rsi
|
||||
- type: ActivatableUI
|
||||
key: enum.SprayPainterUiKey.Key
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.SprayPainterUiKey.Key
|
||||
@@ -23,5 +25,10 @@
|
||||
blue: '#0335FCFF'
|
||||
white: '#FFFFFFFF'
|
||||
black: '#333333FF'
|
||||
# standard atmos pipes
|
||||
waste: '#990000'
|
||||
distro: '#0055cc'
|
||||
air: '#03fcd3'
|
||||
mix: '#947507'
|
||||
- type: StaticPrice
|
||||
price: 40
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/engineering.rsi
|
||||
- type: PaintableAirlock
|
||||
department: Engineering
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockEngineering
|
||||
id: AirlockAtmospherics
|
||||
suffix: Atmospherics
|
||||
components:
|
||||
@@ -29,6 +31,8 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/cargo.rsi
|
||||
- type: PaintableAirlock
|
||||
department: Cargo
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -37,9 +41,11 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/medical.rsi
|
||||
- type: PaintableAirlock
|
||||
department: Medical
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockMedical
|
||||
id: AirlockVirology
|
||||
suffix: Virology
|
||||
components:
|
||||
@@ -47,12 +53,9 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/virology.rsi
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockMedical
|
||||
id: AirlockChemistry
|
||||
suffix: Chemistry
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/medical.rsi
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -61,6 +64,8 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/science.rsi
|
||||
- type: PaintableAirlock
|
||||
department: Science
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -71,6 +76,8 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/command.rsi
|
||||
- type: WiresPanelSecurity
|
||||
securityLevel: medSecurity
|
||||
- type: PaintableAirlock
|
||||
department: Command
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -79,6 +86,8 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/security.rsi
|
||||
- type: PaintableAirlock
|
||||
department: Security
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -89,7 +98,7 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/maint.rsi
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockSecurity # if you get syndie door somehow it counts as sec
|
||||
id: AirlockSyndicate
|
||||
suffix: Syndicate
|
||||
components:
|
||||
@@ -97,7 +106,7 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/syndicate.rsi
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockCargo
|
||||
id: AirlockMining
|
||||
suffix: Mining(Salvage)
|
||||
components:
|
||||
@@ -105,14 +114,12 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/mining.rsi
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockCommand # if you get centcom door somehow it counts as command, also inherit panel
|
||||
id: AirlockCentralCommand
|
||||
suffix: Central Command
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Standard/centcomm.rsi
|
||||
- type: WiresPanelSecurity
|
||||
securityLevel: medSecurity
|
||||
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
@@ -181,7 +188,7 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/engineering.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Engineering
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
@@ -190,18 +197,14 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/maint.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
parent: AirlockEngineeringGlass
|
||||
id: AirlockAtmosphericsGlass
|
||||
suffix: Atmospherics
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/atmospherics.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
@@ -211,17 +214,7 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/cargo.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
id: AirlockChemistryGlass
|
||||
suffix: Chemistry
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/medical.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Cargo
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
@@ -231,17 +224,20 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/medical.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Medical
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
parent: AirlockMedicalGlass
|
||||
id: AirlockChemistryGlass
|
||||
suffix: Chemistry
|
||||
|
||||
- type: entity
|
||||
parent: AirlockMedicalGlass
|
||||
id: AirlockVirologyGlass
|
||||
suffix: Virology
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/virology.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
@@ -251,7 +247,7 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/science.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Science
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
@@ -261,7 +257,7 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/command.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Command
|
||||
- type: WiresPanelSecurity
|
||||
securityLevel: medSecurity
|
||||
|
||||
@@ -273,20 +269,18 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/security.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
department: Security
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
parent: AirlockSecurityGlass # see standard
|
||||
id: AirlockSyndicateGlass
|
||||
suffix: Syndicate
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/syndicate.rsi
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
parent: AirlockCargoGlass
|
||||
id: AirlockMiningGlass
|
||||
suffix: Mining(Salvage)
|
||||
components:
|
||||
@@ -294,7 +288,7 @@
|
||||
sprite: Structures/Doors/Airlocks/Glass/mining.rsi
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
parent: AirlockCommandGlass # see standard
|
||||
id: AirlockCentralCommandGlass
|
||||
suffix: Central Command
|
||||
components:
|
||||
@@ -302,4 +296,3 @@
|
||||
sprite: Structures/Doors/Airlocks/Glass/centcomm.rsi
|
||||
- type: WiresPanelSecurity
|
||||
securityLevel: medSecurity
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
mode: NoSprite
|
||||
- type: PaintableAirlock
|
||||
group: Standard
|
||||
department: Civilian
|
||||
- type: AccessReader
|
||||
- type: StaticPrice
|
||||
price: 150
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/external.rsi
|
||||
- type: PaintableAirlock
|
||||
group: External
|
||||
department: null
|
||||
|
||||
- type: entity
|
||||
parent: AirlockExternal
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
- ForceNoFixRotations
|
||||
- type: PaintableAirlock
|
||||
group: Shuttle
|
||||
department: null
|
||||
- type: Construction
|
||||
graph: AirlockShuttle
|
||||
node: airlock
|
||||
|
||||
@@ -58,3 +58,19 @@
|
||||
iconPriority: 40
|
||||
stylePaths:
|
||||
shuttle: Structures/Doors/Airlocks/Glass/shuttle.rsi
|
||||
|
||||
# fun
|
||||
- type: airlockDepartments
|
||||
id: Departments
|
||||
departments:
|
||||
atmospherics: Engineering
|
||||
basic: Civilian
|
||||
cargo: Cargo
|
||||
command: Command
|
||||
engineering: Engineering
|
||||
freezer: Civilian
|
||||
maintenance: Civilian
|
||||
medical: Medical
|
||||
science: Science
|
||||
security: Security
|
||||
virology: Medical
|
||||
|
||||
Reference in New Issue
Block a user