[Entity] Brig Timers (#15285)
* brigtimer * ok * TextScreen w timer implementation * second commit * working brig timer * signal timers near completion * soon done * removed licenses, fixes noRotation on screens, minor edits * no message * no message * removed my last todos * removed csproj.rej?? * missed a thing with .yml and tests * fix tests * Update base_structureairlocks.yml * timespan type serialize * activation turned into comp * sloth review * Update timer.yml * small changes --------- Co-authored-by: CommieFlowers <rasmus.cedergren@hotmail.com> Co-authored-by: rolfero <45628623+rolfero@users.noreply.github.com>
@@ -0,0 +1,81 @@
|
|||||||
|
using Content.Shared.MachineLinking;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.MachineLinking.UI;
|
||||||
|
|
||||||
|
public sealed class SignalTimerBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
private SignalTimerWindow? _window;
|
||||||
|
|
||||||
|
public SignalTimerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new SignalTimerWindow(this);
|
||||||
|
|
||||||
|
if (State != null)
|
||||||
|
UpdateState(State);
|
||||||
|
|
||||||
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OnCurrentTextChanged += OnTextChanged;
|
||||||
|
_window.OnCurrentDelayMinutesChanged += OnDelayChanged;
|
||||||
|
_window.OnCurrentDelaySecondsChanged += OnDelayChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStartTimer()
|
||||||
|
{
|
||||||
|
SendMessage(new SignalTimerStartMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextChanged(string newText)
|
||||||
|
{
|
||||||
|
SendMessage(new SignalTimerTextChangedMessage(newText));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDelayChanged(string newDelay)
|
||||||
|
{
|
||||||
|
if (_window == null)
|
||||||
|
return;
|
||||||
|
SendMessage(new SignalTimerDelayChangedMessage(_window.GetDelay()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetCurrentTime()
|
||||||
|
{
|
||||||
|
return _gameTiming.CurTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the UI state based on server-sent info
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (_window == null || state is not SignalTimerBoundUserInterfaceState cast)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_window.SetCurrentText(cast.CurrentText);
|
||||||
|
_window.SetCurrentDelayMinutes(cast.CurrentDelayMinutes);
|
||||||
|
_window.SetCurrentDelaySeconds(cast.CurrentDelaySeconds);
|
||||||
|
_window.SetShowText(cast.ShowText);
|
||||||
|
_window.SetTriggerTime(cast.TriggerTime);
|
||||||
|
_window.SetTimerStarted(cast.TimerStarted);
|
||||||
|
_window.SetHasAccess(cast.HasAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Content.Client/MachineLinking/UI/SignalTimerWindow.xaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
|
Title="{Loc signal-timer-menu-title}">
|
||||||
|
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150">
|
||||||
|
<BoxContainer Name="TextEdit" Orientation="Horizontal">
|
||||||
|
<Label Name="CurrentLabel" Text="{Loc signal-timer-menu-label}" />
|
||||||
|
<LineEdit Name="CurrentTextEdit" MinWidth="80" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="DelayEdit" Orientation="Horizontal">
|
||||||
|
<Label Name="CurrentDelay" Text="{Loc signal-timer-menu-delay}" />
|
||||||
|
<LineEdit Name="CurrentDelayEditMinutes" MinWidth="32" />
|
||||||
|
<Label Name="Colon" Text=":" />
|
||||||
|
<LineEdit Name="CurrentDelayEditSeconds" MinWidth="32" />
|
||||||
|
<Label Name="DelayInfo" Text=" (mm:ss)" />
|
||||||
|
</BoxContainer>
|
||||||
|
<Button Name="StartTimer" Text="{Loc signal-timer-menu-start}" />
|
||||||
|
</BoxContainer>
|
||||||
|
</DefaultWindow>
|
||||||
192
Content.Client/MachineLinking/UI/SignalTimerWindow.xaml.cs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Content.Client.TextScreen;
|
||||||
|
|
||||||
|
namespace Content.Client.MachineLinking.UI;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class SignalTimerWindow : DefaultWindow
|
||||||
|
{
|
||||||
|
private const int MaxTextLength = 5;
|
||||||
|
|
||||||
|
public event Action<string>? OnCurrentTextChanged;
|
||||||
|
public event Action<string>? OnCurrentDelayMinutesChanged;
|
||||||
|
public event Action<string>? OnCurrentDelaySecondsChanged;
|
||||||
|
|
||||||
|
private readonly SignalTimerBoundUserInterface _owner;
|
||||||
|
|
||||||
|
private TimeSpan? _triggerTime;
|
||||||
|
|
||||||
|
private bool _timerStarted;
|
||||||
|
|
||||||
|
public SignalTimerWindow(SignalTimerBoundUserInterface owner)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
_owner = owner;
|
||||||
|
|
||||||
|
CurrentTextEdit.OnTextChanged += e => OnCurrentTextChange(e.Text);
|
||||||
|
CurrentDelayEditMinutes.OnTextChanged += e => OnCurrentDelayMinutesChange(e.Text);
|
||||||
|
CurrentDelayEditSeconds.OnTextChanged += e => OnCurrentDelaySecondsChange(e.Text);
|
||||||
|
StartTimer.OnPressed += _ => OnStartTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStartTimer()
|
||||||
|
{
|
||||||
|
if (!_timerStarted)
|
||||||
|
{
|
||||||
|
_timerStarted = true;
|
||||||
|
_triggerTime = _owner.GetCurrentTime() + GetDelay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTimerStarted(false);
|
||||||
|
}
|
||||||
|
_owner.OnStartTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
if (!_timerStarted || _triggerTime == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_owner.GetCurrentTime() < _triggerTime.Value)
|
||||||
|
{
|
||||||
|
StartTimer.Text = TextScreenSystem.TimeToString(_triggerTime.Value - _owner.GetCurrentTime());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTimerStarted(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCurrentTextChange(string text)
|
||||||
|
{
|
||||||
|
if (CurrentTextEdit.Text.Length > MaxTextLength)
|
||||||
|
{
|
||||||
|
CurrentTextEdit.Text = CurrentTextEdit.Text.Remove(MaxTextLength);
|
||||||
|
CurrentTextEdit.CursorPosition = MaxTextLength;
|
||||||
|
}
|
||||||
|
OnCurrentTextChanged?.Invoke(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCurrentDelayMinutesChange(string text)
|
||||||
|
{
|
||||||
|
List<char> toRemove = new();
|
||||||
|
|
||||||
|
foreach (var a in text)
|
||||||
|
{
|
||||||
|
if (!char.IsDigit(a))
|
||||||
|
toRemove.Add(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var a in toRemove)
|
||||||
|
{
|
||||||
|
CurrentDelayEditMinutes.Text = text.Replace(a.ToString(),"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentDelayEditMinutes.Text == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (CurrentDelayEditMinutes.Text[0] == '0' && CurrentDelayEditMinutes.Text.Length > 2)
|
||||||
|
{
|
||||||
|
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentDelayEditMinutes.Text.Length > 2)
|
||||||
|
{
|
||||||
|
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(2);
|
||||||
|
}
|
||||||
|
OnCurrentDelayMinutesChanged?.Invoke(CurrentDelayEditMinutes.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCurrentDelaySecondsChange(string text)
|
||||||
|
{
|
||||||
|
List<char> toRemove = new();
|
||||||
|
|
||||||
|
foreach (var a in text)
|
||||||
|
{
|
||||||
|
if (!char.IsDigit(a))
|
||||||
|
toRemove.Add(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var a in toRemove)
|
||||||
|
{
|
||||||
|
CurrentDelayEditSeconds.Text = text.Replace(a.ToString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentDelayEditSeconds.Text == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (CurrentDelayEditSeconds.Text[0] == '0' && CurrentDelayEditSeconds.Text.Length > 2)
|
||||||
|
{
|
||||||
|
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentDelayEditSeconds.Text.Length > 2)
|
||||||
|
{
|
||||||
|
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(2);
|
||||||
|
}
|
||||||
|
OnCurrentDelaySecondsChanged?.Invoke(CurrentDelayEditSeconds.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCurrentText(string text)
|
||||||
|
{
|
||||||
|
CurrentTextEdit.Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCurrentDelayMinutes(string delay)
|
||||||
|
{
|
||||||
|
CurrentDelayEditMinutes.Text = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCurrentDelaySeconds(string delay)
|
||||||
|
{
|
||||||
|
CurrentDelayEditSeconds.Text = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetShowText(bool showTime)
|
||||||
|
{
|
||||||
|
TextEdit.Visible = showTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTriggerTime(TimeSpan timeSpan)
|
||||||
|
{
|
||||||
|
_triggerTime = timeSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTimerStarted(bool timerStarted)
|
||||||
|
{
|
||||||
|
_timerStarted = timerStarted;
|
||||||
|
|
||||||
|
if (!timerStarted)
|
||||||
|
StartTimer.Text = Loc.GetString("signal-timer-menu-start");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables fields and buttons if you don't have the access.
|
||||||
|
/// </summary>
|
||||||
|
public void SetHasAccess(bool hasAccess)
|
||||||
|
{
|
||||||
|
CurrentTextEdit.Editable = hasAccess;
|
||||||
|
CurrentDelayEditMinutes.Editable = hasAccess;
|
||||||
|
CurrentDelayEditSeconds.Editable = hasAccess;
|
||||||
|
StartTimer.Disabled = !hasAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a TimeSpan from the currently entered delay.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetDelay()
|
||||||
|
{
|
||||||
|
if (!double.TryParse(CurrentDelayEditMinutes.Text, out var minutes))
|
||||||
|
minutes = 0;
|
||||||
|
if (!double.TryParse(CurrentDelayEditSeconds.Text, out var seconds))
|
||||||
|
seconds = 0;
|
||||||
|
return TimeSpan.FromMinutes(minutes) + TimeSpan.FromSeconds(seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
295
Content.Client/TextScreen/TextScreenSystem.cs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
using Content.Shared.TextScreen;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.TextScreen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The TextScreenSystem draws text in the game world using 3x5 sprite states for each character.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains char/state Key/Value pairs. <br/>
|
||||||
|
/// The states in Textures/Effects/text.rsi that special character should be replaced with.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<char, string> CharStatePairs = new()
|
||||||
|
{
|
||||||
|
{ ':', "colon" },
|
||||||
|
{ '!', "exclamation" },
|
||||||
|
{ '?', "question" },
|
||||||
|
{ '*', "star" },
|
||||||
|
{ '+', "plus" },
|
||||||
|
{ '-', "dash" },
|
||||||
|
{ ' ', "blank" }
|
||||||
|
};
|
||||||
|
|
||||||
|
private const string DefaultState = "blank";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A string prefix for all text layers.
|
||||||
|
/// </summary>
|
||||||
|
private const string TextScreenLayerMapKey = "textScreenLayerMapKey";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<TextScreenVisualsComponent, ComponentInit>(OnInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInit(EntityUid uid, TextScreenVisualsComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
if (!TryComp(uid, out SpriteComponent? sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ResetTextLength(uid, component, sprite);
|
||||||
|
PrepareLayerStatesToDraw(uid, component, sprite);
|
||||||
|
UpdateLayersToDraw(uid, component, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all TextScreenComponent sprite layers, through removing them and then creating new ones.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetTextLength(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (key, _) in component.LayerStatesToDraw)
|
||||||
|
{
|
||||||
|
sprite.RemoveLayer(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.LayerStatesToDraw.Clear();
|
||||||
|
|
||||||
|
var length = component.TextLength;
|
||||||
|
component.TextLength = 0;
|
||||||
|
SetTextLength(uid, component, length, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets <see cref="TextScreenVisualsComponent.TextLength"/>, adding or removing sprite layers if necessary.
|
||||||
|
/// </summary>
|
||||||
|
public void SetTextLength(EntityUid uid, TextScreenVisualsComponent component, int newLength, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (newLength == component.TextLength)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (newLength > component.TextLength)
|
||||||
|
{
|
||||||
|
for (var i = component.TextLength; i < newLength; i++)
|
||||||
|
{
|
||||||
|
sprite.LayerMapReserveBlank(TextScreenLayerMapKey + i);
|
||||||
|
component.LayerStatesToDraw.Add(TextScreenLayerMapKey + i, null);
|
||||||
|
sprite.LayerSetRSI(TextScreenLayerMapKey + i, new ResourcePath("Effects/text.rsi"));
|
||||||
|
sprite.LayerSetColor(TextScreenLayerMapKey + i, component.Color);
|
||||||
|
sprite.LayerSetState(TextScreenLayerMapKey + i, DefaultState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = component.TextLength; i > newLength; i--)
|
||||||
|
{
|
||||||
|
sprite.LayerMapGet(TextScreenLayerMapKey + (i - 1));
|
||||||
|
component.LayerStatesToDraw.Remove(TextScreenLayerMapKey + (i - 1));
|
||||||
|
sprite.RemoveLayer(TextScreenLayerMapKey + (i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateOffsets(uid, component, sprite);
|
||||||
|
|
||||||
|
component.TextLength = newLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the layers offsets based on the text length, so it is drawn correctly.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateOffsets(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < component.LayerStatesToDraw.Count; i++)
|
||||||
|
{
|
||||||
|
var offset = i - (component.LayerStatesToDraw.Count - 1) / 2.0f;
|
||||||
|
sprite.LayerSetOffset(TextScreenLayerMapKey + i, new Vector2(offset * TextScreenVisualsComponent.PixelSize * 4f, 0.0f) + component.TextOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, TextScreenVisualsComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
UpdateAppearance(uid, component, args.Component, args.Sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAppearance(EntityUid uid, TextScreenVisualsComponent component, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref appearance, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.On, out bool on, appearance))
|
||||||
|
{
|
||||||
|
component.Activated = on;
|
||||||
|
UpdateVisibility(uid, component, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.Mode, out TextScreenMode mode, appearance))
|
||||||
|
{
|
||||||
|
component.CurrentMode = mode;
|
||||||
|
if (component.CurrentMode == TextScreenMode.Timer)
|
||||||
|
EnsureComp<TextScreenTimerComponent>(uid);
|
||||||
|
else
|
||||||
|
RemComp<TextScreenTimerComponent>(uid);
|
||||||
|
|
||||||
|
UpdateText(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.TargetTime, out TimeSpan time, appearance))
|
||||||
|
{
|
||||||
|
component.TargetTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.ScreenText, out string text, appearance))
|
||||||
|
{
|
||||||
|
component.Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateText(component);
|
||||||
|
PrepareLayerStatesToDraw(uid, component, sprite);
|
||||||
|
UpdateLayersToDraw(uid, component, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If currently in <see cref="TextScreenMode.Text"/> mode: <br/>
|
||||||
|
/// Sets <see cref="TextScreenVisualsComponent.TextToDraw"/> to the value of <see cref="TextScreenVisualsComponent.Text"/>
|
||||||
|
/// </summary>
|
||||||
|
public static void UpdateText(TextScreenVisualsComponent component)
|
||||||
|
{
|
||||||
|
if (component.CurrentMode == TextScreenMode.Text)
|
||||||
|
component.TextToDraw = component.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets visibility of text to <see cref="TextScreenVisualsComponent.Activated"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateVisibility(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (key, _) in component.LayerStatesToDraw)
|
||||||
|
{
|
||||||
|
sprite.LayerSetVisible(key, component.Activated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the states in the <see cref="TextScreenVisualsComponent.LayerStatesToDraw"/> to match the component <see cref="TextScreenVisualsComponent.TextToDraw"/> string.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Remember to set <see cref="TextScreenVisualsComponent.TextToDraw"/> to a string first.
|
||||||
|
/// </remarks>
|
||||||
|
public void PrepareLayerStatesToDraw(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < component.TextLength; i++)
|
||||||
|
{
|
||||||
|
if (i >= component.TextToDraw.Length)
|
||||||
|
{
|
||||||
|
component.LayerStatesToDraw[TextScreenLayerMapKey + i] = DefaultState;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
component.LayerStatesToDraw[TextScreenLayerMapKey + i] = GetStateFromChar(component.TextToDraw[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Iterates through <see cref="TextScreenVisualsComponent.LayerStatesToDraw"/>, setting sprite states to the appropriate layers.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateLayersToDraw(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (key, state) in component.LayerStatesToDraw)
|
||||||
|
{
|
||||||
|
if (state == null)
|
||||||
|
continue;
|
||||||
|
sprite.LayerSetState(key, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<TextScreenVisualsComponent, TextScreenTimerComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var comp, out _))
|
||||||
|
{
|
||||||
|
// Basically Abs(TimeSpan, TimeSpan) -> Gives the difference between the current time and the target time.
|
||||||
|
var timeToShow = _gameTiming.CurTime > comp.TargetTime ? _gameTiming.CurTime - comp.TargetTime : comp.TargetTime - _gameTiming.CurTime;
|
||||||
|
comp.TextToDraw = TimeToString(timeToShow, false);
|
||||||
|
PrepareLayerStatesToDraw(uid, comp);
|
||||||
|
UpdateLayersToDraw(uid, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the <paramref name="timeSpan"/> converted to a string in either HH:MM, MM:SS or potentially SS:mm format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpan">TimeSpan to convert into string.</param>
|
||||||
|
/// <param name="getMilliseconds">Should the string be ss:ms if minutes are less than 1?</param>
|
||||||
|
public static string TimeToString(TimeSpan timeSpan, bool getMilliseconds = true)
|
||||||
|
{
|
||||||
|
string firstString;
|
||||||
|
string lastString;
|
||||||
|
|
||||||
|
if (timeSpan.TotalHours >= 1)
|
||||||
|
{
|
||||||
|
firstString = timeSpan.Hours.ToString("D2");
|
||||||
|
lastString = timeSpan.Minutes.ToString("D2");
|
||||||
|
}
|
||||||
|
else if (timeSpan.TotalMinutes >= 1 || !getMilliseconds)
|
||||||
|
{
|
||||||
|
firstString = timeSpan.Minutes.ToString("D2");
|
||||||
|
// It's nicer to see a timer set at 5 seconds actually start at 00:05 instead of 00:04.
|
||||||
|
var seconds = timeSpan.Seconds + (timeSpan.Milliseconds > 500 ? 1 : 0);
|
||||||
|
lastString = seconds.ToString("D2");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstString = timeSpan.Seconds.ToString("D2");
|
||||||
|
var centiseconds = timeSpan.Milliseconds / 10;
|
||||||
|
lastString = centiseconds.ToString("D2");
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstString + ':' + lastString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Effects/text.rsi state string based on <paramref name="character"/>, or null if none available.
|
||||||
|
/// </summary>
|
||||||
|
public static string? GetStateFromChar(char? character)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// First checks if its one of our special characters
|
||||||
|
if (CharStatePairs.ContainsKey(character.Value))
|
||||||
|
return CharStatePairs[character.Value];
|
||||||
|
|
||||||
|
// Or else it checks if its a normal letter or digit
|
||||||
|
if (char.IsLetterOrDigit(character.Value))
|
||||||
|
return character.Value.ToString().ToLower();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Content.Client/TextScreen/TextScreenTimerComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Content.Client.TextScreen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is an active component for tracking <see cref="TextScreenVisualsComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class TextScreenTimerComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
66
Content.Client/TextScreen/TextScreenVisualsComponent.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using Content.Shared.TextScreen;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Client.TextScreen;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class TextScreenVisualsComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 1/32 - the size of a pixel
|
||||||
|
/// </summary>
|
||||||
|
public const float PixelSize = 1f / EyeManager.PixelsPerMeter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The color of the text drawn.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("color")]
|
||||||
|
public Color Color { get; set; } = Color.FloralWhite;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the screen is on.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("activated")]
|
||||||
|
public bool Activated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current mode of the screen - is it showing text, or currently counting?
|
||||||
|
/// </summary>
|
||||||
|
[DataField("currentMode")]
|
||||||
|
public TextScreenMode CurrentMode = TextScreenMode.Text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time it is counting to or from.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("targetTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan TargetTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset for drawing the text. <br/>
|
||||||
|
/// (0, 8) pixels is the default for the Structures\Wallmounts\textscreen.rsi
|
||||||
|
/// </summary>
|
||||||
|
[DataField("textOffset"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public Vector2 TextOffset = new(0f, 8f * PixelSize);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of characters this component can show.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("textLength")]
|
||||||
|
public int TextLength = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text the screen should show when it's not counting.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("text"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string Text = "";
|
||||||
|
|
||||||
|
public string TextToDraw = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The different layers for each character - this is the currently drawn states.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("layerStatesToDraw")]
|
||||||
|
public Dictionary<string, string?> LayerStatesToDraw = new();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ using Content.Shared.Doors.Systems;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Content.Shared.Wires;
|
using Content.Shared.Wires;
|
||||||
|
using Content.Server.MachineLinking.Events;
|
||||||
|
using Content.Server.MachineLinking.System;
|
||||||
|
|
||||||
namespace Content.Server.Doors.Systems
|
namespace Content.Server.Doors.Systems
|
||||||
{
|
{
|
||||||
@@ -15,12 +17,15 @@ namespace Content.Server.Doors.Systems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly WiresSystem _wiresSystem = default!;
|
[Dependency] private readonly WiresSystem _wiresSystem = default!;
|
||||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||||
|
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<AirlockComponent, ComponentInit>(OnAirlockInit);
|
SubscribeLocalEvent<AirlockComponent, ComponentInit>(OnAirlockInit);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||||
|
|
||||||
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
|
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||||
@@ -28,6 +33,7 @@ namespace Content.Server.Doors.Systems
|
|||||||
SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new [] {typeof(DoorSystem)});
|
SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new [] {typeof(DoorSystem)});
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorGetPryTimeModifierEvent>(OnGetPryMod);
|
SubscribeLocalEvent<AirlockComponent, DoorGetPryTimeModifierEvent>(OnGetPryMod);
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAirlockInit(EntityUid uid, AirlockComponent component, ComponentInit args)
|
private void OnAirlockInit(EntityUid uid, AirlockComponent component, ComponentInit args)
|
||||||
@@ -38,6 +44,14 @@ namespace Content.Server.Doors.Systems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSignalReceived(EntityUid uid, AirlockComponent component, SignalReceivedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Port == component.AutoClosePort)
|
||||||
|
{
|
||||||
|
component.AutoClose = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPowerChanged(EntityUid uid, AirlockComponent component, ref PowerChangedEvent args)
|
private void OnPowerChanged(EntityUid uid, AirlockComponent component, ref PowerChangedEvent args)
|
||||||
{
|
{
|
||||||
if (TryComp<AppearanceComponent>(uid, out var appearanceComponent))
|
if (TryComp<AppearanceComponent>(uid, out var appearanceComponent))
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Server.MachineLinking.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ActiveSignalTimerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time the timer triggers.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("triggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan TriggerTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Shared.MachineLinking;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.MachineLinking.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class SignalTimerComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("delay"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public double Delay = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This shows the Label: text box in the UI.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("canEditLabel"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool CanEditLabel = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The label, used for TextScreen visuals currently.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("label"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string Label = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The port that gets signaled when the timer triggers.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("triggerPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string TriggerPort = "Timer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The port that gets signaled when the timer starts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("startPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string StartPort = "Start";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If not null, this timer will play this sound when done.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("doneSound"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier? DoneSound;
|
||||||
|
}
|
||||||
162
Content.Server/MachineLinking/System/SignalTimerSystem.cs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Content.Server.MachineLinking.Components;
|
||||||
|
using Content.Shared.TextScreen;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Content.Shared.MachineLinking;
|
||||||
|
using Content.Server.UserInterface;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
using Content.Server.Interaction;
|
||||||
|
|
||||||
|
namespace Content.Server.MachineLinking.System;
|
||||||
|
|
||||||
|
public sealed class SignalTimerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||||
|
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||||
|
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SignalTimerComponent, ComponentInit>(OnInit);
|
||||||
|
SubscribeLocalEvent<SignalTimerComponent, AfterActivatableUIOpenEvent>(OnAfterActivatableUIOpen);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SignalTimerComponent, SignalTimerTextChangedMessage>(OnTextChangedMessage);
|
||||||
|
SubscribeLocalEvent<SignalTimerComponent, SignalTimerDelayChangedMessage>(OnDelayChangedMessage);
|
||||||
|
SubscribeLocalEvent<SignalTimerComponent, SignalTimerStartMessage>(OnTimerStartMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent component, AfterActivatableUIOpenEvent args)
|
||||||
|
{
|
||||||
|
var time = TryComp<ActiveSignalTimerComponent>(uid, out var active) ? active.TriggerTime : TimeSpan.Zero;
|
||||||
|
|
||||||
|
if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui))
|
||||||
|
{
|
||||||
|
_ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(component.Label,
|
||||||
|
TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
|
||||||
|
TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
|
||||||
|
component.CanEditLabel,
|
||||||
|
time,
|
||||||
|
active != null,
|
||||||
|
_accessReader.IsAllowed(args.User, uid)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Trigger(EntityUid uid, SignalTimerComponent signalTimer)
|
||||||
|
{
|
||||||
|
RemComp<ActiveSignalTimerComponent>(uid);
|
||||||
|
|
||||||
|
_signalSystem.InvokePort(uid, signalTimer.TriggerPort);
|
||||||
|
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Text);
|
||||||
|
|
||||||
|
if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui))
|
||||||
|
{
|
||||||
|
_ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(signalTimer.Label,
|
||||||
|
TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
|
||||||
|
TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
|
||||||
|
signalTimer.CanEditLabel,
|
||||||
|
TimeSpan.Zero,
|
||||||
|
false,
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
UpdateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTimer()
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<ActiveSignalTimerComponent, SignalTimerComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var active, out var timer))
|
||||||
|
{
|
||||||
|
if (active.TriggerTime > _gameTiming.CurTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Trigger(uid, timer);
|
||||||
|
|
||||||
|
if (timer.DoneSound == null)
|
||||||
|
continue;
|
||||||
|
_audio.PlayPvs(timer.DoneSound, uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a UI <paramref name="message"/> is allowed to be sent by the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The entity that is interacted with.</param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
private bool IsMessageValid(EntityUid uid, BoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
if (message.Session.AttachedEntity is not { Valid: true } mob)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_accessReader.IsAllowed(mob, uid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerTextChangedMessage args)
|
||||||
|
{
|
||||||
|
if (!IsMessageValid(uid, args))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Label = args.Text[..Math.Min(5,args.Text.Length)];
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDelayChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerDelayChangedMessage args)
|
||||||
|
{
|
||||||
|
if (!IsMessageValid(uid, args))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Delay = args.Delay.TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTimerStartMessage(EntityUid uid, SignalTimerComponent component, SignalTimerStartMessage args)
|
||||||
|
{
|
||||||
|
if (!IsMessageValid(uid, args))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryComp<AppearanceComponent>(uid, out var appearance);
|
||||||
|
|
||||||
|
if (!HasComp<ActiveSignalTimerComponent>(uid))
|
||||||
|
{
|
||||||
|
var activeTimer = EnsureComp<ActiveSignalTimerComponent>(uid);
|
||||||
|
activeTimer.TriggerTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Delay);
|
||||||
|
|
||||||
|
if (appearance != null)
|
||||||
|
{
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Timer, appearance);
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, activeTimer.TriggerTime, appearance);
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
_signalSystem.InvokePort(uid, component.StartPort);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemComp<ActiveSignalTimerComponent>(uid);
|
||||||
|
|
||||||
|
if (appearance != null)
|
||||||
|
{
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Text, appearance);
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label, appearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Shared.Doors.Systems;
|
using Content.Shared.Doors.Systems;
|
||||||
|
using Content.Shared.MachineLinking;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
namespace Content.Shared.Doors.Components;
|
namespace Content.Shared.Doors.Components;
|
||||||
|
|
||||||
@@ -82,6 +84,12 @@ public sealed class AirlockComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float AutoCloseDelayModifier = 1.0f;
|
public float AutoCloseDelayModifier = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The receiver port for turning off automatic closing.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("autoClosePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
|
||||||
|
public string AutoClosePort = "AutoClose";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
|
|||||||
68
Content.Shared/MachineLinking/SharedSignalTimerComponent.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.MachineLinking;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SignalTimerUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a SignalTimerComponent state that can be sent to the client
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SignalTimerBoundUserInterfaceState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public string CurrentText;
|
||||||
|
public string CurrentDelayMinutes;
|
||||||
|
public string CurrentDelaySeconds;
|
||||||
|
public bool ShowText;
|
||||||
|
public TimeSpan TriggerTime;
|
||||||
|
public bool TimerStarted;
|
||||||
|
public bool HasAccess;
|
||||||
|
|
||||||
|
public SignalTimerBoundUserInterfaceState(string currentText,
|
||||||
|
string currentDelayMinutes,
|
||||||
|
string currentDelaySeconds,
|
||||||
|
bool showText,
|
||||||
|
TimeSpan triggerTime,
|
||||||
|
bool timerStarted,
|
||||||
|
bool hasAccess)
|
||||||
|
{
|
||||||
|
CurrentText = currentText;
|
||||||
|
CurrentDelayMinutes = currentDelayMinutes;
|
||||||
|
CurrentDelaySeconds = currentDelaySeconds;
|
||||||
|
ShowText = showText;
|
||||||
|
TriggerTime = triggerTime;
|
||||||
|
TimerStarted = timerStarted;
|
||||||
|
HasAccess = hasAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SignalTimerTextChangedMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public string Text { get; }
|
||||||
|
|
||||||
|
public SignalTimerTextChangedMessage(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SignalTimerDelayChangedMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public TimeSpan Delay { get; }
|
||||||
|
public SignalTimerDelayChangedMessage(TimeSpan delay)
|
||||||
|
{
|
||||||
|
Delay = delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SignalTimerStartMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
35
Content.Shared/TextScreen/TextScreenVisuals.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.TextScreen;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum TextScreenVisuals : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Should this show any text? <br/>
|
||||||
|
/// Expects a <see cref="bool"/>.
|
||||||
|
/// </summary>
|
||||||
|
On,
|
||||||
|
/// <summary>
|
||||||
|
/// Is this a timer or a text-screen? <br/>
|
||||||
|
/// Expects a <see cref="TextScreenMode"/>.
|
||||||
|
/// </summary>
|
||||||
|
Mode,
|
||||||
|
/// <summary>
|
||||||
|
/// What text to show? <br/>
|
||||||
|
/// Expects a <see cref="string"/>.
|
||||||
|
/// </summary>
|
||||||
|
ScreenText,
|
||||||
|
/// <summary>
|
||||||
|
/// What is the target time? <br/>
|
||||||
|
/// Expects a <see cref="TimeSpan"/>.
|
||||||
|
/// </summary>
|
||||||
|
TargetTime
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum TextScreenMode : byte
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Timer
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
signal-timer-menu-title = Timer
|
||||||
|
signal-timer-menu-label = Label:
|
||||||
|
signal-timer-menu-delay = Delay:
|
||||||
|
signal-timer-menu-start = Start
|
||||||
@@ -46,6 +46,9 @@ signal-port-description-med-scanner-sender = Medical scanner signal sender
|
|||||||
signal-port-name-med-scanner-receiver = Medical scanner
|
signal-port-name-med-scanner-receiver = Medical scanner
|
||||||
signal-port-description-med-scanner-receiver = Medical scanner signal receiver
|
signal-port-description-med-scanner-receiver = Medical scanner signal receiver
|
||||||
|
|
||||||
|
signal-port-name-hold-open = Hold
|
||||||
|
signal-port-description-hold-open = Turns off automatic closing.
|
||||||
|
|
||||||
signal-port-name-artifact-analyzer-sender = Console
|
signal-port-name-artifact-analyzer-sender = Console
|
||||||
signal-port-description-artifact-analyzer-sender = Analysis console signal sender
|
signal-port-description-artifact-analyzer-sender = Analysis console signal sender
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,9 @@ signal-port-description-right = This port is invoked whenever the lever is moved
|
|||||||
|
|
||||||
signal-port-name-middle = Middle
|
signal-port-name-middle = Middle
|
||||||
signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position.
|
signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position.
|
||||||
|
|
||||||
|
signal-port-name-timer-trigger = Timer Trigger
|
||||||
|
signal-port-description-timer-trigger = This port is invoked whenever the timer triggers.
|
||||||
|
|
||||||
|
signal-port-name-timer-start = Timer Start
|
||||||
|
signal-port-description-timer-start = This port is invoked whenever the timer starts.
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
Open: []
|
Open: []
|
||||||
Close: []
|
Close: []
|
||||||
Toggle: []
|
Toggle: []
|
||||||
|
AutoClose: []
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
interfaces:
|
interfaces:
|
||||||
- key: enum.WiresUiKey.Key
|
- key: enum.WiresUiKey.Key
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
Open: []
|
Open: []
|
||||||
Close: []
|
Close: []
|
||||||
Toggle: []
|
Toggle: []
|
||||||
|
AutoClose: []
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Inorganic
|
damageContainer: Inorganic
|
||||||
damageModifierSet: Glass
|
damageModifierSet: Glass
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
- type: entity
|
||||||
|
id: SignalTimer
|
||||||
|
name: signal timer
|
||||||
|
description: It's a timer for sending timed signals to things.
|
||||||
|
placement:
|
||||||
|
mode: SnapgridCenter
|
||||||
|
snap:
|
||||||
|
- Wallmount
|
||||||
|
components:
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: WallMount
|
||||||
|
arc: 360
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Structures/Wallmounts/switch.rsi
|
||||||
|
netsync: false
|
||||||
|
state: on
|
||||||
|
- type: Appearance
|
||||||
|
- type: Rotatable
|
||||||
|
- type: Fixtures
|
||||||
|
- type: SignalTimer
|
||||||
|
canEditLabel: false
|
||||||
|
- type: SignalTransmitter
|
||||||
|
outputs:
|
||||||
|
Start: []
|
||||||
|
Timer: []
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.SignalTimerUiKey.Key
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SignalTimerUiKey.Key
|
||||||
|
type: SignalTimerBoundUserInterface
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
powerLoad: 100
|
||||||
|
- type: Electrified
|
||||||
|
enabled: false
|
||||||
|
usesApcPower: true
|
||||||
|
- type: ExtensionCableReceiver
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ScreenTimer
|
||||||
|
parent: SignalTimer
|
||||||
|
name: screen timer
|
||||||
|
description: It's a timer for sending timed signals to things, with a built-in screen.
|
||||||
|
components:
|
||||||
|
- type: SignalTimer
|
||||||
|
canEditLabel: true
|
||||||
|
- type: TextScreenVisuals
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Structures/Wallmounts/textscreen.rsi
|
||||||
|
state: textscreen
|
||||||
|
noRot: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BrigTimer
|
||||||
|
parent: ScreenTimer
|
||||||
|
name: brig timer
|
||||||
|
description: It's a timer for brig cells.
|
||||||
|
components:
|
||||||
|
- type: AccessReader
|
||||||
|
access: [["Security"]]
|
||||||
@@ -63,6 +63,11 @@
|
|||||||
name: signal-port-name-med-scanner-receiver
|
name: signal-port-name-med-scanner-receiver
|
||||||
description: signal-port-description-med-scanner-receiver
|
description: signal-port-description-med-scanner-receiver
|
||||||
|
|
||||||
|
- type: receiverPort
|
||||||
|
id: AutoClose
|
||||||
|
name: signal-port-name-hold-open
|
||||||
|
description: signal-port-description-hold-open
|
||||||
|
|
||||||
- type: receiverPort
|
- type: receiverPort
|
||||||
id: ArtifactAnalyzerReceiver
|
id: ArtifactAnalyzerReceiver
|
||||||
name: signal-port-name-artifact-analyzer-receiver
|
name: signal-port-name-artifact-analyzer-receiver
|
||||||
|
|||||||
@@ -50,6 +50,18 @@
|
|||||||
name: signal-port-name-med-scanner-sender
|
name: signal-port-name-med-scanner-sender
|
||||||
description: signal-port-description-med-scanner-sender
|
description: signal-port-description-med-scanner-sender
|
||||||
|
|
||||||
|
- type: transmitterPort
|
||||||
|
id: Timer
|
||||||
|
name: signal-port-name-timer-trigger
|
||||||
|
description: signal-port-description-timer-trigger
|
||||||
|
defaultLinks: [ AutoClose, On, Open, Forward, Trigger ]
|
||||||
|
|
||||||
|
- type: transmitterPort
|
||||||
|
id: Start
|
||||||
|
name: signal-port-name-timer-start
|
||||||
|
description: signal-port-description-timer-start
|
||||||
|
defaultLinks: [ Close, Off ]
|
||||||
|
|
||||||
- type: transmitterPort
|
- type: transmitterPort
|
||||||
id: ArtifactAnalyzerSender
|
id: ArtifactAnalyzerSender
|
||||||
name: signal-port-name-artifact-analyzer-sender
|
name: signal-port-name-artifact-analyzer-sender
|
||||||
|
|||||||
BIN
Resources/Textures/Effects/text.rsi/0.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Effects/text.rsi/1.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
Resources/Textures/Effects/text.rsi/2.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
Resources/Textures/Effects/text.rsi/3.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
Resources/Textures/Effects/text.rsi/4.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
Resources/Textures/Effects/text.rsi/5.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/6.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
Resources/Textures/Effects/text.rsi/7.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/8.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/9.png
Normal file
|
After Width: | Height: | Size: 143 B |
BIN
Resources/Textures/Effects/text.rsi/a.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Effects/text.rsi/b.png
Normal file
|
After Width: | Height: | Size: 143 B |
BIN
Resources/Textures/Effects/text.rsi/blank.png
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
Resources/Textures/Effects/text.rsi/c.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Effects/text.rsi/colon.png
Normal file
|
After Width: | Height: | Size: 129 B |
BIN
Resources/Textures/Effects/text.rsi/d.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
Resources/Textures/Effects/text.rsi/dash.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
Resources/Textures/Effects/text.rsi/e.png
Normal file
|
After Width: | Height: | Size: 134 B |
BIN
Resources/Textures/Effects/text.rsi/exclamation.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
Resources/Textures/Effects/text.rsi/f.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
Resources/Textures/Effects/text.rsi/g.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
Resources/Textures/Effects/text.rsi/h.png
Normal file
|
After Width: | Height: | Size: 145 B |
BIN
Resources/Textures/Effects/text.rsi/i.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/j.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
Resources/Textures/Effects/text.rsi/k.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Effects/text.rsi/l.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
Resources/Textures/Effects/text.rsi/m.png
Normal file
|
After Width: | Height: | Size: 144 B |
140
Resources/Textures/Effects/text.rsi/meta.json
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by rolfero (github) for Space Station 14",
|
||||||
|
"size": {
|
||||||
|
"x": 4,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "g"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "i"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "j"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "k"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "l"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "p"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "q"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "r"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "t"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "u"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "w"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exclamation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "question"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "star"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "colon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blank"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Effects/text.rsi/n.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/o.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
Resources/Textures/Effects/text.rsi/p.png
Normal file
|
After Width: | Height: | Size: 143 B |
BIN
Resources/Textures/Effects/text.rsi/plus.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
Resources/Textures/Effects/text.rsi/q.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
Resources/Textures/Effects/text.rsi/question.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
Resources/Textures/Effects/text.rsi/r.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Effects/text.rsi/s.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
Resources/Textures/Effects/text.rsi/star.png
Normal file
|
After Width: | Height: | Size: 131 B |
BIN
Resources/Textures/Effects/text.rsi/t.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
Resources/Textures/Effects/text.rsi/u.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
Resources/Textures/Effects/text.rsi/v.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/w.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
Resources/Textures/Effects/text.rsi/x.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Effects/text.rsi/y.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
Resources/Textures/Effects/text.rsi/z.png
Normal file
|
After Width: | Height: | Size: 138 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Made by brainfood1183 (Github) for Space Station 14",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "textscreen"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 870 B |