diff --git a/Content.Client/UserInterface/Controls/DialogWindow.xaml.cs b/Content.Client/UserInterface/Controls/DialogWindow.xaml.cs index 22b988b0055..0f99fe5bd95 100644 --- a/Content.Client/UserInterface/Controls/DialogWindow.xaml.cs +++ b/Content.Client/UserInterface/Controls/DialogWindow.xaml.cs @@ -36,7 +36,7 @@ public sealed partial class DialogWindow : FancyWindow private List<(string, Func)> _promptLines; - private delegate (Control, Func) ControlConstructor(Func verifier, string name, QuickDialogEntry entry, bool last, object? value);// are you feeling it now mr krabs? + private delegate (Control, Func) ControlConstructor(Func verifier, string name, QuickDialogEntry entry, bool last, object? value, object? info);// are you feeling it now mr krabs? /// /// Create and open a new dialog with some prompts. @@ -63,6 +63,7 @@ public DialogWindow(string title, List entries, bool ok = true { var entry = entries[i]; var value = entry.Value; + var info = entry.Info; var box = new BoxContainer(); box.AddChild(new Label() { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f }); @@ -72,7 +73,7 @@ public DialogWindow(string title, List entries, bool ok = true // walkthrough: // second item in this tuple is a verifier function for controls who have that option // third item is a backup name in case we have not been provided with one from the server - // first item is a function that takes the other two, the QuickDialogEntry for it, a bool of whether it's the last control in the window, the default value for the control + // first item is a function that takes the other two, the QuickDialogEntry for it, a bool of whether it's the last control in the window, the default value for the control, and additional info (which is used only for 1 or 2 controls) // and returns another tuple: // item 1 is the control itself // item 2 is a Func, a """generic""" function that returns whatever user has done with the control @@ -85,16 +86,17 @@ public DialogWindow(string title, List entries, bool ok = true QuickDialogEntryType.Hex16 => (SetupLineEditHex, VerifyHex16, "hex16"), QuickDialogEntryType.Boolean => (SetupCheckBox, null, "boolean"), QuickDialogEntryType.Void => (SetupVoid, null, "void"), + QuickDialogEntryType.OptionList => (SetupRadio, null, "radio"), _ => throw new ArgumentOutOfRangeException() }; var (setup, valid, name) = notapairanymore; // try use placeholder from the caller, fall back to the generic one for whatever type is being validated. - var (control, returner) = setup(valid!, entry.Placeholder ?? Loc.GetString($"quick-dialog-ui-{name}"), entry, i == entries.Count - 1, value); // ARE YOU FEELING IT NOW MR KRABS? - // yes, valid can be null - // yes, i am just going to ignore that - // go fuck yourself + var (control, returner) = setup(valid!, entry.Placeholder ?? Loc.GetString($"quick-dialog-ui-{name}"), entry, i == entries.Count - 1, value, info); // ARE YOU FEELING IT NOW MR KRABS? + // yes, valid can be null + // yes, i am just going to ignore that + // go fuck yourself _promptLines.Add((entry.FieldId, returner)); box.AddChild(control); @@ -165,7 +167,7 @@ private bool VerifyHex16(string input) - private (Control, Func) SetupLineEdit(Func valid, string name, QuickDialogEntry entry, bool last, object? value) // oh shit i'm feeling it + private (Control, Func) SetupLineEdit(Func valid, string name, QuickDialogEntry entry, bool last, object? value, object? _) // oh shit i'm feeling it { var edit = new LineEdit() { HorizontalExpand = true }; edit.IsValid += valid; @@ -180,15 +182,15 @@ private bool VerifyHex16(string input) return (edit, () => {return edit.Text;} ); } - private (Control, Func) SetupLineEditNumber(Func valid, string name, QuickDialogEntry entry, bool last, object? value) + private (Control, Func) SetupLineEditNumber(Func valid, string name, QuickDialogEntry entry, bool last, object? value, object? _) { - var (control, returner) = SetupLineEdit(valid, name, entry, last, value); + var (control, returner) = SetupLineEdit(valid, name, entry, last, value, _); var le = (LineEdit) control; return (control, ()=> le.Text.Length > 0 ? le.Text : "0"); // Otherwise you'll get kicked for malformed data } - private (Control, Func) SetupLineEditHex(Func valid, string name, QuickDialogEntry entry, bool last, object? value) + private (Control, Func) SetupLineEditHex(Func valid, string name, QuickDialogEntry entry, bool last, object? value, object? _) { - var (control, returner) = SetupLineEditNumber(valid, name, entry, last, value); + var (control, returner) = SetupLineEditNumber(valid, name, entry, last, value, _); var le = (LineEdit) control; if(value is int) { @@ -198,20 +200,32 @@ private bool VerifyHex16(string input) return (control, returner); } - private (Control, Func) SetupCheckBox(Func _, string name, QuickDialogEntry entry, bool last, object? value) + private (Control, Func) SetupCheckBox(Func _, string name, QuickDialogEntry entry, bool last, object? value, object? info) { var check = new CheckBox() { HorizontalExpand = true, HorizontalAlignment = HAlignment.Right }; - check.Text = name; + //check.Text = name; value ??= false; check.Pressed = (bool)value; return (check, () => { return check.Pressed ? "true" : "false"; }); } - private (Control, Func) SetupVoid(Func _, string __, QuickDialogEntry ___, bool ____, object? _____) + private (Control, Func) SetupVoid(Func _, string __, QuickDialogEntry ___, bool ____, object? _____, object? ______) { var control = new Control(); control.Visible = false; - return (control, () => "" ); + return (control, () => ""); + } + + private (Control, Func) SetupRadio(Func valid, string name, QuickDialogEntry entry, bool last, object? value, object? info) + { + var control = new RadioOptions(RadioOptionsLayout.Vertical); + string val = ((string?) value) ?? ""; + foreach(var option in (IEnumerable) info!) + { + control.AddItem(option, option, (args) => control.Select(args.Id)); + } + control.SelectByValue(val); + return (control, () => control.SelectedValue); } diff --git a/Content.Client/_White/MechComp/MechCompSystem.cs b/Content.Client/_White/MechComp/MechCompSystem.cs new file mode 100644 index 00000000000..1abc707c5bd --- /dev/null +++ b/Content.Client/_White/MechComp/MechCompSystem.cs @@ -0,0 +1,217 @@ +using Content.Shared._White.MechComp; +using DrawDepth = Content.Shared.DrawDepth.DrawDepth; +using Content.Shared.Interaction; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Content.Client.Salvage.FultonSystem; + +namespace Content.Client._White.MechComp; + +public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem +{ + //[Dependency] private readonly DeviceLinkSystem _link = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly AnimationPlayerSystem _anim = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + + Dictionary<(EntityUid, TimeSpan, RSI.StateId, string, object), Animation> _cachedAnims = new(); + /// + /// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die) + /// + [Obsolete("This was added in a fit of rage: may be removed later.")] + + + private bool GetMode(EntityUid uid, out T val) + { + return _appearance.TryGetData(uid, MechCompDeviceVisuals.Mode, out val); + } + private Animation _prepFlickAnim(string state, float durationSeconds, object layer) + { + return new Animation() + { + Length = TimeSpan.FromSeconds(durationSeconds), + AnimationTracks = + { + new AnimationTrackSpriteFlick() + { + LayerKey = layer, + KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(state, 0f) } + } + } + }; + } + + + public override void Initialize() + { + SubscribeLocalEvent(OnTeleportInit); + SubscribeLocalEvent(OnTeleportAppearanceChange); + + SubscribeLocalEvent(OnButtonInit); + SubscribeLocalEvent(OnButtonAppearanceChange); + + SubscribeLocalEvent(OnSpeakerInit); + SubscribeLocalEvent(OnSpeakerAppearanceChange); + + SubscribeLocalEvent(OnTranseiverInit); + SubscribeLocalEvent(OnTranseiverAppearanceChange); + } + private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) + { + comp.pressedAnimation = _prepFlickAnim("pressed", 0.5f, MechCompDeviceVisualLayers.Base); + } + + private void OnButtonAppearanceChange(EntityUid uid, MechCompButtonComponent comp, AppearanceChangeEvent args) + { + if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key. + { + _anim.SafePlay(uid, (Animation) comp.pressedAnimation, "button"); + } + } + + private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args) + { + comp.speakAnimation = _prepFlickAnim("speak", 0.6f, MechCompDeviceVisualLayers.Effect1); + } + + private void OnSpeakerAppearanceChange(EntityUid uid, MechCompSpeakerComponent comp, AppearanceChangeEvent args) + { + if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key. + { + _anim.SafePlay(uid, (Animation) comp.speakAnimation, "speaker"); + } + } + + private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args) + { + comp.firingAnimation = _prepFlickAnim("firing", 0.5f, MechCompDeviceVisualLayers.Effect2); + var sprite = Comp(uid); + sprite.LayerSetState(sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect1), "ready"); + } + + private void OnTeleportAppearanceChange(EntityUid uid, MechCompTeleportComponent comp, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + //_handleAnchored(args); + + var effectlayer1 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect1); // used for ready and charging states + //var effectlayer2 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect2); // used for firing animation; layer specified in animation itself + //var effectlayer3 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect3); // (will be) used for a glow effect + + if (GetMode(uid, out string mode)){ + switch (mode) + { + case "ready": + args.Sprite?.LayerSetState(effectlayer1, "ready"); + break; + case "firing": + args.Sprite?.LayerSetState(effectlayer1, "charging"); + _anim.SafePlay(uid, (Animation)comp.firingAnimation, "teleport"); + break; + case "charging": + args.Sprite?.LayerSetState(effectlayer1, "charging"); + break; + } + } + } + + private void OnTranseiverInit(EntityUid uid, MechCompTranseiverComponent comp, ComponentInit args) + { + comp.blinkAnimation = _prepFlickAnim("blink", 0.2f, MechCompDeviceVisualLayers.Effect1); + } + + private void OnTranseiverAppearanceChange(EntityUid uid, MechCompTranseiverComponent comp, ref AppearanceChangeEvent args) + { + if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key. + { + _anim.SafePlay(uid, (Animation) comp.blinkAnimation, "blink"); + } + } +} + + +/// +/// Kinda like a GenericVisualizerComponent, except preconfigured to work with mechcomp devices, but less customisable overall. +/// Also changes DrawDepth to FloorObjects when anchrored and back to SmallObjects when unanchored. +/// +[RegisterComponent] +[Access(typeof(MechCompAnchoredVisualizerSystem))] +public sealed partial class MechCompAnchoredVisualizerComponent : Component { + [DataField("disabled")] + public bool Disabled = false; // todo: figure out how to remove a component from prototye if it's defined in prototype's parent. And get rid of this shit afterwards. + //[DataField("layer")] + //public int Layer = (int)MechCompDeviceVisualLayers.Base; + [DataField("anchoredState")] + public string AnchoredState = "anchored"; + [DataField("unanchoredState")] + public string UnanchoredState = "icon"; + [DataField("anchoredDepth")] + public int AnchoredDepth = (int)DrawDepth.FloorObjects; // todo: figure out how to pass enums in prototypes + [DataField("unanchoredDepth")] + public int UnanchoredDepth = (int)DrawDepth.SmallObjects; + [DataField("hideShowEffectsLayer")] + public bool HideShowEffectsLayer = true; + +} +public sealed class MechCompAnchoredVisualizerSystem : VisualizerSystem +{ + protected override void OnAppearanceChange(EntityUid uid, MechCompAnchoredVisualizerComponent comp, ref AppearanceChangeEvent args) + { + var layerKey = MechCompDeviceVisualLayers.Base; + + if (comp.Disabled || args.Sprite == null) + return; + + //var layer = args.Sprite.LayerMapGet(layer); + + if (AppearanceSystem.TryGetData(uid, MechCompDeviceVisuals.Anchored, out bool anchored)) + { + args.Sprite.LayerSetState(layerKey, anchored ? comp.AnchoredState : comp.UnanchoredState); + args.Sprite.DrawDepth = (int) (anchored ? comp.AnchoredDepth : comp.UnanchoredDepth); + if (comp.HideShowEffectsLayer) + { + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect1), anchored); + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect2), anchored); + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect3), anchored); + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect4), anchored); + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect5), anchored); + args.Sprite.LayerSetVisible(args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect6), anchored); + + } + } + } +} + +/// +/// Move up from this namespace as needed +/// +public static class SafePlayExt +{ + /// + /// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die) + /// + [Obsolete("This was added in a fit of rage: may be removed later.")] + public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation animation, string key) + { + var component = IoCManager.Resolve().EnsureComponent(uid); + anim.Stop(component, key); + anim.Play(new Entity(uid, component), animation, key); + } + /// + /// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die) + /// + [Obsolete("This was added in a fit of rage: may be removed later.")] + public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key) + { + component ??= IoCManager.Resolve().EnsureComponent(uid); + anim.Stop(component, key); + anim.Play(new Entity(uid, component), animation, key); + } +} diff --git a/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs b/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs index 2cb63ffbf4d..ff96df51a7d 100644 --- a/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs +++ b/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs @@ -564,20 +564,22 @@ public void OpenDialog(ICommonSession s /// The action to execute upon the dialog being cancelled. /// /// Tuple structure for dialogEntries argument: - /// Type - int/float/string/LongString/Hex16/VoidOption/null (VoidOption) + /// Type - int/float/string/LongString/Hex16/VoidOption. Null will default to VoidOption. /// string - prompt text - /// object - default value. No checks are performed whether or not it matches the specified type. + /// object? - default value. No checks are performed whether or not it matches the specified type. + /// object? (optional) - additional info, used by some data types. Check for implementation in Content.Client.Administration.QuickDialogSystem. + /// /// [PublicAPI] - public void OpenDialog(ICommonSession session, string title, List<(Type, string, object)> dialogEntries, Action okAction, Action? cancelAction = null) + public void OpenDialog(ICommonSession session, string title, List dialogEntries, Action okAction, Action? cancelAction = null) { List _dialogEntries = new(); for(int i = 0; i < dialogEntries.Count; i++) { - var (type, prompt, defaultValue) = dialogEntries[i]; - _dialogEntries.Add(new QuickDialogEntry((i+1).ToString(), TypeToEntryType(type), prompt??" ", null, defaultValue)); + var (type, prompt, defaultValue, info) = dialogEntries[i]; + _dialogEntries.Add(new QuickDialogEntry((i+1).ToString(), TypeToEntryType(type), prompt??" ", null, defaultValue, info)); } // ^^^ these "indexes" start with 1, for some reason @@ -602,3 +604,5 @@ public void OpenDialog(ICommonSession session, string title, List<(Type, string, ); } } + + diff --git a/Content.Server/Administration/QuickDialogSystem.cs b/Content.Server/Administration/QuickDialogSystem.cs index 4b21f195886..d51544ad40c 100644 --- a/Content.Server/Administration/QuickDialogSystem.cs +++ b/Content.Server/Administration/QuickDialogSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; using Content.Shared.Administration; using Content.Shared.Chemistry; using Robust.Server.Player; @@ -117,8 +118,9 @@ private bool TryParseQuickDialogList(List entries, Dictionary< for(int i = 0; i < entries.Count; i++) { var entryType = entries[i].Type; - var input = responces[(i+1).ToString()]; //starts with "1" - if(!TryParseQuickDialog(entryType, input, out object? o)) + var info = entries[i].Info; + var input = responces[(i+1).ToString()]; // it starts with "1" + if(!TryParseQuickDialog(entryType, input, out object? o, info)) { return false; } @@ -127,7 +129,7 @@ private bool TryParseQuickDialogList(List entries, Dictionary< return true; } - private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input, [NotNullWhen(true)] out T? output) + private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input, [NotNullWhen(true)] out T? output, object? info = null) { switch (entryType) { @@ -163,9 +165,9 @@ private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input } //It's verrrry likely that this will be longstring - var longString = (LongString) input; + // var longString = (LongString) input; // see Hex16 case - output = (T?) (object?) longString; + output = (T?) (object?) input; return output is not null; } case QuickDialogEntryType.Boolean: @@ -185,10 +187,10 @@ private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input } case QuickDialogEntryType.Hex16: { - bool ret = int.TryParse(input, System.Globalization.NumberStyles.HexNumber, null, out var res) && input.Length <= 4 && input == input.ToUpper(); + bool ret = int.TryParse(input, System.Globalization.NumberStyles.HexNumber, null, out var result) && input.Length <= 4 && input == input.ToUpper(); if (ret) - output = (T?) (object?) (Hex16) res; - else + output = (T?) (object?) result; // with T as Hex16 this will result in retun value of Hex16. BUT if T is object, we will return int as an object! What a fucking clown world, and i have absolutely noone to blame for it except my own hubris. + else // If i do actually cast to it, like in Longstring, then i will not be able to blindly convert it to int because if Hex16 is under object variable, the implicit-explicit converters just fuck off, apparently. output = default; return ret; } @@ -197,12 +199,25 @@ private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input output = default; return input == ""; } + case QuickDialogEntryType.OptionList: + { + if (info != null && ((IEnumerable)info).Contains(input)) + { + output = (T) (object) input; + return true; + } + else + { + output = default; + return false; + } + } default: throw new ArgumentOutOfRangeException(nameof(entryType), entryType, null); } } - public QuickDialogEntryType TypeToEntryType(Type T) + public QuickDialogEntryType TypeToEntryType(Type? T) { // yandere station much? if (T == typeof(int) || T == typeof(uint) || T == typeof(long) || T == typeof(ulong)) @@ -223,6 +238,9 @@ public QuickDialogEntryType TypeToEntryType(Type T) if (T == typeof(bool)) return QuickDialogEntryType.Boolean; + if (T == typeof(List)) + return QuickDialogEntryType.OptionList; + if (T == typeof(VoidOption) || T == null) return QuickDialogEntryType.Void; @@ -260,10 +278,12 @@ public static implicit operator int(Hex16 hex16) { return hex16.number; } + public static explicit operator Hex16(int num) { return new(num); } + } /// diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs index 1394760a20d..1cd8c92662a 100644 --- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -21,6 +21,8 @@ using System.Linq; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.FixedPoint; +using Content.Server.DeviceLinking.Events; +using System.Text.RegularExpressions; namespace Content.Server.Chemistry.EntitySystems { @@ -54,6 +56,7 @@ public override void Initialize() SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnNewLink); + SubscribeLocalEvent(OnSignalReceived); SubscribeLocalEvent(OnPortDisconnected); SubscribeLocalEvent(OnAnchorChanged); // WD END @@ -69,6 +72,7 @@ public override void Initialize() private void OnInit(EntityUid uid, ReagentDispenserComponent component, ComponentInit args) { _signalSystem.EnsureSourcePorts(uid, ReagentDispenserComponent.ChemMasterPort); + _signalSystem.EnsureSinkPorts(uid, "MechCompChemDispenserInput"); } private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args) @@ -92,6 +96,19 @@ private void OnNewLink(EntityUid uid, ReagentDispenserComponent component, NewLi UpdateConnection(uid, args.Sink, component, master); } + private void OnSignalReceived(EntityUid uid, ReagentDispenserComponent component, ref SignalReceivedEvent args) + { + //if (args.Data != null && args.Data.TryGetValue("mechcomp_data", out string? signal)) + //{ + // string[] arr = signal!.Split('='); // expecting signal of format reagentname=volume + // if (arr.Length != 2) return; + // if (!GetInventory((uid, component)).Contains(arr[0])) return; + // if (!int.TryParse(arr[1], out var num) || num <= 0) return; + // // todo: how the fuck do i make it piss out potassium??? + //} + } + + private void OnPortDisconnected(EntityUid uid, ReagentDispenserComponent component, PortDisconnectedEvent args) { if (args.Port != ReagentDispenserComponent.ChemMasterPort) diff --git a/Content.Server/DeviceLinking/Events/DeviceLinkTryConnectingAttemptEvent.cs b/Content.Server/DeviceLinking/Events/DeviceLinkTryConnectingAttemptEvent.cs new file mode 100644 index 00000000000..29f5d69c186 --- /dev/null +++ b/Content.Server/DeviceLinking/Events/DeviceLinkTryConnectingAttemptEvent.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server.DeviceLinking.Events; +public sealed class DeviceLinkTryConnectingAttemptEvent : CancellableEntityEventArgs +{ + /// + /// The other entity the user tries to connect us to. + /// + public EntityUid? other; + public EntityUid User; + + //public string cancellationReason = "network-configurator-link-mode-cancelled-generic"; + public DeviceLinkTryConnectingAttemptEvent(EntityUid User, EntityUid? uidOther) + { + this.User = User; + other = uidOther; + } +} + diff --git a/Content.Server/DeviceLinking/NetworkPayloadHelper.cs b/Content.Server/DeviceLinking/NetworkPayloadHelper.cs new file mode 100644 index 00000000000..7152cdf9bcc --- /dev/null +++ b/Content.Server/DeviceLinking/NetworkPayloadHelper.cs @@ -0,0 +1,39 @@ +using Content.Server.DeviceLinking.Components; +using Content.Shared.DeviceNetwork; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server.DeviceLinking; + +/// +/// Helper methods for providing compatibility between different signal keys. +/// +public static class NetworkPayloadHelper +{ + public static bool TryGetState(this NetworkPayload payload, [NotNullWhen(true)] out SignalState value) + { + if (payload.TryGetValue("logic_state", out value)) // DeviceNetworkConstants is in Content.Server, which cannot be accessed from shared. Fuck you whoever designed this. + { + return true; // if a proper logic_state is present, return that. Hopefully noone does that, since it's probably going to confuse the players + } + //otherwise try using mechcomp signal + if (payload.TryGetValue("mechcomp_data", out string? sig)) // DeviceNetworkConstants is in Content.Server, which cannot be accessed from shared. Fuck you whoever designed this. + { + // this is, more or less, the same as it worked in 13 + if (int.TryParse(sig, out int signal_number)) + { + value = signal_number != 0 ? SignalState.High : SignalState.Low; + return true; + } + value = sig.Length > 0 ? SignalState.High : SignalState.Low; + return true; + } + // add any other snowflake-ish checks here as needed + value = SignalState.Momentary; + return false; + } +} diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs index f708237480f..417902dbb56 100644 --- a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs @@ -82,7 +82,7 @@ private void InvokeDirect(Entity source, Entity(source) || !TryComp(sink, out var sinkNetwork)) { - var eventArgs = new SignalReceivedEvent(sinkPort, source); + var eventArgs = new SignalReceivedEvent(sinkPort, source, data); RaiseLocalEvent(sink, ref eventArgs); return; } diff --git a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs index adb9e2c4d91..a11b5b6501d 100644 --- a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs @@ -37,7 +37,7 @@ private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent componen return; var state = SignalState.Momentary; - args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state); + args.Data?.TryGetState(out state); if (args.Port == component.OpenPort) diff --git a/Content.Server/DeviceLinking/Systems/EdgeDetectorSystem.cs b/Content.Server/DeviceLinking/Systems/EdgeDetectorSystem.cs index 10c8a1700b5..d247a8c56e7 100644 --- a/Content.Server/DeviceLinking/Systems/EdgeDetectorSystem.cs +++ b/Content.Server/DeviceLinking/Systems/EdgeDetectorSystem.cs @@ -27,7 +27,7 @@ private void OnSignalReceived(EntityUid uid, EdgeDetectorComponent comp, ref Sig // only handle signals with edges var state = SignalState.Momentary; if (args.Data == null || - !args.Data.TryGetValue(DeviceNetworkConstants.LogicState, out state) || + !args.Data.TryGetState(out state) || state == SignalState.Momentary) return; diff --git a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs index 60ab4712ff9..dfec461782f 100644 --- a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs +++ b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs @@ -98,7 +98,7 @@ private void OnSignalReceived(EntityUid uid, LogicGateComponent comp, ref Signal // default to momentary for compatibility with non-logic signals. // currently only door status and logic gates have logic signal state. var state = SignalState.Momentary; - args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state); + args.Data?.TryGetState(out state); // update the state for the correct port if (args.Port == comp.InputPortA) diff --git a/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs index 6cbad603b41..12517fa7140 100644 --- a/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs +++ b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs @@ -11,7 +11,7 @@ public static class DeviceNetworkConstants /// /// Used by logic gates to transmit the state of their ports /// - public const string LogicState = "logic_state"; + public const string LogicState = "logic_state"; //If you, for whatever reason, going to change this, do a ctrl+shift+F on the entire solution to make sure there aren't any other strings you should change. Like in Content.Shared.DeviceNetwork.NetworkPayload. #region Commands diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 9a038f1c78a..2a49da41515 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Administration.Logs; +using Content.Server.DeviceLinking.Events; using Content.Server.DeviceLinking.Systems; using Content.Server.DeviceNetwork.Components; using Content.Shared.Access.Components; @@ -162,6 +163,14 @@ private void TryLinkDevice(EntityUid uid, NetworkConfiguratorComponent configura return; } + var checkEvent = new DeviceLinkTryConnectingAttemptEvent(user, configurator.ActiveDeviceLink); + RaiseLocalEvent(target.Value, checkEvent); + if(configurator.ActiveDeviceLink != null) + RaiseLocalEvent(target.Value, checkEvent); + + if (checkEvent.Cancelled) + return; + if (configurator.ActiveDeviceLink.HasValue && (HasComp(target) && HasComp(configurator.ActiveDeviceLink) diff --git a/Content.Server/Entry/IgnoredComponents.cs b/Content.Server/Entry/IgnoredComponents.cs index fe073da7a49..99060a53f8e 100644 --- a/Content.Server/Entry/IgnoredComponents.cs +++ b/Content.Server/Entry/IgnoredComponents.cs @@ -20,6 +20,7 @@ public static class IgnoredComponents "LightFade", "HolidayRsiSwap", "OptionsVisualizer", + "MechCompAnchoredVisualizer" // Almost every day i discover a new thing about this game, and i cherish the days when i don't }; } } diff --git a/Content.Server/_White/MechComp/Devices/Button.cs b/Content.Server/_White/MechComp/Devices/Button.cs new file mode 100644 index 00000000000..165910943b9 --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Button.cs @@ -0,0 +1,50 @@ +using Content.Shared._White.MechComp; +using Content.Shared.Interaction; +using Robust.Shared.Audio; + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + private void InitButton() + { + SubscribeLocalEvent(OnButtonInit); + SubscribeLocalEvent(OnButtonConfigAttempt); + SubscribeLocalEvent(OnButtonConfigUpdate); + SubscribeLocalEvent(OnButtonHandInteract); + SubscribeLocalEvent(OnButtonActivation); + } + + private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) + { + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + } + + private void OnButtonConfigAttempt(EntityUid uid, MechCompButtonComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(string), "Сигнал на выходе", comp.outSignal)); + } + private void OnButtonConfigUpdate(EntityUid uid, MechCompButtonComponent comp, MechCompConfigUpdateEvent args) + { + comp.outSignal = (string) args.results[0]; + } + private void OnButtonHandInteract(EntityUid uid, MechCompButtonComponent comp, InteractHandEvent args) + { + ButtonClick(uid, comp); + } + private void OnButtonActivation(EntityUid uid, MechCompButtonComponent comp, ActivateInWorldEvent args) + { + ButtonClick(uid, comp); + } + private void ButtonClick(EntityUid uid, MechCompButtonComponent comp) + { + if (isAnchored(uid) && Cooldown(uid, "pressed", 1f)) + { + _audio.PlayPvs(comp.ClickSound, uid, AudioParams.Default.WithVariation(0.125f).WithVolume(8f)); + SendMechCompSignal(uid, "MechCompStandardOutput", comp.outSignal); + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); // the data will be discarded anyways + } + } + +} diff --git a/Content.Server/_White/MechComp/Devices/Calculator.cs b/Content.Server/_White/MechComp/Devices/Calculator.cs new file mode 100644 index 00000000000..68081822258 --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Calculator.cs @@ -0,0 +1,84 @@ +using Content.Server.DeviceLinking.Events; +using Content.Shared._White.MechComp; +using Robust.Shared.Utility; +using System.Linq; + + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + private Dictionary> _mathFuncs = new() + { + ["A+B"] = (a, b) => { return a + b; }, + ["A-B"] = (a, b) => { return a - b; }, + ["A*B"] = (a, b) => { return a * b; }, + ["A/B"] = (a, b) => { if (b == 0) return null; return a / b; }, + ["A^B"] = (a, b) => { return MathF.Pow(a, b); }, + ["A//B"] = (a, b) => { return (float) (int) (a / b); }, + ["A%B"] = (a, b) => { return a % b; }, + ["sin(A)^B"] = (a, b) => { return MathF.Pow(MathF.Sin(a), b); }, + ["cos(A)^B"] = (a, b) => { return MathF.Pow(MathF.Cos(a), b); } + }; + + private void InitCalculator() + { + SubscribeLocalEvent(OnMathInit); + SubscribeLocalEvent(OnMathConfigAttempt); + SubscribeLocalEvent(OnMathConfigUpdate); + SubscribeLocalEvent(OnMathSignal); + } + public void OnMathInit(EntityUid uid, MechCompMathComponent comp, ComponentInit args) + { + if (!_mathFuncs.ContainsKey(comp.mode)) + comp.mode = _mathFuncs.Keys.First(); + + _link.EnsureSinkPorts(uid, "MechCompNumericInputA", "MechCompNumericInputB", "Trigger"); + _link.EnsureSourcePorts(uid, "MechCompNumericOutput"); + } + + private void OnMathConfigAttempt(EntityUid uid, MechCompMathComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(List), "Операция", comp.mode, _mathFuncs.Keys)); + args.entries.Add((typeof(float), "Число A", comp.A)); + args.entries.Add((typeof(float), "Число B", comp.B)); + } + + private void OnMathConfigUpdate(EntityUid uid, MechCompMathComponent comp, MechCompConfigUpdateEvent args) + { + comp.mode = (string) args.results[0]; + comp.A = (float) args.results[1]; + comp.B = (float) args.results[2]; + } + + + public void OnMathSignal(EntityUid uid, MechCompMathComponent comp, ref SignalReceivedEvent args) + { + string sig; float num; // hurr durr + switch (args.Port) + { + case "MechCompNumericInputA": + if(TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + comp.A = num; + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + comp.B = num; + } + break; + case "Trigger": + float? result = _mathFuncs[comp.mode](comp.A, comp.B); + if (result != null) + { + SendMechCompSignal(uid, "MechCompNumericOutput", result.ToString()!); + } + break; + } + } + + +} diff --git a/Content.Server/_White/MechComp/Devices/Comparer.cs b/Content.Server/_White/MechComp/Devices/Comparer.cs new file mode 100644 index 00000000000..ba0aac7e129 --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Comparer.cs @@ -0,0 +1,104 @@ +using Content.Server.DeviceLinking.Events; +using Content.Shared._White.MechComp; +using System.Linq; + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + + private void InitComparer() + { + SubscribeLocalEvent(OnComparerInit); + SubscribeLocalEvent(OnComparerConfigAttempt); + SubscribeLocalEvent(OnComparerConfigUpdate); + SubscribeLocalEvent(OnComparerSignal); + } + + private Dictionary> _compareFuncs = new() + { + ["A==B"] = (a, b) => { return a == b; }, + ["A!=B"] = (a, b) => { return a != b; }, + ["A>B"] = (a, b) => { if (float.TryParse(a, out var numA) && float.TryParse(b, out var numB)) return numA > numB; else return null; }, + ["A { if (float.TryParse(a, out var numA) && float.TryParse(b, out var numB)) return numA < numB; else return null; }, + ["A>=B"] = (a, b) => { if (float.TryParse(a, out var numA) && float.TryParse(b, out var numB)) return numA >= numB; else return null; }, + ["A<=B"] = (a, b) => { if (float.TryParse(a, out var numA) && float.TryParse(b, out var numB)) return numA <= numB; else return null; }, + }; + public void OnComparerInit(EntityUid uid, MechCompComparerComponent comp, ComponentInit args) + { + //EnsureConfig(uid).Build( + //("valueA", (typeof(string), "Значение A", "0")), + //("valueB", (typeof(string), "Значение B", "0")), + //("outputTrue", (typeof(string), "Значение на выходе в случае истины", "1")), + //("outputFalse", (typeof(string), "Значение на выходе в случае лжи", "1")), + // + //("mode", (typeof(string), "Режим", _compareFuncs.Keys.First(), _compareFuncs.Keys)), + //("_", (null, "Режимы сравнения >, <, >=, <=")), // todo: check if newlines work + //("__", (null, "работают только с числовыми значениями.")) + //); + if (!_compareFuncs.ContainsKey(comp.mode)) + comp.mode = _compareFuncs.Keys.First(); + _link.EnsureSinkPorts(uid, "MechCompInputA", "MechCompInputB"); + _link.EnsureSourcePorts(uid, "MechCompLogicOutputTrue", "MechCompLogicOutputFalse"); + + + } + + private void OnComparerConfigAttempt(EntityUid uid, MechCompComparerComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(float), "Число A", comp.A)); + args.entries.Add((typeof(float), "Число B", comp.B)); + args.entries.Add((typeof(string), "Значение на выходе в случае истины", comp.outputTrue)); + args.entries.Add((typeof(string), "Значение на выходе в случае лжи", comp.outputFalse)); + args.entries.Add((typeof(List), "Операция", comp.mode, _compareFuncs.Keys)); + args.entries.Add((null, "Режимы сравнения >, <, >=, <=")); // todo: check if newlines work + args.entries.Add((null, "работают только с числовыми значениями.")); + } + + private void OnComparerConfigUpdate(EntityUid uid, MechCompComparerComponent comp, MechCompConfigUpdateEvent args) + { + + comp.A = (string) args.results[0]; + comp.B = (string) args.results[1]; + comp.outputTrue = (string) args.results[2]; + comp.outputFalse = (string) args.results[3]; + comp.mode = (string) args.results[4]; + } + + public void OnComparerSignal(EntityUid uid, MechCompComparerComponent comp, ref SignalReceivedEvent args) + { + string sig; + switch (args.Port) + { + case "MechCompNumericInputA": + if (TryGetMechCompSignal(args.Data, out sig)) + { + comp.A = sig; + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig)) + { + comp.B = sig; + } + break; + case "Trigger": + bool? result = _compareFuncs[comp.mode](comp.A, comp.B); + switch (result) + { + case true: + SendMechCompSignal(uid, "MechCompLogicOutputTrue", comp.outputTrue); + break; + case false: + SendMechCompSignal(uid, "MechCompLogicOutputFalse", comp.outputFalse); + break; + case null: + break; + + } + break; + } + } + +} diff --git a/Content.Server/_White/MechComp/Devices/PressurePad.cs b/Content.Server/_White/MechComp/Devices/PressurePad.cs new file mode 100644 index 00000000000..8b3e990d3da --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/PressurePad.cs @@ -0,0 +1,54 @@ +using Content.Shared._White.MechComp; +using Content.Shared.Item; +using Content.Shared.Mobs.Components; +using Content.Shared.StepTrigger.Systems; + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + private void InitPressurePad() + { + SubscribeLocalEvent(OnPressurePadInit); + SubscribeLocalEvent(OnPressurePadConfigAttempt); + SubscribeLocalEvent(OnPressurePadConfigUpdate); + SubscribeLocalEvent(OnPressurePadStepAttempt); + SubscribeLocalEvent(OnPressurePadStep); + } + + public void OnPressurePadInit(EntityUid uid, MechCompPressurePadComponent comp, ComponentInit args) + { + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + } + public void OnPressurePadConfigAttempt(EntityUid uid, MechCompPressurePadComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(bool), "Реагировать на существ", comp.reactToMobs)); + args.entries.Add((typeof(bool), "Реагировать на предметы", comp.reactToItems)); + } + public void OnPressurePadConfigUpdate(EntityUid uid, MechCompPressurePadComponent comp, MechCompConfigUpdateEvent args) + { + comp.reactToMobs = (bool) args.results[0]; + comp.reactToItems = (bool) args.results[1]; + } + + private void OnPressurePadStepAttempt(EntityUid uid, MechCompPressurePadComponent component, ref StepTriggerAttemptEvent args) + { + args.Continue = true; + } + + public void OnPressurePadStep(EntityUid uid, MechCompPressurePadComponent comp, ref StepTriggeredEvent args) + { + if (HasComp(args.Tripper) && comp.reactToMobs) + { + SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); + return; + } + if (HasComp(args.Tripper) && comp.reactToItems) + { + SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); + return; + } + } + +} diff --git a/Content.Server/_White/MechComp/Devices/Speaker.cs b/Content.Server/_White/MechComp/Devices/Speaker.cs new file mode 100644 index 00000000000..d8c17cc6ada --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Speaker.cs @@ -0,0 +1,64 @@ +using Content.Server._White.TTS; +using Content.Server.Chat.Systems; +//using Content.Shared.Administration; +using Content.Server.DeviceLinking.Events; +using Content.Server.VoiceMask; +using Content.Shared._White.MechComp; + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + private void InitSpeaker() + { + SubscribeLocalEvent(OnSpeakerInit); + SubscribeLocalEvent(OnSpeakerConfigAttempt); + SubscribeLocalEvent(OnSpeakerConfigUpdate); + SubscribeLocalEvent(OnSpeakerSignal); + SubscribeLocalEvent(OnSpeakerVoiceTransform); + } + + + private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args) + { + if (comp.name == "") + comp.name = Name(uid); + _link.EnsureSinkPorts(uid, "MechCompStandardInput"); + } + + private void OnSpeakerConfigAttempt(EntityUid uid, MechCompSpeakerComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(bool), "Голосить в радио (;)", comp.inRadio)); + args.entries.Add((typeof(string), "Имя", comp.name)); + } + private void OnSpeakerConfigUpdate(EntityUid uid, MechCompSpeakerComponent comp, MechCompConfigUpdateEvent args) + { + comp.inRadio = (bool) args.results[0]; + comp.name = (string) args.results[1]; + } + private void OnSpeakerSignal(EntityUid uid, MechCompSpeakerComponent comp, ref SignalReceivedEvent args) + { + if (isAnchored(uid) && TryGetMechCompSignal(args.Data, out string msg)) + { + msg = msg.ToUpper(); + + if (comp.inRadio && Cooldown(uid, "speech", 5f)) // higher cooldown if we're speaking in radio + { + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: comp.name); + _radio.SendRadioMessage(uid, msg, "Common", uid); + } + else if (Cooldown(uid, "speech", 1f)) + { + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: comp.name); + } + } + } + + private void OnSpeakerVoiceTransform(EntityUid uid, MechCompSpeakerComponent comp, TransformSpeakerVoiceEvent args) + { + args.VoiceId = comp.name; + } +} diff --git a/Content.Server/_White/MechComp/Devices/Teleporter.cs b/Content.Server/_White/MechComp/Devices/Teleporter.cs new file mode 100644 index 00000000000..d5cfe53057c --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Teleporter.cs @@ -0,0 +1,105 @@ +using Content.Server.Administration; +using Content.Server.DeviceLinking.Events; +using Content.Shared._White.MechComp; +using Content.Shared.Maps; + + +namespace Content.Server._White.MechComp; + +public sealed partial class MechCompDeviceSystem +{ + private void InitTeleport() + { + SubscribeLocalEvent(OnTeleportInit); + SubscribeLocalEvent(OnTeleportConfigAttempt); + SubscribeLocalEvent(OnTeleportConfigUpdate); + SubscribeLocalEvent(OnTeleportSignal); + } + + + private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args) + { + if (comp.teleId < 0 || comp.teleId > 65535) + comp.teleId = _rng.Next(65536); + _link.EnsureSinkPorts(uid, "MechCompTeleIDInput"); + } + + private void OnTeleportConfigAttempt(EntityUid uid, MechCompTeleportComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(Hex16), "ID этого телепорта", comp.teleId)); + args.entries.Add((null, "Установите ID на 0000, чтобы отключить приём.")); + } + private void OnTeleportConfigUpdate(EntityUid uid, MechCompTeleportComponent comp, MechCompConfigUpdateEvent args) + { + comp.teleId = (int) args.results[0]; + } + + private void OnTeleportSignal(EntityUid uid, MechCompTeleportComponent comp, ref SignalReceivedEvent args) + { + if (IsOnCooldown(uid, "teleport")) + { + //_audio.PlayPvs("/Audio/White/MechComp/generic_energy_dryfire.ogg", uid); + return; + } + if (!TryGetMechCompSignal(args.Data, out string _sig) || + !int.TryParse(_sig, System.Globalization.NumberStyles.HexNumber, null, out int targetId) || + targetId == 0) + { + return; + } + + + var xform = Comp(uid); + TransformComponent? target = null; + if (!TryComp(uid, out var telexform)) return; + foreach (var (othercomp, otherbase, otherxform) in EntityQuery()) + { + var otherUid = othercomp.Owner; + var distance = (_xform.GetWorldPosition(uid) - _xform.GetWorldPosition(otherUid)).Length(); + if (otherxform.Anchored && targetId == othercomp.teleId) + { + if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance && xform.MapID == otherxform.MapID) // huh + { + target = otherxform; + break; + } + } + } + if (target == null) + { + _audio.PlayPvs("/Audio/White/MechComp/generic_energy_dryfire.ogg", uid); + Cooldown(uid, "teleport", 0.7f); + return; + } + + var targetUid = target.Owner; + _appearance.SetData(uid, MechCompDeviceVisuals.Mode, "firing"); + _appearance.SetData(targetUid, MechCompDeviceVisuals.Mode, "charging"); + + // because the target tele has a cooldown of a second, it can be used to quickly move + // back and make the original tele and reset it's cooldown down to a second. + // i decided it would be fun to abuse, and thus, it will be left as is + // if it turns out to be not fun, add check that newCooldown > currentCooldown + ForceCooldown(uid, "teleport", 7f, () => { _appearance.SetData(uid, MechCompDeviceVisuals.Mode, "ready"); }); + ForceCooldown(targetUid, "teleport", 1f, () => { _appearance.SetData(target.Owner, MechCompDeviceVisuals.Mode, "ready"); }); + + Spawn("EffectSparks", Transform(uid).Coordinates); + Spawn("EffectSparks", Transform(targetUid).Coordinates); + _audio.PlayPvs("/Audio/White/MechComp/emitter2.ogg", uid); + _audio.PlayPvs("/Audio/White/MechComp/emitter2.ogg", targetUid); + // var sol = new Solution(); + // sol.AddReagent("Water", 500f); // hue hue hue + // _smoke.StartSmoke(uid, sol, 6f, 1); + // sol = new Solution(); + // sol.AddReagent("Water", 500f); // hue hue hue + // _smoke.StartSmoke(uid, sol, 6f, 1); + + foreach (EntityUid u in TurfHelpers.GetEntitiesInTile(telexform.Coordinates, LookupFlags.Uncontained)) + { + if (TryComp(u, out var uxform) && !uxform.Anchored) + { + _xform.SetCoordinates(u, target.Coordinates); + } + } + } +} diff --git a/Content.Server/_White/MechComp/Devices/Transeiver.cs b/Content.Server/_White/MechComp/Devices/Transeiver.cs new file mode 100644 index 00000000000..f4513f2b1ae --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Transeiver.cs @@ -0,0 +1,64 @@ +using Content.Server.Administration; +using Content.Server.DeviceLinking.Events; +using Content.Shared._White.MechComp; +using Content.Shared.DeviceNetwork; +using Content.Shared.Interaction; +using Robust.Shared.Audio; + + +namespace Content.Server._White.MechComp; + +public readonly record struct MechCompWirelessTransmissionReceivedEvent(int targetId, NetworkPayload? Data); + +public sealed partial class MechCompDeviceSystem +{ + private void InitTranseiver() + { + SubscribeLocalEvent(OnTranseiverInit); + SubscribeLocalEvent(OnTranseiverConfigAttempt); + SubscribeLocalEvent(OnTranseiverConfigUpdate); + SubscribeLocalEvent(OnTranseiverSignalReceived); + SubscribeLocalEvent(OnTranseiverTransmissionReceived); + } + + private void OnTranseiverInit(EntityUid uid, MechCompTranseiverComponent comp, ComponentInit args) + { + if (comp.thisId < 0 || comp.thisId > 65535) + comp.thisId = _rng.Next(65536); + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + _link.EnsureSinkPorts(uid, "MechCompStandardInput"); + } + + private void OnTranseiverConfigAttempt(EntityUid uid, MechCompTranseiverComponent comp, MechCompConfigAttemptEvent args) + { + args.entries.Add((typeof(Hex16), "Код этого передатчика", comp.thisId)); + args.entries.Add((typeof(Hex16), "Код целевого передатчика", comp.targetId)); + } + private void OnTranseiverConfigUpdate(EntityUid uid, MechCompTranseiverComponent comp, MechCompConfigUpdateEvent args) + { + comp.thisId = (int) args.results[0]; + comp.targetId = (int) args.results[1]; + } + private void OnTranseiverSignalReceived(EntityUid uid, MechCompTranseiverComponent comp, ref SignalReceivedEvent args) + { + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + foreach (var (othercomp, otherxform) in EntityQuery()) + { + if(othercomp.thisId == comp.targetId && otherxform.Anchored) + { + var otherUid = othercomp.Owner; + //RaiseLocalEvent(new MechCompWirelessTransmissionReceivedEvent(comp.targetId, args.Data)); + ForceSetData(otherUid, MechCompDeviceVisuals.Mode, "activated"); + _link.InvokePort(otherUid, "MechCompStandardOutput", args.Data); + } + } + } + private void OnTranseiverTransmissionReceived(EntityUid uid, MechCompTranseiverComponent comp, ref MechCompWirelessTransmissionReceivedEvent args) + { + if (args.targetId == comp.thisId) + { + _link.InvokePort(uid, "MechCompStandardOutput", args.Data); + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + } + } +} diff --git a/Content.Server/_White/MechComp/DisconnectOnUnanchor.cs b/Content.Server/_White/MechComp/DisconnectOnUnanchor.cs new file mode 100644 index 00000000000..aefc86500c6 --- /dev/null +++ b/Content.Server/_White/MechComp/DisconnectOnUnanchor.cs @@ -0,0 +1,33 @@ +using Content.Server.DeviceLinking.Systems; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server._White.MechComp; + + +//[RegisterComponent] +//public sealed partial class DisconnectOnUnanchorComponent : Component +//{ +//} +// +// +//public sealed partial class DisconnectOnUnanchorSystem : EntitySystem +//{ +// [Dependency] private readonly DeviceLinkSystem _link = default!; +// private void OnUnanchor(EntityUid uid, DisconnectOnUnanchorComponent component, AnchorStateChangedEvent args) +// { +// if (!args.Anchored) +// { +// _link.RemoveAllFromSink(uid); +// _link.RemoveAllFromSource(uid); +// } +// } +// public override void Initialize() +// { +// SubscribeLocalEvent(OnUnanchor); +// +// } +//} diff --git a/Content.Server/_White/MechComp/MechCompDeviceSystem.cs b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs new file mode 100644 index 00000000000..4aca5c07af3 --- /dev/null +++ b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Server.Administration; +using Content.Server.Chat.Systems; +using Content.Server.DeviceLinking.Events; +using Content.Server.DeviceLinking.Systems; +using Content.Server.Fluids.EntitySystems; +using Content.Server.Radio.EntitySystems; +using Content.Server.VoiceMask; +using Content.Shared._White.MechComp; +using Content.Shared.Administration; +using Content.Shared.Chemistry.Components; +using Content.Shared.Construction.Components; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking.Events; +using Content.Shared.DeviceNetwork; +using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.Maps; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Content.Shared.StepTrigger.Systems; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + + +namespace Content.Server._White.MechComp; + +public sealed class MechCompConfigAttemptEvent : EntityEventArgs +{ + public List entries = new(); +} +public sealed class MechCompConfigUpdateEvent : EntityEventArgs +{ + public object[] results; + public MechCompConfigUpdateEvent(object[] results) { this.results = results; } +} + +public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem +{ + [Dependency] private readonly DeviceLinkSystem _link = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly QuickDialogSystem _dialog = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly SmokeSystem _smoke = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _rng = default!; + + Dictionary<(EntityUid, string), (TimeSpan, Action?)> _timeSpans = new(); + + public override void Initialize() + { + SubscribeLocalEvent>(GetInteractionVerb); // todo: currently BaseMechCompComponent handles config and + SubscribeLocalEvent(OnAnchorStateChanged); // unanchoring stuff. Functional mechcomp components + SubscribeLocalEvent(OnConnectUIAttempt); // still process SignalReceivedEvents directly. Perhaps + SubscribeLocalEvent(OnConnectAttempt); // I should make some MechCompSignalReceived event and + // have them process that? + InitButton(); + InitSpeaker(); + InitTeleport(); + InitComparer(); + InitCalculator(); + InitPressurePad(); + InitTranseiver(); + } + + #region Helper functions + + private bool isAnchored(EntityUid uid) // perhaps i'm reinventing the wheel here + { + return TryComp(uid, out var comp) && comp.Anchored; + } + private void OpenMechCompConfigDialog(EntityUid deviceUid, EntityUid playerUid, BaseMechCompComponent comp) + { + if(!Exists(deviceUid) || !Exists(playerUid)) + { + return; + } + if(!_playerManager.TryGetSessionByEntity(playerUid, out var player)) + { + return; + } + + var ev = new MechCompConfigAttemptEvent(); + RaiseLocalEvent(deviceUid, ev); + var entries = ev.entries; + _dialog.OpenDialog( + player, + Name(deviceUid) + " configuration", + entries, + (results) => { + //config.SetFromObjectArray(results); + RaiseLocalEvent(deviceUid, new MechCompConfigUpdateEvent(results)); + // The mechcompconfig holds the relevant config values, while the device components + // hold the "default" values, which immediately get written into the config and then never used again + // Other options i have involve redesigning this whole swamp and moving part of config handling to events, + // to be handled by each device component separately. + // + // All of this to say, this is fucking retarded. + // That's what i get for overengineering. + } + ); + } + + #region cooldown shite + /// + /// Convenience function for managing cooldowns for all devices. + /// + /// True if the cooldown period hasn't passed yet; false otherwise + private bool IsOnCooldown(EntityUid uid, string key) + { + return !Cooldown(uid, key, null); + } + /// + /// Convenience function for managing cooldowns for all mechcomp devices. If the previously set cooldown did not expire, does not set a new one and returns false. + /// + /// True if managed to set cooldown; false otherwise + private bool Cooldown(EntityUid uid, string key, float seconds, Action? callback = null) + { + return Cooldown(uid, key, TimeSpan.FromSeconds(seconds), callback); + } + /// + /// Convenience function for managing cooldowns for all mechcomp devices. If the previously set cooldown did not expire, does not set a new one and returns false. + /// + /// True if was able to set cooldown; false otherwise. If timespan is null, acts the same except won't actually set any cooldown. + private bool Cooldown(EntityUid uid, string key, TimeSpan? timespan, Action? callback = null) + { + var tuple = (uid, key); + if (!_timeSpans.TryGetValue(tuple, out var entry)) // || _timing.CurTime > entry.Item1) + { + if(timespan != null) + { + _timeSpans[tuple] = (_timing.CurTime + timespan.Value, callback); + } + return true; + } + return false; + } + /// + /// Sets a cooldown regardless of whether or not it has passed yet. + /// + /// EntityUid for which we set the cooldown + /// Cooldown key. Multiple different cooldowns with different keys. can be set on the same EntityUid. + /// + /// Delegate to execute after the cooldown expires. + /// If overwriting a cooldown with a new one, will run it's delegate if true. + private void ForceCooldown(EntityUid uid, string key, float seconds, Action? callback = null, bool fastForwardLastCD = false) + { + ForceCooldown(uid, key, TimeSpan.FromSeconds(seconds), callback, fastForwardLastCD); + } + private void ForceCooldown(EntityUid uid, string key, TimeSpan timespan, Action? callback = null, bool fastForwardLastCD = false) + { + var tuple = (uid, key); + if (fastForwardLastCD && _timeSpans.TryGetValue(tuple, out var entry) && entry.Item2 != null) + { + entry.Item2(); + } + _timeSpans[tuple] = (_timing.CurTime + timespan, callback); + } + /// + /// Cancel cooldown. + /// + /// EntityUid for which we cancel the cooldown + /// Cooldown key. + /// Run the delegate if the cooldown has one. + /// True if the cooldown existed and was removed; false otherwise. + private bool CancelCooldown(EntityUid uid, string key, bool fastForward) + { + var tuple = (uid, key); + if (fastForward && _timeSpans.TryGetValue(tuple, out var entry) && entry.Item2 != null) + { + entry.Item2(); + } + return _timeSpans.Remove(tuple); + } + #endregion + #region device signals + private void SendMechCompSignal(EntityUid uid, string port, string signal, DeviceLinkSourceComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return; + + var data = new NetworkPayload + { + ["mechcomp_data"] = signal + }; + //var data = new NetworkPayload(); + //data.Add("mechcomp_data", signal); + _link.InvokePort(uid, port, data, comp); + } + private bool TryGetMechCompSignal(NetworkPayload? packet, out string signal) + { + //Logger.Debug($"TryGetMechCompSignal called ({packet?.ToString()}) ({packet != null}) ({packet?.TryGetValue("mechcomp_data", out string? shit)})"); + + if (packet != null && packet.TryGetValue("mechcomp_data", out string? sig)) + { + signal = sig; + return true; + } + else + { + signal = ""; + return false; + } + } + #endregion + /// + /// , but it forces + /// the update by first setting the key value to a placeholder, and then to actual value. Used by stuff that hijacks + /// the appearance system to send messages on when they're supposed to play animations n' shiet. (buttons, speakers) + /// + private void ForceSetData(EntityUid uid, Enum key, object value, AppearanceComponent? component = null) + { + object placeholder = 0xA55B1A57; + if (value == placeholder) // what the fuck are you doing? + placeholder = 0x5318008; + _appearance.SetData(uid, key, placeholder, component); + _appearance.SetData(uid, key, value, component); + + } + #endregion + + + public override void Update(float frameTime) + { + List<(EntityUid, string)> keysToRemove = new(); + foreach(var kv in _timeSpans) + { + var key = kv.Key; + var value = kv.Value; + if(value.Item1 <= _timing.CurTime) + { + if(value.Item2 != null) { value.Item2(); } + keysToRemove.Add(key); + } + } + foreach(var key in keysToRemove) + { + _timeSpans.Remove(key); + } + } + + //private void OnButtonUnanchor(EntityUid uid, BaseMechCompComponent comp, AnchorStateChangedEvent args) + //{ + // throw new NotImplementedException(); + //} + // These are shit names, i know. + // This one is fired when opening network configurator's linking UI, and will be cancelled + private void OnConnectUIAttempt(EntityUid uid, BaseMechCompComponent comp, DeviceLinkTryConnectingAttemptEvent args) + { + if (!isAnchored(uid)) + { + _popup.PopupEntity(Loc.GetString("network-configurator-link-mode-cancelled-mechcomp-unanchored"), uid, args.User); + args.Cancel(); + } + } + // These are shit names, i know. + // This one is fired when 2 components are about to be connected, and will be cancelled if this component is unanchored. + private void OnConnectAttempt(EntityUid uid, BaseMechCompComponent comp, LinkAttemptEvent args) + { + if (!isAnchored(uid)) + { + if(args.User != null) + _popup.PopupEntity(Loc.GetString("network-configurator-link-mode-cancelled-mechcomp-unanchored"), uid, args.User.Value); + args.Cancel(); + } + } + + private void OnAnchorStateChanged(EntityUid uid, BaseMechCompComponent comp, AnchorStateChangedEvent args) + { + if (!args.Anchored) + { + if(HasComp(uid)) + _link.RemoveAllFromSink(uid); + if (HasComp(uid)) + _link.RemoveAllFromSource(uid); + } + _appearance.SetData(uid, MechCompDeviceVisuals.Anchored, args.Anchored); + } + + // todo: move this to client to get rid of annoying "waiting for server" shite + private void GetInteractionVerb(EntityUid uid, BaseMechCompComponent comp, GetVerbsEvent args) + { + if (!HasComp(args.Using) || !comp.hasConfig) + { + return; + } + + args.Verbs.Add(new InteractionVerb() + { + Text = Loc.GetString("mechcomp-configure-device-verb-text"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), // placeholder + Act = () => { OpenMechCompConfigDialog(uid, args.User, comp); } + }); + } +} + diff --git a/Content.Shared/Administration/QDEntry.cs b/Content.Shared/Administration/QDEntry.cs new file mode 100644 index 00000000000..c3b37f42816 --- /dev/null +++ b/Content.Shared/Administration/QDEntry.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared.Administration; + + +/// +/// DO NOT fucking pass to Value if your type is not or typeof(void) or typeof(VoidOption). You have been warned. +/// +public class QDEntry +{ + /// + /// Option type for quick dialog system. typeof(bool) will show a checkmark, + /// typeof(string) will show a text field, typeof(int) will show a number field, etc. + /// + /// Passing null here is the same as passing typeof(VoidOption) and is used to show + /// a text label without any corresponding dialog options. + /// + /// See TypeToEntryType() in QuickDialogSystem for accepted types. + /// + public Type? type { get; private set; } + /// + /// Option description. It's the text label that is shown to the left of the option. + /// + public string description { get; private set; } + /// + /// Current value of the option. It's only expected to be null if the type is null or typeof(VoidOption). + /// In other cases it will lead to errors. + /// + public object? Value { get; set; } + + /// + /// Any additional arguments for the dialog. Currently only used for typeof(List) for available options. + /// + public object? info { get; private set; } + + public QDEntry(Type? type, string description, object? Value = null, object? info = null) + { + this.type = type; + this.description = description; + this.Value = Value; + this.info = info; + } + + public static implicit operator QDEntry((Type type, string desc, object? Value, object? info) tuple) + { + return new QDEntry(tuple.type, tuple.desc, tuple.Value, tuple.info); + } + public static implicit operator QDEntry((Type type, string desc, object? Value) tuple) + { + return new QDEntry(tuple.type, tuple.desc, tuple.Value); + } + public static implicit operator QDEntry((Type? type, string desc) tuple) // nullable type only here because it marks a bare label with no control + { // if you're making a label in a quickdialog, you don't need to specify a "default value" as it's never used. + return new QDEntry(tuple.type, tuple.desc); + } + // deconstruct is used to get relevant info for QuickDialogSystem. deleg is only used in mechcompdevicesystem, so it's not returned here. + public void Deconstruct(out Type? type, out string description, out object? Value, out object? info) + { + type = this.type; + description = this.description; + Value = this.Value; + info = this.info; + } +} diff --git a/Content.Shared/Administration/QuickDialogOpenEvent.cs b/Content.Shared/Administration/QuickDialogOpenEvent.cs index 656d14fea88..87cfff88b24 100644 --- a/Content.Shared/Administration/QuickDialogOpenEvent.cs +++ b/Content.Shared/Administration/QuickDialogOpenEvent.cs @@ -93,17 +93,22 @@ public sealed class QuickDialogEntry /// public object? Value; /// + /// Additional info for creating more complex controls. + /// + public object? Info; + /// /// String to replace the type-specific placeholder with. /// public string? Placeholder; - public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt, string? placeholder = null, object? defaultValue = null) + public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt, string? placeholder = null, object? defaultValue = null, object? info = null) { FieldId = fieldId; Type = type; Prompt = prompt; Placeholder = placeholder; Value = defaultValue; + Info = info; } } @@ -147,6 +152,10 @@ public enum QuickDialogEntryType /// Boolean, /// + /// List of options. Only supported in non-generic OpenDialog(). + /// + OptionList, + /// /// No control will be shown, only the prompt label. /// Void diff --git a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs index 2ac525d154d..4bcc1e9bb2a 100644 --- a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs +++ b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs @@ -362,6 +362,20 @@ public void RemoveAllFromSink(EntityUid sinkUid, DeviceLinkSinkComponent? sinkCo RemoveSinkFromSource(sourceUid, sinkUid, null, sinkComponent); } } + /// + /// Removes every link from the given source + /// + public void RemoveAllFromSource(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null) + { + if (!Resolve(sourceUid, ref sourceComponent)) + return; + + foreach (var sinkUid in sourceComponent.LinkedPorts.Keys) // fuck you + { + if (TryComp(sinkUid, out var sinkComponent)) + RemoveSinkFromSource(sourceUid, sinkUid, null, sinkComponent); + } + } /// /// Removes all links between a source and a sink diff --git a/Content.Shared/_White/MechComp/MechComp.cs b/Content.Shared/_White/MechComp/MechComp.cs new file mode 100644 index 00000000000..621aa4ab533 --- /dev/null +++ b/Content.Shared/_White/MechComp/MechComp.cs @@ -0,0 +1,128 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Shared.Administration; + +namespace Content.Shared._White.MechComp; +public abstract class SharedMechCompDeviceSystem : EntitySystem +{ +} + +[RegisterComponent] +public partial class BaseMechCompComponent : Component +{ + [DataField] + public bool hasConfig = true; +} + + + + + +[RegisterComponent] +public sealed partial class MechCompButtonComponent : Component +{ + [DataField("clickSound")] + public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg"); + public object pressedAnimation = default!; + + [DataField] + public string outSignal = "1"; +} +[RegisterComponent] +public sealed partial class MechCompSpeakerComponent : Component +{ + public object speakAnimation = default!; + + [DataField] + public bool inRadio = false; + [DataField] + public string name = ""; +} + +[RegisterComponent] +public sealed partial class MechCompTeleportComponent : Component +{ + public object firingAnimation = default!; // i genuinely cannot believe people over at wizden think this is okay + public object glowAnimation = default!; + + [DataField] + public int teleId = -1; + + [DataField("maxDistance", serverOnly: true)] + public float MaxDistance = 25f; +} + +[Serializable, NetSerializable] +public enum MechCompDeviceVisualLayers : byte +{ + Base = 0, // base sprite, changed by anchoring visualiser + Effect1, + Effect2, + Effect3, + Effect4, + Effect5, + Effect6 +} +[Serializable, NetSerializable] +public enum MechCompDeviceVisuals : byte +{ + Mode = 0, + Anchored +} + + +[RegisterComponent] +public sealed partial class MechCompMathComponent : Component +{ + [DataField] + public string mode = "A+B"; // keep in sync with the list of available ops in serverside system + [DataField] + public float A = 0; + [DataField] + public float B = 0; +} + + +[RegisterComponent] +public sealed partial class MechCompPressurePadComponent : Component +{ + [DataField] + public bool reactToMobs = true; + [DataField] + public bool reactToItems = false; + +} + +[RegisterComponent] +public sealed partial class MechCompComparerComponent : Component +{ + [DataField] + public string mode = "A==B"; // keep in sync with the list of available ops in serverside system + [DataField] + public string A = "0"; + [DataField] + public string B = "0"; + [DataField] + public string outputTrue = "1"; + [DataField] + public string outputFalse = "1"; +} + +[RegisterComponent] +public sealed partial class MechCompTranseiverComponent : Component +{ + public object blinkAnimation = default!; + [DataField] + public int thisId = -1; + [DataField] + public int targetId = 0; +} diff --git a/Resources/Audio/White/MechComp/emitter2.ogg b/Resources/Audio/White/MechComp/emitter2.ogg new file mode 100644 index 00000000000..72b6a1c5725 Binary files /dev/null and b/Resources/Audio/White/MechComp/emitter2.ogg differ diff --git a/Resources/Audio/White/MechComp/generic_energy_dryfire.ogg b/Resources/Audio/White/MechComp/generic_energy_dryfire.ogg new file mode 100644 index 00000000000..e92df55ce01 Binary files /dev/null and b/Resources/Audio/White/MechComp/generic_energy_dryfire.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Devices/MechComp/generic_ports.yml b/Resources/Prototypes/Entities/Objects/Devices/MechComp/generic_ports.yml new file mode 100644 index 00000000000..667241a666a --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/MechComp/generic_ports.yml @@ -0,0 +1,277 @@ +#For when you only have one input/output +- type: sinkPort + id: MechCompInput + name: mechcomp-generic-input + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompOutput + name: mechcomp-generic-output + description: mechcomp-generic-output-port-description + + + +#For when you're about to get funky +- type: sinkPort + id: MechCompInputA + name: mechcomp-generic-input-A + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputB + name: mechcomp-generic-input-B + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputC + name: mechcomp-generic-input-C + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputD + name: mechcomp-generic-input-D + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputE + name: mechcomp-generic-input-E + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputF + name: mechcomp-generic-input-F + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputG + name: mechcomp-generic-input-G + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputH + name: mechcomp-generic-input-H + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputI + name: mechcomp-generic-input-I + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputJ + name: mechcomp-generic-input-J + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputK + name: mechcomp-generic-input-K + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputL + name: mechcomp-generic-input-L + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputM + name: mechcomp-generic-input-M + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputN + name: mechcomp-generic-input-N + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputO + name: mechcomp-generic-input-O + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputP + name: mechcomp-generic-input-P + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputQ + name: mechcomp-generic-input-Q + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputR + name: mechcomp-generic-input-R + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputS + name: mechcomp-generic-input-S + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputT + name: mechcomp-generic-input-T + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputU + name: mechcomp-generic-input-U + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputV + name: mechcomp-generic-input-V + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputW + name: mechcomp-generic-input-W + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputX + name: mechcomp-generic-input-X + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputY + name: mechcomp-generic-input-Y + description: mechcomp-generic-input-port-description + +- type: sinkPort + id: MechCompInputZ + name: mechcomp-generic-input-Z + description: mechcomp-generic-input-port-description + + + + + +- type: sinkPort + id: MechCompOutputA + name: mechcomp-generic-output-A + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputB + name: mechcomp-generic-output-B + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputC + name: mechcomp-generic-output-C + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputD + name: mechcomp-generic-output-D + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputE + name: mechcomp-generic-output-E + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputF + name: mechcomp-generic-output-F + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputG + name: mechcomp-generic-output-G + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputH + name: mechcomp-generic-output-H + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputI + name: mechcomp-generic-output-I + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputJ + name: mechcomp-generic-output-J + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputK + name: mechcomp-generic-output-K + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputL + name: mechcomp-generic-output-L + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputM + name: mechcomp-generic-output-M + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputN + name: mechcomp-generic-output-N + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputO + name: mechcomp-generic-output-O + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputP + name: mechcomp-generic-output-P + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputQ + name: mechcomp-generic-output-Q + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputR + name: mechcomp-generic-output-R + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputS + name: mechcomp-generic-output-S + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputT + name: mechcomp-generic-output-T + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputU + name: mechcomp-generic-output-U + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputV + name: mechcomp-generic-output-V + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputW + name: mechcomp-generic-output-W + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputX + name: mechcomp-generic-output-X + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputY + name: mechcomp-generic-output-Y + description: mechcomp-generic-output-port-description + +- type: sinkPort + id: MechCompOutputZ + name: mechcomp-generic-output-Z + description: mechcomp-generic-output-port-description diff --git a/Resources/Prototypes/Entities/Objects/Devices/MechComp/ports.yml b/Resources/Prototypes/Entities/Objects/Devices/MechComp/ports.yml new file mode 100644 index 00000000000..9e4aedbaf79 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/MechComp/ports.yml @@ -0,0 +1,51 @@ +- type: sourcePort + id: MechCompStandardOutput + name: mechcomp-standart-output-port + description: mechcomp-standart-output-port-description + defaultLinks: [ MechcompStandardInput ] + +- type: sourcePort + id: MechCompNumericOutput + name: mechcomp-numeric-output-port + description: mechcomp-numeric-output-port-description + +- type: sourcePort + id: MechCompChemLogicOutputTrue + name: mechcomp-logic-output-true + description: mechcomp-logic-output-port-description + +- type: sourcePort + id: MechCompChemLogicOutputFalse + name: mechcomp-logic-output-false + description: mechcomp-logic-output-port-description + + + +- type: sinkPort + id: MechCompStandardInput + name: mechcomp-standart-input-port + description: mechcomp-standart-input-port-description + +- type: sinkPort + id: MechCompTeleIDInput + name: mechcomp-teleid-input-port + description: mechcomp-teleid-input-port-description + +- type: sinkPort + id: MechCompNumericInputA + name: mechcomp-numeric-input-A + description: mechcomp-numeric-input-port-description + +- type: sinkPort + id: MechCompNumericInputB + name: mechcomp-numeric-input-B + description: mechcomp-numeric-input-port-description + +- type: sinkPort + id: MechCompChemDispenserInput + name: mechcomp-chem-dispenser-input + description: mechcomp-chem-dispenser-input-port-description + + + + diff --git a/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml new file mode 100644 index 00000000000..171c5753bfc --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml @@ -0,0 +1,167 @@ +#- type: entity +# name: MechComp Connection Utility Mechanism +# parent: BaseItem +# id: MechCompTool +# description: Used to connect MechComp devices together. +# components: +# - type: MechCompTool +# - type: EmitSoundOnPickup +# sound: +# path: /Audio/White/Items/handling/multitool_pickup.ogg +# - type: EmitSoundOnDrop +# sound: +# path: /Audio/Items/multitool_drop.ogg +# - type: EmitSoundOnLand +# sound: +# path: /Audio/Items/multitool_drop.ogg +# - type: Sprite +# sprite: White/MechComp/mechcomptool.rsi +# layers: +# - state: icon +# - state: green-unlit +# shader: unshaded +# - type: Item +# size: Small +# quickEquip: false +# slots: +# - Belt + + + +- type: entity + name: MechComp Component + parent: BaseItem + id: MechCompBase + description: You aren't sure what it actually does. Only one way to find out! + abstract: true + components: + - type: Appearance + - type: BaseMechComp + - type: MechCompAnchoredVisualizer + - type: Anchorable + snap: false + - type: Item + size: Large + - type: DeviceLinkSource + range: 7 + - type: Sprite + sprite: White/MechComp/base.rsi + layers: + - state: icon + map: [ "enum.MechCompDeviceVisualLayers.Base" ] + - map: [ "enum.MechCompDeviceVisualLayers.Effect1" ] + shader: unshaded + visible: false + - map: [ "enum.MechCompDeviceVisualLayers.Effect2" ] + shader: unshaded + visible: false + - map: [ "enum.MechCompDeviceVisualLayers.Effect3" ] + shader: unshaded + visible: false + - map: [ "enum.MechCompDeviceVisualLayers.Effect4" ] + shader: unshaded + visible: false + - map: [ "enum.MechCompDeviceVisualLayers.Effect5" ] + shader: unshaded + visible: false + - map: [ "enum.MechCompDeviceVisualLayers.Effect6" ] + shader: unshaded + visible: false + +- type: entity + name: MechComp Button + id: MechCompButton + parent: MechCompBase + description: A comically large button, for use with MechComp devices. + components: + - type: MechCompButton + - type: MechCompAnchoredVisualizer + anchoredState: icon + - type: Sprite + sprite: White/MechComp/button.rsi + + +- type: entity + name: MechComp Speaker + id: MechCompSpeaker + parent: MechCompBase + description: An oversized speaker with a voice synthesizer. + components: + - type: MechCompSpeaker + - type: Speech + - type: Sprite + sprite: White/MechComp/speaker.rsi + + +- type: entity + name: MechComp Teleport + id: MechCompTeleport + parent: MechCompBase + description: A teleport, able to move everything on it instantaneously to another teleport! + components: + - type: MechCompTeleport + - type: Sprite + sprite: White/MechComp/teleport.rsi + + +- type: entity + name: MechComp Calculator + id: MechCompMath + parent: MechCompBase + description: A general-purspose component, able to perform a number of operations on two values. + components: + - type: MechCompMath + - type: Sprite + sprite: White/MechComp/math.rsi + +- type: entity + name: MechComp Pressure Pad + id: MechCompPressurePad + parent: MechCompBase + description: Sends a signal when threaded on. + components: + - type: MechCompPressurePad + - type: Sprite + sprite: White/MechComp/pressurepad.rsi + - type: StepTrigger + intersectRatio: 0.2 + - type: CollisionWake + enabled: false + - type: Physics + bodyType: Dynamic + - type: Fixtures + fixtures: + slips: + shape: + !type:PhysShapeAabb + bounds: "-0.4,-0.3,0.4,0.3" + layer: + - SlipLayer + hard: false + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.4,-0.3,0.4,0.3" + density: 10 + mask: + - ItemMask + +- type: entity + name: MechComp Comparer + id: MechCompComparer + parent: MechCompBase + description: Compares signals. Has a very judgemental aura about it. + components: + - type: MechCompComparer + - type: Sprite + sprite: White/MechComp/comparer.rsi + +- type: entity + name: MechComp Transeiver + id: MechCompTranseiver + parent: MechCompBase + description: A self-sufficient transeiver. Utilizes *legally-distinct* Cyanspace™®© technology to instantly transmit data packets over great distances. + components: + - type: MechCompTranseiver + - type: Sprite + sprite: White/MechComp/transeiver.rsi \ No newline at end of file diff --git a/Resources/Textures/White/MechComp/base.rsi/icon.png b/Resources/Textures/White/MechComp/base.rsi/icon.png new file mode 100644 index 00000000000..ab0d1408684 Binary files /dev/null and b/Resources/Textures/White/MechComp/base.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/base.rsi/meta.json b/Resources/Textures/White/MechComp/base.rsi/meta.json new file mode 100644 index 00000000000..510453bfa02 --- /dev/null +++ b/Resources/Textures/White/MechComp/base.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] +} diff --git a/Resources/Textures/White/MechComp/button.rsi/icon.png b/Resources/Textures/White/MechComp/button.rsi/icon.png new file mode 100644 index 00000000000..1cad170635b Binary files /dev/null and b/Resources/Textures/White/MechComp/button.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/button.rsi/meta.json b/Resources/Textures/White/MechComp/button.rsi/meta.json new file mode 100644 index 00000000000..f1abd452b3c --- /dev/null +++ b/Resources/Textures/White/MechComp/button.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "pressed", + "delays": [ + [ + 0.3, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/White/MechComp/button.rsi/piss.png b/Resources/Textures/White/MechComp/button.rsi/piss.png new file mode 100644 index 00000000000..6575d1ba6b6 Binary files /dev/null and b/Resources/Textures/White/MechComp/button.rsi/piss.png differ diff --git a/Resources/Textures/White/MechComp/button.rsi/pressed.png b/Resources/Textures/White/MechComp/button.rsi/pressed.png new file mode 100644 index 00000000000..563011ff154 Binary files /dev/null and b/Resources/Textures/White/MechComp/button.rsi/pressed.png differ diff --git a/Resources/Textures/White/MechComp/comparer.rsi/anchored.png b/Resources/Textures/White/MechComp/comparer.rsi/anchored.png new file mode 100644 index 00000000000..783a48ed42c Binary files /dev/null and b/Resources/Textures/White/MechComp/comparer.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/comparer.rsi/icon.png b/Resources/Textures/White/MechComp/comparer.rsi/icon.png new file mode 100644 index 00000000000..cd105f51aed Binary files /dev/null and b/Resources/Textures/White/MechComp/comparer.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/comparer.rsi/meta.json b/Resources/Textures/White/MechComp/comparer.rsi/meta.json new file mode 100644 index 00000000000..233fdeba7e2 --- /dev/null +++ b/Resources/Textures/White/MechComp/comparer.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + } + ] +} diff --git a/Resources/Textures/White/MechComp/math.rsi/anchored.png b/Resources/Textures/White/MechComp/math.rsi/anchored.png new file mode 100644 index 00000000000..7b0e30adaba Binary files /dev/null and b/Resources/Textures/White/MechComp/math.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/math.rsi/icon.png b/Resources/Textures/White/MechComp/math.rsi/icon.png new file mode 100644 index 00000000000..b582a6ab4ea Binary files /dev/null and b/Resources/Textures/White/MechComp/math.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/math.rsi/meta.json b/Resources/Textures/White/MechComp/math.rsi/meta.json new file mode 100644 index 00000000000..233fdeba7e2 --- /dev/null +++ b/Resources/Textures/White/MechComp/math.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + } + ] +} diff --git a/Resources/Textures/White/MechComp/mechcomptool.rsi/icon.png b/Resources/Textures/White/MechComp/mechcomptool.rsi/icon.png new file mode 100644 index 00000000000..de09f5600d1 Binary files /dev/null and b/Resources/Textures/White/MechComp/mechcomptool.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-left.png b/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-left.png new file mode 100644 index 00000000000..6ea2c1414ae Binary files /dev/null and b/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-left.png differ diff --git a/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-right.png b/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-right.png new file mode 100644 index 00000000000..aa62319792a Binary files /dev/null and b/Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-right.png differ diff --git a/Resources/Textures/White/MechComp/mechcomptool.rsi/meta.json b/Resources/Textures/White/MechComp/mechcomptool.rsi/meta.json new file mode 100644 index 00000000000..baa79b76d75 --- /dev/null +++ b/Resources/Textures/White/MechComp/mechcomptool.rsi/meta.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "open", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/White/MechComp/mechcomptool.rsi/open.png b/Resources/Textures/White/MechComp/mechcomptool.rsi/open.png new file mode 100644 index 00000000000..f31615f4f80 Binary files /dev/null and b/Resources/Textures/White/MechComp/mechcomptool.rsi/open.png differ diff --git a/Resources/Textures/White/MechComp/pressurepad.rsi/anchored.png b/Resources/Textures/White/MechComp/pressurepad.rsi/anchored.png new file mode 100644 index 00000000000..e78e77f6179 Binary files /dev/null and b/Resources/Textures/White/MechComp/pressurepad.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/pressurepad.rsi/icon.png b/Resources/Textures/White/MechComp/pressurepad.rsi/icon.png new file mode 100644 index 00000000000..2fa0db43abe Binary files /dev/null and b/Resources/Textures/White/MechComp/pressurepad.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/pressurepad.rsi/meta.json b/Resources/Textures/White/MechComp/pressurepad.rsi/meta.json new file mode 100644 index 00000000000..233fdeba7e2 --- /dev/null +++ b/Resources/Textures/White/MechComp/pressurepad.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + } + ] +} diff --git a/Resources/Textures/White/MechComp/speaker.rsi/anchored.png b/Resources/Textures/White/MechComp/speaker.rsi/anchored.png new file mode 100644 index 00000000000..4686984b64b Binary files /dev/null and b/Resources/Textures/White/MechComp/speaker.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/speaker.rsi/icon.png b/Resources/Textures/White/MechComp/speaker.rsi/icon.png new file mode 100644 index 00000000000..86269351861 Binary files /dev/null and b/Resources/Textures/White/MechComp/speaker.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/speaker.rsi/meta.json b/Resources/Textures/White/MechComp/speaker.rsi/meta.json new file mode 100644 index 00000000000..34164c13ff4 --- /dev/null +++ b/Resources/Textures/White/MechComp/speaker.rsi/meta.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + }, + { + "name": "speak", + "delays":[ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/White/MechComp/speaker.rsi/speak.png b/Resources/Textures/White/MechComp/speaker.rsi/speak.png new file mode 100644 index 00000000000..a9f99de2b8f Binary files /dev/null and b/Resources/Textures/White/MechComp/speaker.rsi/speak.png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/anchored.png b/Resources/Textures/White/MechComp/teleport.rsi/anchored.png new file mode 100644 index 00000000000..8ba0830f36a Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/charging.png b/Resources/Textures/White/MechComp/teleport.rsi/charging.png new file mode 100644 index 00000000000..98b2d778c1d Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/charging.png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/firing.png b/Resources/Textures/White/MechComp/teleport.rsi/firing.png new file mode 100644 index 00000000000..1e7e3793bb9 Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/firing.png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/glow(unused).png b/Resources/Textures/White/MechComp/teleport.rsi/glow(unused).png new file mode 100644 index 00000000000..c74649f6a49 Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/glow(unused).png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/icon.png b/Resources/Textures/White/MechComp/teleport.rsi/icon.png new file mode 100644 index 00000000000..668b7b8d02e Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/teleport.rsi/meta.json b/Resources/Textures/White/MechComp/teleport.rsi/meta.json new file mode 100644 index 00000000000..79e8487e82b --- /dev/null +++ b/Resources/Textures/White/MechComp/teleport.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + }, + { + "name": "ready" + } + , + { + "name": "firing", + "delays":[ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + + }, + { + "name": "charging", + "delays":[ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/White/MechComp/teleport.rsi/ready.png b/Resources/Textures/White/MechComp/teleport.rsi/ready.png new file mode 100644 index 00000000000..517a8fdd93c Binary files /dev/null and b/Resources/Textures/White/MechComp/teleport.rsi/ready.png differ diff --git a/Resources/Textures/White/MechComp/transeiver.rsi/anchored.png b/Resources/Textures/White/MechComp/transeiver.rsi/anchored.png new file mode 100644 index 00000000000..7b11c5cf61f Binary files /dev/null and b/Resources/Textures/White/MechComp/transeiver.rsi/anchored.png differ diff --git a/Resources/Textures/White/MechComp/transeiver.rsi/blink.png b/Resources/Textures/White/MechComp/transeiver.rsi/blink.png new file mode 100644 index 00000000000..b600b943772 Binary files /dev/null and b/Resources/Textures/White/MechComp/transeiver.rsi/blink.png differ diff --git a/Resources/Textures/White/MechComp/transeiver.rsi/icon.png b/Resources/Textures/White/MechComp/transeiver.rsi/icon.png new file mode 100644 index 00000000000..ecb2096a55d Binary files /dev/null and b/Resources/Textures/White/MechComp/transeiver.rsi/icon.png differ diff --git a/Resources/Textures/White/MechComp/transeiver.rsi/meta.json b/Resources/Textures/White/MechComp/transeiver.rsi/meta.json new file mode 100644 index 00000000000..b8dd6d3919d --- /dev/null +++ b/Resources/Textures/White/MechComp/transeiver.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "license": "contact 88005553535 for details", + "copyright": "White Dream", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "anchored" + }, + { + "name": "blink", + "delays":[ + [ + 0.1, + 0.1 + ] + ] + } + + + ] +}