Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new "OptionsVisualizer" #25128

Merged
Merged
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
84 changes: 84 additions & 0 deletions Content.Client/Options/OptionsVisualizerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Content.Shared.CCVar;

namespace Content.Client.Options;

/// <summary>
/// Allows specifying sprite alternatives depending on the client's accessibility options.
/// </summary>
/// <remarks>
/// A list of layer mappings is given that the component applies to,
/// and it will pick one entry to apply based on the settings configuration. Example:
///
/// <code>
/// - type: Sprite
/// sprite: Effects/optionsvisualizertest.rsi
/// layers:
/// - state: none
/// map: [ "layer" ]
/// - type: OptionsVisualizer
/// visuals:
/// layer:
/// - options: Default
/// data: { state: none }
/// - options: Test
/// data: { state: test }
/// - options: ReducedMotion
/// data: { state: motion }
/// - options: [Test, ReducedMotion]
/// data: { state: both }
/// </code>
/// </remarks>
/// <seealso cref="OptionsVisualizerSystem"/>
/// <seealso cref="OptionVisualizerOptions"/>
[RegisterComponent]
public sealed partial class OptionsVisualizerComponent : Component
{
/// <summary>
/// A mapping storing data about which sprite layer keys should be controlled.
/// </summary>
/// <remarks>
/// Each layer stores an array of possible options. The last entry with a
/// <see cref="LayerDatum.Options"/> matching the active user preferences will be picked.
/// This allows choosing a priority if multiple entries are matched.
/// </remarks>
[DataField(required: true)]
public Dictionary<string, LayerDatum[]> Visuals = default!;

/// <summary>
/// A single option for a layer to be selected.
/// </summary>
[DataDefinition]
public sealed partial class LayerDatum
{
/// <summary>
/// Which options must be set by the user to make this datum match.
/// </summary>
[DataField]
public OptionVisualizerOptions Options { get; set; }

/// <summary>
/// The sprite layer data to set on the sprite when this datum matches.
/// </summary>
[DataField]
public PrototypeLayerData Data { get; set; }
}
}

[Flags]
public enum OptionVisualizerOptions
{
/// <summary>
/// Corresponds to no special options being set, can be used as a "default" state.
/// </summary>
Default = 0,

/// <summary>
/// Corresponds to the <see cref="CCVars.DebugOptionVisualizerTest"/> CVar being set.
/// </summary>
Test = 1 << 0,

/// <summary>
/// Corresponds to the <see cref="CCVars.ReducedMotion"/> CVar being set.
/// </summary>
ReducedMotion = 1 << 1,
}
97 changes: 97 additions & 0 deletions Content.Client/Options/OptionsVisualizerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using Content.Shared.CCVar;
using Robust.Client.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Reflection;

namespace Content.Client.Options;

/// <summary>
/// Implements <see cref="OptionsVisualizerComponent"/>.
/// </summary>
public sealed class OptionsVisualizerSystem : EntitySystem
{
private static readonly (OptionVisualizerOptions, CVarDef<bool>)[] OptionVars =
{
(OptionVisualizerOptions.Test, CCVars.DebugOptionVisualizerTest),
(OptionVisualizerOptions.ReducedMotion, CCVars.ReducedMotion),
};

[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;

private OptionVisualizerOptions _currentOptions;

public override void Initialize()
{
base.Initialize();

foreach (var (_, cvar) in OptionVars)
{
Subs.CVar(_cfg, cvar, _ => CVarChanged());
}

UpdateActiveOptions();

SubscribeLocalEvent<OptionsVisualizerComponent, ComponentStartup>(OnComponentStartup);
}

private void CVarChanged()
{
UpdateActiveOptions();
UpdateAllComponents();
}

private void UpdateActiveOptions()
{
_currentOptions = OptionVisualizerOptions.Default;

foreach (var (value, cVar) in OptionVars)
{
if (_cfg.GetCVar(cVar))
_currentOptions |= value;
}
}

private void UpdateAllComponents()
{
var query = EntityQueryEnumerator<OptionsVisualizerComponent, SpriteComponent>();
while (query.MoveNext(out _, out var component, out var sprite))
{
UpdateComponent(component, sprite);
}
}

private void OnComponentStartup(EntityUid uid, OptionsVisualizerComponent component, ComponentStartup args)
{
if (!TryComp(uid, out SpriteComponent? sprite))
return;

UpdateComponent(component, sprite);
}

private void UpdateComponent(OptionsVisualizerComponent component, SpriteComponent sprite)
{
foreach (var (layerKeyRaw, layerData) in component.Visuals)
{
object layerKey = _reflection.TryParseEnumReference(layerKeyRaw, out var @enum)
? @enum
: layerKeyRaw;

OptionsVisualizerComponent.LayerDatum? matchedDatum = null;
foreach (var datum in layerData)
{
if ((datum.Options & _currentOptions) != datum.Options)
continue;

matchedDatum = datum;
}

if (matchedDatum == null)
continue;

var layerIndex = sprite.LayerMapReserveBlank(layerKey);
sprite.LayerSetData(layerIndex, matchedDatum.Data);
}
}
}

1 change: 1 addition & 0 deletions Content.Server/Entry/IgnoredComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static class IgnoredComponents
"InventorySlots",
"LightFade",
"HolidayRsiSwap",
"OptionsVisualizer",
};
}
}
10 changes: 10 additions & 0 deletions Content.Shared/CCVar/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2011,5 +2011,15 @@ public static readonly CVarDef<float>

public static readonly CVarDef<bool> GatewayGeneratorEnabled =
CVarDef.Create("gateway.generator_enabled", true);

/*
* DEBUG
*/

/// <summary>
/// A simple toggle to test <c>OptionsVisualizerComponent</c>.
/// </summary>
public static readonly CVarDef<bool> DebugOptionVisualizerTest =
CVarDef.Create("debug.option_visualizer_test", false, CVar.CLIENTONLY);
}
}
24 changes: 24 additions & 0 deletions Resources/Prototypes/Entities/Debugging/options_visualizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
- type: entity
id: OptionsVisualizerTest
suffix: DEBUG
components:
- type: Tag
tags:
- Debug
- type: Sprite
sprite: Effects/optionsvisualizertest.rsi
layers:
- state: none
map: [ "layer" ]
- type: OptionsVisualizer
visuals:
layer:
- options: Default
data: { state: none }
- options: Test
data: { state: test }
- options: ReducedMotion
data: { state: motion }
- options: [Test, ReducedMotion]
data: { state: both }

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"size":{"x":32,"y":32},"license":"CC-BY-SA-4.0","copyright":"Discord pjb","states":[{"name":"none"},{"name":"both"},{"name":"motion"},{"name":"test"}]}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading