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:
deltanedas
2024-02-01 22:30:46 +00:00
committed by GitHub
parent e36844de47
commit c49c78bafa
15 changed files with 424 additions and 306 deletions

View File

@@ -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));
}
}
}

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View 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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -134,6 +134,7 @@
mode: NoSprite
- type: PaintableAirlock
group: Standard
department: Civilian
- type: AccessReader
- type: StaticPrice
price: 150

View File

@@ -18,6 +18,7 @@
sprite: Structures/Doors/Airlocks/Standard/external.rsi
- type: PaintableAirlock
group: External
department: null
- type: entity
parent: AirlockExternal

View File

@@ -64,6 +64,7 @@
- ForceNoFixRotations
- type: PaintableAirlock
group: Shuttle
department: null
- type: Construction
graph: AirlockShuttle
node: airlock

View File

@@ -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