Hair style improvements:
1. made the magic mirror actually reflect your current hair state when you open it. 2. Made magic mirror one window. 3. Use color sliders for magic mirror.
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Preferences.Appearance;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects.Components.Renderable;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.GameObjects.Components.SharedMagicMirrorComponent;
|
||||
using static Content.Client.StaticIoC;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class MagicMirrorBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IResourceCache _resourceCache;
|
||||
[Dependency] private readonly ILocalizationManager _localization;
|
||||
#pragma warning restore 649
|
||||
private MagicMirrorWindow _window;
|
||||
|
||||
public MagicMirrorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
@@ -30,19 +30,30 @@ namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new MagicMirrorWindow(this,_resourceCache, _localization);
|
||||
_window = new MagicMirrorWindow(this);
|
||||
_window.OnClose += Close;
|
||||
_window.Open();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case MagicMirrorInitialDataMessage initialData:
|
||||
_window.SetInitialData(initialData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void HairSelected(string name, bool isFacialHair)
|
||||
{
|
||||
SendMessage(new SharedMagicMirrorComponent.HairSelectedMessage(name, isFacialHair));
|
||||
SendMessage(new HairSelectedMessage(name, isFacialHair));
|
||||
}
|
||||
|
||||
internal void HairColorSelected(Color color, bool isFacialHair)
|
||||
{
|
||||
SendMessage(new SharedMagicMirrorComponent.HairColorSelectedMessage(color, isFacialHair));
|
||||
SendMessage(new HairColorSelectedMessage((color.RByte, color.GByte, color.BByte),
|
||||
isFacialHair));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -56,61 +67,76 @@ namespace Content.Client.GameObjects.Components
|
||||
}
|
||||
}
|
||||
|
||||
public class FacialHairPickerWindow : HairPickerWindow
|
||||
public class FacialHairStylePicker : HairStylePicker
|
||||
{
|
||||
public FacialHairPickerWindow(IResourceCache resourceCache, ILocalizationManager localization) : base(resourceCache, localization)
|
||||
{
|
||||
Title = "Facial hair";
|
||||
}
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
var humanFacialHairRSIPath = SharedSpriteComponent.TextureRoot / "Mob/human_facial_hair.rsi";
|
||||
var humanFacialHairRSI = ResourceCache.GetResource<RSIResource>(humanFacialHairRSIPath).RSI;
|
||||
foreach (var (styleName, styleState) in HairStyles.FacialHairStylesMap)
|
||||
var humanFacialHairRSI = ResC.GetResource<RSIResource>(humanFacialHairRSIPath).RSI;
|
||||
|
||||
var styles = HairStyles.FacialHairStylesMap.ToList();
|
||||
styles.Sort(HairStyles.FacialHairStyleComparer);
|
||||
|
||||
foreach (var (styleName, styleState) in styles)
|
||||
{
|
||||
Items.AddItem(styleName, humanFacialHairRSI[styleState].Frame0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class HairPickerWindow : SS14Window
|
||||
public class HairStylePicker : Control
|
||||
{
|
||||
public event Action<Color> OnHairColorPicked;
|
||||
public event Action<string> OnHairStylePicked;
|
||||
|
||||
protected readonly IResourceCache ResourceCache;
|
||||
protected readonly ItemList Items;
|
||||
protected override Vector2? CustomSize => (300, 300);
|
||||
public HairPickerWindow(IResourceCache resourceCache, ILocalizationManager localization)
|
||||
{
|
||||
Title = "Hair";
|
||||
ResourceCache = resourceCache;
|
||||
var vBox = new VBoxContainer();
|
||||
Contents.AddChild(vBox);
|
||||
|
||||
var colorHBox = new HBoxContainer();
|
||||
vBox.AddChild(colorHBox);
|
||||
private readonly ColorSlider _colorSliderR;
|
||||
private readonly ColorSlider _colorSliderG;
|
||||
private readonly ColorSlider _colorSliderB;
|
||||
|
||||
var colorLabel = new Label
|
||||
{
|
||||
Text = localization.GetString("Color: ")
|
||||
};
|
||||
colorHBox.AddChild(colorLabel);
|
||||
private Color _lastColor;
|
||||
|
||||
var colorEdit = new LineEdit
|
||||
public void SetInitialData(Color color, string styleName)
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
colorEdit.OnTextChanged += args =>
|
||||
_lastColor = color;
|
||||
|
||||
_colorSliderR.ColorValue = color.RByte;
|
||||
_colorSliderG.ColorValue = color.GByte;
|
||||
_colorSliderB.ColorValue = color.BByte;
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
var color = Color.TryFromHex(args.Text);
|
||||
if (color.HasValue)
|
||||
if (item.Text == styleName)
|
||||
{
|
||||
OnHairColorPicked?.Invoke(color.Value);
|
||||
item.Selected = true;
|
||||
}
|
||||
};
|
||||
colorHBox.AddChild(colorEdit);
|
||||
}
|
||||
|
||||
UpdateStylePickerColor();
|
||||
}
|
||||
|
||||
private void UpdateStylePickerColor()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
item.IconModulate = _lastColor;
|
||||
}
|
||||
}
|
||||
|
||||
public HairStylePicker()
|
||||
{
|
||||
var vBox = new VBoxContainer();
|
||||
AddChild(vBox);
|
||||
|
||||
vBox.AddChild(_colorSliderR = new ColorSlider(NanoStyle.StyleClassSliderRed));
|
||||
vBox.AddChild(_colorSliderG = new ColorSlider(NanoStyle.StyleClassSliderGreen));
|
||||
vBox.AddChild(_colorSliderB = new ColorSlider(NanoStyle.StyleClassSliderBlue));
|
||||
|
||||
Action colorValueChanged = ColorValueChanged;
|
||||
_colorSliderR.OnValueChanged += colorValueChanged;
|
||||
_colorSliderG.OnValueChanged += colorValueChanged;
|
||||
_colorSliderB.OnValueChanged += colorValueChanged;
|
||||
|
||||
Items = new ItemList
|
||||
{
|
||||
@@ -120,11 +146,28 @@ namespace Content.Client.GameObjects.Components
|
||||
Items.OnItemSelected += ItemSelected;
|
||||
}
|
||||
|
||||
private void ColorValueChanged()
|
||||
{
|
||||
var newColor = new Color(
|
||||
_colorSliderR.ColorValue,
|
||||
_colorSliderG.ColorValue,
|
||||
_colorSliderB.ColorValue
|
||||
);
|
||||
|
||||
OnHairColorPicked?.Invoke(newColor);
|
||||
_lastColor = newColor;
|
||||
UpdateStylePickerColor();
|
||||
}
|
||||
|
||||
public virtual void Populate()
|
||||
{
|
||||
var humanHairRSIPath = SharedSpriteComponent.TextureRoot / "Mob/human_hair.rsi";
|
||||
var humanHairRSI = ResourceCache.GetResource<RSIResource>(humanHairRSIPath).RSI;
|
||||
foreach (var (styleName, styleState) in HairStyles.HairStylesMap)
|
||||
var humanHairRSI = ResC.GetResource<RSIResource>(humanHairRSIPath).RSI;
|
||||
|
||||
var styles = HairStyles.HairStylesMap.ToList();
|
||||
styles.Sort(HairStyles.HairStyleComparer);
|
||||
|
||||
foreach (var (styleName, styleState) in styles)
|
||||
{
|
||||
Items.AddItem(styleName, humanHairRSI[styleState].Frame0);
|
||||
}
|
||||
@@ -134,43 +177,114 @@ namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
OnHairStylePicked?.Invoke(Items[args.ItemIndex].Text);
|
||||
}
|
||||
|
||||
private sealed class ColorSlider : Control
|
||||
{
|
||||
private readonly Slider _slider;
|
||||
private readonly LineEdit _textBox;
|
||||
private byte _colorValue;
|
||||
private bool _ignoreEvents;
|
||||
|
||||
public event Action OnValueChanged;
|
||||
|
||||
public byte ColorValue
|
||||
{
|
||||
get => _colorValue;
|
||||
set
|
||||
{
|
||||
_ignoreEvents = true;
|
||||
_colorValue = value;
|
||||
_slider.Value = value;
|
||||
_textBox.Text = value.ToString();
|
||||
_ignoreEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
public ColorSlider(string styleClass)
|
||||
{
|
||||
_slider = new Slider
|
||||
{
|
||||
StyleClasses = {styleClass},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
MaxValue = byte.MaxValue
|
||||
};
|
||||
_textBox = new LineEdit
|
||||
{
|
||||
CustomMinimumSize = (50, 0)
|
||||
};
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
_slider,
|
||||
_textBox
|
||||
}
|
||||
});
|
||||
|
||||
_slider.OnValueChanged += _ =>
|
||||
{
|
||||
if (_ignoreEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_colorValue = (byte) _slider.Value;
|
||||
_textBox.Text = _colorValue.ToString();
|
||||
|
||||
OnValueChanged?.Invoke();
|
||||
};
|
||||
|
||||
_textBox.OnTextChanged += ev =>
|
||||
{
|
||||
if (_ignoreEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.TryParse(ev.Text, out var result))
|
||||
{
|
||||
result = result.Clamp(0, byte.MaxValue);
|
||||
|
||||
_ignoreEvents = true;
|
||||
_colorValue = (byte) result;
|
||||
_slider.Value = result;
|
||||
_ignoreEvents = false;
|
||||
|
||||
OnValueChanged?.Invoke();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MagicMirrorWindow : SS14Window
|
||||
{
|
||||
private readonly HairPickerWindow _hairPickerWindow;
|
||||
private readonly FacialHairPickerWindow _facialHairPickerWindow;
|
||||
private readonly HairStylePicker _hairStylePicker;
|
||||
private readonly FacialHairStylePicker _facialHairStylePicker;
|
||||
|
||||
public MagicMirrorWindow(MagicMirrorBoundUserInterface owner, IResourceCache resourceCache, ILocalizationManager localization)
|
||||
protected override Vector2? CustomSize => (500, 360);
|
||||
|
||||
public MagicMirrorWindow(MagicMirrorBoundUserInterface owner)
|
||||
{
|
||||
Title = "Magic Mirror";
|
||||
Title = Loc.GetString("Magic Mirror");
|
||||
|
||||
_hairPickerWindow = new HairPickerWindow(resourceCache, localization);
|
||||
_hairPickerWindow.Populate();
|
||||
_hairPickerWindow.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, false);
|
||||
_hairPickerWindow.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, false);
|
||||
_hairStylePicker = new HairStylePicker {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||
_hairStylePicker.Populate();
|
||||
_hairStylePicker.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, false);
|
||||
_hairStylePicker.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, false);
|
||||
|
||||
_facialHairPickerWindow = new FacialHairPickerWindow(resourceCache, localization);
|
||||
_facialHairPickerWindow.Populate();
|
||||
_facialHairPickerWindow.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, true);
|
||||
_facialHairPickerWindow.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, true);
|
||||
_facialHairStylePicker = new FacialHairStylePicker {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||
_facialHairStylePicker.Populate();
|
||||
_facialHairStylePicker.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, true);
|
||||
_facialHairStylePicker.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, true);
|
||||
|
||||
var vBox = new VBoxContainer();
|
||||
Contents.AddChild(vBox);
|
||||
|
||||
var hairButton = new Button
|
||||
Contents.AddChild(new HBoxContainer
|
||||
{
|
||||
Text = localization.GetString("Customize hair")
|
||||
};
|
||||
hairButton.OnPressed += args => _hairPickerWindow.Open();
|
||||
vBox.AddChild(hairButton);
|
||||
|
||||
var facialHairButton = new Button
|
||||
{
|
||||
Text = localization.GetString("Customize facial hair")
|
||||
};
|
||||
facialHairButton.OnPressed += args => _facialHairPickerWindow.Open();
|
||||
vBox.AddChild(facialHairButton);
|
||||
SeparationOverride = 8,
|
||||
Children = {_hairStylePicker, _facialHairStylePicker}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -179,9 +293,15 @@ namespace Content.Client.GameObjects.Components
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_hairPickerWindow.Dispose();
|
||||
_facialHairPickerWindow.Dispose();
|
||||
_hairStylePicker.Dispose();
|
||||
_facialHairStylePicker.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInitialData(MagicMirrorInitialDataMessage initialData)
|
||||
{
|
||||
_facialHairStylePicker.SetInitialData(initialData.FacialHairColor, initialData.FacialHairName);
|
||||
_hairStylePicker.SetInitialData(initialData.HairColor, initialData.HairName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Preferences.Appearance;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
@@ -24,7 +27,11 @@ namespace Content.Server.GameObjects.Components
|
||||
|
||||
private static void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
var hair = obj.Session.AttachedEntity.GetComponent<HairComponent>();
|
||||
if (!obj.Session.AttachedEntity.TryGetComponent(out HairComponent hair))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (obj.Message)
|
||||
{
|
||||
case HairSelectedMessage msg:
|
||||
@@ -43,14 +50,18 @@ namespace Content.Server.GameObjects.Components
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HairColorSelectedMessage msg:
|
||||
var (r, g, b) = msg.HairColor;
|
||||
var color = new Color(r, g, b);
|
||||
|
||||
if (msg.IsFacialHair)
|
||||
{
|
||||
hair.FacialHairColor = msg.HairColor;
|
||||
hair.FacialHairColor = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
hair.HairColor = msg.HairColor;
|
||||
hair.HairColor = color;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -64,7 +75,18 @@ namespace Content.Server.GameObjects.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventArgs.User.TryGetComponent(out HairComponent hair))
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't have any hair!"));
|
||||
return;
|
||||
}
|
||||
|
||||
_userInterface.Open(actor.playerSession);
|
||||
|
||||
var msg = new MagicMirrorInitialDataMessage(hair.HairColor, hair.FacialHairColor, hair.HairStyleName,
|
||||
hair.FacialHairStyleName);
|
||||
|
||||
_userInterface.SendMessage(msg, actor.playerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Content.Shared.Preferences.Appearance;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -8,10 +9,12 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
||||
{
|
||||
public abstract class SharedHairComponent : Component
|
||||
{
|
||||
private string _facialHairStyleName;
|
||||
private string _hairStyleName;
|
||||
private Color _hairColor;
|
||||
private Color _facialHairColor;
|
||||
private static readonly Color DefaultHairColor = Color.FromHex("#232323");
|
||||
|
||||
private string _facialHairStyleName = HairStyles.DefaultFacialHairStyle;
|
||||
private string _hairStyleName = HairStyles.DefaultHairStyle;
|
||||
private Color _hairColor = DefaultHairColor;
|
||||
private Color _facialHairColor = DefaultHairColor;
|
||||
|
||||
public sealed override string Name => "Hair";
|
||||
public sealed override uint? NetID => ContentNetIDs.HAIR;
|
||||
@@ -76,6 +79,16 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
||||
FacialHairColor = cast.FacialHairColor;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _hairColor, "hairColor", DefaultHairColor);
|
||||
serializer.DataField(ref _facialHairColor, "facialHairColor", DefaultHairColor);
|
||||
serializer.DataField(ref _hairStyleName, "hairStyle", HairStyles.DefaultHairStyle);
|
||||
serializer.DataField(ref _facialHairStyleName, "facialHairStyle", HairStyles.DefaultFacialHairStyle);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
private sealed class HairComponentState : ComponentState
|
||||
{
|
||||
|
||||
@@ -32,14 +32,31 @@ namespace Content.Shared.GameObjects.Components
|
||||
[Serializable, NetSerializable]
|
||||
public class HairColorSelectedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly Color HairColor;
|
||||
public (byte r, byte g, byte b) HairColor;
|
||||
public readonly bool IsFacialHair;
|
||||
|
||||
public HairColorSelectedMessage(Color color, bool isFacialHair)
|
||||
public HairColorSelectedMessage((byte r, byte g, byte b) color, bool isFacialHair)
|
||||
{
|
||||
HairColor = color;
|
||||
IsFacialHair = isFacialHair;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class MagicMirrorInitialDataMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly Color HairColor;
|
||||
public readonly Color FacialHairColor;
|
||||
public readonly string HairName;
|
||||
public readonly string FacialHairName;
|
||||
|
||||
public MagicMirrorInitialDataMessage(Color hairColor, Color facialHairColor, string hairName, string facialHairName)
|
||||
{
|
||||
HairColor = hairColor;
|
||||
FacialHairColor = facialHairColor;
|
||||
HairName = hairName;
|
||||
FacialHairName = facialHairName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
@@ -227,5 +228,45 @@ namespace Content.Shared.Preferences.Appearance
|
||||
{"Sideburns", "sideburn"},
|
||||
{"Shaved", "shaved"}
|
||||
};
|
||||
|
||||
// These comparers put the default hair style (shaved/bald) at the very top.
|
||||
// For in the hair style pickers.
|
||||
|
||||
public static readonly IComparer<KeyValuePair<string, string>> HairStyleComparer =
|
||||
Comparer<KeyValuePair<string, string>>.Create((a, b) =>
|
||||
{
|
||||
var styleA = a.Key;
|
||||
var styleB = b.Key;
|
||||
if (styleA == DefaultHairStyle)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (styleB == DefaultHairStyle)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return string.Compare(styleA, styleB, StringComparison.CurrentCulture);
|
||||
});
|
||||
|
||||
public static readonly IComparer<KeyValuePair<string, string>> FacialHairStyleComparer =
|
||||
Comparer<KeyValuePair<string, string>>.Create((a, b) =>
|
||||
{
|
||||
var styleA = a.Key;
|
||||
var styleB = b.Key;
|
||||
|
||||
if (styleA == DefaultFacialHairStyle)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (styleB == DefaultFacialHairStyle)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return string.Compare(styleA, styleB, StringComparison.CurrentCulture);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user