градиенты волос (#2652)

This commit is contained in:
Ligyb 2025-08-01 00:32:37 +05:00 committed by GitHub
parent d6aa7be5bf
commit d1ed5109e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 6274 additions and 69 deletions

View file

@ -23,15 +23,29 @@ public sealed class DisplacementMapSystem : EntitySystem
Entity<SpriteComponent> sprite,
int index,
object key,
out string displacementKey)
out string displacementKey,
ShaderInstance? shaderOverride = null) // Sunrise-Edit
{
displacementKey = $"{key}-displacement";
if (key.ToString() is null)
return false;
// Sunrise-Start
// sprite.Comp.LayerSetShader(index, data.ShaderOverride);
// TODO: костыль пиздец, когда появится возможность устанавливать 2 шейдера на один леер - удалить эту хуйню
if (data.ShaderOverride != null)
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
{
if (shaderOverride != null)
{
shaderOverride.SetParameter("useDisplacement", true);
shaderOverride.SetParameter("displacementSize", 127);
sprite.Comp.LayerSetShader(index, shaderOverride);
}
else
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
}
// Sunrise-End
_sprite.RemoveLayer(sprite.AsNullable(), displacementKey, false);

View file

@ -1,15 +1,19 @@
using System.Linq;
using System.Numerics;
using Content.Client._Sunrise.MarkingEffectsClient;
using Content.Client.DisplacementMap;
using Content.Shared.CCVar;
using Content.Shared.Humanoid;
using Content.Shared.CCVar;
using Content.Shared._Sunrise;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@ -181,14 +185,25 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager)
? profile.Appearance.SkinColor.WithAlpha(hairAlpha)
: profile.Appearance.HairColor;
var hairMarkingEffects = profile.Appearance.HairMarkingEffect != null
? new List<MarkingEffect> { profile.Appearance.HairMarkingEffect }
: new List<MarkingEffect>();
var hair = new Marking(profile.Appearance.HairStyleId,
new[] { hairColor });
new[] { hairColor },
hairMarkingEffects);
var facialHairMarkingEffects = profile.Appearance.FacialHairMarkingEffect != null
? new List<MarkingEffect> { profile.Appearance.FacialHairMarkingEffect }
: new List<MarkingEffect>();
var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager)
? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha)
: profile.Appearance.FacialHairColor;
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
new[] { facialHairColor });
new[] { facialHairColor },
facialHairMarkingEffects);
if (_markingManager.CanBeApplied(profile.Species, profile.Sex, hair, _prototypeManager))
{
@ -258,7 +273,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
{
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity);
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity, marking.MarkingEffects); // Sunrise-Edit
//if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
// applyUndergarmentTop = false;
//else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
@ -350,7 +365,8 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
public void ApplyMarking(MarkingPrototype markingPrototype, // Sunrise-Edit
IReadOnlyList<Color>? colors,
bool visible,
Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
Entity<HumanoidAppearanceComponent, SpriteComponent> entity,
IReadOnlyList<MarkingEffect>? markingEffects = null)
{
var humanoid = entity.Comp1;
var sprite = entity.Comp2;
@ -389,22 +405,55 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
continue;
}
// Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
// and we need to check the index is correct.
// So if that happens just default to white?
if (colors != null && j < colors.Count)
// Sunrise-Edit-Start
// // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
// // and we need to check the index is correct.
// // So if that happens just default to white?
// if (colors != null && j < colors.Count)
// {
// _sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
// }
// else
// {
// _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
// }
ShaderInstance? shaderOverride = null;
if (markingEffects != null && j < markingEffects.Count && markingEffects[j].Type != MarkingEffectType.Color)
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
float texWidth = sprite.AllLayers.Max(x => x.PixelSize.X);
float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y);
var shaderName = markingEffects[j].Type.ToString();
var instance = _prototypeManager.Index<ShaderPrototype>(shaderName).InstanceUnique();
shaderOverride = instance;
instance.ApplyShaderParams(markingEffects[j], new Vector2(texWidth, texHeight));
sprite.LayerSetShader(layerId, instance);
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
else
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
if (colors != null && j < colors.Count)
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
}
else
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
}
//Sunrise-Edit-End
var displacementData = GetMarkingDisplacement(entity.Owner, markingPrototype.BodyPart, humanoid);
if (displacementData != null && markingPrototype.CanBeDisplaced)
{
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _);
// TODO: в шейдер нужно ещё вставлять displacementSize, сейчас в нём хардкод 127
// TODO: костыль пиздец, когда появится возможность устанавливать 2 шейдера на один леер - удалить эту хуйню (shaderOverride)
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _, shaderOverride); // Sunrise-Edit
}
}
}
@ -498,7 +547,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
foreach (var marking in markingList)
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite));
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite), marking.MarkingEffects); // Sunrise-Edit
}
}
}

View file

@ -17,9 +17,11 @@
<ItemList Name="MarkingList" VerticalExpand="True" />
</ScrollContainer>
<!-- Sunrise-Edit -->
<!-- Color sliders -->
<ScrollContainer MinHeight="200" HorizontalExpand="True">
<BoxContainer Name="ColorSelectorContainer" HorizontalExpand="True" />
</ScrollContainer>
<BoxContainer Name="ColorSelectorContainer" HorizontalExpand="True" />
<!-- <ScrollContainer MinHeight="200" HorizontalExpand="True"> -->
<!-- <BoxContainer Name="ColorSelectorContainer" HorizontalExpand="True" /> -->
<!-- </ScrollContainer> -->
</BoxContainer>
</BoxContainer>

View file

@ -1,4 +1,6 @@
using System.Linq;
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.Humanoid.Markings;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@ -41,6 +43,10 @@ public sealed partial class SingleMarkingPicker : BoxContainer
/// </summary>
public Action<(int slot, Marking marking)>? OnColorChanged;
// sunrise gradient edit start
public Action<(int slot, Marking marking)>? OnExtendedColorChanged;
// sunrise gradient edit end
// current selected slot
private int _slot = -1;
private int Slot
@ -229,23 +235,40 @@ public sealed partial class SingleMarkingPicker : BoxContainer
ColorSelectorContainer.DisposeAllChildren();
ColorSelectorContainer.RemoveAllChildren();
if (marking.MarkingColors.Count != proto.Sprites.Count)
if (marking.MarkingColors.Count != proto.Sprites.Count ||
marking.MarkingEffects.Count != proto.Sprites.Count)
{
marking = new Marking(marking.MarkingId, proto.Sprites.Count);
}
for (var i = 0; i < marking.MarkingColors.Count; i++)
{
var selector = new ColorSelectorSliders
MarkingEffect selectorColor;
var selectorType = MarkingEffectType.Color;
if(marking.MarkingEffects == null)
selectorColor = new ColorMarkingEffect(marking.MarkingColors[i]);
else
{
selectorColor = marking.MarkingEffects[i];
selectorType = marking.MarkingEffects[i].Type;
}
var selector = new MarkingEffectSelectorSliders(selectorColor)
{
HorizontalExpand = true
};
selector.Color = marking.MarkingColors[i];
selector.CurrentType = selectorType;
var colorIndex = i;
selector.OnColorChanged += color =>
{
marking.SetColor(colorIndex, color);
var newCol = color.Colors["base"];
if (marking.MarkingColors[colorIndex] != newCol)
marking.SetColor(colorIndex, newCol);
if(marking.MarkingEffects?[colorIndex].Equals(color) != true)
marking.SetMarkingEffect(colorIndex, color.Clone());
OnColorChanged!((_slot, marking));
};
@ -272,6 +295,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
for (var i = 0; i < _markings[Slot].MarkingColors.Count && i < oldMarking.MarkingColors.Count; i++)
{
_markings[Slot].SetColor(i, oldMarking.MarkingColors[i]);
_markings[Slot].SetMarkingEffect(i, oldMarking.MarkingEffects[i]); // Sunrise-Edit
}
PopulateColors();

View file

@ -10,6 +10,7 @@ using Content.Client.Sprite;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._Sunrise;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
@ -332,6 +333,30 @@ namespace Content.Client.Lobby.UI
#region Hair
// sunrise gradient edit start
HairStylePicker.OnExtendedColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairExtendedColor(newColor.marking.MarkingEffects[0]));
UpdateCMarkingsHair();
ReloadPreview();
};
FacialHairPicker.OnExtendedColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairExtendedColor(newColor.marking.MarkingEffects[0]));
UpdateCMarkingsFacialHair();
ReloadPreview();
};
// sunrise gradient edit end
HairStylePicker.OnMarkingSelect += newStyle =>
{
if (Profile is null)
@ -345,8 +370,9 @@ namespace Content.Client.Lobby.UI
{
if (Profile is null)
return;
var newExtended = newColor.marking.MarkingEffects[0].Clone();
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0], newExtended)); // sunrise gradient edit
UpdateCMarkingsHair();
ReloadPreview();
};
@ -364,8 +390,9 @@ namespace Content.Client.Lobby.UI
{
if (Profile is null)
return;
var newExtended = newColor.marking.MarkingEffects[0].Clone();
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0], newExtended)); // sunrise gradient edit
UpdateCMarkingsFacialHair();
ReloadPreview();
};
@ -1657,13 +1684,29 @@ namespace Content.Client.Lobby.UI
var hairMarking = Profile.Appearance.HairStyleId switch
{
HairStyles.DefaultHairStyle => new List<Marking>(),
_ => new() { new(Profile.Appearance.HairStyleId, new List<Color>() { Profile.Appearance.HairColor }) },
_ => new List<Marking>
{
new(
Profile.Appearance.HairStyleId,
new[] { Profile.Appearance.HairColor },
Profile.Appearance.HairMarkingEffect is { } hairExt
? new List<MarkingEffect> { hairExt.Clone() }
: null)
}
};
var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
{
HairStyles.DefaultFacialHairStyle => new List<Marking>(),
_ => new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) },
_ => new List<Marking>
{
new(
Profile.Appearance.FacialHairStyleId,
new[] { Profile.Appearance.FacialHairColor },
Profile.Appearance.FacialHairMarkingEffect is { } facialExt
? new List<MarkingEffect> { facialExt.Clone() }
: null)
}
};
HairStylePicker.UpdateData(
@ -1703,7 +1746,12 @@ namespace Content.Client.Lobby.UI
}
if (hairColor != null)
{
Markings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color>() { hairColor.Value });
Markings.HairMarking = new (
Profile.Appearance.HairStyleId,
new List<Color>() { hairColor.Value },
Profile.Appearance.HairMarkingEffect is { } hairExt
? new List<MarkingEffect> { hairExt.Clone() }
: null);
}
else
{
@ -1737,7 +1785,12 @@ namespace Content.Client.Lobby.UI
}
if (facialHairColor != null)
{
Markings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color>() { facialHairColor.Value });
Markings.FacialHairMarking = new(
Profile.Appearance.FacialHairStyleId,
new List<Color>() { facialHairColor.Value },
Profile.Appearance.FacialHairMarkingEffect is { } facialExt
? new List<MarkingEffect> { facialExt.Clone() }
: null);
}
else
{
@ -1912,7 +1965,6 @@ namespace Content.Client.Lobby.UI
}
CBodyTypesButton.Select(_bodyTypes.FindIndex(x => x.ID == Profile.BodyType));
IsDirty = true;
}
}
}

View file

@ -0,0 +1,16 @@
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
using Robust.Client.UserInterface;
namespace Content.Client._Sunrise.MarkingEffectsClient;
public sealed class ColorMarkingEffectUiBuilder : IMarkingEffectUiBuilder
{
public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent)
{
if (effect is not ColorMarkingEffect)
return;
parent.CreateSelector(type: MarkingEffectType.Color);
}
}

View file

@ -0,0 +1,45 @@
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
namespace Content.Client._Sunrise.MarkingEffectsClient;
public sealed class GradientMarkingEffectUiBuilder : IMarkingEffectUiBuilder
{
private const float ToIntScaling = 100;
private const int OffsetMin = -200;
private const int OffsetMax = 100;
private const int SizeMin = 30;
private const int SizeMax = 500;
private const int RotationMin = 0;
private const int RotationMax = 360;
public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent)
{
if (effect is not GradientMarkingEffect gradient)
return;
parent.CreateSelector(type: MarkingEffectType.Gradient);
parent.CreateSelector("gradient", MarkingEffectType.Gradient);
parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-offset"),
(int)(gradient.Offset.Y * ToIntScaling), OffsetMin, OffsetMax,
v => gradient.Offset.Y = v / ToIntScaling
);
parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-size"),
(int)(gradient.Size.Y * ToIntScaling), SizeMin, SizeMax,
v => gradient.Size.Y = v / ToIntScaling);
parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-rotation"),
(int)gradient.Rotation, RotationMin, RotationMax,
v => gradient.Rotation = v);
parent.CreateToggle(Loc.GetString("marking-effect-gradient-parameter-pixelation"),
gradient.Pixelated,
v => gradient.Pixelated = v);
parent.CreateToggle(Loc.GetString("marking-effect-gradient-parameter-mirror"),
gradient.Mirrored,
v => gradient.Mirrored = v);
}
}

View file

@ -0,0 +1,14 @@
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
using Robust.Client.UserInterface;
namespace Content.Client._Sunrise.MarkingEffectsClient;
public interface IMarkingEffectUiBuilder
{
/// <summary>
/// Builds UI elements to customize <see cref="MarkingEffect"/>
/// </summary>
void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent);
}

View file

@ -0,0 +1,44 @@
using System.Numerics;
using Content.Shared._Sunrise.MarkingEffects;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
namespace Content.Client._Sunrise.MarkingEffectsClient;
public static class MarkingEffectShaders
{
public static Robust.Shared.Maths.Vector3 ColorToVec(Color col)
{
return new Robust.Shared.Maths.Vector3(col.R, col.G, col.B);
}
public static void ApplyShaderParams(this ShaderInstance instance, MarkingEffect color, Vector2 texScale)
{
instance.SetParameter("useDisplacement", false);
switch (color.Type)
{
case MarkingEffectType.Gradient:
if (color is not GradientMarkingEffect gradient)
return;
instance.SetParameter("color1", ColorToVec(gradient.Colors["base"]));
instance.SetParameter("color2", ColorToVec(gradient.Colors["gradient"]));
instance.SetParameter("texScale", texScale);
instance.SetParameter("offset", gradient.Offset);
instance.SetParameter("size", gradient.Size);
instance.SetParameter("rotation", gradient.Rotation);
instance.SetParameter("pixelated", gradient.Pixelated);
instance.SetParameter("mirrored", gradient.Mirrored);
break;
case MarkingEffectType.RoughGradient:
if (color is not RoughGradientMarkingEffect roughGradient)
return;
instance.SetParameter("color1", ColorToVec(roughGradient.Colors["base"]));
instance.SetParameter("color2", ColorToVec(roughGradient.Colors["gradient"]));
instance.SetParameter("horizontal", roughGradient.Horizontal);
break;
}
}
}

View file

@ -0,0 +1,21 @@
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
using Robust.Client.UserInterface;
namespace Content.Client._Sunrise.MarkingEffectsClient;
public sealed class RoughGradientMarkingEffectUiBuilder : IMarkingEffectUiBuilder
{
public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent)
{
if (effect is not RoughGradientMarkingEffect rough)
return;
parent.CreateSelector(type: MarkingEffectType.RoughGradient);
parent.CreateSelector("gradient", type: MarkingEffectType.RoughGradient);
parent.CreateToggle(Loc.GetString("marking-effect-roughgradient-parameter-horizontal"),
rough.Horizontal,
val => rough.Horizontal = val);
}
}

View file

@ -0,0 +1,445 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client._Sunrise.UserInterface.Controls;
using System;
using System.Collections.Generic;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
// condensed version of the original ColorSlider set
public sealed class CustomColorSelectorSliders : Control
{
public Color Color
{
get => _currentColor;
set
{
_currentColor = value;
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
case ColorSelectorType.Hsv:
_colorData = Color.ToHsv(value);
break;
}
Update();
}
}
public ColorSelectorType SelectorType
{
get => _currentType;
set
{
switch ((_currentType, value))
{
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
_colorData = Color.ToHsv(Color);
break;
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
}
_currentType = value;
UpdateType();
Update();
}
}
public bool IsAlphaVisible
{
get => _isAlphaVisible;
set
{
_isAlphaVisible = value;
_alphaSliderBox.Visible = _isAlphaVisible;
}
}
public Action<Color>? OnColorChanged;
public Action<Color>? OnColorReleased;
private bool _updating = false;
private Color _currentColor = Color.White;
private Vector4 _colorData;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
private bool _isAlphaVisible = false;
private ColorableSlider _topColorSlider;
private ColorableSlider _middleColorSlider;
private ColorableSlider _bottomColorSlider;
private Slider _alphaSlider;
private BoxContainer _alphaSliderBox = new();
private SpinBox _topInputBox;
private SpinBox _middleInputBox;
private SpinBox _bottomInputBox;
private SpinBox _alphaInputBox;
private Label _topSliderLabel = new();
private Label _middleSliderLabel = new();
private Label _bottomSliderLabel = new();
private Label _alphaSliderLabel = new();
private OptionButton _typeSelector;
private List<ColorSelectorType> _types = new();
private ColorSelectorStyleBox _topStyle;
private ColorSelectorStyleBox _middleStyle;
private ColorSelectorStyleBox _bottomStyle;
public CustomColorSelectorSliders() : this(ColorSelectorType.Rgb){} // Sunrise-Edit
public CustomColorSelectorSliders(ColorSelectorType type = ColorSelectorType.Rgb, string? label = null) // Sunrise-Edit
{
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _topStyle = new(),
MaxValue = 1.0f
};
_middleColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _middleStyle = new(),
MaxValue = 1.0f
};
_bottomColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _bottomStyle = new(),
MaxValue = 1.0f
};
_alphaSlider = new Slider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MaxValue = 1.0f,
};
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
_topColorSlider.OnReleased += _ => { OnColorSet(true); };
_middleColorSlider.OnReleased += _ => { OnColorSet(true); };
_bottomColorSlider.OnReleased += _ => { OnColorSet(true); };
_alphaSlider.OnReleased += _ => { OnColorSet(true); };
_topInputBox = new SpinBox
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Top)
};
_topInputBox.InitDefaultButtons();
_middleInputBox = new SpinBox
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Middle)
};
_middleInputBox.InitDefaultButtons();
_bottomInputBox = new SpinBox
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Bottom)
};
_bottomInputBox.InitDefaultButtons();
_alphaInputBox = new SpinBox
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Alpha)
};
_alphaInputBox.InitDefaultButtons();
_topInputBox.ValueChanged += value =>
{
_topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top);
};
_middleInputBox.ValueChanged += value =>
{
_middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle);
};
_bottomInputBox.ValueChanged += value =>
{
_bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom);
};
_alphaInputBox.ValueChanged += value =>
{
_alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha);
};
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
_typeSelector = new OptionButton();
foreach (var ty in Enum.GetValues<ColorSelectorType>())
{
_typeSelector.AddItem(Loc.GetString($"color-selector-sliders-{ty.ToString().ToLower()}"));
_types.Add(ty);
}
_typeSelector.OnItemSelected += args =>
{
SelectorType = _types[args.Id];
_typeSelector.Select(args.Id);
};
// TODO: Maybe some engine widgets could be laid out in XAML?
var rootBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};
AddChild(rootBox);
var headerBox = new BoxContainer();
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
//Sunrise-Edit-Start
if (label != null)
{
var textLabel = new Label
{
Text = label,
};
headerBox.AddChild(textLabel);
}
//Sunrise-Edit-End
var bodyBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};
// pita
var topSliderBox = new BoxContainer();
topSliderBox.AddChild(_topSliderLabel);
topSliderBox.AddChild(_topColorSlider);
topSliderBox.AddChild(_topInputBox);
var middleSliderBox = new BoxContainer();
middleSliderBox.AddChild(_middleSliderLabel);
middleSliderBox.AddChild(_middleColorSlider);
middleSliderBox.AddChild(_middleInputBox);
var bottomSliderBox = new BoxContainer();
bottomSliderBox.AddChild(_bottomSliderLabel);
bottomSliderBox.AddChild(_bottomColorSlider);
bottomSliderBox.AddChild(_bottomInputBox);
_alphaSliderBox.Visible = IsAlphaVisible;
_alphaSliderBox.AddChild(_alphaSliderLabel);
_alphaSliderBox.AddChild(_alphaSlider);
_alphaSliderBox.AddChild(_alphaInputBox);
bodyBox.AddChild(topSliderBox);
bodyBox.AddChild(middleSliderBox);
bodyBox.AddChild(bottomSliderBox);
bodyBox.AddChild(_alphaSliderBox);
rootBox.AddChild(bodyBox);
SelectorType = type; // Sunrise-Edit
_typeSelector.TrySelect(_types.IndexOf(_currentType)); // Sunrise-Edit
UpdateType();
Color = _currentColor;
}
private void UpdateType()
{
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
_topSliderLabel.Text = labels.topLabel;
_middleSliderLabel.Text = labels.middleLabel;
_bottomSliderLabel.Text = labels.bottomLabel;
bool hsv = SelectorType == ColorSelectorType.Hsv;
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
}
private void Update()
{
// This code is a mess of UI events causing stack overflows. Also, updating one slider triggers all sliders to
// update, which due to rounding errors causes them to actually change values, specifically for HSV sliders.
if (_updating)
return;
_updating = true;
_topStyle.SetBaseColor(_colorData);
_middleStyle.SetBaseColor(_colorData);
_bottomStyle.SetBaseColor(_colorData);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_topColorSlider.Value = _colorData.X;
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_topInputBox.Value = (int)(_colorData.X * 255.0f);
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
break;
case ColorSelectorType.Hsv:
// dumb workaround because the formula for
// HSV calculation results in a negative
// number in any value past 300 degrees
if (_colorData.X > 0)
{
_topColorSlider.Value = _colorData.X;
_topInputBox.Value = (int)(_colorData.X * 360.0f);
}
else
{
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
}
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
break;
}
_alphaSlider.Value = Color.A;
_alphaInputBox.Value = (int)(Color.A * 100.0f);
_updating = false;
}
private bool IsSpinBoxValid(int value, ColorSliderOrder ordering)
{
if (value < 0)
{
return false;
}
if (ordering == ColorSliderOrder.Alpha)
{
return value <= 100;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return value <= byte.MaxValue;
case ColorSelectorType.Hsv:
switch (ordering)
{
case ColorSliderOrder.Top:
return value <= 360;
default:
return value <= 100;
}
}
return false;
}
private (string, string, string) GetSliderLabels()
{
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return (
Loc.GetString("color-selector-sliders-red"),
Loc.GetString("color-selector-sliders-green"),
Loc.GetString("color-selector-sliders-blue")
);
case ColorSelectorType.Hsv:
return (
Loc.GetString("color-selector-sliders-hue"),
Loc.GetString("color-selector-sliders-saturation"),
Loc.GetString("color-selector-sliders-value")
);
}
return ("ERR", "ERR", "ERR");
}
private float GetColorValueDivisor(ColorSliderOrder order)
{
if (order == ColorSliderOrder.Alpha)
{
return 100.0f;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return 255.0f;
case ColorSelectorType.Hsv:
switch (order)
{
case ColorSliderOrder.Top:
return 360.0f;
default:
return 100.0f;
}
}
return 0.0f;
}
private void OnColorSet(bool release = false)
{
// stack overflow otherwise due to value sets
if (_updating)
{
return;
}
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
_currentColor = SelectorType switch
{
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
};
Update();
if (release)
OnColorReleased?.Invoke(_currentColor);
else
OnColorChanged?.Invoke(_currentColor);
}
private enum ColorSliderOrder
{
Top,
Middle,
Bottom,
Alpha
}
public enum ColorSelectorType
{
Rgb,
Hsv,
}
}

View file

@ -0,0 +1,238 @@
using Content.Client._Sunrise.MarkingEffectsClient;
using Content.Shared._Sunrise.MarkingEffects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client._Sunrise.UserInterface.Controls;
public sealed class MarkingEffectSelectorSliders : Control
{
private MarkingEffect Effect { get; set; }
private static readonly Dictionary<MarkingEffectType, IMarkingEffectUiBuilder> UiBuilders = new()
{
{ MarkingEffectType.Color, new ColorMarkingEffectUiBuilder() },
{ MarkingEffectType.Gradient, new GradientMarkingEffectUiBuilder() },
{ MarkingEffectType.RoughGradient, new RoughGradientMarkingEffectUiBuilder() },
};
private readonly Dictionary<string, CustomColorSelectorSliders> _colorSelectors = new();
private readonly OptionButton _typeSelector;
private readonly List<MarkingEffectType> _types = new();
private MarkingEffectType _currentType;
private readonly BoxContainer _selectorsContainer;
private readonly BoxContainer _slidersContainer;
private readonly BoxContainer _toggleContainer;
public Action<MarkingEffect>? OnColorChanged;
public MarkingEffectType CurrentType
{
get => _currentType;
set
{
if (_currentType == value)
return;
_currentType = value;
Populate(_currentType);
}
}
public MarkingEffectSelectorSliders(MarkingEffect? defaultEffect = null)
{
defaultEffect ??= ColorMarkingEffect.White;
_typeSelector = new OptionButton();
foreach (var type in Enum.GetValues<MarkingEffectType>())
{
_typeSelector.AddItem(Loc.GetString($"marking-effect-type-{type.ToString().ToLower()}"));
_types.Add(type);
}
_typeSelector.OnItemSelected += args =>
{
CurrentType = _types[args.Id];
_typeSelector.Select(args.Id);
OnColorsChanged();
};
var rootBox = new BoxContainer
{ Orientation = BoxContainer.LayoutOrientation.Vertical };
AddChild(rootBox);
var headerBox = new BoxContainer();
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
var bodyBox = new BoxContainer
{ Orientation = BoxContainer.LayoutOrientation.Vertical };
rootBox.AddChild(bodyBox);
_selectorsContainer = new BoxContainer();
bodyBox.AddChild(_selectorsContainer);
_slidersContainer = new BoxContainer
{ Orientation = BoxContainer.LayoutOrientation.Vertical };
bodyBox.AddChild(_slidersContainer);
_toggleContainer = new BoxContainer();
bodyBox.AddChild(_toggleContainer);
_currentType = defaultEffect.Type;
_typeSelector.TrySelect(_types.IndexOf(_currentType));
Effect = defaultEffect;
Populate(_currentType, defaultEffect);
}
public CustomColorSelectorSliders CreateSelector(string key = "base", MarkingEffectType type = MarkingEffectType.Color)
{
var colorSelector = new CustomColorSelectorSliders(
CustomColorSelectorSliders.ColorSelectorType.Hsv,
Loc.GetString($"marking-effect-{type.ToString().ToLower()}-color-{key}"));
colorSelector.HorizontalExpand = true;
colorSelector.HorizontalAlignment = HAlignment.Stretch;
if (Effect.Colors.TryGetValue(key, out var defaultColor))
colorSelector.Color = defaultColor;
colorSelector.OnColorReleased += _ => OnColorsChanged();
_colorSelectors.Add(key, colorSelector);
var selectorContainer = new BoxContainer
{
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Stretch,
};
_selectorsContainer.AddChild(selectorContainer);
selectorContainer.AddChild(colorSelector);
return colorSelector;
}
public void CreateSlider(string label,
int defaultValue,
int minValue,
int maxValue,
Action<float> onValueChanged)
{
var slider = new Slider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
};
slider.MinValue = minValue;
slider.MaxValue = maxValue;
slider.Value = defaultValue;
var sliderContainer = new BoxContainer();
var sliderLabel = new Label();
sliderLabel.Text = label;
var spinBox = new SpinBox
{
IsValid = value => IsSpinBoxValid(value, minValue, maxValue)
};
spinBox.InitDefaultButtons();
spinBox.Value = defaultValue;
sliderContainer.AddChild(sliderLabel);
sliderContainer.AddChild(slider);
sliderContainer.AddChild(spinBox);
_slidersContainer.AddChild(sliderContainer);
BindSlider(slider, spinBox, onValueChanged);
}
private void BindSlider(Slider slider, SpinBox spinBox, Action<float> setValue)
{
slider.OnReleased += val =>
{
setValue(val.Value);
spinBox.Value = (int)(val.Value);
OnColorsChanged();
};
spinBox.ValueChanged += val =>
{
setValue(val.Value);
slider.SetValueWithoutEvent(val.Value);
OnColorsChanged();
};
}
public void CreateToggle(string label, bool defaultValue, Action<bool> onValueChanged)
{
var button = new Button
{
Text = label,
ToggleMode = true,
Pressed = defaultValue,
};
button.OnToggled += _ => OnColorsChanged();
_toggleContainer.AddChild(button);
BindToggle(button, onValueChanged);
}
private void BindToggle(Button toggle, Action<bool> setValue)
{
toggle.OnToggled += val =>
{
setValue(val.Pressed);
OnColorsChanged();
};
}
private bool IsSpinBoxValid(int value, float min, float max)
{
return (value >= min) && (value <= max);
}
private void OnColorsChanged()
{
foreach (var (key, selector) in _colorSelectors)
{
Effect.Colors[key] = selector.Color;
}
OnColorChanged?.Invoke(Effect);
}
private void Populate(MarkingEffectType type, MarkingEffect? defaultEffect = null)
{
_colorSelectors.Clear();
_selectorsContainer.DisposeAllChildren();
_slidersContainer.DisposeAllChildren();
_toggleContainer.DisposeAllChildren();
defaultEffect ??= type switch
{
MarkingEffectType.Color => ColorMarkingEffect.White,
MarkingEffectType.Gradient => new GradientMarkingEffect(),
MarkingEffectType.RoughGradient => new RoughGradientMarkingEffect(),
_ => ColorMarkingEffect.White,
};
Effect = defaultEffect;
if (UiBuilders.TryGetValue(type, out var builder))
builder.BuildUI(Effect, this);
else
Logger.Warning($"No UI builder for marking effect: {type}");
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class Gradient : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "facial_hair_color_type",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "facial_hair_extended_color",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<int>(
name: "hair_color_type",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "hair_extended_color",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "facial_hair_color_type",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_extended_color",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_color_type",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_extended_color",
table: "profile");
}
}
}

View file

@ -895,6 +895,15 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("facial_hair_color");
b.Property<int>("FacialHairColorType")
.HasColumnType("integer")
.HasColumnName("facial_hair_color_type");
b.Property<string>("FacialHairExtendedColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_extended_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("text")
@ -915,6 +924,15 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("hair_color");
b.Property<int>("HairColorType")
.HasColumnType("integer")
.HasColumnName("hair_color_type");
b.Property<string>("HairExtendedColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_extended_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("text")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class Gradient : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "facial_hair_color_type",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "facial_hair_extended_color",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<int>(
name: "hair_color_type",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "hair_extended_color",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "facial_hair_color_type",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_extended_color",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_color_type",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_extended_color",
table: "profile");
}
}
}

View file

@ -844,6 +844,15 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<int>("FacialHairColorType")
.HasColumnType("INTEGER")
.HasColumnName("facial_hair_color_type");
b.Property<string>("FacialHairExtendedColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_extended_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT")
@ -864,6 +873,15 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<int>("HairColorType")
.HasColumnType("INTEGER")
.HasColumnName("hair_color_type");
b.Property<string>("HairExtendedColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_extended_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT")

View file

@ -420,6 +420,12 @@ namespace Content.Server.Database
public string HairColor { get; set; } = null!;
public string FacialHairName { get; set; } = null!;
public string FacialHairColor { get; set; } = null!;
// sunrise gradient start
public int HairColorType { get; set; } = 0;
public string HairExtendedColor { get; set; } = null!;
public int FacialHairColorType { get; set; } = 0;
public string FacialHairExtendedColor { get; set; } = null!;
// sunrise gradient end
public string EyeColor { get; set; } = null!;
public string SkinColor { get; set; } = null!;
public int SpawnPriority { get; set; } = 0;

View file

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.Administration.Logs;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Database;
@ -283,6 +284,12 @@ namespace Content.Server.Database
Color.FromHex(profile.EyeColor),
Color.FromHex(profile.SkinColor),
markings,
//sunrise gradient start
(MarkingEffectType)profile.HairColorType,
MarkingEffect.Parse(profile.HairExtendedColor),
(MarkingEffectType)profile.FacialHairColorType,
MarkingEffect.Parse(profile.FacialHairExtendedColor),
//sunrise gradient end
profile.Width,
profile.Height
),
@ -320,6 +327,12 @@ namespace Content.Server.Database
profile.HairColor = appearance.HairColor.ToHex();
profile.FacialHairName = appearance.FacialHairStyleId;
profile.FacialHairColor = appearance.FacialHairColor.ToHex();
// sunrise gradient start
profile.HairColorType = (int)appearance.HairMarkingEffectType;
profile.HairExtendedColor = appearance.HairMarkingEffect?.ToString() ?? "";
profile.FacialHairColorType = (int)appearance.FacialHairMarkingEffectType;
profile.FacialHairExtendedColor = appearance.FacialHairMarkingEffect?.ToString() ?? "";
// sunrise gradient end
profile.EyeColor = appearance.EyeColor.ToHex();
profile.SkinColor = appearance.SkinColor.ToHex();
profile.SpawnPriority = (int) humanoid.SpawnPriority;

View file

@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared._Sunrise.MarkingEffects;
using System.Numerics;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
@ -24,6 +25,22 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
[DataField]
public Color FacialHairColor { get; set; } = Color.Black;
// sunrise gradient edit start
[DataField]
public MarkingEffectType HairMarkingEffectType { get; set; } = MarkingEffectType.Color;
[DataField]
public MarkingEffect? HairMarkingEffect { get; set; }
[DataField]
public MarkingEffectType FacialHairMarkingEffectType { get; set; } = MarkingEffectType.Color;
[DataField]
public MarkingEffect? FacialHairMarkingEffect { get; set; }
// sunrise gradient edit end
[DataField]
public Color EyeColor { get; set; } = Color.Black;
@ -46,6 +63,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
Color eyeColor,
Color skinColor,
List<Marking> markings,
//sunrise gradient start
MarkingEffectType hairMarkingEffectType = MarkingEffectType.Color,
MarkingEffect? hairMarkingEffect = null,
MarkingEffectType facialHairMarkingEffectType = MarkingEffectType.Color,
MarkingEffect? facialHairMarkingEffect = null,
//sunrise gradient end
float width, //Sunrise
float height) //Sunrise
{
@ -56,10 +79,19 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
EyeColor = ClampColor(eyeColor);
SkinColor = ClampColor(skinColor);
Markings = markings;
//sunrise gradient start
HairMarkingEffectType = hairMarkingEffectType;
HairMarkingEffect = hairMarkingEffect;
FacialHairMarkingEffectType = facialHairMarkingEffectType;
FacialHairMarkingEffect = facialHairMarkingEffect;
Width = width; //Sunrise
Height = height; //Sunrise
//sunrise gradient end
}
public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) :
this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings), other.HairMarkingEffectType, other.HairMarkingEffect, other.FacialHairMarkingEffectType, other.FacialHairMarkingEffect, other.Width, other.Height); // sunrise gradient edit
public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) :
this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings), other.Width, other.Height)
{
@ -68,50 +100,49 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
public HumanoidCharacterAppearance WithHairStyleName(string newName)
{
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height);
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithHairColor(Color newColor)
public HumanoidCharacterAppearance WithHairColor(Color newColor, MarkingEffect? newExtendedColor = null)
{
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height);
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? HairMarkingEffectType, newExtendedColor ?? HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithFacialHairStyleName(string newName)
{
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height);
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height);
}
public HumanoidCharacterAppearance WithFacialHairColor(Color newColor)
public HumanoidCharacterAppearance WithFacialHairColor(Color newColor, MarkingEffect? newFacialExtendedColor = null)
{
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, Width, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? FacialHairMarkingEffectType, newFacialExtendedColor ?? FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithEyeColor(Color newColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, Width, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithSkinColor(Color newColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, Width, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithMarkings(List<Marking> newMarkings)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, Width, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
//Sunrise start
public HumanoidCharacterAppearance WithWidth(float newWidth)
// sunrise gradient edit start
public HumanoidCharacterAppearance WithHairExtendedColor(MarkingEffect? newExtendedColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newWidth, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? MarkingEffectType.Color, newExtendedColor, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
}
public HumanoidCharacterAppearance WithHeight(float newHeight)
public HumanoidCharacterAppearance WithFacialHairExtendedColor(MarkingEffect? newFacialExtendedColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, newHeight);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? MarkingEffectType.Color, newFacialExtendedColor, Width, Height); // sunrise gradient edit
}
//Sunrise end
// sunrise gradient edit end
public static HumanoidCharacterAppearance DefaultWithSpecies(string species)
{
@ -135,6 +166,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
Color.Black,
skinColor,
new (),
// sunrise gradient edit start
MarkingEffectType.Color,
null,
MarkingEffectType.Color,
null,
// sunrise gradient edit end
speciesPrototype.DefaultWidth, //Sunrise
speciesPrototype.DefaultHeight //Sunrise
);
@ -315,7 +352,7 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
//Sunrise end
// at the end of all that, we should have new values for each of these, so we set the character appearance to these new values.
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, newMarkings, newWidth, newHeight); // Sunrise edit
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, newMarkings, MarkingEffectType.Color, null, MarkingEffectType.Color, null, newWidth, newHeight);
// helper functions:
float RandomizeColor(float channel)
@ -446,6 +483,24 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
markingSet.FilterSponsor(sponsorPrototypes, markingManager); // Sunrise-Sponsors
}
// sunrise gradient start
MarkingEffect? hairExtendedColor = null;
if (appearance.HairMarkingEffect != null)
{
hairExtendedColor = appearance.HairMarkingEffect;
foreach (var (key, value) in hairExtendedColor.Colors)
hairExtendedColor.Colors[key] = ClampColor(value);
}
MarkingEffect? facialHairExtendedColor = null;
if (appearance.FacialHairMarkingEffect != null)
{
facialHairExtendedColor = appearance.FacialHairMarkingEffect;
foreach (var (key, value) in facialHairExtendedColor.Colors)
facialHairExtendedColor.Colors[key] = ClampColor(value);
}
// sunrise gradient end
return new HumanoidCharacterAppearance(
hairStyleId,
hairColor,
@ -454,10 +509,13 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
eyeColor,
skinColor,
markingSet.GetForwardEnumerator().ToList(),
appearance.HairMarkingEffectType,
hairExtendedColor,
appearance.FacialHairMarkingEffectType,
facialHairExtendedColor,
width,
height);
}
public bool MemberwiseEquals(ICharacterAppearance maybeOther)
{
if (maybeOther is not HumanoidCharacterAppearance other) return false;
@ -468,6 +526,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
if (!EyeColor.Equals(other.EyeColor)) return false;
if (!SkinColor.Equals(other.SkinColor)) return false;
if (!Markings.SequenceEqual(other.Markings)) return false;
// sunrise gradient edit start
if (!HairMarkingEffectType.Equals(other.HairMarkingEffectType)) return false;
if (!Equals(HairMarkingEffect, other.HairMarkingEffect)) return false;
if (!FacialHairMarkingEffectType.Equals(other.FacialHairMarkingEffectType)) return false;
if (!Equals(FacialHairMarkingEffect, other.FacialHairMarkingEffect)) return false;
// sunrise gradient edit end
if (Width != other.Width) return false; //Sunrise
if (Height != other.Height) return false; //Sunrise
return true;
@ -484,6 +548,11 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
EyeColor.Equals(other.EyeColor) &&
SkinColor.Equals(other.SkinColor) &&
Markings.SequenceEqual(other.Markings) &&
// sunrise gradient edit start
HairMarkingEffectType.Equals(other.HairMarkingEffectType) &&
Equals(HairMarkingEffect, other.HairMarkingEffect) &&
FacialHairMarkingEffectType.Equals(other.FacialHairMarkingEffectType) &&
Equals(FacialHairMarkingEffect, other.FacialHairMarkingEffect) &&
Width == other.Width && //starlight
Height == other.Height;
}

View file

@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@ -12,20 +13,32 @@ namespace Content.Shared.Humanoid.Markings
[DataField("markingColor")]
private List<Color> _markingColors = new();
// sunrise gradient edit start
[DataField("markingEffects")]
public List<MarkingEffect> MarkingEffects = new();
// sunrise gradient edit end
private Marking()
{
}
public Marking(string markingId,
List<Color> markingColors)
List<Color> markingColors,
List<MarkingEffect>? markingEffects = null)
{
MarkingId = markingId;
_markingColors = markingColors;
MarkingEffects = markingEffects ?? new(); // sunrise gradient edit
}
public Marking(string markingId,
IReadOnlyList<Color> markingColors)
: this(markingId, new List<Color>(markingColors))
IReadOnlyList<Color> markingColors,
IReadOnlyList<MarkingEffect>? markingEffects = null)
: this(
markingId,
new List<Color>(markingColors),
markingEffects is not null ? new List<MarkingEffect>(markingEffects) : new List<MarkingEffect>())
{
}
@ -34,7 +47,11 @@ namespace Content.Shared.Humanoid.Markings
MarkingId = markingId;
List<Color> colors = new();
for (int i = 0; i < colorCount; i++)
{
colors.Add(Color.White);
MarkingEffects.Add(ColorMarkingEffect.White);
}
_markingColors = colors;
}
@ -42,6 +59,7 @@ namespace Content.Shared.Humanoid.Markings
{
MarkingId = other.MarkingId;
_markingColors = new(other.MarkingColors);
MarkingEffects = other.MarkingEffects.Select(e => e.Clone()).ToList();
Visible = other.Visible;
Forced = other.Forced;
}
@ -81,6 +99,17 @@ namespace Content.Shared.Humanoid.Markings
}
}
public void SetMarkingEffect(int colorIndex, MarkingEffect effect) =>
MarkingEffects[colorIndex] = effect;
public void SetMarkingEffect(MarkingEffect effect)
{
for (int i = 0; i < MarkingEffects.Count; i++)
{
MarkingEffects[i] = effect;
}
}
public int CompareTo(Marking? marking)
{
if (marking == null)
@ -122,27 +151,53 @@ namespace Content.Shared.Humanoid.Markings
// doesn't seem to have compatible interfaces? this 'works'
// for now but should eventually be improved so that this can,
// in fact just be serialized through a convenient interface
new public string ToString()
public new string ToString()
{
// reserved character
string sanitizedName = this.MarkingId.Replace('@', '_');
List<string> colorStringList = new();
foreach (Color color in _markingColors)
colorStringList.Add(color.ToHex());
return $"{sanitizedName}@{String.Join(',', colorStringList)}";
var colorStringList = _markingColors.Select(c => c.ToHex()).ToList();
if (MarkingEffects == null || MarkingEffects.Count == 0)
return $"{sanitizedName}@{string.Join(',', colorStringList)}";
var extColorsList = MarkingEffects.Select(ext => ext.ToString());
var extColorsString = string.Join(";", extColorsList);
return $"{sanitizedName}@{string.Join(',', colorStringList)}@{extColorsString}";
}
public static Marking? ParseFromDbString(string input)
{
if (input.Length == 0) return null;
var split = input.Split('@');
if (split.Length != 2) return null;
List<Color> colorList = new();
foreach (string color in split[1].Split(','))
colorList.Add(Color.FromHex(color));
if (string.IsNullOrWhiteSpace(input))
return null;
return new Marking(split[0], colorList);
var split = input.Split('@');
if (split.Length < 2)
return null;
var name = split[0];
var colorsRaw = split[1];
var colorList = new List<Color>();
foreach (var colorHex in colorsRaw.Split(','))
{
colorList.Add(Color.FromHex(colorHex));
}
if (split.Length == 2)
return new Marking(name, colorList);
var extColorsRaw = split[2];
var markingEffects = new List<MarkingEffect>();
foreach (var extColorStr in extColorsRaw.Split(';'))
{
var parsed = MarkingEffect.Parse(extColorStr);
if (parsed != null)
markingEffects.Add(parsed);
}
return new Marking(name, colorList, markingEffects);
}
}
}

View file

@ -3,6 +3,7 @@ using System.Linq;
using Content.Shared._Sunrise;
using Content.Shared._Sunrise.TTS;
using System.Numerics;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.CCVar;
using Content.Shared.Decals;
using Content.Shared.Examine;
@ -456,13 +457,13 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) &&
_markingManager.CanBeApplied(profile.Species, profile.Sex, hairPrototype, _proto))
{
AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false);
AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false, markingEffect: profile.Appearance.HairMarkingEffect);
}
if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) &&
_markingManager.CanBeApplied(profile.Species, profile.Sex, facialHairPrototype, _proto))
{
AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false);
AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false, markingEffect: profile.Appearance.FacialHairMarkingEffect);
}
humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _proto);
@ -506,7 +507,7 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
/// <param name="sync">Whether to immediately sync this marking or not</param>
/// <param name="forced">If this marking was forced (ignores marking points)</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null, MarkingEffect? markingEffect = null)
{
if (!Resolve(uid, ref humanoid)
|| !_markingManager.Markings.TryGetValue(marking, out var prototype))
@ -521,6 +522,8 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
for (var i = 0; i < prototype.Sprites.Count; i++)
{
markingObject.SetColor(i, color.Value);
if(markingEffect != null)
markingObject.SetMarkingEffect(i, markingEffect);
}
}

View file

@ -0,0 +1,62 @@
using System.Linq;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.MarkingEffects;
[Serializable, NetSerializable]
public sealed partial class ColorMarkingEffect : MarkingEffect
{
public override MarkingEffectType Type => MarkingEffectType.Color;
public Color GetColor()
=> Colors.TryGetValue("base", out var col) ? col : Color.White;
#region Constructors
public ColorMarkingEffect(Color color) : base(color) { }
public static ColorMarkingEffect White => new(Color.White);
#endregion
#region Parsers
public override string ToString()
{
Dictionary<string, string> dict = new();
var color = GetColor();
dict.Add($"color.base", color.ToHex());
var result = string.Join(",", dict.Select(kvp => $"{kvp.Key}={kvp.Value}"));
return $"{Type.ToString()}=={result}";
}
public static ColorMarkingEffect? Parse(Dictionary<string, string> dict)
{
var color = Color.White;
foreach (var (type, value) in dict)
{
if (type == "color.base")
color = Color.TryFromHex(value) ?? Color.White;
}
return new ColorMarkingEffect(color);
}
#endregion
#region Other methods
public override ColorMarkingEffect Clone()
{
return new ColorMarkingEffect(Colors["base"]);
}
public override bool Equals(MarkingEffect? maybeOther)
{
if (maybeOther is not ColorMarkingEffect other)
return false;
return DictionaryEquals(Colors, other.Colors);
}
#endregion
}

View file

@ -0,0 +1,139 @@
using System.Linq;
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.MarkingEffects;
[Serializable, NetSerializable]
public sealed partial class GradientMarkingEffect : MarkingEffect
{
public override MarkingEffectType Type => MarkingEffectType.Gradient;
public Vector2 Offset = new(0, -190/100f);
public Vector2 Size = new(1, 33/100f);
public float Rotation = 0;
public float Speed = 1;
public bool Pixelated = true;
public bool Mirrored = false;
#region Parsing
public override string ToString()
{
var dict = new Dictionary<string, string>();
dict.Add("offset", ParamToString(Offset));
dict.Add("size", ParamToString(Size));
dict.Add("rotation", ParamToString(Rotation));
dict.Add("speed", ParamToString(Speed));
dict.Add("pixelated", ParamToString(Pixelated));
dict.Add("mirrored", ParamToString(Mirrored));
foreach (var (k, v) in Colors)
dict.Add($"color.{k}", $"{v.ToHex()}");
var result = string.Join(",", dict.Select(kvp => $"{kvp.Key}={kvp.Value}"));
return $"{Type.ToString()}=={result}";
}
public static GradientMarkingEffect? Parse(Dictionary<string, string> dict)
{
var colors = new Dictionary<string, Color>();
var offset = new Vector2(0, -190/100f);
var size = new Vector2(1, 33/100f);
var rotation = 0f;
var speed = 1f;
var pixelated = true;
var mirrored = false;
foreach (var (type, value) in dict)
{
switch (type)
{
case "offset":
TryParseParam(value, out offset);
break;
case "size":
TryParseParam(value, out size);
break;
case "rotation":
TryParseParam(value, out rotation);
break;
case "speed":
TryParseParam(value, out speed);
break;
case "pixelated":
TryParseParam(value, out pixelated);
break;
case "mirrored":
TryParseParam(value, out mirrored);
break;
default:
{
if (type.StartsWith("color."))
colors[type["color.".Length..]] = Color.TryFromHex(value) ?? Color.White;
break;
}
}
}
return new GradientMarkingEffect(colors, offset, size, rotation, speed, pixelated, mirrored);
}
#endregion
#region Constructors
public GradientMarkingEffect()
{
Colors = new Dictionary<string, Color>()
{
{ "base", Color.White }
};
}
public GradientMarkingEffect(Color color)
{
Colors = new Dictionary<string, Color>
{
{"base", color }
};
}
public GradientMarkingEffect(Dictionary<string, Color> colors,
Vector2 offset,
Vector2 size,
float rotation,
float speed,
bool pixelated,
bool mirrored)
{
Colors = colors;
Offset = offset;
Size = size;
Rotation = rotation;
Speed = speed;
Pixelated = pixelated;
Mirrored = mirrored;
}
#endregion
#region Other methods
public override GradientMarkingEffect Clone()
{
return new GradientMarkingEffect(new(Colors), new(Offset.X, Offset.Y), new(Size.X, Size.Y), Rotation, Speed, Pixelated, Mirrored);
}
public override bool Equals(MarkingEffect? maybeOther)
{
if (maybeOther is not GradientMarkingEffect other)
return false;
return DictionaryEquals(Colors, other.Colors)
&& Offset.Equals(other.Offset)
&& Size.Equals(other.Size)
&& Rotation.Equals(other.Rotation)
&& Speed.Equals(other.Speed)
&& Pixelated.Equals(other.Pixelated)
&& Mirrored.Equals(other.Mirrored);
}
#endregion
}

View file

@ -0,0 +1,150 @@
using System.Globalization;
using System.Linq;
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.MarkingEffects;
[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable]
public abstract partial class MarkingEffect
{
public abstract MarkingEffectType Type { get; }
public Dictionary<string, Color> Colors;
public abstract override string ToString();
public abstract MarkingEffect Clone();
public abstract bool Equals(MarkingEffect? other);
#region Constructors
protected MarkingEffect()
{
Colors = new Dictionary<string, Color>
{
{ "base", Color.White }
};
}
protected MarkingEffect(Color color)
{
Colors = new Dictionary<string, Color>
{
{ "base", color }
};
}
#endregion
#region Parsers
protected static Dictionary<string, string>? ParseToDict(string input)
{
if (string.IsNullOrWhiteSpace(input))
return null;
var spl = input.Split("==");
if (spl.Length > 1)
input = spl[1];
var lines = input.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (lines.Length == 0)
return null;
return lines
.Select(line => line.Split('=', 2))
.Where(parts => parts.Length == 2 && !string.IsNullOrWhiteSpace(parts[0]) && !string.IsNullOrWhiteSpace(parts[1]))
.ToDictionary(parts => parts[0], parts => parts[1]);
}
public static MarkingEffect? Parse(string input)
{
var pair = input.Split("==");
if (pair.Length != 2 || !Enum.TryParse<MarkingEffectType>(pair[0], true, out var type))
return null;
var dict = ParseToDict(input);
if (dict == null)
return null;
return MarkingEffectTypes.TypeParsers.TryGetValue(type, out var parser) ? parser(dict) : null;
}
public static string ParamToString<T>(T param)
{
if (param == null)
return "";
return param switch
{
null => "",
float f => f.ToString(CultureInfo.InvariantCulture),
bool b => b.ToString(),
Vector2 v => Vector2ToString(v),
_ => "",
};
}
public static bool TryParseParam<T>(string input, out T param)
{
param = default!;
if (typeof(T) == typeof(float))
{
if (!float.TryParse(input, CultureInfo.InvariantCulture, out var floatResult))
return false;
param = (T)(object)floatResult;
}
else if (typeof(T) == typeof(bool))
{
if (!bool.TryParse(input, out var boolResult))
return false;
param = (T)(object)boolResult;
}
else if (typeof(T) == typeof(Vector2))
param = (T)(object)ParseVector2(input);
else
return false;
return true;
}
#endregion
#region Static methods
public static Vector2 ParseVector2(string str)
{
str = str.Trim('(', ')');
var parts = str.Split('=');
if (parts.Length != 2
|| !float.TryParse(parts[0], CultureInfo.InvariantCulture, out var x)
|| !float.TryParse(parts[1], CultureInfo.InvariantCulture, out var y))
return new(0, 0);
return new Vector2(x, y);
}
public static bool DictionaryEquals<TKey, TValue>(
Dictionary<TKey, TValue>? a,
Dictionary<TKey, TValue>? b)
where TKey : notnull
{
if (a == b)
return true;
if (a == null || b == null)
return false;
return a.Count == b.Count && !a.Except(b).Any();
}
public static string Vector2ToString(Vector2 v)
{
return $"({v.X.ToString(CultureInfo.InvariantCulture)}={v.Y.ToString(CultureInfo.InvariantCulture)})";
}
#endregion
}

View file

@ -0,0 +1,20 @@
namespace Content.Shared._Sunrise.MarkingEffects;
// какой-то прям костыль, надо как-то по-другому парсер реализовать
public enum MarkingEffectType
{
Color,
Gradient,
RoughGradient,
}
public static class MarkingEffectTypes
{
public static readonly Dictionary<MarkingEffectType, Func<Dictionary<string, string>, MarkingEffect?>> TypeParsers = new()
{
{ MarkingEffectType.Color, ColorMarkingEffect.Parse },
{ MarkingEffectType.Gradient, GradientMarkingEffect.Parse },
{ MarkingEffectType.RoughGradient, RoughGradientMarkingEffect.Parse },
};
}

View file

@ -0,0 +1,93 @@
using System.Linq;
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.MarkingEffects;
[Serializable, NetSerializable]
public sealed partial class RoughGradientMarkingEffect : MarkingEffect
{
public override MarkingEffectType Type => MarkingEffectType.RoughGradient;
public bool Horizontal = false;
#region Parsing
public override string ToString()
{
var dict = new Dictionary<string, string>();
dict.Add("horizontal", ParamToString(Horizontal));
foreach (var (k, v) in Colors)
dict.Add($"color.{k}", $"{v.ToHex()}");
var result = string.Join(",", dict.Select(kvp => $"{kvp.Key}={kvp.Value}"));
return $"{Type.ToString()}=={result}";
}
public static RoughGradientMarkingEffect? Parse(Dictionary<string, string> dict)
{
var colors = new Dictionary<string, Color>();
var horizontal = false;
foreach (var (type, value) in dict)
{
switch (type)
{
case "horizontal":
TryParseParam(value, out horizontal);
break;
default:
{
if (type.StartsWith("color."))
colors[type["color.".Length..]] = Color.TryFromHex(value) ?? Color.White;
break;
}
}
}
return new RoughGradientMarkingEffect(colors, horizontal);
}
#endregion
#region Constructors
public RoughGradientMarkingEffect()
{
Colors = new Dictionary<string, Color>()
{
{ "base", Color.White }
};
}
public RoughGradientMarkingEffect(Color color)
{
Colors = new Dictionary<string, Color>
{
{"base", color }
};
}
public RoughGradientMarkingEffect(Dictionary<string, Color> colors, bool horizontal)
{
Colors = colors;
Horizontal = horizontal;
}
#endregion
#region Other methods
public override RoughGradientMarkingEffect Clone()
{
return new RoughGradientMarkingEffect(new(Colors), Horizontal);
}
public override bool Equals(MarkingEffect? maybeOther)
{
if (maybeOther is not RoughGradientMarkingEffect other)
return false;
return DictionaryEquals(Colors, other.Colors)
&& Equals(Horizontal, other.Horizontal);
}
#endregion
}

View file

@ -0,0 +1,19 @@
marking-effect-type-color = Стандартный
marking-effect-color-color-base = Цвет
marking-effect-type-gradient = Градиент
marking-effect-gradient-color-base = Начало
marking-effect-gradient-color-gradient = Конец
marking-effect-gradient-parameter-offset = Позиция
marking-effect-gradient-parameter-size = Размер
marking-effect-gradient-parameter-rotation = Поворот
marking-effect-gradient-parameter-pixelation = Пикселизация
marking-effect-gradient-parameter-mirror = Отражение эффекта
marking-effect-type-roughgradient = Неровный градиент
marking-effect-roughgradient-color-base = Начало
marking-effect-roughgradient-color-gradient = Конец
marking-effect-roughgradient-parameter-horizontal = Горизонтальный

View file

@ -28,7 +28,7 @@
hideLayersOnEquip:
- Hair
- Snout
# MARKINGS START
# MARKINGS START
bodyTypeMarkingsDisplacement:
body-fat-m:
Hair:
@ -557,7 +557,7 @@
categories: [ HideSpawnMenu ]
components:
- type: HumanoidAppearance
# MARKINGS START
# MARKINGS START
bodyTypeMarkingsDisplacement:
body-fat-m:
Hair:
@ -1071,4 +1071,4 @@
sizeMaps:
32:
sprite: _Sunrise/Mobs/Species/Human/Distrofik/displacement.rsi
state: back
state: back

View file

@ -1,4 +1,4 @@
- type: shader
- type: shader
id: NVDDisplay
kind: source
path: "/Textures/_Sunrise/Shaders/NVDDisplay.swsl"
@ -34,3 +34,13 @@
NightVisionBoost: 2.0
NightVisionThreshold: 0.3
BlueTintIntensity: 0.6
- type: shader
id: Gradient
kind: source
path: "/Textures/_Sunrise/Shaders/gradient.swsl"
- type: shader
id: RoughGradient
kind: source
path: "/Textures/_Sunrise/Shaders/gradient_rough.swsl"

View file

@ -0,0 +1,66 @@
uniform highp vec3 color1;
uniform highp vec3 color2;
uniform highp vec2 texScale;
uniform highp vec2 offset;
uniform highp vec2 size;
uniform highp float rotation;
uniform bool pixelated;
uniform bool mirrored;
// DISPLACEMENT START
uniform sampler2D displacementMap;
uniform highp float displacementSize;
uniform highp vec4 displacementUV;
varying highp vec2 displacementUVOut;
void vertex() {
if(useDisplacement == true) {
displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2);
}
}
uniform bool useDisplacement;
// DISPLACEMENT END
highp float mirror(highp float x) {
return mix(x, abs(fract(x) * 2.0 - 1.0), float(mirrored));
}
void fragment() {
highp vec4 color = texture2D(TEXTURE, UV);
highp vec2 uv = UV2;
highp float rad = radians(rotation);
highp vec2 center = vec2(0.5);
uv -= center;
highp float cos_a = cos(rad);
highp float sin_a = sin(rad);
highp mat2 rot = mat2(
cos_a, -sin_a,
sin_a, cos_a
);
if (pixelated) {
uv = floor(uv * texScale) / texScale;
}
uv = rot * uv;
uv += center;
highp float t = mirror(uv.y / size.y + offset.y);
t = clamp(t, 0.0, 1.0);
highp vec3 gradient = mix(color2, color1, t);
// DISPLACEMENT START
if(useDisplacement == true) {
highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut);
highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0);
color = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0));
color.a *= displacementSample.a;
}
// DISPLACEMENT END
COLOR = color * vec4(gradient, 1);
}

View file

@ -0,0 +1,69 @@
uniform highp vec3 color1;
uniform highp vec3 color2;
uniform bool horizontal;
// DISPLACEMENT START
uniform sampler2D displacementMap;
uniform highp float displacementSize;
uniform highp vec4 displacementUV;
varying highp vec2 displacementUVOut;
void vertex() {
if(useDisplacement == true) {
displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2);
}
}
uniform bool useDisplacement;
// DISPLACEMENT END
highp float find_bound(bool forward, bool horizontal) {
highp float step = horizontal ? TEXTURE_PIXEL_SIZE.x : TEXTURE_PIXEL_SIZE.y;
highp float current = horizontal ? UV.x : UV.y;
const highp int MAX_ITERATIONS = 256;
highp int iterations = 0;
highp vec4 result = texture2D(TEXTURE, UV);
while ((forward ? current < 1.0 : current > 0.0) && result.a > 0.0 && iterations < MAX_ITERATIONS) {
current += forward ? step : -step;
iterations++;
highp vec2 probeUV = UV;
if (horizontal)
probeUV.x = current;
else
probeUV.y = current;
result = texture2D(TEXTURE, probeUV);
}
return current;
}
void fragment() {
highp vec4 color = texture2D(TEXTURE, UV);
highp float a = find_bound(false, horizontal);
highp float b = find_bound(true, horizontal);
highp float diff = max(0.001, b - a);
highp float pos = horizontal ? UV.x : UV.y;
highp float norm = clamp((pos - a) / diff, 0.0, 1.0);
highp vec3 gradient = mix(color2, color1, norm);
// DISPLACEMENT START
if(useDisplacement == true) {
highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut);
highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0);
color = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0));
color.a *= displacementSample.a;
}
// DISPLACEMENT END
COLOR = vec4(color.rgb * gradient, color.a);
}