From 70c9116be94c19133c013d7e8f07edfd1b5b6759 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Fri, 1 Mar 2024 03:01:14 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=BE=D0=BE=D0=BE=D0=BE=D0=BE,=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B2=D0=B8=D0=BC=20=D0=BD=D0=B0=20=D0=B7=D0=B5?= =?UTF-8?q?=D1=80=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 0381fac1c7bee550f799e198ac1ab8a8cca27183 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Fri, 15 Mar 2024 19:42:11 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D1=8D=D1=82=D0=BE=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=85=D0=BE=D0=B4,=20=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BD=D0=B0=20=D0=B7=D0=B5=D1=80=D0=BE=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D0=B3=D0=B4=D0=B0=20=D0=B2=D0=B5=D0=B7=D0=BB=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/DialogWindow.xaml.cs | 44 +- .../_White/MechComp/MechCompSystem.cs | 223 +++++++ .../QuickDialogSystem.OpenDialog.cs | 14 +- .../Administration/QuickDialogSystem.cs | 38 +- .../EntitySystems/ReagentDispenserSystem.cs | 17 + .../DeviceLinkTryConnectingAttemptEvent.cs | 23 + .../DeviceLinking/NetworkPayloadHelper.cs | 39 ++ .../Systems/DoorSignalControlSystem.cs | 2 +- .../Systems/EdgeDetectorSystem.cs | 2 +- .../DeviceLinking/Systems/LogicGateSystem.cs | 2 +- .../DeviceNetwork/DeviceNetworkConstants.cs | 2 +- .../Systems/NetworkConfiguratorSystem.cs | 9 + Content.Server/Entry/IgnoredComponents.cs | 1 + .../_White/MechComp/DisconnectOnUnanchor.cs | 33 + .../_White/MechComp/MechCompDeviceSystem.cs | 612 ++++++++++++++++++ Content.Shared/Administration/QDEntry.cs | 42 ++ .../Administration/QuickDialogOpenEvent.cs | 11 +- .../DeviceLinking/SharedDeviceLinkSystem.cs | 14 + Content.Shared/_White/MechComp/MechComp.cs | 215 ++++++ Resources/Audio/White/MechComp/emitter2.ogg | Bin 0 -> 20743 bytes .../White/MechComp/generic_energy_dryfire.ogg | Bin 0 -> 20728 bytes .../Devices/MechComp/generic_ports.yml | 277 ++++++++ .../Objects/Devices/MechComp/ports.yml | 51 ++ .../Objects/Devices/MechComp/some.yml | 132 ++++ .../Textures/White/MechComp/base.rsi/icon.png | Bin 0 -> 290 bytes .../White/MechComp/base.rsi/meta.json | 14 + .../White/MechComp/button.rsi/icon.png | Bin 0 -> 366 bytes .../White/MechComp/button.rsi/meta.json | 23 + .../White/MechComp/button.rsi/piss.png | Bin 0 -> 243 bytes .../White/MechComp/button.rsi/pressed.png | Bin 0 -> 476 bytes .../White/MechComp/comparer.rsi/anchored.png | Bin 0 -> 291 bytes .../White/MechComp/comparer.rsi/icon.png | Bin 0 -> 382 bytes .../White/MechComp/comparer.rsi/meta.json | 17 + .../White/MechComp/math.rsi/anchored.png | Bin 0 -> 289 bytes .../Textures/White/MechComp/math.rsi/icon.png | Bin 0 -> 340 bytes .../White/MechComp/math.rsi/meta.json | 17 + .../White/MechComp/mechcomptool.rsi/icon.png | Bin 0 -> 824 bytes .../MechComp/mechcomptool.rsi/inhand-left.png | Bin 0 -> 242 bytes .../mechcomptool.rsi/inhand-right.png | Bin 0 -> 250 bytes .../White/MechComp/mechcomptool.rsi/meta.json | 41 ++ .../White/MechComp/mechcomptool.rsi/open.png | Bin 0 -> 1187 bytes .../MechComp/pressurepad.rsi/anchored.png | Bin 0 -> 320 bytes .../White/MechComp/pressurepad.rsi/icon.png | Bin 0 -> 364 bytes .../White/MechComp/pressurepad.rsi/meta.json | 17 + .../White/MechComp/speaker.rsi/anchored.png | Bin 0 -> 257 bytes .../White/MechComp/speaker.rsi/icon.png | Bin 0 -> 418 bytes .../White/MechComp/speaker.rsi/meta.json | 30 + .../White/MechComp/speaker.rsi/speak.png | Bin 0 -> 511 bytes .../White/MechComp/teleport.rsi/anchored.png | Bin 0 -> 311 bytes .../White/MechComp/teleport.rsi/charging.png | Bin 0 -> 294 bytes .../White/MechComp/teleport.rsi/firing.png | Bin 0 -> 257 bytes .../MechComp/teleport.rsi/glow(unused).png | Bin 0 -> 181 bytes .../White/MechComp/teleport.rsi/icon.png | Bin 0 -> 469 bytes .../White/MechComp/teleport.rsi/meta.json | 47 ++ .../White/MechComp/teleport.rsi/ready.png | Bin 0 -> 136 bytes 55 files changed, 1975 insertions(+), 34 deletions(-) create mode 100644 Content.Client/_White/MechComp/MechCompSystem.cs create mode 100644 Content.Server/DeviceLinking/Events/DeviceLinkTryConnectingAttemptEvent.cs create mode 100644 Content.Server/DeviceLinking/NetworkPayloadHelper.cs create mode 100644 Content.Server/_White/MechComp/DisconnectOnUnanchor.cs create mode 100644 Content.Server/_White/MechComp/MechCompDeviceSystem.cs create mode 100644 Content.Shared/Administration/QDEntry.cs create mode 100644 Content.Shared/_White/MechComp/MechComp.cs create mode 100644 Resources/Audio/White/MechComp/emitter2.ogg create mode 100644 Resources/Audio/White/MechComp/generic_energy_dryfire.ogg create mode 100644 Resources/Prototypes/Entities/Objects/Devices/MechComp/generic_ports.yml create mode 100644 Resources/Prototypes/Entities/Objects/Devices/MechComp/ports.yml create mode 100644 Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml create mode 100644 Resources/Textures/White/MechComp/base.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/base.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/button.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/button.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/button.rsi/piss.png create mode 100644 Resources/Textures/White/MechComp/button.rsi/pressed.png create mode 100644 Resources/Textures/White/MechComp/comparer.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/comparer.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/comparer.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/math.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/math.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/math.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/mechcomptool.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-left.png create mode 100644 Resources/Textures/White/MechComp/mechcomptool.rsi/inhand-right.png create mode 100644 Resources/Textures/White/MechComp/mechcomptool.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/mechcomptool.rsi/open.png create mode 100644 Resources/Textures/White/MechComp/pressurepad.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/pressurepad.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/pressurepad.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/speaker.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/speaker.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/speaker.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/speaker.rsi/speak.png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/charging.png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/firing.png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/glow(unused).png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/meta.json create mode 100644 Resources/Textures/White/MechComp/teleport.rsi/ready.png 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..723e50adf8b --- /dev/null +++ b/Content.Client/_White/MechComp/MechCompSystem.cs @@ -0,0 +1,223 @@ +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(); + //private void _flick(EntityUid uid, TimeSpan duration, RSI.StateId iconstate, string key, object layerKey) // I really think it should be a standard part of SpriteSystem, i really do not want to create an anim track myself for a simple iconstate flick + //{ + // if (_cachedAnims.TryGetValue((uid, duration, iconstate, key, layerKey), out var anim)) // So it's *almost* the same as creating all the anims in the ComponentInit and playing them as needed + // { // expect it looks more compact and nicer (as long as you just hide this method lol) + // _anim.Play(uid, anim, key); // and except it's not GC'd, so it leaks memory, althrough this should only be a problem if you're triggering _flick() on a dozen new uids every second. + // return; + // } + // + // anim = new() + // { + // Length = duration, + // AnimationTracks = + // { + // new AnimationTrackSpriteFlick + // { + // LayerKey = layerKey, + // KeyFrames = + // { + // new AnimationTrackSpriteFlick.KeyFrame(iconstate, 0f), + // } + // } + // } + // }; + // _cachedAnims.Add((uid, duration, iconstate, key, layerKey), anim); + // _anim.Play(uid, anim, key); + //} + //private void _flick(EntityUid uid, float seconds, RSI.StateId iconstate, string key, object? layerKey = null) + //{ _flick(uid, TimeSpan.FromSeconds(seconds), iconstate, key, layerKey); } + //private void _handleAnchored(AppearanceChangeEvent args, string anchoredState = "anchored", string unanchoredState = "icon", bool hideOrShowEffectsLayer = true) + //{ + // + // if (args.Sprite != null) + // { + // var uid = args.Sprite.Owner; + // var layer = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Base); + // + // if (_appearance.TryGetData(uid, MechCompDeviceVisuals.Anchored, out bool value)) + // { + // Logger.Debug($"[ASS BLAST USA] OOOOOO {value}"); + // + // args.Sprite.LayerSetState(layer, value ? anchoredState : unanchoredState); + // args.Sprite.DrawDepth = (int) (value ? DrawDepth.FloorObjects : DrawDepth.SmallObjects); + // if (hideOrShowEffectsLayer) + // { + // var effectslayer = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect); + // args.Sprite.LayerSetVisible(effectslayer, value); + // } + // } + // } + //} + + 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); + + + } + 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"); + args.Sprite?.LayerSetAnimationTime(MechCompDeviceVisualLayers.Base, 0); // hack: Stop()ing and immediately Play()ing an animation does not seem to restart it + } + } + + 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"); + args.Sprite?.LayerSetAnimationTime(MechCompDeviceVisualLayers.Effect1, 0); // hack: Stop()ing and immediately Play()ing an animation does not seem to restart it + } + } + + 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; + } + } + } +} + + +/// +/// 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); + + } + } + } +} 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/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/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..bc39bc98bd3 --- /dev/null +++ b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs @@ -0,0 +1,612 @@ +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.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 Microsoft.CodeAnalysis.CSharp.Syntax; +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 MechCompConfigUpdateEvent : EntityEventArgs +{ + +} + +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(); +#region Helper functions +#region mechcomp config get/set functions + private MechCompConfig GetConfig(EntityUid uid) { return Comp(uid).config;} + private int GetConfigInt(EntityUid uid, string key) { return GetConfig(uid).GetInt(key); } + private float GetConfigFloat(EntityUid uid, string key) { return GetConfig(uid).GetFloat(key); } + private string GetConfigString(EntityUid uid, string key) { return GetConfig(uid).GetString(key); } + private bool GetConfigBool(EntityUid uid, string key) { return GetConfig(uid).GetBool(key); } + private void SetConfigInt(EntityUid uid, string key, int value) { GetConfig(uid).SetInt(key, value); } + private void SetConfigFloat(EntityUid uid, string key, float value) { GetConfig(uid).SetFloat(key, value); } + private void SetConfigString(EntityUid uid, string key, string value) { GetConfig(uid).SetString(key, value); } + private void SetConfigBool(EntityUid uid, string key, bool value) { GetConfig(uid).SetBool(key, value); } + +#endregion + /// + /// A helper function for use in ComponentInit event handlers. + /// Ensures a BaseMechCompComponent exists and returns its config. + /// + private MechCompConfig EnsureConfig(EntityUid uid) + { + return EnsureComp(uid).config; + } + private bool isAnchored(EntityUid uid) // perhaps i'm reinventing the wheel here + { + return TryComp(uid, out var comp) && comp.Anchored; + } + #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 + private void OpenMechCompConfigDialog(EntityUid deviceUid, EntityUid playerUid, BaseMechCompComponent comp) + { + if(!Exists(deviceUid) || !Exists(playerUid)) + { + return; + } + if(!_playerManager.TryGetSessionByEntity(playerUid, out var player)) + { + return; + } + + var config = comp!.config; + var entries = config.GetOrdered(); + _dialog.OpenDialog( + player, + Name(deviceUid) + " configuration", + entries, (results) => { + config.SetFromObjectArray(results); + RaiseLocalEvent(deviceUid, new MechCompConfigUpdateEvent()); + } + ); + } + 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; + } + } + /// + /// , 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 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? + SubscribeLocalEvent(OnButtonInit); + SubscribeLocalEvent(OnButtonHandInteract); + SubscribeLocalEvent(OnButtonActivation); + + + SubscribeLocalEvent(OnSpeakerInit); + SubscribeLocalEvent(OnSpeakerConfigUpdate); + SubscribeLocalEvent(OnSpeakerSignal); + + SubscribeLocalEvent(OnTeleportInit); + SubscribeLocalEvent(OnTeleportSignal); + + SubscribeLocalEvent(OnMathInit); + SubscribeLocalEvent(OnMathSignal); + + SubscribeLocalEvent(OnPressurePadInit); + SubscribeLocalEvent(OnPressurePadStep); + + SubscribeLocalEvent(OnComparerInit); + SubscribeLocalEvent(OnComparerSignal); + + } + 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); + } + private void GetInteractionVerb(EntityUid uid, BaseMechCompComponent comp, GetVerbsEvent args) + { + if (!HasComp(args.Using)) + { + 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); } + }); + } + + private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("outsignal", (typeof(string), "Сигнал на выходе", "1")) + ); + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + + } + 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", GetConfigString(uid, "outsignal")); + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); // the data will be discarded anyways + } + } + + + private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args) + { + _link.EnsureSinkPorts(uid, "MechCompStandardInput"); + + EnsureConfig(uid).Build( + ("inradio", (typeof(bool), "Голосить в радио (;)", false )), + ("name", (typeof(string), "Имя", Name(uid) )) + ); + EnsureComp(uid, out var maskcomp); + maskcomp.VoiceName = Name(uid); // better safe than █████ ███ ██████ + } + private void OnSpeakerConfigUpdate(EntityUid uid, MechCompSpeakerComponent comp, MechCompConfigUpdateEvent args) + { + Comp(uid).VoiceName = GetConfigString(uid, "name"); + } + private void OnSpeakerSignal(EntityUid uid, MechCompSpeakerComponent comp, ref SignalReceivedEvent args) + { + + //Logger.Debug($"MechComp speaker received signal ({args.ToString()}) ({args.Data?.ToString()}) ({ToPrettyString(uid)})"); + if (isAnchored(uid) && TryGetMechCompSignal(args.Data, out string msg)) + { + msg = msg.ToUpper(); + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + //Logger.Debug($"MechComp speaker spoke ({msg}) ({ToPrettyString(uid)})"); + if (GetConfigBool(uid, "inradio") && Cooldown(uid, "speech", 5f)) + { + + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); + _radio.SendRadioMessage(uid, msg, "Common", uid); + } + else if (Cooldown(uid, "speech", 1f)) { + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); + } + } + } + + + + private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("TeleID", (typeof(Hex16), "ID этого телепорта", _rng.Next(65536))), + ("_", (null, "Установите ID на 0000, чтобы отключить приём.")) + ); + _link.EnsureSinkPorts(uid, "MechCompTeleIDInput"); + } + + + 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; + } + + + 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 == GetConfigInt(otherUid, "TeleID")) + { + if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance) // 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(target.Owner, 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); + } + } + } + + 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); } + }; + public void OnMathInit(EntityUid uid, MechCompMathComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("mode", (typeof(List), "Операция", _mathFuncs.Keys.First(), _mathFuncs.Keys.ToArray()) ), + ("numberA", (typeof(float), "Число A", "0") ), + ("numberB", (typeof(float), "Число B", "0") ) + ); + _link.EnsureSinkPorts(uid, "MechCompNumericInputA", "MechCompNumericInputB", "Trigger"); + _link.EnsureSourcePorts(uid, "MechCompNumericOutput"); + } + + public void OnMathSignal(EntityUid uid, MechCompMathComponent comp, ref SignalReceivedEvent args) + { + string sig; float num; // hurr durr + var cfg = GetConfig(uid); + switch (args.Port) + { + case "MechCompNumericInputA": + if(TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + SetConfigFloat(uid, "numberA", num); + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + SetConfigFloat(uid, "numberB", num); + } + break; + case "Trigger": + float numA = GetConfigFloat(uid, "numberA"); + float numB = GetConfigFloat(uid, "numberB"); + float? result = _mathFuncs[GetConfigString(uid, "mode")](numA, numB); + if (result != null) + { + SendMechCompSignal(uid, "MechCompNumericOutput", result.ToString()!); + } + break; + } + } + + public void OnPressurePadInit(EntityUid uid, MechCompPressurePadComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("triggered_by_mobs", (typeof(bool), "Реагировать на существ", true) ), + ("triggered_by_items", (typeof(bool), "Реагировать на предметы", false)) + ); + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + } + public void OnPressurePadStep(EntityUid uid, MechCompPressurePadComponent comp, ref StepTriggeredEvent args) + { + if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_mobs")) + { + SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); + return; + } + if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_items")) + { + SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); + return; + } + } + + 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, "работают только с числовыми значениями.")) + ); + _link.EnsureSinkPorts(uid, "MechCompInputA", "MechCompInputB"); + _link.EnsureSourcePorts(uid, "MechCompLogicOutputA", "MechCompLogicOutputB"); + + } + + public void OnComparerSignal(EntityUid uid, MechCompComparerComponent comp, ref SignalReceivedEvent args) + { + string sig; + var cfg = GetConfig(uid); + switch (args.Port) + { + case "MechCompNumericInputA": + if (TryGetMechCompSignal(args.Data, out sig)) + { + SetConfigString(uid, "valueA", sig); + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig)) + { + SetConfigString(uid, "valueB", sig); + } + break; + case "Trigger": + string valA = GetConfigString(uid, "ValueA"); + string valB = GetConfigString(uid, "ValueB"); + bool? result = _compareFuncs[GetConfigString(uid, "mode")](valA, valB); + switch (result) + { + case true: + SendMechCompSignal(uid, "MechCompLogicOutputTrue", GetConfigString(uid, "outputTrue")); + break; + case false: + SendMechCompSignal(uid, "MechCompLogicOutputFalse", GetConfigString(uid, "outputFalse")); + break; + case null: + break; + + } + break; + } + } + + +} +[RegisterComponent] +public partial class BaseMechCompComponent : Component +{ + public MechCompConfig config = new(); + + +} diff --git a/Content.Shared/Administration/QDEntry.cs b/Content.Shared/Administration/QDEntry.cs new file mode 100644 index 00000000000..082ff488936 --- /dev/null +++ b/Content.Shared/Administration/QDEntry.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared.Administration; + +public class QDEntry +{ + public Type? type { get; private set; } + public string description { get; private set; } + public object? Value { get; set; } + 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) tuple4) + { + return new QDEntry(tuple4.type, tuple4.desc, tuple4.Value, tuple4.info); + } + public static implicit operator QDEntry((Type type, string desc, object? Value) tuple3) + { + return new QDEntry(tuple3.type, tuple3.desc, tuple3.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". + return new QDEntry(tuple.type, tuple.desc); + } + 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..591447d4672 --- /dev/null +++ b/Content.Shared/_White/MechComp/MechComp.cs @@ -0,0 +1,215 @@ +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 sealed partial class MechCompButtonComponent : Component +{ + [DataField("clickSound")] + public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg"); + public object pressedAnimation = default!; +} +[RegisterComponent] +public sealed partial class MechCompSpeakerComponent : Component +{ + public object speakAnimation = default!; +} + +[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("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 +{ + //public float A = 0; + //public float B = 0; +} + + +[RegisterComponent] +public sealed partial class MechCompPressurePadComponent : Component +{ + +} + +[RegisterComponent] +public sealed partial class MechCompComparerComponent : Component +{ + +} + + + +// todo: replace this with proper prototypes? maybe? +// +//public class MechCompConfigEntry +//{ +// +// public MechCompConfigEntry(T val, string desc) +// { +// value = val; +// description = desc; +// } +// T value = default!; +// string description; +//} +// +// +///// +///// Prototype for mechcomp config entry. +///// +//[Prototype("mechCompConfig")] +//[Serializable, NetSerializable] +//public abstract class DevicePortPrototype +//{ +// [IdDataField] +// public string ID { get; private set; } = default!; +// +// /// +// /// Localization string for the config entry name. Displayed in the config dialog. +// /// +// [DataField("name", required: true)] +// public string Name = default!; +// +// // ??? +// //[DataField("type", required: true)] +// //public Type Type = default!; +//} + + +/// +/// key is, well, key +/// (object, string) is the config entry, where +/// object is the value +/// string is the config entry short description, which shows up in the config dialog +/// if config entry tuple is null, then no control will be created for the key (see quickdialogsystem) +/// + +public class MechCompConfig +{ + List entryOrder = new(); // OrderedDictionary is non generic, and i don't feel like using non generic dictionaries + + Dictionary dict = new(); + + public void Build(params (string, QDEntry)[] entries) // Yes, it's coupled to QuickDialogSystem. I *really* do not want to write a new window from scratch for EVERY component. + { + if(dict.Count != 0) { throw new InvalidOperationException("Trying to build already built mechcomp config."); } + if(entries.Length > 10) { throw new ArgumentException("Mechcomp config does not support more than 10 config entries."); } + foreach(var ( key, entry ) in entries) + { + dict.Add(key, entry); + entryOrder.Add(key); + } + } + /// + /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. + /// + public int GetInt(string key) // Also Hex16 + { + return (int) dict[key].Value!; + } + /// + /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. + /// + public float GetFloat(string key) + { + return (float) dict[key].Value!; + } + /// + /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. + /// + public string GetString(string key) // also LongString + { + return (string) dict[key].Value!; + } + /// + /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. + /// + public bool GetBool(string key) + { + return (bool) dict[key].Value!; + } + public void SetInt(string key, int value) // Also Hex16 + { + dict[key].Value = value; + } + public void SetFloat(string key, float value) + { + dict[key].Value = value; + } + public void SetString(string key, string value) // also LongString + { + dict[key].Value = value; + } + public void SetBool(string key, bool value) + { + dict[key].Value = value; + } + /// + /// Specifically for use with QuickDialogSystem + /// + public List GetOrdered() + { + List ret = new(); + foreach(string key in entryOrder) + { + ret.Add(dict[key]); + } + return ret; + } + + public void SetFromObjectArray(object[] o) + { + if(o.Length != dict.Count) // dict and entryOrder length should be the same + { + throw new ArgumentException("Argument array length does not match config length"); + } + for(int i = 0; i < o.Length; i++) + { + dict[entryOrder[i]].Value = o[i]; + } + } +} + + diff --git a/Resources/Audio/White/MechComp/emitter2.ogg b/Resources/Audio/White/MechComp/emitter2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..72b6a1c572549b2d2cb59538ecbf2cd753019ff6 GIT binary patch literal 20743 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 W_bgwU01f~GN&o-=002-cfGPk8aYYgU literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e92df55ce01f9c1c6af4e472fe369ca93f9065b0 GIT binary patch literal 20728 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1cad170635b1022a8ae379c4375f26e7e08a9e21 GIT binary patch literal 366 zcmV-!0g?WRP)Px$C`m*?R9J=WmN9CDKomufA;r`KV)Zv*3JbT9EJXN?E3tG7>61D;u$aaTSPC`) zndUPoqT>Hre=`rn(C9(Jj*fw;#K{7dS+RM>N~eNcoYDDQp&4$ z7y!Wg^}4Cuc4~i-cn7StBuT>8gJF^+SZmJ(u<<1_#yI%>-&$+Nal~4SF=lz^G=SMa z^_^?mcDE<}o)}GTn!prD7GtgTVm@EVrw0-MV+@ab;F|}$647Mn_Ucmo02W$yoTp65%m4rY M07*qoM6N<$g1c**@c;k- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6575d1ba6b631f0175b40acd6ccdba7816ad8e2b GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=$zQoB4|978JRyuGrK_mF`A>xKLy zTr-=Da@`b)7fgA1F;3!R5>GJGaDd zl<$2RvwG5}uRCAI2F#32&wlmq$PbCruFo<;gJQ2gUFOBr=5UtzgTurFj0KKI=e$_m z#MQ@;)N}OT(TMXV4E#1K50ZTz3+z)}AIrGo-u`y;tXXUu4nG%)xP7c>1!K(Awd@Sb oS?gDvby=_aTUZbV{;rXBd#}T>`SS#Gpf4CaUHx3vIVCg!0N{aREC2ui literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..563011ff154ead4ed77c96b9032c1d0c045ac4a3 GIT binary patch literal 476 zcmV<20VDp2P)b;z6o&s)9XwkKS;__IkRjJ72SLih0WxubjJX1PJNE_*UUC7pBC%9LUMhDWjuHrn zXE}LN>rX-vcJTN7{DPDR1OkD;KPKW1;BB>b`rWcBP51lzYTfSTqbeU>=b0k4!HUD) z49)9!9#I2uVOf`8{}WYiH=ku$?=ve3q-lDyI(%UL01$V^zniOzpI!k%;MNJe2ti$rjJV^tH+KtC z1z5IuesuyrdG4~56WalotDjr}T5BXpg3k+Kk|fYtI|ae%CnZ3~^GGRWfb;VvrNrrU zg4P;JsrDXn6pVg)1;q169QLNJ>%lqddfa|;0xXNgV(19RN4v1a!MHUB>PPjpcW0iAgE1=Y*K7CFaxb{5B}1#PN7+ zGZl&##pl1|VN@#=S~0HqWzpTjBp0Y3ds9ic{OhQ`OnxBnMY#_C)G SZh^)C0000ST5Jk^X3QvKUf`-yvJ=s$g* z&K8$z2RU`h>dpWRzyJ)u003}~`DvbveYaoKO8gvPo{VkV7M5iR_NA0C41=XYkO21P z+WfETVYV(sMnqbC3W002ovPDHLkV1f;4c-#O0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cd105f51aed198cd2a7f048cdd504cf6bdeb36d1 GIT binary patch literal 382 zcmV-^0fGLBP)U5Jf+$l~bfhn}!riol~TXw#qHGBnL@uLP?iX&~FjZMT(RnvTPZ#%^De)Aq0VF z{3Ro>?D>zGK_>YAi7}@>W_9-f9>4>5005uOi7ZXZDGj&#L%;%LX;Ow^IHfNC378l~Pbj!5C8;lsN$Gj%Qreij1YT=4^NXH|?A*r??ugAUKgF=rZ3d z9+ja@`72W)1e*mCpjmec!ZoH!(pQ~L*rj(Hadd#DF#y)weukq0P`$vJcWLlhU`sIc z^RpVI1e__C_O}4Iw^#?ass!uc2Ce%U_5#TByw_%dI{^G&hp7gz-5-Kkk7H`V)SCT| c$OCwQKYeDWaSOCN>i_@%07*qoM6N<$g1+pYzW@LL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7b0e30adaba17feb0317a11591136b515336dbd3 GIT binary patch literal 289 zcmV++0p9+JP)!)_A$zysh1?cfVXM zNDw3dXN@n40`PnRwXa_4=1_vO##53Q3=qtlrs)_41t6_8AmU&MQUbAgY?`8^TM!b6 zlb94BB#@HCe#V~qOr^WTsG7=QsDk91K1uE)Kl00000NkvXXu0mjfz^8g7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b582a6ab4eacb7ebcafb4c69109d857c9c4cba59 GIT binary patch literal 340 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 IbxsLQ0M}!fZ2$lO literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4686984b64b839b02e30b3b68ba2e5bb35d3a268 GIT binary patch literal 257 zcmV+c0sj7pP)LQxS@C8*a<3wQp2oZN*(NBPqQXXT3X`1S|MNq;z1#Nj5Ibi{M07xkvJkN7ihOgkG1SknVd7guF?!bM^K1#rU3eGvQ zEF;H;VK@l-Qs92SKb^E(F3J9}@Q%P{e+(x598&{6oyq@*W`SAY4=LQl?-g_M4gdfE M07*qoM6N<$f~}XgZvX%Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a9f99de2b8f221ceed08cdc89d3abb08c70a349d GIT binary patch literal 511 zcmVPx$xk*GpRCt{2n$eQOAP7bE|3BJ&(OJeZ2_hJ$Ip=BH$h}UaK)Ou{0000000000 z0002s1C+{J`d{}4f#;3VXw@|({hyPof3JVKznAwe{V-NGYmIF;Yc&z%!D~MRo{2cC zr%6=H9t{2=oY(>K-NjN$$vLOC=bZEHdKp*Or&5YKZscA6Hv6~unLUi+SN*N=*8A6V zEawM;*^1;@!7LRqx`OZax5j#E{4%o@DP;X9UdDR(_nKERjR^lMea-b%wEoduVwaDW z#}O!{wM3qc@7uPAf7Q4gC*-&#-gEcw^_%XMzgLnx>G$JiRC4a}nc=n~u55NCzCW;4 ze~reVLc05R`cECeB}qmF<<6fDAb6t#^syfP7L619ojbr02?qcGa0ZM(2m3Pu#lu6e zSTA4m5F7&!!HO8Z2IXteUGR^vE~XLTf2FUvzKYiWYaQT`x?z3_!A~K6lE)E9jw6N& zXl`|VyR`bZ_}MY6k6-n-#;=SD*7F0w8x^coQO^plt=`ccNc$NdbcjQ#+ zrp-e1uA#(2i#0&6kJkK-PZ~M3)tO-adopDuFWbZhR?hGP&waFbA`p#H$YrJh6 zDVXQE0}(<1B9c>J?|{!Zw9i)bAo4!L&uahxD9e)US4sf@u*MI-yaGgY+ieYwN(kZ3 zaEcv7+#ImR+#HCWgogmdIvK4s-0qJc!BCz5`xyPYqUS(#fE`bIjg4B~u5|za002ov JPDHLkV1iQMg?#`3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..98b2d778c1d056dc3cb8154cafb8abe3dec5682b GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^2Y^_CgAGW!%qVXKQg1z7978JRyuG=x@UQ_7gTrm> z3jXzp*H>OnQ`^2O)H=I@pTALLrLvrcmc{FT(Sb)C8MuPlnSmPNV9&(lU%k~w9(`V) zTz9_Z^A?%^Uw7vIIlV{b|IdxNe_khB{`;PL|L66q|K%>`{5!L2$!C#^UUr-HUK_b@ z>6#xoy|mV4%PjlNdaJGe-}?348_diMzULUuG5r5migS1|0@Xu61E2S|Hd`gT z*m@oBs`EW-js9C!)~!6FKl%Ck0*LcfxJoA zloyG`r`=t;g~in9(6j|@ZZ|evKk!r6@vl#dtzvJ-2K6P1g5Ta4Ce-`^Nm? yKFf*k#cU;PHgne}#2+;N!9U}vy#mk`7oPC`vAF4;d%|-z$W5NEelF{r5}E+PifLE? literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c74649f6a4986bc430e8697cec393dc80165fbab GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ8c!F;kcv6UUry?zNO&ADS+K3& zD&Y#_Vm^t|11p%_WDQ<51hVt6v2U2L>S5)kV<9!4CKczWTt32R(B0enakcenhCuf< z&K_KhGoJ3aQy{kL*_+5mXH`xzrk{7xRanp_;;@A|!I6tQ$?4UKcML_k3M_0k(tQvc a7#PZ{q?Xk5PYeUPgTd3)&t;ucLK6T~uRtLH literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..668b7b8d02ed62525d0a4b0c96728ae8ad00a820 GIT binary patch literal 469 zcmV;`0V@89P)Kficoe7uz23-kiLKrhe>yo11yTgc+Dd?e`eYcc2~008s( zoLxH{4wy_PH^HMQV%HkCAdAD&vMd%1;AS{*{v+H(*$Hp}&-0A1&1Q46m{H((JOTj5 z<1qkWyWIi+9LF&>q&yO#KAi>X4GEkii7}t@sBXPpcN56su$;|iD2n2uDS>UlRS7flYaN zp0oLl*D%j>W6`}Kgt)qn7Xn!vmO=>q-F00kr68q55CjNcM6O>IKW?5&*EE8EG<|6E&I0@W{wdJubgDnU*BepQ>{;;-?^Uo3y=0iD00000 LNkvXXu0mjflflaZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..517a8fdd93c078cbe26ade2848e41454f1b0a302 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdze@_?3kcv5PuNVq32=KTB7DlEQ z-O316VUv8ef7)G*2|^4E4hDtlt6mlC?bv_Wro!yb=JU3H+|sHiD$bsM+MLtA!S{vi j3vT{BP3`{}AB6Mv^Jsal*^=`XXf%VTtDnm{r-UW|o%Swn literal 0 HcmV?d00001 From 2df9e52a614556c54aea25e4b84a91ab47de87b5 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:27:57 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=9F=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BB=D0=B0=D1=82=D1=91=D0=B6=2010=20=D1=80?= =?UTF-8?q?=D1=83=D0=B1.=20=D0=A1=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B2=20=D1=81=D1=87=D1=91=D1=82=20=D0=BA=D0=BE=D0=BC=D0=B8?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D0=B8=2015=20=D1=80=D1=83=D0=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Objects/Devices/MechComp/some.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml index 508c19a888e..f2e2b0056d5 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml @@ -120,6 +120,28 @@ - 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 From 5a268837f203d4ec6753fc85c6b696458ef1a1c7 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:31:53 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=A1=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=D0=BC=20=D0=BA=D0=B0=D0=BA=20=D0=B2=D0=B8=D0=B4=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D1=83=D1=81=D1=81=D1=82=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_White/MechComp/MechCompSystem.cs | 82 ++-- .../_White/MechComp/Devices/Button.cs | 44 ++ .../_White/MechComp/Devices/Calculator.cs | 74 ++++ .../_White/MechComp/Devices/Comparer.cs | 81 ++++ .../_White/MechComp/Devices/PressurePad.cs | 46 +++ .../_White/MechComp/Devices/Speaker.cs | 58 +++ .../_White/MechComp/Devices/Teleporter.cs | 95 +++++ .../_White/MechComp/MechCompDeviceSystem.cs | 377 +++--------------- 8 files changed, 475 insertions(+), 382 deletions(-) create mode 100644 Content.Server/_White/MechComp/Devices/Button.cs create mode 100644 Content.Server/_White/MechComp/Devices/Calculator.cs create mode 100644 Content.Server/_White/MechComp/Devices/Comparer.cs create mode 100644 Content.Server/_White/MechComp/Devices/PressurePad.cs create mode 100644 Content.Server/_White/MechComp/Devices/Speaker.cs create mode 100644 Content.Server/_White/MechComp/Devices/Teleporter.cs diff --git a/Content.Client/_White/MechComp/MechCompSystem.cs b/Content.Client/_White/MechComp/MechCompSystem.cs index 723e50adf8b..6e9cb361f2e 100644 --- a/Content.Client/_White/MechComp/MechCompSystem.cs +++ b/Content.Client/_White/MechComp/MechCompSystem.cs @@ -21,56 +21,11 @@ public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem [Dependency] private readonly AppearanceSystem _appearance = default!; Dictionary<(EntityUid, TimeSpan, RSI.StateId, string, object), Animation> _cachedAnims = new(); - //private void _flick(EntityUid uid, TimeSpan duration, RSI.StateId iconstate, string key, object layerKey) // I really think it should be a standard part of SpriteSystem, i really do not want to create an anim track myself for a simple iconstate flick - //{ - // if (_cachedAnims.TryGetValue((uid, duration, iconstate, key, layerKey), out var anim)) // So it's *almost* the same as creating all the anims in the ComponentInit and playing them as needed - // { // expect it looks more compact and nicer (as long as you just hide this method lol) - // _anim.Play(uid, anim, key); // and except it's not GC'd, so it leaks memory, althrough this should only be a problem if you're triggering _flick() on a dozen new uids every second. - // return; - // } - // - // anim = new() - // { - // Length = duration, - // AnimationTracks = - // { - // new AnimationTrackSpriteFlick - // { - // LayerKey = layerKey, - // KeyFrames = - // { - // new AnimationTrackSpriteFlick.KeyFrame(iconstate, 0f), - // } - // } - // } - // }; - // _cachedAnims.Add((uid, duration, iconstate, key, layerKey), anim); - // _anim.Play(uid, anim, key); - //} - //private void _flick(EntityUid uid, float seconds, RSI.StateId iconstate, string key, object? layerKey = null) - //{ _flick(uid, TimeSpan.FromSeconds(seconds), iconstate, key, layerKey); } - //private void _handleAnchored(AppearanceChangeEvent args, string anchoredState = "anchored", string unanchoredState = "icon", bool hideOrShowEffectsLayer = true) - //{ - // - // if (args.Sprite != null) - // { - // var uid = args.Sprite.Owner; - // var layer = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Base); - // - // if (_appearance.TryGetData(uid, MechCompDeviceVisuals.Anchored, out bool value)) - // { - // Logger.Debug($"[ASS BLAST USA] OOOOOO {value}"); - // - // args.Sprite.LayerSetState(layer, value ? anchoredState : unanchoredState); - // args.Sprite.DrawDepth = (int) (value ? DrawDepth.FloorObjects : DrawDepth.SmallObjects); - // if (hideOrShowEffectsLayer) - // { - // var effectslayer = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect); - // args.Sprite.LayerSetVisible(effectslayer, value); - // } - // } - // } - //} + /// + /// 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) { @@ -221,3 +176,30 @@ protected override void OnAppearanceChange(EntityUid uid, MechCompAnchoredVisual } } } + +/// +/// 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 void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation animation, string key) + { + var component = EnsureComp(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 void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key) + { + component ??= EntityManager.EnsureComponent(uid); + anim.Stop(component, key); + anim.Play(new Entity(uid, component), animation, key); + } +} diff --git a/Content.Server/_White/MechComp/Devices/Button.cs b/Content.Server/_White/MechComp/Devices/Button.cs new file mode 100644 index 00000000000..4d484d39735 --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Button.cs @@ -0,0 +1,44 @@ +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(OnButtonHandInteract); + SubscribeLocalEvent(OnButtonActivation); + + } + + private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("outsignal", (typeof(string), "Сигнал на выходе", "1")) + ); + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + + } + 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", GetConfigString(uid, "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..be11f88b109 --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Calculator.cs @@ -0,0 +1,74 @@ +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 void InitCalculator() + { + SubscribeLocalEvent(OnMathInit); + SubscribeLocalEvent(OnMathSignal); + } + + + + 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); } + }; + public void OnMathInit(EntityUid uid, MechCompMathComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("mode", (typeof(List), "Операция", _mathFuncs.Keys.First(), _mathFuncs.Keys.ToArray()) ), + ("numberA", (typeof(float), "Число A", "0") ), + ("numberB", (typeof(float), "Число B", "0") ) + ); + _link.EnsureSinkPorts(uid, "MechCompNumericInputA", "MechCompNumericInputB", "Trigger"); + _link.EnsureSourcePorts(uid, "MechCompNumericOutput"); + } + + public void OnMathSignal(EntityUid uid, MechCompMathComponent comp, ref SignalReceivedEvent args) + { + string sig; float num; // hurr durr + var cfg = GetConfig(uid); + switch (args.Port) + { + case "MechCompNumericInputA": + if(TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + SetConfigFloat(uid, "numberA", num); + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) + { + SetConfigFloat(uid, "numberB", num); + } + break; + case "Trigger": + float numA = GetConfigFloat(uid, "numberA"); + float numB = GetConfigFloat(uid, "numberB"); + float? result = _mathFuncs[GetConfigString(uid, "mode")](numA, numB); + 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..d64fc04a05b --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Comparer.cs @@ -0,0 +1,81 @@ +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(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, "работают только с числовыми значениями.")) + ); + _link.EnsureSinkPorts(uid, "MechCompInputA", "MechCompInputB"); + _link.EnsureSourcePorts(uid, "MechCompLogicOutputA", "MechCompLogicOutputB"); + + } + + public void OnComparerSignal(EntityUid uid, MechCompComparerComponent comp, ref SignalReceivedEvent args) + { + string sig; + var cfg = GetConfig(uid); + switch (args.Port) + { + case "MechCompNumericInputA": + if (TryGetMechCompSignal(args.Data, out sig)) + { + SetConfigString(uid, "valueA", sig); + } + break; + case "MechCompNumericInputB": + if (TryGetMechCompSignal(args.Data, out sig)) + { + SetConfigString(uid, "valueB", sig); + } + break; + case "Trigger": + string valA = GetConfigString(uid, "ValueA"); + string valB = GetConfigString(uid, "ValueB"); + bool? result = _compareFuncs[GetConfigString(uid, "mode")](valA, valB); + switch (result) + { + case true: + SendMechCompSignal(uid, "MechCompLogicOutputTrue", GetConfigString(uid, "outputTrue")); + break; + case false: + SendMechCompSignal(uid, "MechCompLogicOutputFalse", GetConfigString(uid, "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..acd12657fed --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/PressurePad.cs @@ -0,0 +1,46 @@ +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(OnPressurePadStepAttempt); + SubscribeLocalEvent(OnPressurePadStep); + } + + public void OnPressurePadInit(EntityUid uid, MechCompPressurePadComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("triggered_by_mobs", (typeof(bool), "Реагировать на существ", true) ), + ("triggered_by_items", (typeof(bool), "Реагировать на предметы", false)) + ); + _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); + } + + private void OnPressurePadStepAttempt(EntityUid uid, MechCompPressurePadComponent component, StepTriggerAttemptEvent args) + { + args.Continue = true; + } + + public void OnPressurePadStep(EntityUid uid, MechCompPressurePadComponent comp, ref StepTriggeredEvent args) + { + if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_mobs")) + { + SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); + return; + } + if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_items")) + { + 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..728e4ed966b --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Speaker.cs @@ -0,0 +1,58 @@ +using Content.Server.Chat.Systems; +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(OnSpeakerConfigUpdate); + SubscribeLocalEvent(OnSpeakerSignal); + } + + + private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args) + { + _link.EnsureSinkPorts(uid, "MechCompStandardInput"); + + EnsureConfig(uid).Build( + ("inradio", (typeof(bool), "Голосить в радио (;)", false)), + ("name", (typeof(string), "Имя", Name(uid))) + ); + EnsureComp(uid, out var maskcomp); + maskcomp.VoiceName = Name(uid); // better safe than █████ ███ ██████ + } + private void OnSpeakerConfigUpdate(EntityUid uid, MechCompSpeakerComponent comp, MechCompConfigUpdateEvent args) + { + Comp(uid).VoiceName = GetConfigString(uid, "name"); + } + private void OnSpeakerSignal(EntityUid uid, MechCompSpeakerComponent comp, ref SignalReceivedEvent args) + { + + //Logger.Debug($"MechComp speaker received signal ({args.ToString()}) ({args.Data?.ToString()}) ({ToPrettyString(uid)})"); + if (isAnchored(uid) && TryGetMechCompSignal(args.Data, out string msg)) + { + msg = msg.ToUpper(); + ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); + //Logger.Debug($"MechComp speaker spoke ({msg}) ({ToPrettyString(uid)})"); + if (GetConfigBool(uid, "inradio") && Cooldown(uid, "speech", 5f)) + { + + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); + _radio.SendRadioMessage(uid, msg, "Common", uid); + } + else if (Cooldown(uid, "speech", 1f)) + { + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "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..1a20ef1f8cd --- /dev/null +++ b/Content.Server/_White/MechComp/Devices/Teleporter.cs @@ -0,0 +1,95 @@ +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(OnTeleportSignal); + } + + + private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args) + { + EnsureConfig(uid).Build( + ("TeleID", (typeof(Hex16), "ID этого телепорта", _rng.Next(65536))), + ("_", (null, "Установите ID на 0000, чтобы отключить приём.")) + ); + _link.EnsureSinkPorts(uid, "MechCompTeleIDInput"); + } + + + 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; + } + + + 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 == GetConfigInt(otherUid, "TeleID")) + { + if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance) // 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(target.Owner, 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/MechCompDeviceSystem.cs b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs index bc39bc98bd3..ed6dc4f0f59 100644 --- a/Content.Server/_White/MechComp/MechCompDeviceSystem.cs +++ b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs @@ -26,7 +26,6 @@ using Content.Shared.Popups; using Content.Shared.StepTrigger.Systems; using Content.Shared.Verbs; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Audio; @@ -60,8 +59,24 @@ public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem [Dependency] private readonly IRobustRandom _rng = default!; Dictionary<(EntityUid, string), (TimeSpan, Action?)> _timeSpans = new(); -#region Helper functions -#region mechcomp config get/set functions + + 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(); + } + + #region Helper functions + #region mechcomp config private MechCompConfig GetConfig(EntityUid uid) { return Comp(uid).config;} private int GetConfigInt(EntityUid uid, string key) { return GetConfig(uid).GetInt(key); } private float GetConfigFloat(EntityUid uid, string key) { return GetConfig(uid).GetFloat(key); } @@ -72,7 +87,6 @@ public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem private void SetConfigString(EntityUid uid, string key, string value) { GetConfig(uid).SetString(key, value); } private void SetConfigBool(EntityUid uid, string key, bool value) { GetConfig(uid).SetBool(key, value); } -#endregion /// /// A helper function for use in ComponentInit event handlers. /// Ensures a BaseMechCompComponent exists and returns its config. @@ -85,6 +99,29 @@ private bool isAnchored(EntityUid uid) // perhaps i'm reinventing the wheel her { 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 config = comp!.config; + var entries = config.GetOrdered(); + _dialog.OpenDialog( + player, + Name(deviceUid) + " configuration", + entries, (results) => { + config.SetFromObjectArray(results); + RaiseLocalEvent(deviceUid, new MechCompConfigUpdateEvent()); + } + ); + } +#endregion #region cooldown shite /// /// Convenience function for managing cooldowns for all devices. @@ -156,29 +193,8 @@ private bool CancelCooldown(EntityUid uid, string key, bool fastForward) } return _timeSpans.Remove(tuple); } -#endregion - private void OpenMechCompConfigDialog(EntityUid deviceUid, EntityUid playerUid, BaseMechCompComponent comp) - { - if(!Exists(deviceUid) || !Exists(playerUid)) - { - return; - } - if(!_playerManager.TryGetSessionByEntity(playerUid, out var player)) - { - return; - } - - var config = comp!.config; - var entries = config.GetOrdered(); - _dialog.OpenDialog( - player, - Name(deviceUid) + " configuration", - entries, (results) => { - config.SetFromObjectArray(results); - RaiseLocalEvent(deviceUid, new MechCompConfigUpdateEvent()); - } - ); - } + #endregion + #region device signals private void SendMechCompSignal(EntityUid uid, string port, string signal, DeviceLinkSourceComponent? comp = null) { if (!Resolve(uid, ref comp)) @@ -207,6 +223,7 @@ private bool TryGetMechCompSignal(NetworkPayload? packet, out string 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 @@ -222,35 +239,8 @@ private void ForceSetData(EntityUid uid, Enum key, object value, AppearanceCompo } #endregion - 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? - SubscribeLocalEvent(OnButtonInit); - SubscribeLocalEvent(OnButtonHandInteract); - SubscribeLocalEvent(OnButtonActivation); - - SubscribeLocalEvent(OnSpeakerInit); - SubscribeLocalEvent(OnSpeakerConfigUpdate); - SubscribeLocalEvent(OnSpeakerSignal); - SubscribeLocalEvent(OnTeleportInit); - SubscribeLocalEvent(OnTeleportSignal); - - SubscribeLocalEvent(OnMathInit); - SubscribeLocalEvent(OnMathSignal); - - SubscribeLocalEvent(OnPressurePadInit); - SubscribeLocalEvent(OnPressurePadStep); - - SubscribeLocalEvent(OnComparerInit); - SubscribeLocalEvent(OnComparerSignal); - - } public override void Update(float frameTime) { List<(EntityUid, string)> keysToRemove = new(); @@ -322,285 +312,8 @@ private void GetInteractionVerb(EntityUid uid, BaseMechCompComponent comp, GetVe }); } - private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) - { - EnsureConfig(uid).Build( - ("outsignal", (typeof(string), "Сигнал на выходе", "1")) - ); - _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); - - } - 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", GetConfigString(uid, "outsignal")); - ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); // the data will be discarded anyways - } - } - - - private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args) - { - _link.EnsureSinkPorts(uid, "MechCompStandardInput"); - - EnsureConfig(uid).Build( - ("inradio", (typeof(bool), "Голосить в радио (;)", false )), - ("name", (typeof(string), "Имя", Name(uid) )) - ); - EnsureComp(uid, out var maskcomp); - maskcomp.VoiceName = Name(uid); // better safe than █████ ███ ██████ - } - private void OnSpeakerConfigUpdate(EntityUid uid, MechCompSpeakerComponent comp, MechCompConfigUpdateEvent args) - { - Comp(uid).VoiceName = GetConfigString(uid, "name"); - } - private void OnSpeakerSignal(EntityUid uid, MechCompSpeakerComponent comp, ref SignalReceivedEvent args) - { - - //Logger.Debug($"MechComp speaker received signal ({args.ToString()}) ({args.Data?.ToString()}) ({ToPrettyString(uid)})"); - if (isAnchored(uid) && TryGetMechCompSignal(args.Data, out string msg)) - { - msg = msg.ToUpper(); - ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); - //Logger.Debug($"MechComp speaker spoke ({msg}) ({ToPrettyString(uid)})"); - if (GetConfigBool(uid, "inradio") && Cooldown(uid, "speech", 5f)) - { - - _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); - _radio.SendRadioMessage(uid, msg, "Common", uid); - } - else if (Cooldown(uid, "speech", 1f)) { - _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); - } - } - } - - - - private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args) - { - EnsureConfig(uid).Build( - ("TeleID", (typeof(Hex16), "ID этого телепорта", _rng.Next(65536))), - ("_", (null, "Установите ID на 0000, чтобы отключить приём.")) - ); - _link.EnsureSinkPorts(uid, "MechCompTeleIDInput"); - } - - - 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; - } - - - 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 == GetConfigInt(otherUid, "TeleID")) - { - if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance) // 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(target.Owner, 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); - } - } - } - - 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); } - }; - public void OnMathInit(EntityUid uid, MechCompMathComponent comp, ComponentInit args) - { - EnsureConfig(uid).Build( - ("mode", (typeof(List), "Операция", _mathFuncs.Keys.First(), _mathFuncs.Keys.ToArray()) ), - ("numberA", (typeof(float), "Число A", "0") ), - ("numberB", (typeof(float), "Число B", "0") ) - ); - _link.EnsureSinkPorts(uid, "MechCompNumericInputA", "MechCompNumericInputB", "Trigger"); - _link.EnsureSourcePorts(uid, "MechCompNumericOutput"); - } - - public void OnMathSignal(EntityUid uid, MechCompMathComponent comp, ref SignalReceivedEvent args) - { - string sig; float num; // hurr durr - var cfg = GetConfig(uid); - switch (args.Port) - { - case "MechCompNumericInputA": - if(TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) - { - SetConfigFloat(uid, "numberA", num); - } - break; - case "MechCompNumericInputB": - if (TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) - { - SetConfigFloat(uid, "numberB", num); - } - break; - case "Trigger": - float numA = GetConfigFloat(uid, "numberA"); - float numB = GetConfigFloat(uid, "numberB"); - float? result = _mathFuncs[GetConfigString(uid, "mode")](numA, numB); - if (result != null) - { - SendMechCompSignal(uid, "MechCompNumericOutput", result.ToString()!); - } - break; - } - } - - public void OnPressurePadInit(EntityUid uid, MechCompPressurePadComponent comp, ComponentInit args) - { - EnsureConfig(uid).Build( - ("triggered_by_mobs", (typeof(bool), "Реагировать на существ", true) ), - ("triggered_by_items", (typeof(bool), "Реагировать на предметы", false)) - ); - _link.EnsureSourcePorts(uid, "MechCompStandardOutput"); - } - public void OnPressurePadStep(EntityUid uid, MechCompPressurePadComponent comp, ref StepTriggeredEvent args) - { - if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_mobs")) - { - SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); - return; - } - if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_items")) - { - SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); - return; - } - } - - 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, "работают только с числовыми значениями.")) - ); - _link.EnsureSinkPorts(uid, "MechCompInputA", "MechCompInputB"); - _link.EnsureSourcePorts(uid, "MechCompLogicOutputA", "MechCompLogicOutputB"); - - } - - public void OnComparerSignal(EntityUid uid, MechCompComparerComponent comp, ref SignalReceivedEvent args) - { - string sig; - var cfg = GetConfig(uid); - switch (args.Port) - { - case "MechCompNumericInputA": - if (TryGetMechCompSignal(args.Data, out sig)) - { - SetConfigString(uid, "valueA", sig); - } - break; - case "MechCompNumericInputB": - if (TryGetMechCompSignal(args.Data, out sig)) - { - SetConfigString(uid, "valueB", sig); - } - break; - case "Trigger": - string valA = GetConfigString(uid, "ValueA"); - string valB = GetConfigString(uid, "ValueB"); - bool? result = _compareFuncs[GetConfigString(uid, "mode")](valA, valB); - switch (result) - { - case true: - SendMechCompSignal(uid, "MechCompLogicOutputTrue", GetConfigString(uid, "outputTrue")); - break; - case false: - SendMechCompSignal(uid, "MechCompLogicOutputFalse", GetConfigString(uid, "outputFalse")); - break; - case null: - break; - - } - break; - } - } + } [RegisterComponent] From e3a8500fe4da75864397794598ad5a68c0044e15 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:37:45 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=BF=D0=BE=D0=B8=D0=B3=D1=80=D0=B0=D0=BB?= =?UTF-8?q?=20=D1=81=20=D0=BE=D1=87=D0=BA=D0=BE=D0=BC=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B8=D0=B3=D1=80=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/_White/MechComp/MechCompSystem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Client/_White/MechComp/MechCompSystem.cs b/Content.Client/_White/MechComp/MechCompSystem.cs index 6e9cb361f2e..725f2cca23a 100644 --- a/Content.Client/_White/MechComp/MechCompSystem.cs +++ b/Content.Client/_White/MechComp/MechCompSystem.cs @@ -186,9 +186,9 @@ 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 void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation animation, string key) + public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation animation, string key) { - var component = EnsureComp(uid); + var component = IoCManager.Resolve().EnsureComponent(uid); anim.Stop(component, key); anim.Play(new Entity(uid, component), animation, key); } @@ -196,9 +196,9 @@ public void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation a /// 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 void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key) + public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key) { - component ??= EntityManager.EnsureComponent(uid); + component ??= IoCManager.Resolve().EnsureComponent(uid); anim.Stop(component, key); anim.Play(new Entity(uid, component), animation, key); } From 3ae5289cf24c01797fd5d6c1f77b06a564deaded Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:33:30 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=BF=D1=80=D0=B8=D1=85=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=BE=20=D0=BC=D0=BD=D0=B5,=20=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D1=88=D0=B0=D1=82=D1=8C=20=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BB=D0=B0=D1=81=D1=82=D0=B8=D0=BD=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_White/MechComp/Devices/Button.cs | 16 +- .../_White/MechComp/Devices/Calculator.cs | 48 +++-- .../_White/MechComp/Devices/Comparer.cs | 61 ++++-- .../_White/MechComp/Devices/PressurePad.cs | 22 ++- .../_White/MechComp/Devices/Speaker.cs | 40 ++-- .../_White/MechComp/Devices/Teleporter.cs | 24 ++- .../_White/MechComp/MechCompDeviceSystem.cs | 61 +++--- Content.Shared/Administration/QDEntry.cs | 38 +++- Content.Shared/_White/MechComp/MechComp.cs | 183 +++++------------- 9 files changed, 242 insertions(+), 251 deletions(-) diff --git a/Content.Server/_White/MechComp/Devices/Button.cs b/Content.Server/_White/MechComp/Devices/Button.cs index 4d484d39735..165910943b9 100644 --- a/Content.Server/_White/MechComp/Devices/Button.cs +++ b/Content.Server/_White/MechComp/Devices/Button.cs @@ -10,18 +10,24 @@ 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) { - EnsureConfig(uid).Build( - ("outsignal", (typeof(string), "Сигнал на выходе", "1")) - ); _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) { @@ -36,7 +42,7 @@ 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", GetConfigString(uid, "outsignal")); + 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 index be11f88b109..68081822258 100644 --- a/Content.Server/_White/MechComp/Devices/Calculator.cs +++ b/Content.Server/_White/MechComp/Devices/Calculator.cs @@ -9,14 +9,6 @@ namespace Content.Server._White.MechComp; public sealed partial class MechCompDeviceSystem { - private void InitCalculator() - { - SubscribeLocalEvent(OnMathInit); - SubscribeLocalEvent(OnMathSignal); - } - - - private Dictionary> _mathFuncs = new() { ["A+B"] = (a, b) => { return a + b; }, @@ -29,39 +21,57 @@ private void InitCalculator() ["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) { - EnsureConfig(uid).Build( - ("mode", (typeof(List), "Операция", _mathFuncs.Keys.First(), _mathFuncs.Keys.ToArray()) ), - ("numberA", (typeof(float), "Число A", "0") ), - ("numberB", (typeof(float), "Число B", "0") ) - ); + 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 - var cfg = GetConfig(uid); switch (args.Port) { case "MechCompNumericInputA": if(TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) { - SetConfigFloat(uid, "numberA", num); + comp.A = num; } break; case "MechCompNumericInputB": if (TryGetMechCompSignal(args.Data, out sig) && float.TryParse(sig, out num)) { - SetConfigFloat(uid, "numberB", num); + comp.B = num; } break; case "Trigger": - float numA = GetConfigFloat(uid, "numberA"); - float numB = GetConfigFloat(uid, "numberB"); - float? result = _mathFuncs[GetConfigString(uid, "mode")](numA, numB); + float? result = _mathFuncs[comp.mode](comp.A, comp.B); if (result != null) { SendMechCompSignal(uid, "MechCompNumericOutput", result.ToString()!); diff --git a/Content.Server/_White/MechComp/Devices/Comparer.cs b/Content.Server/_White/MechComp/Devices/Comparer.cs index d64fc04a05b..ba0aac7e129 100644 --- a/Content.Server/_White/MechComp/Devices/Comparer.cs +++ b/Content.Server/_White/MechComp/Devices/Comparer.cs @@ -11,6 +11,8 @@ public sealed partial class MechCompDeviceSystem private void InitComparer() { SubscribeLocalEvent(OnComparerInit); + SubscribeLocalEvent(OnComparerConfigAttempt); + SubscribeLocalEvent(OnComparerConfigUpdate); SubscribeLocalEvent(OnComparerSignal); } @@ -25,50 +27,71 @@ private void InitComparer() }; 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, "работают только с числовыми значениями.")) - ); + //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, "MechCompLogicOutputA", "MechCompLogicOutputB"); + _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; - var cfg = GetConfig(uid); switch (args.Port) { case "MechCompNumericInputA": if (TryGetMechCompSignal(args.Data, out sig)) { - SetConfigString(uid, "valueA", sig); + comp.A = sig; } break; case "MechCompNumericInputB": if (TryGetMechCompSignal(args.Data, out sig)) { - SetConfigString(uid, "valueB", sig); + comp.B = sig; } break; case "Trigger": - string valA = GetConfigString(uid, "ValueA"); - string valB = GetConfigString(uid, "ValueB"); - bool? result = _compareFuncs[GetConfigString(uid, "mode")](valA, valB); + bool? result = _compareFuncs[comp.mode](comp.A, comp.B); switch (result) { case true: - SendMechCompSignal(uid, "MechCompLogicOutputTrue", GetConfigString(uid, "outputTrue")); + SendMechCompSignal(uid, "MechCompLogicOutputTrue", comp.outputTrue); break; case false: - SendMechCompSignal(uid, "MechCompLogicOutputFalse", GetConfigString(uid, "outputFalse")); + SendMechCompSignal(uid, "MechCompLogicOutputFalse", comp.outputFalse); break; case null: break; diff --git a/Content.Server/_White/MechComp/Devices/PressurePad.cs b/Content.Server/_White/MechComp/Devices/PressurePad.cs index acd12657fed..8b3e990d3da 100644 --- a/Content.Server/_White/MechComp/Devices/PressurePad.cs +++ b/Content.Server/_White/MechComp/Devices/PressurePad.cs @@ -11,32 +11,40 @@ 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) { - EnsureConfig(uid).Build( - ("triggered_by_mobs", (typeof(bool), "Реагировать на существ", true) ), - ("triggered_by_items", (typeof(bool), "Реагировать на предметы", false)) - ); _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, StepTriggerAttemptEvent args) + 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) && GetConfig(uid).GetBool("triggered_by_mobs")) + if (HasComp(args.Tripper) && comp.reactToMobs) { SendMechCompSignal(uid, "MechCompStandardOutput", Comp(args.Tripper).EntityName); return; } - if (HasComp(args.Tripper) && GetConfig(uid).GetBool("triggered_by_items")) + 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 index 728e4ed966b..d8c17cc6ada 100644 --- a/Content.Server/_White/MechComp/Devices/Speaker.cs +++ b/Content.Server/_White/MechComp/Devices/Speaker.cs @@ -1,4 +1,6 @@ +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; @@ -10,49 +12,53 @@ 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"); + } - EnsureConfig(uid).Build( - ("inradio", (typeof(bool), "Голосить в радио (;)", false)), - ("name", (typeof(string), "Имя", Name(uid))) - ); - EnsureComp(uid, out var maskcomp); - maskcomp.VoiceName = Name(uid); // better safe than █████ ███ ██████ + 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(uid).VoiceName = GetConfigString(uid, "name"); + comp.inRadio = (bool) args.results[0]; + comp.name = (string) args.results[1]; } private void OnSpeakerSignal(EntityUid uid, MechCompSpeakerComponent comp, ref SignalReceivedEvent args) { - - //Logger.Debug($"MechComp speaker received signal ({args.ToString()}) ({args.Data?.ToString()}) ({ToPrettyString(uid)})"); if (isAnchored(uid) && TryGetMechCompSignal(args.Data, out string msg)) { msg = msg.ToUpper(); - ForceSetData(uid, MechCompDeviceVisuals.Mode, "activated"); - //Logger.Debug($"MechComp speaker spoke ({msg}) ({ToPrettyString(uid)})"); - if (GetConfigBool(uid, "inradio") && Cooldown(uid, "speech", 5f)) - { - _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); + 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)) { - _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true, checkRadioPrefix: false, nameOverride: GetConfigString(uid, "name")); + 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 index 1a20ef1f8cd..5f7a22fc387 100644 --- a/Content.Server/_White/MechComp/Devices/Teleporter.cs +++ b/Content.Server/_White/MechComp/Devices/Teleporter.cs @@ -11,19 +11,28 @@ 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) { - EnsureConfig(uid).Build( - ("TeleID", (typeof(Hex16), "ID этого телепорта", _rng.Next(65536))), - ("_", (null, "Установите ID на 0000, чтобы отключить приём.")) - ); + 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) { @@ -40,15 +49,16 @@ private void OnTeleportSignal(EntityUid uid, MechCompTeleportComponent comp, ref } + 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 == GetConfigInt(otherUid, "TeleID")) + if (otherxform.Anchored && targetId == othercomp.teleId) { - if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance) // huh + if (distance <= comp.MaxDistance && distance <= othercomp.MaxDistance && xform.MapID == otherxform.MapID) // huh { target = otherxform; break; @@ -64,7 +74,7 @@ private void OnTeleportSignal(EntityUid uid, MechCompTeleportComponent comp, ref var targetUid = target.Owner; _appearance.SetData(uid, MechCompDeviceVisuals.Mode, "firing"); - _appearance.SetData(target.Owner, MechCompDeviceVisuals.Mode, "charging"); + _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. diff --git a/Content.Server/_White/MechComp/MechCompDeviceSystem.cs b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs index ed6dc4f0f59..198cd785dab 100644 --- a/Content.Server/_White/MechComp/MechCompDeviceSystem.cs +++ b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs @@ -11,6 +11,7 @@ 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; @@ -38,9 +39,14 @@ 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 @@ -76,25 +82,7 @@ public override void Initialize() } #region Helper functions - #region mechcomp config - private MechCompConfig GetConfig(EntityUid uid) { return Comp(uid).config;} - private int GetConfigInt(EntityUid uid, string key) { return GetConfig(uid).GetInt(key); } - private float GetConfigFloat(EntityUid uid, string key) { return GetConfig(uid).GetFloat(key); } - private string GetConfigString(EntityUid uid, string key) { return GetConfig(uid).GetString(key); } - private bool GetConfigBool(EntityUid uid, string key) { return GetConfig(uid).GetBool(key); } - private void SetConfigInt(EntityUid uid, string key, int value) { GetConfig(uid).SetInt(key, value); } - private void SetConfigFloat(EntityUid uid, string key, float value) { GetConfig(uid).SetFloat(key, value); } - private void SetConfigString(EntityUid uid, string key, string value) { GetConfig(uid).SetString(key, value); } - private void SetConfigBool(EntityUid uid, string key, bool value) { GetConfig(uid).SetBool(key, value); } - /// - /// A helper function for use in ComponentInit event handlers. - /// Ensures a BaseMechCompComponent exists and returns its config. - /// - private MechCompConfig EnsureConfig(EntityUid uid) - { - return EnsureComp(uid).config; - } private bool isAnchored(EntityUid uid) // perhaps i'm reinventing the wheel here { return TryComp(uid, out var comp) && comp.Anchored; @@ -110,18 +98,27 @@ private void OpenMechCompConfigDialog(EntityUid deviceUid, EntityUid playerUid, return; } - var config = comp!.config; - var entries = config.GetOrdered(); + 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()); + 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. } ); } -#endregion + #region cooldown shite /// /// Convenience function for managing cooldowns for all devices. @@ -297,9 +294,11 @@ private void OnAnchorStateChanged(EntityUid uid, BaseMechCompComponent comp, Anc } _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)) + if (!HasComp(args.Using) || !comp.hasConfig) { return; } @@ -311,15 +310,5 @@ private void GetInteractionVerb(EntityUid uid, BaseMechCompComponent comp, GetVe Act = () => { OpenMechCompConfigDialog(uid, args.User, comp); } }); } - - - - } -[RegisterComponent] -public partial class BaseMechCompComponent : Component -{ - public MechCompConfig config = new(); - -} diff --git a/Content.Shared/Administration/QDEntry.cs b/Content.Shared/Administration/QDEntry.cs index 082ff488936..c3b37f42816 100644 --- a/Content.Shared/Administration/QDEntry.cs +++ b/Content.Shared/Administration/QDEntry.cs @@ -6,11 +6,35 @@ 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) @@ -20,19 +44,21 @@ public QDEntry(Type? type, string description, object? Value = null, object? inf this.Value = Value; this.info = info; } - public static implicit operator QDEntry((Type type, string desc, object? Value, object? info) tuple4) + + public static implicit operator QDEntry((Type type, string desc, object? Value, object? info) tuple) { - return new QDEntry(tuple4.type, tuple4.desc, tuple4.Value, tuple4.info); + return new QDEntry(tuple.type, tuple.desc, tuple.Value, tuple.info); } - public static implicit operator QDEntry((Type type, string desc, object? Value) tuple3) + public static implicit operator QDEntry((Type type, string desc, object? Value) tuple) { - return new QDEntry(tuple3.type, tuple3.desc, tuple3.Value); + 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". + { // 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); } - public void Deconstruct(out Type? type, out string description, out object? Value, out object? info) + // 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; diff --git a/Content.Shared/_White/MechComp/MechComp.cs b/Content.Shared/_White/MechComp/MechComp.cs index 591447d4672..621aa4ab533 100644 --- a/Content.Shared/_White/MechComp/MechComp.cs +++ b/Content.Shared/_White/MechComp/MechComp.cs @@ -16,6 +16,16 @@ public abstract class SharedMechCompDeviceSystem : EntitySystem { } +[RegisterComponent] +public partial class BaseMechCompComponent : Component +{ + [DataField] + public bool hasConfig = true; +} + + + + [RegisterComponent] public sealed partial class MechCompButtonComponent : Component @@ -23,11 +33,19 @@ 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] @@ -36,6 +54,9 @@ 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; } @@ -62,154 +83,46 @@ public enum MechCompDeviceVisuals : byte [RegisterComponent] public sealed partial class MechCompMathComponent : Component { - //public float A = 0; - //public float B = 0; + [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"; } - - -// todo: replace this with proper prototypes? maybe? -// -//public class MechCompConfigEntry -//{ -// -// public MechCompConfigEntry(T val, string desc) -// { -// value = val; -// description = desc; -// } -// T value = default!; -// string description; -//} -// -// -///// -///// Prototype for mechcomp config entry. -///// -//[Prototype("mechCompConfig")] -//[Serializable, NetSerializable] -//public abstract class DevicePortPrototype -//{ -// [IdDataField] -// public string ID { get; private set; } = default!; -// -// /// -// /// Localization string for the config entry name. Displayed in the config dialog. -// /// -// [DataField("name", required: true)] -// public string Name = default!; -// -// // ??? -// //[DataField("type", required: true)] -// //public Type Type = default!; -//} - - -/// -/// key is, well, key -/// (object, string) is the config entry, where -/// object is the value -/// string is the config entry short description, which shows up in the config dialog -/// if config entry tuple is null, then no control will be created for the key (see quickdialogsystem) -/// - -public class MechCompConfig +[RegisterComponent] +public sealed partial class MechCompTranseiverComponent : Component { - List entryOrder = new(); // OrderedDictionary is non generic, and i don't feel like using non generic dictionaries - - Dictionary dict = new(); - - public void Build(params (string, QDEntry)[] entries) // Yes, it's coupled to QuickDialogSystem. I *really* do not want to write a new window from scratch for EVERY component. - { - if(dict.Count != 0) { throw new InvalidOperationException("Trying to build already built mechcomp config."); } - if(entries.Length > 10) { throw new ArgumentException("Mechcomp config does not support more than 10 config entries."); } - foreach(var ( key, entry ) in entries) - { - dict.Add(key, entry); - entryOrder.Add(key); - } - } - /// - /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. - /// - public int GetInt(string key) // Also Hex16 - { - return (int) dict[key].Value!; - } - /// - /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. - /// - public float GetFloat(string key) - { - return (float) dict[key].Value!; - } - /// - /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. - /// - public string GetString(string key) // also LongString - { - return (string) dict[key].Value!; - } - /// - /// Does NOT check if the entry is not null. It will only be null when building config entry with a null as a defaultValue. Make sure to not do a stupid. - /// - public bool GetBool(string key) - { - return (bool) dict[key].Value!; - } - public void SetInt(string key, int value) // Also Hex16 - { - dict[key].Value = value; - } - public void SetFloat(string key, float value) - { - dict[key].Value = value; - } - public void SetString(string key, string value) // also LongString - { - dict[key].Value = value; - } - public void SetBool(string key, bool value) - { - dict[key].Value = value; - } - /// - /// Specifically for use with QuickDialogSystem - /// - public List GetOrdered() - { - List ret = new(); - foreach(string key in entryOrder) - { - ret.Add(dict[key]); - } - return ret; - } - - public void SetFromObjectArray(object[] o) - { - if(o.Length != dict.Count) // dict and entryOrder length should be the same - { - throw new ArgumentException("Argument array length does not match config length"); - } - for(int i = 0; i < o.Length; i++) - { - dict[entryOrder[i]].Value = o[i]; - } - } + public object blinkAnimation = default!; + [DataField] + public int thisId = -1; + [DataField] + public int targetId = 0; } - - From 9bd99b2142637b1d66d82fa05dbb64a5a69a9e40 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:35:45 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=94=D0=B8=D0=B2=D0=B0=D0=B9=D0=BD=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B5=D0=B4=D0=B8,=20=D0=9F=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=20=D0=93=D1=8D=D0=B1=D1=80=D0=B8=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B8=20=D0=A1=D1=82=D0=B8=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_White/MechComp/MechCompSystem.cs | 18 ++++- .../_White/MechComp/Devices/Transeiver.cs | 64 ++++++++++++++++++ .../_White/MechComp/MechCompDeviceSystem.cs | 1 + .../Objects/Devices/MechComp/some.yml | 17 ++++- .../MechComp/transeiver.rsi/anchored.png | Bin 0 -> 317 bytes .../White/MechComp/transeiver.rsi/blink.png | Bin 0 -> 334 bytes .../White/MechComp/transeiver.rsi/icon.png | Bin 0 -> 387 bytes .../White/MechComp/transeiver.rsi/meta.json | 28 ++++++++ 8 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 Content.Server/_White/MechComp/Devices/Transeiver.cs create mode 100644 Resources/Textures/White/MechComp/transeiver.rsi/anchored.png create mode 100644 Resources/Textures/White/MechComp/transeiver.rsi/blink.png create mode 100644 Resources/Textures/White/MechComp/transeiver.rsi/icon.png create mode 100644 Resources/Textures/White/MechComp/transeiver.rsi/meta.json diff --git a/Content.Client/_White/MechComp/MechCompSystem.cs b/Content.Client/_White/MechComp/MechCompSystem.cs index 725f2cca23a..1abc707c5bd 100644 --- a/Content.Client/_White/MechComp/MechCompSystem.cs +++ b/Content.Client/_White/MechComp/MechCompSystem.cs @@ -59,7 +59,8 @@ public override void Initialize() SubscribeLocalEvent(OnSpeakerInit); SubscribeLocalEvent(OnSpeakerAppearanceChange); - + SubscribeLocalEvent(OnTranseiverInit); + SubscribeLocalEvent(OnTranseiverAppearanceChange); } private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args) { @@ -71,7 +72,6 @@ private void OnButtonAppearanceChange(EntityUid uid, MechCompButtonComponent com 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"); - args.Sprite?.LayerSetAnimationTime(MechCompDeviceVisualLayers.Base, 0); // hack: Stop()ing and immediately Play()ing an animation does not seem to restart it } } @@ -85,7 +85,6 @@ private void OnSpeakerAppearanceChange(EntityUid uid, MechCompSpeakerComponent c 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"); - args.Sprite?.LayerSetAnimationTime(MechCompDeviceVisualLayers.Effect1, 0); // hack: Stop()ing and immediately Play()ing an animation does not seem to restart it } } @@ -122,6 +121,19 @@ private void OnTeleportAppearanceChange(EntityUid uid, MechCompTeleportComponent } } } + + 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"); + } + } } 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/MechCompDeviceSystem.cs b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs index 198cd785dab..4aca5c07af3 100644 --- a/Content.Server/_White/MechComp/MechCompDeviceSystem.cs +++ b/Content.Server/_White/MechComp/MechCompDeviceSystem.cs @@ -79,6 +79,7 @@ public override void Initialize() InitComparer(); InitCalculator(); InitPressurePad(); + InitTranseiver(); } #region Helper functions diff --git a/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml index f2e2b0056d5..171c5753bfc 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/MechComp/some.yml @@ -36,11 +36,14 @@ 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: @@ -105,7 +108,7 @@ name: MechComp Calculator id: MechCompMath parent: MechCompBase - description: A general-purspose math component, able to perform a number of operations on two values. + description: A general-purspose component, able to perform a number of operations on two values. components: - type: MechCompMath - type: Sprite @@ -115,7 +118,7 @@ name: MechComp Pressure Pad id: MechCompPressurePad parent: MechCompBase - description: Sends a signal when stepped on. + description: Sends a signal when threaded on. components: - type: MechCompPressurePad - type: Sprite @@ -152,3 +155,13 @@ - 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/transeiver.rsi/anchored.png b/Resources/Textures/White/MechComp/transeiver.rsi/anchored.png new file mode 100644 index 0000000000000000000000000000000000000000..7b11c5cf61f411113f5d9eb0f777c7c8ac7f8bfc GIT binary patch literal 317 zcmV-D0mA-?P)Z?!&lLJ(xZlWkcUDscAAq4U~ zZ@3UF0Ao(qhqJGg0uiwwdIQ+zGRd5KiZo_Juo5ulI(7daWRjui=#2tkoByVu(-=C9 zA(IT3^KKdd+x#~LQ|n4+6oBJ)4FGM-CIw?``t-W@zLPbGgb-gKw$y&ZxvEtF^#7Dr zrEZi`?Yg)<|A2^yHIP^UE(lv(b6~(ET!k_qqWMPgP@^u*{u+7!58wgb;FoFI%Uo_W P00000NkvXXu0mjfvi*f; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b600b9437720ed71077586b3d656da20c1946a47 GIT binary patch literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=%;U|zyuIa?Cv2_lM3;z1tBV z#W;?B51u~f@dlZVoLeV{GViI_{_f<*x>vJ|6;C$=)XZVcyZi8(?B6R7^KNUlM6$I! zdaU!|y1lr{hW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ecb2096a55df850a44b3075a855eae0f27a70b6a GIT binary patch literal 387 zcmV-}0et?6P)Nkl5dr6h=Q+kvnwAnuRXRd5UgPro2T)`XFTNTV$%T@f6uBHDb%up}N8Xr2{e|wy*h# zpo{Nhz;KQ~8yUoQpJ49#{5Ul2r`hR71%V(C1cJcAJaIl9Z&L!-o1I_<&ZpxoilQm` zdVr0Dl>h+5aeQ}NmSxkfs;W0k-=~xU5rK%TK2Cx515Du8?~51u`ut;^lTre}FoB{d z?nVv(mtu*x_XBfBU0Iq~5=tqyKP$k&v(&$R>prd|dJA;HxfHTAL6#<#gp`s^!70$5 zcNd&XL0@x8aOGWuK)tA3a5DuH+; Date: Wed, 20 Mar 2024 00:57:16 +0300 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=D0=B0=D0=BD=D1=82=D0=B0=D0=B3=20?= =?UTF-8?q?=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=B0=D1=91=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=D1=85?= =?UTF-8?q?=D0=B0=D0=BB=D1=8F=D0=B2=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=B2?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=B5=2012345=20=D0=B2=20=D0=BA=D0=B0=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B2=D0=B5=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D1=81=D0=BE=D0=BD=D0=B0=D0=B6=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Server/_White/MechComp/Devices/Teleporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/_White/MechComp/Devices/Teleporter.cs b/Content.Server/_White/MechComp/Devices/Teleporter.cs index 5f7a22fc387..d5cfe53057c 100644 --- a/Content.Server/_White/MechComp/Devices/Teleporter.cs +++ b/Content.Server/_White/MechComp/Devices/Teleporter.cs @@ -39,7 +39,7 @@ private void OnTeleportSignal(EntityUid uid, MechCompTeleportComponent comp, ref if (IsOnCooldown(uid, "teleport")) { //_audio.PlayPvs("/Audio/White/MechComp/generic_energy_dryfire.ogg", uid); - //return; + return; } if (!TryGetMechCompSignal(args.Data, out string _sig) || !int.TryParse(_sig, System.Globalization.NumberStyles.HexNumber, null, out int targetId) ||