Skip to content
This repository was archived by the owner on Aug 22, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions Content.Client/UserInterface/Controls/DialogWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public sealed partial class DialogWindow : FancyWindow

private List<(string, Func<string>)> _promptLines;

private delegate (Control, Func<string>) ControlConstructor(Func<string, bool> verifier, string name, QuickDialogEntry entry, bool last, object? value);// are you feeling it now mr krabs?
private delegate (Control, Func<string>) ControlConstructor(Func<string, bool> verifier, string name, QuickDialogEntry entry, bool last, object? value, object? info);// are you feeling it now mr krabs?

/// <summary>
/// Create and open a new dialog with some prompts.
Expand All @@ -63,6 +63,7 @@ public DialogWindow(string title, List<QuickDialogEntry> 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 });

Expand All @@ -72,7 +73,7 @@ public DialogWindow(string title, List<QuickDialogEntry> 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<string>, a """generic""" function that returns whatever user has done with the control
Expand All @@ -85,16 +86,17 @@ public DialogWindow(string title, List<QuickDialogEntry> 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);
Expand Down Expand Up @@ -165,7 +167,7 @@ private bool VerifyHex16(string input)



private (Control, Func<string>) SetupLineEdit(Func<string, bool> valid, string name, QuickDialogEntry entry, bool last, object? value) // oh shit i'm feeling it
private (Control, Func<string>) SetupLineEdit(Func<string, bool> 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;
Expand All @@ -180,15 +182,15 @@ private bool VerifyHex16(string input)

return (edit, () => {return edit.Text;} );
}
private (Control, Func<string>) SetupLineEditNumber(Func<string, bool> valid, string name, QuickDialogEntry entry, bool last, object? value)
private (Control, Func<string>) SetupLineEditNumber(Func<string, bool> 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<string>) SetupLineEditHex(Func<string, bool> valid, string name, QuickDialogEntry entry, bool last, object? value)
private (Control, Func<string>) SetupLineEditHex(Func<string, bool> 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)
{
Expand All @@ -198,20 +200,32 @@ private bool VerifyHex16(string input)
return (control, returner);
}

private (Control, Func<string>) SetupCheckBox(Func<string, bool> _, string name, QuickDialogEntry entry, bool last, object? value)
private (Control, Func<string>) SetupCheckBox(Func<string, bool> _, 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<string>) SetupVoid(Func<string, bool> _, string __, QuickDialogEntry ___, bool ____, object? _____)
private (Control, Func<string>) SetupVoid(Func<string, bool> _, string __, QuickDialogEntry ___, bool ____, object? _____, object? ______)
{
var control = new Control();
control.Visible = false;
return (control, () => "" );
return (control, () => "");
}

private (Control, Func<string>) SetupRadio(Func<string, bool> valid, string name, QuickDialogEntry entry, bool last, object? value, object? info)
{
var control = new RadioOptions<string>(RadioOptionsLayout.Vertical);
string val = ((string?) value) ?? "";
foreach(var option in (IEnumerable<string>) info!)
{
control.AddItem(option, option, (args) => control.Select(args.Id));
}
control.SelectByValue(val);
return (control, () => control.SelectedValue);
}


Expand Down
217 changes: 217 additions & 0 deletions Content.Client/_White/MechComp/MechCompSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
using Content.Shared._White.MechComp;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
using Content.Shared.Interaction;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Content.Client.Salvage.FultonSystem;

namespace Content.Client._White.MechComp;

public sealed partial class MechCompDeviceSystem : SharedMechCompDeviceSystem
{
//[Dependency] private readonly DeviceLinkSystem _link = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly AnimationPlayerSystem _anim = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;

Dictionary<(EntityUid, TimeSpan, RSI.StateId, string, object), Animation> _cachedAnims = new();
/// <summary>
/// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die)
/// </summary>
[Obsolete("This was added in a fit of rage: may be removed later.")]


private bool GetMode<T>(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<MechCompTeleportComponent, ComponentInit>(OnTeleportInit);
SubscribeLocalEvent<MechCompTeleportComponent, AppearanceChangeEvent>(OnTeleportAppearanceChange);

SubscribeLocalEvent<MechCompButtonComponent, ComponentInit>(OnButtonInit);
SubscribeLocalEvent<MechCompButtonComponent, AppearanceChangeEvent>(OnButtonAppearanceChange);

SubscribeLocalEvent<MechCompSpeakerComponent, ComponentInit>(OnSpeakerInit);
SubscribeLocalEvent<MechCompSpeakerComponent, AppearanceChangeEvent>(OnSpeakerAppearanceChange);

SubscribeLocalEvent<MechCompTranseiverComponent, ComponentInit>(OnTranseiverInit);
SubscribeLocalEvent<MechCompTranseiverComponent, AppearanceChangeEvent>(OnTranseiverAppearanceChange);
}
private void OnButtonInit(EntityUid uid, MechCompButtonComponent comp, ComponentInit args)
{
comp.pressedAnimation = _prepFlickAnim("pressed", 0.5f, MechCompDeviceVisualLayers.Base);
}

private void OnButtonAppearanceChange(EntityUid uid, MechCompButtonComponent comp, AppearanceChangeEvent args)
{
if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key.
{
_anim.SafePlay(uid, (Animation) comp.pressedAnimation, "button");
}
}

private void OnSpeakerInit(EntityUid uid, MechCompSpeakerComponent comp, ComponentInit args)
{
comp.speakAnimation = _prepFlickAnim("speak", 0.6f, MechCompDeviceVisualLayers.Effect1);
}

private void OnSpeakerAppearanceChange(EntityUid uid, MechCompSpeakerComponent comp, AppearanceChangeEvent args)
{
if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key.
{
_anim.SafePlay(uid, (Animation) comp.speakAnimation, "speaker");
}
}

private void OnTeleportInit(EntityUid uid, MechCompTeleportComponent comp, ComponentInit args)
{
comp.firingAnimation = _prepFlickAnim("firing", 0.5f, MechCompDeviceVisualLayers.Effect2);
var sprite = Comp<SpriteComponent>(uid);
sprite.LayerSetState(sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect1), "ready");
}

private void OnTeleportAppearanceChange(EntityUid uid, MechCompTeleportComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
//_handleAnchored(args);

var effectlayer1 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect1); // used for ready and charging states
//var effectlayer2 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect2); // used for firing animation; layer specified in animation itself
//var effectlayer3 = args.Sprite.LayerMapGet(MechCompDeviceVisualLayers.Effect3); // (will be) used for a glow effect

if (GetMode(uid, out string mode)){
switch (mode)
{
case "ready":
args.Sprite?.LayerSetState(effectlayer1, "ready");
break;
case "firing":
args.Sprite?.LayerSetState(effectlayer1, "charging");
_anim.SafePlay(uid, (Animation)comp.firingAnimation, "teleport");
break;
case "charging":
args.Sprite?.LayerSetState(effectlayer1, "charging");
break;
}
}
}

private void OnTranseiverInit(EntityUid uid, MechCompTranseiverComponent comp, ComponentInit args)
{
comp.blinkAnimation = _prepFlickAnim("blink", 0.2f, MechCompDeviceVisualLayers.Effect1);
}

private void OnTranseiverAppearanceChange(EntityUid uid, MechCompTranseiverComponent comp, ref AppearanceChangeEvent args)
{
if (GetMode(uid, out string _)) // not expecting any specific value, only the fact that it's under the MechCompVisuals.Mode key.
{
_anim.SafePlay(uid, (Animation) comp.blinkAnimation, "blink");
}
}
}


/// <summary>
/// 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.
/// </summary>
[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<MechCompAnchoredVisualizerComponent>
{
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);

}
}
}
}

/// <summary>
/// Move up from this namespace as needed
/// </summary>
public static class SafePlayExt
{
/// <summary>
/// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die)
/// </summary>
[Obsolete("This was added in a fit of rage: may be removed later.")]
public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, Animation animation, string key)
{
var component = IoCManager.Resolve<EntityManager>().EnsureComponent<AnimationPlayerComponent>(uid);
anim.Stop(component, key);
anim.Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
/// <summary>
/// Start playing an animation. If an animation under given key is already playing, replace it instead of the default behaviour (Shit pants and die)
/// </summary>
[Obsolete("This was added in a fit of rage: may be removed later.")]
public static void SafePlay(this AnimationPlayerSystem anim, EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
{
component ??= IoCManager.Resolve<EntityManager>().EnsureComponent<AnimationPlayerComponent>(uid);
anim.Stop(component, key);
anim.Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
}
14 changes: 9 additions & 5 deletions Content.Server/Administration/QuickDialogSystem.OpenDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,20 +564,22 @@ public void OpenDialog<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(ICommonSession s
/// <param name="cancelAction">The action to execute upon the dialog being cancelled.</param>
/// <remarks>
/// 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.
///
/// </remarks>

[PublicAPI]
public void OpenDialog(ICommonSession session, string title, List<(Type, string, object)> dialogEntries, Action<object[]> okAction, Action? cancelAction = null)
public void OpenDialog(ICommonSession session, string title, List<QDEntry> dialogEntries, Action<object[]> okAction, Action? cancelAction = null)
{
List<QuickDialogEntry> _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


Expand All @@ -602,3 +604,5 @@ public void OpenDialog(ICommonSession session, string title, List<(Type, string,
);
}
}


Loading