Wires refactor (#7699)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
76
Content.Server/Wires/BaseToggleWireAction.cs
Normal file
76
Content.Server/Wires/BaseToggleWireAction.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class meant to be implemented. This is to
|
||||
/// toggle a value whenever a wire is cut, mended,
|
||||
/// or pulsed.
|
||||
/// </summary>
|
||||
public abstract class BaseToggleWireAction : BaseWireAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the value on the given entity. An implementor
|
||||
/// is expected to handle the value toggle appropriately.
|
||||
/// </summary>
|
||||
public abstract void ToggleValue(EntityUid owner, bool setting);
|
||||
/// <summary>
|
||||
/// Gets the value on the given entity. An implementor
|
||||
/// is expected to handle the value getter properly.
|
||||
/// </summary>
|
||||
public abstract bool GetValue(EntityUid owner);
|
||||
/// <summary>
|
||||
/// Timeout key for the wire, if it is pulsed.
|
||||
/// If this is null, there will be no value revert
|
||||
/// after a given delay, otherwise, the value will
|
||||
/// be set to the opposite of what it currently is
|
||||
/// (according to GetValue)
|
||||
/// </summary>
|
||||
public virtual object? TimeoutKey { get; } = null;
|
||||
public virtual int Delay { get; } = 30;
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire)
|
||||
{
|
||||
ToggleValue(wire.Owner, false);
|
||||
|
||||
if (TimeoutKey != null)
|
||||
{
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, TimeoutKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire)
|
||||
{
|
||||
ToggleValue(wire.Owner, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Pulse(EntityUid user, Wire wire)
|
||||
{
|
||||
ToggleValue(wire.Owner, !GetValue(wire.Owner));
|
||||
|
||||
if (TimeoutKey != null)
|
||||
{
|
||||
WiresSystem.StartWireAction(wire.Owner, Delay, TimeoutKey, new TimedWireEvent(AwaitPulseCancel, wire));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(Wire wire)
|
||||
{
|
||||
if (TimeoutKey != null && !IsPowered(wire.Owner))
|
||||
{
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, TimeoutKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void AwaitPulseCancel(Wire wire)
|
||||
{
|
||||
if (!wire.IsCut)
|
||||
{
|
||||
ToggleValue(wire.Owner, !GetValue(wire.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Server/Wires/BaseWireAction.cs
Normal file
54
Content.Server/Wires/BaseWireAction.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
/// <summary><see cref="IWireAction" /></summary>
|
||||
public abstract class BaseWireAction : IWireAction
|
||||
{
|
||||
public IEntityManager EntityManager = default!;
|
||||
public WiresSystem WiresSystem = default!;
|
||||
|
||||
// not virtual so implementors are aware that they need a nullable here
|
||||
public abstract object? StatusKey { get; }
|
||||
|
||||
// ugly, but IoC doesn't work during deserialization
|
||||
public virtual void Initialize()
|
||||
{
|
||||
EntityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
WiresSystem = EntitySystem.Get<WiresSystem>();
|
||||
}
|
||||
|
||||
public virtual bool AddWire(Wire wire, int count) => count == 1;
|
||||
public abstract bool Cut(EntityUid user, Wire wire);
|
||||
public abstract bool Mend(EntityUid user, Wire wire);
|
||||
public abstract bool Pulse(EntityUid user, Wire wire);
|
||||
public virtual void Update(Wire wire)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public abstract StatusLightData? GetStatusLightData(Wire wire);
|
||||
|
||||
// most things that use wires are powered by *something*, so
|
||||
//
|
||||
// this isn't required by any wire system methods though, so whatever inherits it here
|
||||
// can use it
|
||||
/// <summary>
|
||||
/// Utility function to check if this given entity is powered.
|
||||
/// </summary>
|
||||
/// <returns>true if powered, false otherwise</returns>
|
||||
public bool IsPowered(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ApcPowerReceiverComponent>(uid, out var power)
|
||||
|| power.PowerDisabled) // there's some kind of race condition here?
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return power.Powered;
|
||||
}
|
||||
}
|
||||
29
Content.Server/Wires/DummyWireAction.cs
Normal file
29
Content.Server/Wires/DummyWireAction.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
// Exists so that dummy wires can be added.
|
||||
//
|
||||
// You *shouldn't* be adding these as raw
|
||||
// wire actions, but it's here anyways as
|
||||
// a serializable class for consistency.
|
||||
// C'est la vie.
|
||||
[DataDefinition]
|
||||
public sealed class DummyWireAction : BaseWireAction
|
||||
{
|
||||
public override object? StatusKey { get; } = null;
|
||||
|
||||
public override StatusLightData? GetStatusLightData(Wire wire) => null;
|
||||
public override bool AddWire(Wire wire, int count) => true;
|
||||
public override bool Cut(EntityUid user, Wire wire) => true;
|
||||
public override bool Mend(EntityUid user, Wire wire) => true;
|
||||
public override bool Pulse(EntityUid user, Wire wire) => true;
|
||||
|
||||
// doesn't matter if you get any information off of this,
|
||||
// if you really want to mess with dummy wires, you should
|
||||
// probably code your own implementation?
|
||||
private enum DummyWireActionIdentifier
|
||||
{
|
||||
Key,
|
||||
}
|
||||
}
|
||||
80
Content.Server/Wires/IWireAction.cs
Normal file
80
Content.Server/Wires/IWireAction.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
/// <summary>
|
||||
/// An interface used by WiresSystem to allow compositional wiresets.
|
||||
/// This is expected to be flyweighted, do not store per-entity state
|
||||
/// within an object/class that implements IWireAction.
|
||||
/// </summary>
|
||||
public interface IWireAction
|
||||
{
|
||||
/// <summary>
|
||||
/// This is to link the wire's status with
|
||||
/// its corresponding UI key. If this is null,
|
||||
/// GetStatusLightData MUST also return null,
|
||||
/// otherwise nothing happens.
|
||||
/// </summary>
|
||||
public object? StatusKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the wire in the layout
|
||||
/// is created for the first time. Ensures
|
||||
/// that the referenced action has all
|
||||
/// the correct system references (plus
|
||||
/// other information if needed,
|
||||
/// but wire actions should NOT be stateful!)
|
||||
/// </summary>
|
||||
public void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Called when a wire is finally processed
|
||||
/// by WiresSystem upon wire layout
|
||||
/// creation. Use this to set specific details
|
||||
/// about the state of the entity in question.
|
||||
///
|
||||
/// If this returns false, this will convert
|
||||
/// the given wire into a 'dummy' wire instead.
|
||||
/// </summary>
|
||||
/// <param name="wire">The wire in the entity's WiresComponent.</param>
|
||||
/// <param name="count">The current count of this instance of the wire type.</param>
|
||||
public bool AddWire(Wire wire, int count);
|
||||
|
||||
/// <summary>
|
||||
/// What happens when this wire is cut.
|
||||
/// </summary>
|
||||
/// <param name="user">The user attempting to interact with the wire.</param>
|
||||
/// <param name="wire">The wire being interacted with.</param>
|
||||
/// <returns>true if successful, false otherwise.</summary>
|
||||
public bool Cut(EntityUid user, Wire wire);
|
||||
|
||||
/// <summary>
|
||||
/// What happens when this wire is mended.
|
||||
/// </summary>
|
||||
/// <param name="user">The user attempting to interact with the wire.</param>
|
||||
/// <param name="wire">The wire being interacted with.</param>
|
||||
/// <returns>true if successful, false otherwise.</summary>
|
||||
public bool Mend(EntityUid user, Wire wire);
|
||||
|
||||
/// <summary>
|
||||
/// What happens when this wire is pulsed.
|
||||
/// </summary>
|
||||
/// <param name="user">The user attempting to interact with the wire.</param>
|
||||
/// <param name="wire">The wire being interacted with.</param>
|
||||
/// <returns>true if successful, false otherwise.</summary>
|
||||
public bool Pulse(EntityUid user, Wire wire);
|
||||
|
||||
/// <summary>
|
||||
/// Used when a wire's state on an entity needs to be updated.
|
||||
/// Mostly for things related to entity events, e.g., power.
|
||||
/// </summary>
|
||||
public void Update(Wire wire);
|
||||
|
||||
/// <summary>
|
||||
/// Used for when WiresSystem requires the status light data
|
||||
/// for display on the client.
|
||||
/// </summary>
|
||||
/// <returns>StatusLightData to display light data, null to have no status light.</returns>
|
||||
public StatusLightData? GetStatusLightData(Wire wire);
|
||||
}
|
||||
38
Content.Server/Wires/WireLayout.cs
Normal file
38
Content.Server/Wires/WireLayout.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
/// <summary>
|
||||
/// WireLayout prototype.
|
||||
///
|
||||
/// This is meant for ease of organizing wire sets on entities that use
|
||||
/// wires. Once one of these is initialized, it should be stored in the
|
||||
/// WiresSystem as a functional wire set.
|
||||
/// </summary>
|
||||
[Prototype("wireLayout")]
|
||||
public sealed class WireLayoutPrototype : IPrototype, IInheritingPrototype
|
||||
{
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[ParentDataField(typeof(AbstractPrototypeIdSerializer<WireLayoutPrototype>))]
|
||||
public string? Parent { get; } = default!;
|
||||
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How many wires in this layout will do
|
||||
/// nothing (these are added upon layout
|
||||
/// initialization)
|
||||
/// </summary>
|
||||
[DataField("dummyWires")]
|
||||
public int DummyWires { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// All the valid IWireActions currently in this layout.
|
||||
/// </summary>
|
||||
[DataField("wires")]
|
||||
public List<IWireAction>? Wires { get; }
|
||||
}
|
||||
90
Content.Server/Wires/WiresComponent.cs
Normal file
90
Content.Server/Wires/WiresComponent.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Content.Shared.Sound;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class WiresComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the panel open for this entity's wires?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsPanelOpen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should this entity's wires panel be visible at all?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsPanelVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The name of this entity's internal board.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("BoardName")]
|
||||
public string BoardName { get; set; } = "Wires";
|
||||
|
||||
/// <summary>
|
||||
/// The layout ID of this entity's wires.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("LayoutId", required: true)]
|
||||
public string LayoutId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The serial number of this board. Randomly generated upon start,
|
||||
/// does not need to be set.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string? SerialNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The seed that dictates the wires appearance, as well as
|
||||
/// the status ordering on the UI client side.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int WireSeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of wires currently active on this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<Wire> WiresList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Queue of wires saved while the wire's DoAfter event occurs, to prevent too much spam.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<int> WiresQueue { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// If this should follow the layout saved the first time the layout dictated by the
|
||||
/// layout ID is generated, or if a new wire order should be generated every time.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("alwaysRandomize")]
|
||||
public bool AlwaysRandomize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Per wire status, keyed by an object.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<object, object> Statuses { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The state data for the set of wires inside of this entity.
|
||||
/// This is so that wire objects can be flyweighted between
|
||||
/// entities without any issues.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<object, object> StateData { get; } = new();
|
||||
|
||||
[DataField("pulseSound")]
|
||||
public SoundSpecifier PulseSound = new SoundPathSpecifier("/Audio/Effects/multitool_pulse.ogg");
|
||||
|
||||
[DataField("screwdriverOpenSound")]
|
||||
public SoundSpecifier ScrewdriverOpenSound = new SoundPathSpecifier("/Audio/Machines/screwdriveropen.ogg");
|
||||
|
||||
[DataField("screwdriverCloseSound")]
|
||||
public SoundSpecifier ScrewdriverCloseSound = new SoundPathSpecifier("/Audio/Machines/screwdriverclose.ogg");
|
||||
}
|
||||
954
Content.Server/Wires/WiresSystem.cs
Normal file
954
Content.Server/Wires/WiresSystem.cs
Normal file
@@ -0,0 +1,954 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Tools;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Wires;
|
||||
|
||||
public sealed class WiresSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
|
||||
// This is where all the wire layouts are stored.
|
||||
[ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
|
||||
|
||||
private const float ScrewTime = 2.5f;
|
||||
private const float ToolTime = 1f;
|
||||
|
||||
private static DummyWireAction _dummyWire = new DummyWireAction();
|
||||
|
||||
#region Initialization
|
||||
public override void Initialize()
|
||||
{
|
||||
_dummyWire.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
|
||||
// this is a broadcast event
|
||||
SubscribeLocalEvent<WireToolFinishedEvent>(OnToolFinished);
|
||||
SubscribeLocalEvent<WiresComponent, ComponentStartup>(OnWiresStartup);
|
||||
SubscribeLocalEvent<WiresComponent, WiresActionMessage>(OnWiresActionMessage);
|
||||
SubscribeLocalEvent<WiresComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<WiresComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<WiresComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
|
||||
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
|
||||
SubscribeLocalEvent<WiresComponent, OnWireDoAfterEvent>(OnWireDoAfter);
|
||||
SubscribeLocalEvent<WiresComponent, OnWireDoAfterCancelEvent>(OnWireDoAfterCancel);
|
||||
}
|
||||
|
||||
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return;
|
||||
|
||||
WireLayout? layout = null;
|
||||
List<Wire>? wireSet = null;
|
||||
if (wires.LayoutId != null)
|
||||
{
|
||||
if (!wires.AlwaysRandomize)
|
||||
{
|
||||
TryGetLayout(wires.LayoutId, out layout);
|
||||
}
|
||||
|
||||
if (!_protoMan.TryIndex(wires.LayoutId, out WireLayoutPrototype? layoutPrototype))
|
||||
return;
|
||||
|
||||
// does the prototype have a parent (and are the wires empty?) if so, we just create
|
||||
// a new layout based on that
|
||||
//
|
||||
// TODO: Merge wire layouts...
|
||||
if (!string.IsNullOrEmpty(layoutPrototype.Parent) && layoutPrototype.Wires == null)
|
||||
{
|
||||
var parent = layoutPrototype.Parent;
|
||||
|
||||
if (!_protoMan.TryIndex(parent, out WireLayoutPrototype? parentPrototype))
|
||||
return;
|
||||
|
||||
layoutPrototype = parentPrototype;
|
||||
}
|
||||
|
||||
if (layoutPrototype.Wires != null)
|
||||
{
|
||||
foreach (var wire in layoutPrototype.Wires)
|
||||
{
|
||||
wire.Initialize();
|
||||
}
|
||||
|
||||
wireSet = CreateWireSet(uid, layout, layoutPrototype.Wires, layoutPrototype.DummyWires);
|
||||
}
|
||||
}
|
||||
|
||||
if (wireSet == null || wireSet.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wires.WiresList.AddRange(wireSet);
|
||||
|
||||
Dictionary<object, int> types = new Dictionary<object, int>();
|
||||
|
||||
if (layout != null)
|
||||
{
|
||||
for (var i = 0; i < wireSet.Count; i++)
|
||||
{
|
||||
wires.WiresList[layout.Specifications[i].Position] = wireSet[i];
|
||||
}
|
||||
|
||||
var id = 0;
|
||||
foreach (var wire in wires.WiresList)
|
||||
{
|
||||
var wireType = wire.Action.GetType();
|
||||
if (types.ContainsKey(wireType))
|
||||
{
|
||||
types[wireType] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
types.Add(wireType, 1);
|
||||
}
|
||||
|
||||
wire.Id = id;
|
||||
id++;
|
||||
|
||||
// don't care about the result, this should've
|
||||
// been handled in layout creation
|
||||
wire.Action.AddWire(wire, types[wireType]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var enumeratedList = new List<(int, Wire)>();
|
||||
var data = new Dictionary<int, WireLayout.WireData>();
|
||||
for (int i = 0; i < wireSet.Count; i++)
|
||||
{
|
||||
enumeratedList.Add((i, wireSet[i]));
|
||||
}
|
||||
_random.Shuffle(enumeratedList);
|
||||
|
||||
for (var i = 0; i < enumeratedList.Count; i++)
|
||||
{
|
||||
(int id, Wire d) = enumeratedList[i];
|
||||
|
||||
var wireType = d.Action.GetType();
|
||||
if (types.ContainsKey(wireType))
|
||||
{
|
||||
types[wireType] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
types.Add(wireType, 1);
|
||||
}
|
||||
|
||||
d.Id = i;
|
||||
|
||||
if (!d.Action.AddWire(d, types[wireType]))
|
||||
{
|
||||
d.Action = _dummyWire;
|
||||
}
|
||||
|
||||
data.Add(id, new WireLayout.WireData(d.Letter, d.Color, i));
|
||||
|
||||
wires.WiresList[i] = wireSet[id];
|
||||
}
|
||||
|
||||
if (!wires.AlwaysRandomize && !string.IsNullOrEmpty(wires.LayoutId))
|
||||
{
|
||||
AddLayout(wires.LayoutId, new WireLayout(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Wire>? CreateWireSet(EntityUid uid, WireLayout? layout, List<IWireAction> wires, int dummyWires)
|
||||
{
|
||||
if (wires.Count == 0)
|
||||
return null;
|
||||
|
||||
List<WireColor> colors =
|
||||
new((WireColor[]) Enum.GetValues(typeof(WireColor)));
|
||||
|
||||
List<WireLetter> letters =
|
||||
new((WireLetter[]) Enum.GetValues(typeof(WireLetter)));
|
||||
|
||||
|
||||
var wireSet = new List<Wire>();
|
||||
for (var i = 0; i < wires.Count; i++)
|
||||
{
|
||||
wireSet.Add(CreateWire(uid, wires[i], i, layout, colors, letters));
|
||||
}
|
||||
|
||||
for (var i = 1; i <= dummyWires; i++)
|
||||
{
|
||||
wireSet.Add(CreateWire(uid, _dummyWire, wires.Count + i, layout, colors, letters));
|
||||
}
|
||||
|
||||
return wireSet;
|
||||
}
|
||||
|
||||
private Wire CreateWire(EntityUid uid, IWireAction action, int position, WireLayout? layout, List<WireColor> colors, List<WireLetter> letters)
|
||||
{
|
||||
WireLetter letter;
|
||||
WireColor color;
|
||||
|
||||
if (layout != null
|
||||
&& layout.Specifications.TryGetValue(position, out var spec))
|
||||
{
|
||||
color = spec.Color;
|
||||
letter = spec.Letter;
|
||||
colors.Remove(color);
|
||||
letters.Remove(letter);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = colors.Count == 0 ? WireColor.Red : _random.PickAndTake(colors);
|
||||
letter = letters.Count == 0 ? WireLetter.α : _random.PickAndTake(letters);
|
||||
}
|
||||
|
||||
return new Wire(
|
||||
uid,
|
||||
false,
|
||||
color,
|
||||
letter,
|
||||
action);
|
||||
}
|
||||
|
||||
private void OnWiresStartup(EntityUid uid, WiresComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(component.LayoutId))
|
||||
SetOrCreateWireLayout(uid, component);
|
||||
|
||||
UpdateUserInterface(uid);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DoAfters
|
||||
private void OnTimedWire(EntityUid uid, WiresComponent component, TimedWireEvent args)
|
||||
{
|
||||
args.Delegate(args.Wire);
|
||||
UpdateUserInterface(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to cancel an active wire action via the given key that it's stored in.
|
||||
/// </summary>
|
||||
/// <param id="key">The key used to cancel the action.</param>
|
||||
public bool TryCancelWireAction(EntityUid owner, object key)
|
||||
{
|
||||
if (TryGetData(owner, key, out CancellationTokenSource? token))
|
||||
{
|
||||
token.Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a timed action for this entity.
|
||||
/// </summary>
|
||||
/// <param id="delay">How long this takes to finish</param>
|
||||
/// <param id="key">The key used to cancel the action</param>
|
||||
/// <param id="onFinish">The event that is sent out when the wire is finished <see cref="TimedWireEvent" /></param>
|
||||
public void StartWireAction(EntityUid owner, float delay, object key, TimedWireEvent onFinish)
|
||||
{
|
||||
if (!HasComp<WiresComponent>(owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_activeWires.ContainsKey(owner))
|
||||
{
|
||||
_activeWires.Add(owner, new());
|
||||
}
|
||||
|
||||
CancellationTokenSource tokenSource = new();
|
||||
|
||||
// Starting an already started action will do nothing.
|
||||
if (HasData(owner, key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetData(owner, key, tokenSource);
|
||||
|
||||
_activeWires[owner].Add(new ActiveWireAction
|
||||
(
|
||||
key,
|
||||
delay,
|
||||
tokenSource.Token,
|
||||
onFinish
|
||||
));
|
||||
}
|
||||
|
||||
private Dictionary<EntityUid, List<ActiveWireAction>> _activeWires = new();
|
||||
private List<(EntityUid, ActiveWireAction)> _finishedWires = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var (owner, activeWires) in _activeWires)
|
||||
{
|
||||
foreach (var wire in activeWires)
|
||||
{
|
||||
if (wire.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
RaiseLocalEvent(owner, wire.OnFinish);
|
||||
_finishedWires.Add((owner, wire));
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.TimeLeft -= frameTime;
|
||||
if (wire.TimeLeft <= 0)
|
||||
{
|
||||
RaiseLocalEvent(owner, wire.OnFinish);
|
||||
_finishedWires.Add((owner, wire));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_finishedWires.Count != 0)
|
||||
{
|
||||
foreach (var (owner, wireAction) in _finishedWires)
|
||||
{
|
||||
// sure
|
||||
_activeWires[owner].RemoveAll(action => action.CancelToken == wireAction.CancelToken);
|
||||
|
||||
if (_activeWires[owner].Count == 0)
|
||||
{
|
||||
_activeWires.Remove(owner);
|
||||
}
|
||||
|
||||
RemoveData(owner, wireAction.Id);
|
||||
}
|
||||
|
||||
_finishedWires.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private class ActiveWireAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The wire action's ID. This is so that once the action is finished,
|
||||
/// any related data can be removed from the state dictionary.
|
||||
/// </summary>
|
||||
public object Id;
|
||||
|
||||
/// <summary>
|
||||
/// How much time is left in this action before it finishes.
|
||||
/// </summary>
|
||||
public float TimeLeft;
|
||||
|
||||
/// <summary>
|
||||
/// The token used to cancel the action.
|
||||
/// </summary>
|
||||
public CancellationToken CancelToken;
|
||||
|
||||
/// <summary>
|
||||
/// The event called once the action finishes.
|
||||
/// </summary>
|
||||
public TimedWireEvent OnFinish;
|
||||
|
||||
public ActiveWireAction(object identifier, float time, CancellationToken cancelToken, TimedWireEvent onFinish)
|
||||
{
|
||||
Id = identifier;
|
||||
TimeLeft = time;
|
||||
CancelToken = cancelToken;
|
||||
OnFinish = onFinish;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
private void OnWiresPowered(EntityUid uid, WiresComponent component, PowerChangedEvent args)
|
||||
{
|
||||
UpdateUserInterface(uid);
|
||||
foreach (var wire in component.WiresList)
|
||||
{
|
||||
wire.Action.Update(wire);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWiresActionMessage(EntityUid uid, WiresComponent component, WiresActionMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var player = (EntityUid) args.Session.AttachedEntity;
|
||||
|
||||
if (!EntityManager.TryGetComponent(player, out HandsComponent? handsComponent))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-no-hands"), uid, Filter.Entities(player));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(player, uid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-cannot-reach"), uid, Filter.Entities(player));
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHand = handsComponent.ActiveHand;
|
||||
|
||||
if (activeHand == null)
|
||||
return;
|
||||
|
||||
if (activeHand.HeldEntity == null)
|
||||
return;
|
||||
|
||||
var activeHandEntity = activeHand.HeldEntity.Value;
|
||||
if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool))
|
||||
return;
|
||||
|
||||
TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
|
||||
}
|
||||
|
||||
private void OnWireDoAfter(EntityUid uid, WiresComponent component, OnWireDoAfterEvent args)
|
||||
{
|
||||
UpdateWires(args.Target, args.User, args.Tool, args.Id, args.Action, component);
|
||||
}
|
||||
|
||||
private void OnWireDoAfterCancel(EntityUid uid, WiresComponent component, OnWireDoAfterCancelEvent args)
|
||||
{
|
||||
component.WiresQueue.Remove(args.Id);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool))
|
||||
return;
|
||||
|
||||
if (component.IsPanelOpen &&
|
||||
_toolSystem.HasQuality(args.Used, "Cutting", tool) ||
|
||||
_toolSystem.HasQuality(args.Used, "Pulsing", tool))
|
||||
{
|
||||
if (EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
||||
{
|
||||
_uiSystem.GetUiOrNull(uid, WiresUiKey.Key)?.Open(actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
else if (_toolSystem.UseTool(args.Used, args.User, uid, 0f, ScrewTime, new string[]{ "Screwing" }, doAfterCompleteEvent:new WireToolFinishedEvent(uid), toolComponent:tool))
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToolFinished(WireToolFinishedEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(args.Target, out WiresComponent? component))
|
||||
return;
|
||||
|
||||
component.IsPanelOpen = !component.IsPanelOpen;
|
||||
UpdateAppearance(args.Target);
|
||||
|
||||
if (component.IsPanelOpen)
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(args.Target), component.ScrewdriverOpenSound.GetSound(), args.Target);
|
||||
}
|
||||
else
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(args.Target), component.ScrewdriverCloseSound.GetSound(), args.Target);
|
||||
_uiSystem.GetUiOrNull(args.Target, WiresUiKey.Key)?.CloseAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, WiresComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(component.IsPanelOpen
|
||||
? "wires-component-on-examine-panel-open"
|
||||
: "wires-component-on-examine-panel-closed"));
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.SerialNumber == null)
|
||||
{
|
||||
GenerateSerialNumber(uid, component);
|
||||
}
|
||||
|
||||
if (component.WireSeed == 0)
|
||||
{
|
||||
component.WireSeed = _random.Next(1, int.MaxValue);
|
||||
UpdateUserInterface(uid);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Entity API
|
||||
private void GenerateSerialNumber(EntityUid uid, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return;
|
||||
|
||||
Span<char> data = stackalloc char[9];
|
||||
data[4] = '-';
|
||||
|
||||
if (_random.Prob(0.01f))
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
// Cyrillic Letters
|
||||
data[i] = (char) _random.Next(0x0410, 0x0430);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
// Letters
|
||||
data[i] = (char) _random.Next(0x41, 0x5B);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 5; i < 9; i++)
|
||||
{
|
||||
// Digits
|
||||
data[i] = (char) _random.Next(0x30, 0x3A);
|
||||
}
|
||||
|
||||
wires.SerialNumber = new string(data);
|
||||
UpdateUserInterface(uid);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, AppearanceComponent? appearance = null, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref appearance, ref wires))
|
||||
return;
|
||||
|
||||
appearance.SetData(WiresVisuals.MaintenancePanelState, wires.IsPanelOpen && wires.IsPanelVisible);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, WiresComponent? wires = null, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires, ref ui, false)) // logging this means that we get a bunch of errors
|
||||
return;
|
||||
|
||||
var clientList = new List<ClientWire>();
|
||||
foreach (var entry in wires.WiresList)
|
||||
{
|
||||
clientList.Add(new ClientWire(entry.Id, entry.IsCut, entry.Color,
|
||||
entry.Letter));
|
||||
|
||||
var statusData = entry.Action.GetStatusLightData(entry);
|
||||
if (statusData != null && entry.Action.StatusKey != null)
|
||||
{
|
||||
wires.Statuses[entry.Action.StatusKey] = statusData;
|
||||
}
|
||||
}
|
||||
|
||||
_uiSystem.GetUiOrNull(uid, WiresUiKey.Key)?.SetState(
|
||||
new WiresBoundUserInterfaceState(
|
||||
clientList.ToArray(),
|
||||
wires.Statuses.Select(p => new StatusEntry(p.Key, p.Value)).ToArray(),
|
||||
wires.BoardName,
|
||||
wires.SerialNumber,
|
||||
wires.WireSeed));
|
||||
}
|
||||
|
||||
public void OpenUserInterface(EntityUid uid, IPlayerSession player)
|
||||
{
|
||||
_uiSystem.GetUiOrNull(uid, WiresUiKey.Key)?.Open(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a wire on this entity by its integer id.
|
||||
/// </summary>
|
||||
/// <returns>The wire if found, otherwise null</returns>
|
||||
public Wire? TryGetWire(EntityUid uid, int id, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return null;
|
||||
|
||||
return id >= 0 && id < wires.WiresList.Count
|
||||
? wires.WiresList[id]
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get all the wires on this entity by the wire action type.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator of all wires in this entity according to the given type.</returns>
|
||||
public IEnumerable<Wire> TryGetWires<T>(EntityUid uid, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
yield break;
|
||||
|
||||
foreach (var wire in wires.WiresList)
|
||||
{
|
||||
if (wire.GetType() == typeof(T))
|
||||
{
|
||||
yield return wire;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryDoWireAction(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
|
||||
{
|
||||
if (!Resolve(used, ref wires)
|
||||
|| !Resolve(toolEntity, ref tool))
|
||||
return;
|
||||
|
||||
if (wires.WiresQueue.Contains(id))
|
||||
return;
|
||||
|
||||
var wire = TryGetWire(used, id, wires);
|
||||
|
||||
if (wire == null)
|
||||
return;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case WiresAction.Cut:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Cutting", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case WiresAction.Mend:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Cutting", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case WiresAction.Pulse:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Pulsing", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var args = new DoAfterEventArgs(user, ToolTime, default, used)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnStun = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnUserMove = true,
|
||||
TargetFinishedEvent = new OnWireDoAfterEvent
|
||||
{
|
||||
Target = used,
|
||||
User = user,
|
||||
Tool = toolEntity,
|
||||
Action = action,
|
||||
Id = id
|
||||
},
|
||||
TargetCancelledEvent = new OnWireDoAfterCancelEvent
|
||||
{
|
||||
Id = id
|
||||
}
|
||||
};
|
||||
_doAfter.DoAfter(args);
|
||||
|
||||
wires.WiresQueue.Add(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
|
||||
{
|
||||
if (!Resolve(used, ref wires)
|
||||
|| !Resolve(toolEntity, ref tool))
|
||||
return;
|
||||
|
||||
var wire = TryGetWire(used, id, wires);
|
||||
|
||||
if (wire == null)
|
||||
return;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case WiresAction.Cut:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Cutting", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
_toolSystem.PlayToolSound(toolEntity, tool);
|
||||
if (wire.Action.Cut(user, wire))
|
||||
{
|
||||
wire.IsCut = true;
|
||||
}
|
||||
|
||||
UpdateUserInterface(used);
|
||||
break;
|
||||
case WiresAction.Mend:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Cutting", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
_toolSystem.PlayToolSound(toolEntity, tool);
|
||||
if (wire.Action.Mend(user, wire))
|
||||
{
|
||||
wire.IsCut = false;
|
||||
}
|
||||
|
||||
UpdateUserInterface(used);
|
||||
break;
|
||||
case WiresAction.Pulse:
|
||||
if (!_toolSystem.HasQuality(toolEntity, "Pulsing", tool))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
if (wire.IsCut)
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-pulse-cut-wire"), Filter.Entities(user));
|
||||
return;
|
||||
}
|
||||
|
||||
wire.Action.Pulse(user, wire);
|
||||
|
||||
UpdateUserInterface(used);
|
||||
SoundSystem.Play(Filter.Pvs(used), wires.PulseSound.GetSound(), used);
|
||||
break;
|
||||
}
|
||||
|
||||
wires.WiresQueue.Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the stateful data stored in this entity's WiresComponent.
|
||||
/// </summary>
|
||||
/// <param id="identifier">The key that stores the data in the WiresComponent.</param>
|
||||
public bool TryGetData<T>(EntityUid uid, object identifier, [NotNullWhen(true)] out T? data, WiresComponent? wires = null)
|
||||
{
|
||||
data = default(T);
|
||||
if (!Resolve(uid, ref wires))
|
||||
return false;
|
||||
|
||||
wires.StateData.TryGetValue(identifier, out var result);
|
||||
|
||||
if (result is not T)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
data = (T) result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets data in the entity's WiresComponent state dictionary by key.
|
||||
/// </summary>
|
||||
/// <param id="identifier">The key that stores the data in the WiresComponent.</param>
|
||||
/// <param id="data">The data to store using the given identifier.</param>
|
||||
public void SetData(EntityUid uid, object identifier, object data, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return;
|
||||
|
||||
if (wires.StateData.TryGetValue(identifier, out var storedMessage))
|
||||
{
|
||||
if (storedMessage == data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
wires.StateData[identifier] = data;
|
||||
UpdateUserInterface(uid, wires);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this entity has data stored via this key in the WiresComponent it has
|
||||
/// </summary>
|
||||
public bool HasData(EntityUid uid, object identifier, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return false;
|
||||
|
||||
return wires.StateData.ContainsKey(identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes data from this entity stored in the given key from the entity's WiresComponent.
|
||||
/// </summary>
|
||||
/// <param id="identifier">The key that stores the data in the WiresComponent.</param>
|
||||
public void RemoveData(EntityUid uid, object identifier, WiresComponent? wires = null)
|
||||
{
|
||||
if (!Resolve(uid, ref wires))
|
||||
return;
|
||||
|
||||
wires.StateData.Remove(identifier);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Layout Handling
|
||||
private bool TryGetLayout(string id, [NotNullWhen(true)] out WireLayout? layout)
|
||||
{
|
||||
return _layouts.TryGetValue(id, out layout);
|
||||
}
|
||||
|
||||
private void AddLayout(string id, WireLayout layout)
|
||||
{
|
||||
_layouts.Add(id, layout);
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent args)
|
||||
{
|
||||
_layouts.Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
private sealed class WireToolFinishedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Target { get; }
|
||||
|
||||
public WireToolFinishedEvent(EntityUid target)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class OnWireDoAfterEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid User { get; set; }
|
||||
public EntityUid Target { get; set; }
|
||||
public EntityUid Tool { get; set; }
|
||||
public WiresAction Action { get; set; }
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
private sealed class OnWireDoAfterCancelEvent : EntityEventArgs
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public sealed class Wire
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that registered the wire.
|
||||
/// </summary>
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the wire is cut.
|
||||
/// </summary>
|
||||
public bool IsCut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used in client-server communication to identify a wire without telling the client what the wire does.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the wire.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public WireColor Color { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The greek letter shown below the wire.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public WireLetter Letter { get; }
|
||||
|
||||
// The action that this wire performs upon activation.
|
||||
public IWireAction Action { get; set; }
|
||||
|
||||
public Wire(EntityUid owner, bool isCut, WireColor color, WireLetter letter, IWireAction action)
|
||||
{
|
||||
Owner = owner;
|
||||
IsCut = isCut;
|
||||
Color = color;
|
||||
Letter = letter;
|
||||
Action = action;
|
||||
}
|
||||
}
|
||||
|
||||
// this is here so that when a DoAfter event is called,
|
||||
// WiresSystem can call the action in question after the
|
||||
// doafter is finished (either through cancellation
|
||||
// or completion - this is implementation dependent)
|
||||
public delegate void WireActionDelegate(Wire wire);
|
||||
|
||||
// callbacks over the event bus,
|
||||
// because async is banned
|
||||
public sealed class TimedWireEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The function to be called once
|
||||
/// the timed event is complete.
|
||||
/// </summary>
|
||||
public WireActionDelegate Delegate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The wire tied to this timed wire event.
|
||||
/// </summary>
|
||||
public Wire Wire { get; }
|
||||
|
||||
public TimedWireEvent(WireActionDelegate @delegate, Wire wire)
|
||||
{
|
||||
Delegate = @delegate;
|
||||
Wire = wire;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WireLayout
|
||||
{
|
||||
// why is this an <int, WireData>?
|
||||
// List<T>.Insert panics,
|
||||
// and I needed a uniquer key for wires
|
||||
// which allows me to have a unified identifier
|
||||
[ViewVariables] public IReadOnlyDictionary<int, WireData> Specifications { get; }
|
||||
|
||||
public WireLayout(IReadOnlyDictionary<int, WireData> specifications)
|
||||
{
|
||||
Specifications = specifications;
|
||||
}
|
||||
|
||||
public sealed class WireData
|
||||
{
|
||||
public WireLetter Letter { get; }
|
||||
public WireColor Color { get; }
|
||||
public int Position { get; }
|
||||
|
||||
public WireData(WireLetter letter, WireColor color, int position)
|
||||
{
|
||||
Letter = letter;
|
||||
Color = color;
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user