Попытка сделать адекватный градиент (#3195)

This commit is contained in:
UnilTan 2025-09-27 02:50:02 +05:00 committed by GitHub
parent c38d90ee53
commit c286124224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1595 additions and 4011 deletions

View file

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

View file

@ -1,20 +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.Client.Graphics; //Sunrise
using Robust.Shared.Maths; //Sunrise
using Robust.Shared.Configuration;
using Robust.Shared.Log; //Lua: for debug logging
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@ -185,25 +184,14 @@ 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 },
hairMarkingEffects);
var facialHairMarkingEffects = profile.Appearance.FacialHairMarkingEffect != null
? new List<MarkingEffect> { profile.Appearance.FacialHairMarkingEffect }
: new List<MarkingEffect>();
new[] { hairColor });
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 },
facialHairMarkingEffects);
new[] { facialHairColor });
if (_markingManager.CanBeApplied(profile.Species, profile.Sex, hair, _prototypeManager))
{
@ -249,6 +237,20 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
humanoid.Width = profile.Appearance.Width; // Sunrise
humanoid.Height = profile.Appearance.Height; // Sunrise
//Sunrise start Copy hair/facial hair gradient settings for preview
humanoid.HairGradientEnabled = profile.Appearance.HairGradientEnabled;
humanoid.HairGradientSecondaryColor = profile.Appearance.HairGradientSecondaryColor;
humanoid.HairGradientDirection = profile.Appearance.HairGradientDirection;
humanoid.FacialHairGradientEnabled = profile.Appearance.FacialHairGradientEnabled;
humanoid.FacialHairGradientSecondaryColor = profile.Appearance.FacialHairGradientSecondaryColor;
humanoid.FacialHairGradientDirection = profile.Appearance.FacialHairGradientDirection;
humanoid.AllMarkingsGradientEnabled = profile.Appearance.AllMarkingsGradientEnabled;
humanoid.AllMarkingsGradientSecondaryColor = profile.Appearance.AllMarkingsGradientSecondaryColor;
humanoid.AllMarkingsGradientDirection = profile.Appearance.AllMarkingsGradientDirection;
// Set cached hair colors for gradient shader
humanoid.CachedHairColor = hairColor;
humanoid.CachedFacialHairColor = facialHairColor; //Sunrise end
UpdateSprite((uid, humanoid, Comp<SpriteComponent>(uid)));
}
@ -273,7 +275,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
{
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity, marking.MarkingEffects); // Sunrise-Edit
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity);
//if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
// applyUndergarmentTop = false;
//else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
@ -365,8 +367,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
public void ApplyMarking(MarkingPrototype markingPrototype, // Sunrise-Edit
IReadOnlyList<Color>? colors,
bool visible,
Entity<HumanoidAppearanceComponent, SpriteComponent> entity,
IReadOnlyList<MarkingEffect>? markingEffects = null)
Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
{
var humanoid = entity.Comp1;
var sprite = entity.Comp2;
@ -405,37 +406,49 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
continue;
}
// 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);
// }
//Sunrise start Hair/FacialHair gradient support
var isHair = markingPrototype.BodyPart == HumanoidVisualLayers.Hair;
var isFacialHair = markingPrototype.BodyPart == HumanoidVisualLayers.FacialHair;
ShaderInstance? shaderOverride = null;
if (markingEffects != null && j < markingEffects.Count && markingEffects[j].Type != MarkingEffectType.Color)
if (isHair && humanoid.HairGradientEnabled)
{
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);
Log.Debug($"Applying hair gradient: enabled={humanoid.HairGradientEnabled}, secondaryColor={humanoid.HairGradientSecondaryColor}, direction={humanoid.HairGradientDirection}"); //Lua: debug
var inst = _prototypeManager.Index<ShaderPrototype>("HairGradient").InstanceUnique();
var baseCol = (colors != null && j < colors.Count)
? colors[j]
: (humanoid.CachedHairColor ?? Color.White);
inst.SetParameter("color1", new Vector3(baseCol.R, baseCol.G, baseCol.B));
inst.SetParameter("color2", new Vector3(humanoid.HairGradientSecondaryColor.R, humanoid.HairGradientSecondaryColor.G, humanoid.HairGradientSecondaryColor.B));
inst.SetParameter("direction", (float) humanoid.HairGradientDirection);
sprite.LayerSetShader(layerId, inst, "HairGradient");
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
else if (isFacialHair && humanoid.FacialHairGradientEnabled)
{
var inst2 = _prototypeManager.Index<ShaderPrototype>("HairGradient").InstanceUnique();
var baseCol2 = (colors != null && j < colors.Count)
? colors[j]
: (humanoid.CachedFacialHairColor ?? Color.White);
inst2.SetParameter("color1", new Vector3(baseCol2.R, baseCol2.G, baseCol2.B));
inst2.SetParameter("color2", new Vector3(humanoid.FacialHairGradientSecondaryColor.R, humanoid.FacialHairGradientSecondaryColor.G, humanoid.FacialHairGradientSecondaryColor.B));
inst2.SetParameter("direction", (float) humanoid.FacialHairGradientDirection);
sprite.LayerSetShader(layerId, inst2, "HairGradient");
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
else if (humanoid.AllMarkingsGradientEnabled && markingPrototype.BodyPart != HumanoidVisualLayers.Head && markingPrototype.BodyPart != HumanoidVisualLayers.HeadTop && markingPrototype.BodyPart != HumanoidVisualLayers.HeadSide)
{
// Apply gradient to any marking if enabled globally (excluding skin/base-like head parts)
var instG = _prototypeManager.Index<ShaderPrototype>("HairGradient").InstanceUnique();
var baseColor = (colors != null && j < colors.Count) ? colors[j] : Color.White;
instG.SetParameter("color1", new Vector3(baseColor.R, baseColor.G, baseColor.B));
instG.SetParameter("color2", new Vector3(humanoid.AllMarkingsGradientSecondaryColor.R, humanoid.AllMarkingsGradientSecondaryColor.G, humanoid.AllMarkingsGradientSecondaryColor.B));
instG.SetParameter("direction", (float) humanoid.AllMarkingsGradientDirection);
sprite.LayerSetShader(layerId, instG, "HairGradient");
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
else
{
//Sunrise end Default color application
if (colors != null && j < colors.Count)
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
@ -443,17 +456,13 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
else
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
} //Sunrise
}
//Sunrise-Edit-End
var displacementData = GetMarkingDisplacement(entity.Owner, markingPrototype.BodyPart, humanoid);
if (displacementData != null && markingPrototype.CanBeDisplaced)
{
// TODO: в шейдер нужно ещё вставлять displacementSize, сейчас в нём хардкод 127
// TODO: костыль пиздец, когда появится возможность устанавливать 2 шейдера на один леер - удалить эту хуйню (shaderOverride)
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _, shaderOverride); // Sunrise-Edit
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _);
}
}
}
@ -547,7 +556,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), marking.MarkingEffects); // Sunrise-Edit
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite));
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
using System.Linq;
using Content.Client._Sunrise.UserInterface.Controls;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.Humanoid.Markings;
using Robust.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
@ -235,40 +235,24 @@ public sealed partial class SingleMarkingPicker : BoxContainer
ColorSelectorContainer.DisposeAllChildren();
ColorSelectorContainer.RemoveAllChildren();
if (marking.MarkingColors.Count != proto.Sprites.Count ||
marking.MarkingEffects.Count != proto.Sprites.Count)
if (marking.MarkingColors.Count != proto.Sprites.Count)
{
marking = new Marking(marking.MarkingId, proto.Sprites.Count);
}
for (var i = 0; i < marking.MarkingColors.Count; i++)
{
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)
var selector = new CustomColorSelectorSliders()
{
HorizontalExpand = true
};
// selector.Color = marking.MarkingColors[i];
selector.CurrentType = selectorType;
selector.Color = marking.MarkingColors[i];
var colorIndex = i;
selector.OnColorChanged += 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());
if (marking.MarkingColors[colorIndex] != color)
marking.SetColor(colorIndex, color);
OnColorChanged!((_slot, marking));
};
@ -298,11 +282,6 @@ public sealed partial class SingleMarkingPicker : BoxContainer
_markings[Slot].SetColor(i, oldMarking.MarkingColors[i]);
}
for (var i = 0; i < _markings[Slot].MarkingEffects.Count && i < oldMarking.MarkingEffects.Count; i++)
{
_markings[Slot].SetMarkingEffect(i, oldMarking.MarkingEffects[i]); // Sunrise-Edit
}
PopulateColors();
OnMarkingSelect!((_slot, id));

View file

@ -127,9 +127,43 @@
<BoxContainer Name="RgbSkinColorContainer" Visible="False" Orientation="Vertical" HorizontalExpand="True"></BoxContainer>
</BoxContainer>
<!-- Hair -->
<BoxContainer Margin="10" Orientation="Horizontal">
<humanoid:SingleMarkingPicker Name="HairStylePicker" Category="Hair" />
<humanoid:SingleMarkingPicker Name="FacialHairPicker" Category="FacialHair" />
<BoxContainer Margin="10" Orientation="Vertical"> <!-- Sunrise Horizontal<Vertical -->
<BoxContainer Orientation="Horizontal">
<humanoid:SingleMarkingPicker Name="HairStylePicker" Category="Hair" />
<humanoid:SingleMarkingPicker Name="FacialHairPicker" Category="FacialHair" />
</BoxContainer> <!-- Sunrise start -->
<!-- Hair gradient controls -->
<BoxContainer Margin="10 5 0 0" Orientation="Vertical">
<Label Text="{Loc 'humanoid-profile-editor-hair-gradient-label'}" />
<BoxContainer Orientation="Horizontal" SeparationOverride="5" HorizontalExpand="True">
<CheckBox Name="HairGradientToggle" Text="{Loc 'humanoid-profile-editor-hair-gradient-enable'}" VerticalAlignment="Center" />
<ColorSelectorSliders Name="HairGradientSecondColorSelector" MinSize="280 0" HorizontalExpand="True" />
<Label Text="{Loc 'humanoid-profile-editor-hair-gradient-direction'}" VerticalAlignment="Center" />
<OptionButton Name="HairGradientDirectionSelector" MinSize="160 24" VerticalAlignment="Center" />
</BoxContainer>
</BoxContainer>
<!-- Facial hair gradient controls -->
<BoxContainer Margin="10 5 0 0" Orientation="Vertical">
<Label Text="{Loc 'humanoid-profile-editor-facialhair-gradient-label'}" />
<BoxContainer Orientation="Horizontal" SeparationOverride="5" HorizontalExpand="True">
<CheckBox Name="FacialHairGradientToggle" Text="{Loc 'humanoid-profile-editor-hair-gradient-enable'}" VerticalAlignment="Center" />
<ColorSelectorSliders Name="FacialHairGradientSecondColorSelector" MinSize="280 0" HorizontalExpand="True" />
<Label Text="{Loc 'humanoid-profile-editor-hair-gradient-direction'}" VerticalAlignment="Center" />
<OptionButton Name="FacialHairGradientDirectionSelector" MinSize="160 24" VerticalAlignment="Center" />
</BoxContainer>
</BoxContainer>
<!-- Global markings gradient controls (except skin) -->
<BoxContainer Margin="10 5 0 0" Orientation="Vertical">
<Label Text="{Loc 'humanoid-profile-editor-allmarkings-gradient-label'}" />
<BoxContainer Orientation="Horizontal" SeparationOverride="5" HorizontalExpand="True">
<CheckBox Name="AllMarkingsGradientToggle" Text="{Loc 'humanoid-profile-editor-hair-gradient-enable'}" VerticalAlignment="Center" />
<ColorSelectorSliders Name="AllMarkingsGradientSecondColorSelector" MinSize="280 0" HorizontalExpand="True" />
<Label Text="{Loc 'humanoid-profile-editor-hair-gradient-direction'}" VerticalAlignment="Center" />
<OptionButton Name="AllMarkingsGradientDirectionSelector" MinSize="160 24" VerticalAlignment="Center" />
</BoxContainer>
</BoxContainer> <!-- Sunrise end -->
</BoxContainer>
<!-- Eyes -->
<BoxContainer Margin="10" Orientation="Vertical">

View file

@ -1,6 +1,7 @@
using System.IO;
using System.Linq;
using System.Numerics;
using Robust.Shared.Log;
using Content.Client.Humanoid;
using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles;
@ -10,7 +11,6 @@ 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;
@ -253,6 +253,10 @@ namespace Content.Client.Lobby.UI
#endregion
// Sunrise: Инициализируем градиентные контролы ПЕРЕД RefreshSpecies
InitializeHairGradientControls(); //Sunrise
InitializeAllMarkingsGradientControls(); //Sunrise
RefreshSpecies();
SpeciesButton.OnItemSelected += args =>
@ -329,29 +333,6 @@ 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 =>
{
@ -366,9 +347,8 @@ 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], newExtended)); // sunrise gradient edit
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsHair();
ReloadPreview();
};
@ -386,9 +366,8 @@ 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], newExtended)); // sunrise gradient edit
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsFacialHair();
ReloadPreview();
};
@ -716,7 +695,7 @@ namespace Content.Client.Lobby.UI
SpeciesButton.Clear();
_species.Clear();
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart).OrderBy(s => Loc.GetString(s.Name))); //Lua: Сортировка рас по алфавиту
var speciesIds = _species.Select(o => o.ID).ToList();
for (var i = 0; i < _species.Count; i++)
@ -892,6 +871,7 @@ namespace Content.Client.Lobby.UI
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
UpdateGradientControls(); //Sunrise: Ensure gradient controls are updated
RefreshAntags();
RefreshJobs();
@ -1741,19 +1721,13 @@ namespace Content.Client.Lobby.UI
{
return;
}
var hairMarking = Profile.Appearance.HairStyleId == HairStyles.DefaultHairStyle
? new List<Marking>()
: new() { new(Profile.Appearance.HairStyleId, new List<Color>() { Profile.Appearance.HairColor }) };
var hairMarking = CreateHairMarkings(
Profile.Appearance.HairStyleId,
HairStyles.DefaultHairStyle,
Profile.Appearance.HairColor,
Profile.Appearance.HairMarkingEffect);
var facialHairMarking = CreateHairMarkings(
Profile.Appearance.FacialHairStyleId,
HairStyles.DefaultFacialHairStyle,
Profile.Appearance.FacialHairColor,
Profile.Appearance.FacialHairMarkingEffect);
var facialHairMarking = Profile.Appearance.FacialHairStyleId == HairStyles.DefaultFacialHairStyle
? new List<Marking>()
: new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) };
HairStylePicker.UpdateData(
hairMarking,
@ -1763,29 +1737,9 @@ namespace Content.Client.Lobby.UI
facialHairMarking,
Profile.Species,
1);
}
private static List<Marking> CreateHairMarkings(
string styleId,
string defaultStyleId,
Color color,
MarkingEffect? effect)
{
if (styleId == defaultStyleId)
return new List<Marking>();
var effects = effect is { } ext
? new List<MarkingEffect> { ext.Clone() }
: null;
return new List<Marking>()
{
new(
styleId,
new[] { color },
effects
)
};
// Sunrise start: Apply gradient toggles to UI controls if they exist
UpdateGradientControls(); //Sunrise end
}
private void UpdateCMarkingsHair()
@ -1817,10 +1771,7 @@ namespace Content.Client.Lobby.UI
{
Markings.HairMarking = new (
Profile.Appearance.HairStyleId,
new List<Color>() { hairColor.Value },
Profile.Appearance.HairMarkingEffect is { } hairExt
? new List<MarkingEffect> { hairExt.Clone() }
: null);
new List<Color>() { hairColor.Value });
}
else
{
@ -1856,10 +1807,7 @@ namespace Content.Client.Lobby.UI
{
Markings.FacialHairMarking = new(
Profile.Appearance.FacialHairStyleId,
new List<Color>() { facialHairColor.Value },
Profile.Appearance.FacialHairMarkingEffect is { } facialExt
? new List<MarkingEffect> { facialExt.Clone() }
: null);
new List<Color>() { facialHairColor.Value });
}
else
{
@ -2035,5 +1983,190 @@ namespace Content.Client.Lobby.UI
CBodyTypesButton.Select(_bodyTypes.FindIndex(x => x.ID == Profile.BodyType));
}
//Sunrise start - Hair gradient initialization methods
private void InitializeHairGradientControls()
{
// Initialize hair gradient direction selector
HairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-bottom-top"), 0);
HairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-top-bottom"), 1);
HairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-left-right"), 2);
HairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-right-left"), 3);
// Initialize facial hair gradient direction selector
FacialHairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-bottom-top"), 0);
FacialHairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-top-bottom"), 1);
FacialHairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-left-right"), 2);
FacialHairGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-right-left"), 3);
// Hair gradient toggle
HairGradientToggle.OnToggled += args =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairGradientEnabled(args.Pressed));
SetDirty();
ReloadPreview();
};
// Hair gradient secondary color
HairGradientSecondColorSelector.OnColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairGradientSecondaryColor(newColor));
SetDirty();
ReloadPreview();
};
// Hair gradient direction
HairGradientDirectionSelector.OnItemSelected += args =>
{
if (Profile is null)
return;
HairGradientDirectionSelector.SelectId(args.Id);
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairGradientDirection(args.Id));
SetDirty();
ReloadPreview();
};
// Facial hair gradient toggle
FacialHairGradientToggle.OnToggled += args =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairGradientEnabled(args.Pressed));
SetDirty();
ReloadPreview();
};
// Facial hair gradient secondary color
FacialHairGradientSecondColorSelector.OnColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairGradientSecondaryColor(newColor));
SetDirty();
ReloadPreview();
};
// Facial hair gradient direction
FacialHairGradientDirectionSelector.OnItemSelected += args =>
{
if (Profile is null)
return;
FacialHairGradientDirectionSelector.SelectId(args.Id);
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairGradientDirection(args.Id));
SetDirty();
ReloadPreview();
};
}
private void InitializeAllMarkingsGradientControls()
{
// Initialize all markings gradient direction selector
AllMarkingsGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-bottom-top"), 0);
AllMarkingsGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-top-bottom"), 1);
AllMarkingsGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-left-right"), 2);
AllMarkingsGradientDirectionSelector.AddItem(Loc.GetString("humanoid-profile-editor-hair-gradient-dir-right-left"), 3);
// All markings gradient toggle
AllMarkingsGradientToggle.OnToggled += args =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithAllMarkingsGradientEnabled(args.Pressed));
SetDirty();
ReloadPreview();
};
// All markings gradient secondary color
AllMarkingsGradientSecondColorSelector.OnColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithAllMarkingsGradientSecondaryColor(newColor));
SetDirty();
ReloadPreview();
};
// All markings gradient direction
AllMarkingsGradientDirectionSelector.OnItemSelected += args =>
{
if (Profile is null)
return;
AllMarkingsGradientDirectionSelector.SelectId(args.Id);
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithAllMarkingsGradientDirection(args.Id));
SetDirty();
ReloadPreview();
};
}
private void UpdateGradientControls()
{
if (Profile == null)
return;
try
{
// Hair gradient controls
if (HairGradientToggle != null)
{
HairGradientToggle.Pressed = Profile.Appearance.HairGradientEnabled;
}
if (HairGradientSecondColorSelector != null)
{
HairGradientSecondColorSelector.Color = Profile.Appearance.HairGradientSecondaryColor;
}
if (HairGradientDirectionSelector != null)
{
HairGradientDirectionSelector.SelectId(Profile.Appearance.HairGradientDirection);
}
// Facial hair gradient controls
if (FacialHairGradientToggle != null)
{
FacialHairGradientToggle.Pressed = Profile.Appearance.FacialHairGradientEnabled;
}
if (FacialHairGradientSecondColorSelector != null)
{
FacialHairGradientSecondColorSelector.Color = Profile.Appearance.FacialHairGradientSecondaryColor;
}
if (FacialHairGradientDirectionSelector != null)
{
FacialHairGradientDirectionSelector.SelectId(Profile.Appearance.FacialHairGradientDirection);
}
// All markings gradient controls
if (AllMarkingsGradientToggle != null)
{
AllMarkingsGradientToggle.Pressed = Profile.Appearance.AllMarkingsGradientEnabled;
}
if (AllMarkingsGradientSecondColorSelector != null)
{
AllMarkingsGradientSecondColorSelector.Color = Profile.Appearance.AllMarkingsGradientSecondaryColor;
}
if (AllMarkingsGradientDirectionSelector != null)
{
AllMarkingsGradientDirectionSelector.SelectId(Profile.Appearance.AllMarkingsGradientDirection);
}
Logger.Debug($"Sunrise: Градиентные контролы обновлены - Hair: {Profile.Appearance.HairGradientEnabled}, FacialHair: {Profile.Appearance.FacialHairGradientEnabled}, AllMarkings: {Profile.Appearance.AllMarkingsGradientEnabled}");
}
catch (Exception ex)
{
Logger.Error($"Sunrise: Ошибка при обновлении градиентных контролов: {ex}");
}
}
//Sunrise end
}
}

View file

@ -1,16 +0,0 @@
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

@ -1,45 +0,0 @@
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 = 100;
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

@ -1,14 +0,0 @@
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

@ -1,53 +0,0 @@
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 Vector3 ColorToVec(Color col)
{
return new 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;
// Safely get colors with fallback for old imported characters
var baseColor = gradient.Colors.TryGetValue("base", out var bColor) ? bColor : Color.White;
var gradientColor = gradient.Colors.TryGetValue("gradient", out var gColor) ? gColor : baseColor;
instance.SetParameter("color1", ColorToVec(baseColor));
instance.SetParameter("color2", ColorToVec(gradientColor));
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;
// Safely get colors with fallback for old imported characters
var baseColor2 = roughGradient.Colors.TryGetValue("base", out var bColor2) ? bColor2 : Color.White;
var gradientColor2 = roughGradient.Colors.TryGetValue("gradient", out var gColor2) ? gColor2 : baseColor2;
instance.SetParameter("color1", ColorToVec(baseColor2));
instance.SetParameter("color2", ColorToVec(gradientColor2));
instance.SetParameter("horizontal", roughGradient.Horizontal);
break;
}
}
}

View file

@ -1,21 +0,0 @@
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

@ -1,29 +1,32 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.ColorNaming;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
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
{
[Dependency] private readonly ILocalizationManager _localization = default!;
public Color Color
{
get => _currentColor;
set
{
_currentColor = value;
_colorData = GetStrategy().ToColorData(value);
UpdateAllSliders();
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();
}
}
@ -32,12 +35,18 @@ public sealed class CustomColorSelectorSliders : Control
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;
_typeSelector.Select(_types.IndexOf(value));
_colorData = GetStrategy().ToColorData(_currentColor);
UpdateType();
UpdateAllSliders();
Update();
}
}
@ -53,12 +62,9 @@ public sealed class CustomColorSelectorSliders : Control
}
public Action<Color>? OnColorChanged;
public Action<Color>? OnColorReleased;
private readonly static HsvSliderStrategy _hsvStrategy = new();
private readonly static RgbSliderStrategy _rgbStrategy = new();
private const float AlphaDivisor = 100.0f;
private bool _updating = false;
private Color _currentColor = Color.White;
private Vector4 _colorData;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
@ -80,8 +86,6 @@ public sealed class CustomColorSelectorSliders : Control
private Label _middleSliderLabel = new();
private Label _bottomSliderLabel = new();
private Label _alphaSliderLabel = new();
//Sunrise-Edit
// private Label _colorDescriptionLabel = new();
private OptionButton _typeSelector;
private List<ColorSelectorType> _types = new();
@ -90,13 +94,11 @@ public sealed class CustomColorSelectorSliders : Control
private ColorSelectorStyleBox _middleStyle;
private ColorSelectorStyleBox _bottomStyle;
public CustomColorSelectorSliders() : this(ColorSelectorType.Rgb){} // Sunrise-Edit
public CustomColorSelectorSliders(ColorSelectorType type = ColorSelectorType.Rgb, string? label = null) // Sunrise-Edit
{
IoCManager.InjectDependencies(this);
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
@ -128,10 +130,15 @@ public sealed class CustomColorSelectorSliders : Control
MaxValue = 1.0f,
};
_topColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Top); };
_middleColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Middle); };
_bottomColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Bottom); };
_alphaSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Alpha); };
_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
{
@ -157,10 +164,25 @@ public sealed class CustomColorSelectorSliders : Control
};
_alphaInputBox.InitDefaultButtons();
_topInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Top); };
_middleInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Middle); };
_bottomInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Bottom); };
_alphaInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Alpha); };
_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");
@ -177,9 +199,6 @@ public sealed class CustomColorSelectorSliders : Control
_typeSelector.Select(args.Id);
};
//Sunrise-Edit
// _colorDescriptionLabel.Text = ColorNaming.Describe(_currentColor, _localization);
// TODO: Maybe some engine widgets could be laid out in XAML?
var rootBox = new BoxContainer
@ -247,113 +266,168 @@ public sealed class CustomColorSelectorSliders : Control
Color = _currentColor;
}
private ColorSliderStrategy GetStrategy()
{
return SelectorType switch
{
ColorSelectorType.Rgb => _rgbStrategy,
ColorSelectorType.Hsv => _hsvStrategy,
_ => throw new ArgumentOutOfRangeException(),
};
}
private (Slider slider, SpinBox inputBox) GetSliderByOrder(ColorSliderOrder order)
{
return order switch
{
ColorSliderOrder.Top => (_topColorSlider, _topInputBox),
ColorSliderOrder.Middle => (_middleColorSlider, _middleInputBox),
ColorSliderOrder.Bottom => (_bottomColorSlider, _bottomInputBox),
ColorSliderOrder.Alpha => (_alphaSlider, _alphaInputBox),
_ => throw new ArgumentOutOfRangeException(),
};
}
private float GetColorValueDivisor(ColorSliderOrder order)
{
return order == ColorSliderOrder.Alpha
? AlphaDivisor
: GetStrategy().GetColorValueDivisor(order);
}
private void UpdateType()
{
var strategy = GetStrategy();
var labels = strategy.GetSliderLabelTexts();
_topSliderLabel.Text = labels.top;
_middleSliderLabel.Text = labels.middle;
_bottomSliderLabel.Text = labels.bottom;
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
_topStyle.ConfigureSlider(strategy.TopSliderStyle);
_middleStyle.ConfigureSlider(strategy.MiddleSliderStyle);
_bottomStyle.ConfigureSlider(strategy.BottomSliderStyle);
_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 UpdateSlider(ColorSliderOrder order)
private void Update()
{
var (slider, inputBox) = GetSliderByOrder(order);
var divisor = GetColorValueDivisor(order);
// 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;
var dataValue = order switch
{
ColorSliderOrder.Top => _colorData.X,
ColorSliderOrder.Middle => _colorData.Y,
ColorSliderOrder.Bottom => _colorData.Z,
ColorSliderOrder.Alpha => _colorData.W,
_ => throw new ArgumentOutOfRangeException(nameof(order))
};
slider.SetValueWithoutEvent(dataValue);
inputBox.OverrideValue((int)(dataValue * divisor));
}
private void UpdateSliderVisuals()
{
_updating = true;
_topStyle.SetBaseColor(_colorData);
_middleStyle.SetBaseColor(_colorData);
_bottomStyle.SetBaseColor(_colorData);
}
private void UpdateAllSliders()
{
UpdateSliderVisuals();
UpdateSlider(ColorSliderOrder.Top);
UpdateSlider(ColorSliderOrder.Middle);
UpdateSlider(ColorSliderOrder.Bottom);
UpdateSlider(ColorSliderOrder.Alpha);
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)
{
var divisor = GetColorValueDivisor(ordering);
var channelValue = value / divisor;
if (value < 0)
{
return false;
}
return channelValue >= 0.0f && channelValue <= 1.0f;
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 void OnInputBoxValueChanged(ValueChangedEventArgs args, ColorSliderOrder order)
private (string, string, string) GetSliderLabels()
{
var (slider, _) = GetSliderByOrder(order);
var value = args.Value / GetColorValueDivisor(order);
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")
);
}
// We are intentionally triggering the slider OnValueChanged event here.
// This is so that the color data values of the sliders are updated accordingly.
slider.Value = value;
return ("ERR", "ERR", "ERR");
}
private void OnSliderValueChanged(ColorSliderOrder order)
private float GetColorValueDivisor(ColorSliderOrder order)
{
_colorData = new Vector4(
_topColorSlider.Value,
_middleColorSlider.Value,
_bottomColorSlider.Value,
_alphaSlider.Value);
if (order == ColorSliderOrder.Alpha)
{
return 100.0f;
}
_currentColor = GetStrategy().FromColorData(_colorData);
OnColorChanged?.Invoke(_currentColor);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return 255.0f;
case ColorSelectorType.Hsv:
switch (order)
{
case ColorSliderOrder.Top:
return 360.0f;
default:
return 100.0f;
}
}
UpdateSliderVisuals();
UpdateSlider(order);
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
@ -369,121 +443,4 @@ public sealed class CustomColorSelectorSliders : Control
Rgb,
Hsv,
}
private abstract class ColorSliderStrategy
{
/// <summary>
/// The style preset used by the top slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle { get; }
/// <summary>
/// The style preset used by the middle slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle { get; }
/// <summary>
/// The style preset used by the bottom slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle { get; }
/// <summary>
/// Converts a Color to a Vector4 representation of its components.
/// </summary>
/// <remarks>
/// Each value in the Vector4 must be between 0.0f and 1.0f; this is used in the
/// context of slider values, which are between these ranges.
/// </remarks>
/// <param name="color">A Color to convert into Vector4 slider values.</param>
/// <returns>A Vector4 representation of a Color's slider values.</returns>
public abstract Vector4 ToColorData(Color color);
/// <summary>
/// Converts a Vector4 representation of color slider values into a Color.
/// </summary>
/// <param name="colorData">A Vector4 representation of color slider values.</param>
/// <returns>A color generated from slider values.</returns>
public abstract Color FromColorData(Vector4 colorData);
/// <summary>
/// Gets a color component divisor for the given slider.
/// </summary>
/// <remarks>
/// This is used for converting slider values to/from color component values.
/// For example, in RGB coloration, each channel ranges from 0 to 255,
/// so if you had a slider value of 0.2, you would multiply 0.2 * 255 = 51
/// for the "channel" value.
///
/// This does not apply to the Alpha channel, as the Alpha channel
/// always uses the same divisor; this is defined in ColorSelectorSliders.
/// </remarks>
/// <param name="order">The slider to retrieve a divisor for.</param>
/// <returns>The divisor for the given slider.</returns>
public abstract float GetColorValueDivisor(ColorSliderOrder order);
/// <summary>
/// Gets a label text string for the first three color sliders.
/// </summary>
/// <returns>Label text strings for the top, middle, and bottom sliders.</returns>
public abstract (string top, string middle, string bottom) GetSliderLabelTexts();
}
private sealed class RgbSliderStrategy : ColorSliderStrategy
{
private const float ChannelMaxValue = byte.MaxValue;
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Red;
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Green;
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Blue;
public override Vector4 ToColorData(Color color) => new(color.R, color.G, color.B, color.A);
public override Color FromColorData(Vector4 colorData)
=> new(colorData.X, colorData.Y, colorData.Z, colorData.W);
public override float GetColorValueDivisor(ColorSliderOrder order) => ChannelMaxValue;
public override (string top, string middle, string bottom) GetSliderLabelTexts()
{
return (
Loc.GetString("color-selector-sliders-red"),
Loc.GetString("color-selector-sliders-green"),
Loc.GetString("color-selector-sliders-blue"));
}
}
private sealed class HsvSliderStrategy : ColorSliderStrategy
{
private const float HueMaxValue = 360.0f;
private const float SliderMaxValue = 100.0f;
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Hue;
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Saturation;
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Value;
public override Vector4 ToColorData(Color color) => Color.ToHsv(color);
public override Color FromColorData(Vector4 colorData) => Color.FromHsv(colorData);
public override float GetColorValueDivisor(ColorSliderOrder order)
{
return order switch
{
ColorSliderOrder.Top => HueMaxValue,
_ => SliderMaxValue,
};
}
public override (string top, string middle, string bottom) GetSliderLabelTexts()
{
return (
Loc.GetString("color-selector-sliders-hue"),
Loc.GetString("color-selector-sliders-saturation"),
Loc.GetString("color-selector-sliders-value"));
}
}
}

View file

@ -1,242 +0,0 @@
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();
_typeSelector.HorizontalExpand = true;
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.OnColorChanged += _ => 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,
HorizontalExpand = true,
};
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();
Logger.Debug($"{defaultEffect}");
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}");
}
}

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Database;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
@ -55,12 +54,8 @@ namespace Content.IntegrationTests.Tests.Preferences
Color.Azure,
Color.Beige,
new (),
MarkingEffectType.Color,
null,
MarkingEffectType.Color,
null,
1,
1)
1.0f,
1.0f)
};
}

View file

@ -1,62 +0,0 @@
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

@ -0,0 +1,117 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class GradientFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "hair_gradient_enabled",
table: "profile",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "hair_gradient_secondary_color",
table: "profile",
type: "text",
nullable: false,
defaultValue: "#FFFFFF");
migrationBuilder.AddColumn<int>(
name: "hair_gradient_direction",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "facial_hair_gradient_enabled",
table: "profile",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "facial_hair_gradient_secondary_color",
table: "profile",
type: "text",
nullable: false,
defaultValue: "#FFFFFF");
migrationBuilder.AddColumn<int>(
name: "facial_hair_gradient_direction",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "all_markings_gradient_enabled",
table: "profile",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "all_markings_gradient_secondary_color",
table: "profile",
type: "text",
nullable: false,
defaultValue: "#FFFFFF");
migrationBuilder.AddColumn<int>(
name: "all_markings_gradient_direction",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "hair_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_gradient_secondary_color",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_gradient_direction",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_secondary_color",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_direction",
table: "profile");
migrationBuilder.DropColumn(
name: "all_markings_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "all_markings_gradient_secondary_color",
table: "profile");
migrationBuilder.DropColumn(
name: "all_markings_gradient_direction",
table: "profile");
}
}
}

View file

@ -1008,15 +1008,6 @@ 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")
@ -1037,15 +1028,6 @@ 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")
@ -1099,6 +1081,45 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("real")
.HasColumnName("width");
b.Property<bool>("AllMarkingsGradientEnabled")
.HasColumnType("boolean")
.HasColumnName("all_markings_gradient_enabled");
b.Property<string>("AllMarkingsGradientSecondaryColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("all_markings_gradient_secondary_color");
b.Property<int>("AllMarkingsGradientDirection")
.HasColumnType("integer")
.HasColumnName("all_markings_gradient_direction");
b.Property<bool>("FacialHairGradientEnabled")
.HasColumnType("boolean")
.HasColumnName("facial_hair_gradient_enabled");
b.Property<string>("FacialHairGradientSecondaryColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_gradient_secondary_color");
b.Property<int>("FacialHairGradientDirection")
.HasColumnType("integer")
.HasColumnName("facial_hair_gradient_direction");
b.Property<bool>("HairGradientEnabled")
.HasColumnType("boolean")
.HasColumnName("hair_gradient_enabled");
b.Property<string>("HairGradientSecondaryColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_gradient_secondary_color");
b.Property<int>("HairGradientDirection")
.HasColumnType("integer")
.HasColumnName("hair_gradient_direction");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -1,62 +0,0 @@
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

@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Content.Server.Database.Migrations.Sqlite
{
[DbContext(typeof(SqliteServerDbContext))]
[Migration("20250724212307_Gradient")]
partial class Gradient
[Migration("20250910035429_GradientFields")]
partial class GradientFields
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -136,6 +136,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("admin_log_id");
b.Property<long>("CurTime")
.HasColumnType("INTEGER")
.HasColumnName("cur_time");
b.Property<DateTime>("Date")
.HasColumnType("TEXT")
.HasColumnName("date");
@ -827,6 +831,19 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<int>("AllMarkingsGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("all_markings_gradient_direction");
b.Property<bool>("AllMarkingsGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("all_markings_gradient_enabled");
b.Property<string>("AllMarkingsGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("all_markings_gradient_secondary_color");
b.Property<string>("BodyType")
.IsRequired()
.HasColumnType("TEXT")
@ -847,14 +864,18 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<int>("FacialHairColorType")
b.Property<int>("FacialHairGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("facial_hair_color_type");
.HasColumnName("facial_hair_gradient_direction");
b.Property<string>("FacialHairExtendedColor")
b.Property<bool>("FacialHairGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("facial_hair_gradient_enabled");
b.Property<string>("FacialHairGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_extended_color");
.HasColumnName("facial_hair_gradient_secondary_color");
b.Property<string>("FacialHairName")
.IsRequired()
@ -876,20 +897,28 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<int>("HairColorType")
b.Property<int>("HairGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("hair_color_type");
.HasColumnName("hair_gradient_direction");
b.Property<string>("HairExtendedColor")
b.Property<bool>("HairGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("hair_gradient_enabled");
b.Property<string>("HairGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_extended_color");
.HasColumnName("hair_gradient_secondary_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_name");
b.Property<float>("Height")
.HasColumnType("REAL")
.HasColumnName("height");
b.Property<byte[]>("Markings")
.HasColumnType("jsonb")
.HasColumnName("markings");
@ -930,6 +959,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("voice");
b.Property<float>("Width")
.HasColumnType("REAL")
.HasColumnName("width");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -0,0 +1,117 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class GradientFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "all_markings_gradient_direction",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "all_markings_gradient_enabled",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "all_markings_gradient_secondary_color",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "#FFFFFF");
migrationBuilder.AddColumn<int>(
name: "facial_hair_gradient_direction",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "facial_hair_gradient_enabled",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "facial_hair_gradient_secondary_color",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "#FFFFFF");
migrationBuilder.AddColumn<int>(
name: "hair_gradient_direction",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "hair_gradient_enabled",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "hair_gradient_secondary_color",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "#FFFFFF");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "all_markings_gradient_direction",
table: "profile");
migrationBuilder.DropColumn(
name: "all_markings_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "all_markings_gradient_secondary_color",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_direction",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "facial_hair_gradient_secondary_color",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_gradient_direction",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_gradient_enabled",
table: "profile");
migrationBuilder.DropColumn(
name: "hair_gradient_secondary_color",
table: "profile");
}
}
}

View file

@ -933,6 +933,19 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<int>("AllMarkingsGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("all_markings_gradient_direction");
b.Property<bool>("AllMarkingsGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("all_markings_gradient_enabled");
b.Property<string>("AllMarkingsGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("all_markings_gradient_secondary_color");
b.Property<string>("BodyType")
.IsRequired()
.HasColumnType("TEXT")
@ -953,14 +966,18 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<int>("FacialHairColorType")
b.Property<int>("FacialHairGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("facial_hair_color_type");
.HasColumnName("facial_hair_gradient_direction");
b.Property<string>("FacialHairExtendedColor")
b.Property<bool>("FacialHairGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("facial_hair_gradient_enabled");
b.Property<string>("FacialHairGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_extended_color");
.HasColumnName("facial_hair_gradient_secondary_color");
b.Property<string>("FacialHairName")
.IsRequired()
@ -982,14 +999,18 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<int>("HairColorType")
b.Property<int>("HairGradientDirection")
.HasColumnType("INTEGER")
.HasColumnName("hair_color_type");
.HasColumnName("hair_gradient_direction");
b.Property<string>("HairExtendedColor")
b.Property<bool>("HairGradientEnabled")
.HasColumnType("INTEGER")
.HasColumnName("hair_gradient_enabled");
b.Property<string>("HairGradientSecondaryColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_extended_color");
.HasColumnName("hair_gradient_secondary_color");
b.Property<string>("HairName")
.IsRequired()

View file

@ -422,14 +422,23 @@ 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!;
// Sunrise: Hair gradient fields
public bool HairGradientEnabled { get; set; } = false;
public string HairGradientSecondaryColor { get; set; } = "#FFFFFF";
public int HairGradientDirection { get; set; } = 0;
// Sunrise: Facial hair gradient fields
public bool FacialHairGradientEnabled { get; set; } = false;
public string FacialHairGradientSecondaryColor { get; set; } = "#FFFFFF";
public int FacialHairGradientDirection { get; set; } = 0;
// Sunrise: All markings gradient fields
public bool AllMarkingsGradientEnabled { get; set; } = false;
public string AllMarkingsGradientSecondaryColor { get; set; } = "#FFFFFF";
public int AllMarkingsGradientDirection { get; set; } = 0;
public int SpawnPriority { get; set; } = 0;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();

View file

@ -26,6 +26,7 @@ namespace Content.Server.Database
options.ConfigureWarnings(x =>
{
x.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning);
x.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning); //Lua: Игнорируем предупреждение о pending changes
#if DEBUG
// for tests
x.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning);

View file

@ -8,9 +8,8 @@ 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._Sunrise.MentorHelp;
using Content.Shared.Administration.Logs;
using Content.Shared._Sunrise.MentorHelp;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Database;
using Content.Shared.Humanoid;
@ -25,6 +24,7 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Database
{
public abstract class ServerDbBase
@ -207,6 +207,9 @@ namespace Content.Server.Database
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
{
// Debug logging for gradient load
Console.WriteLine($"Sunrise: Loading gradient settings from DB - Hair: {profile.HairGradientEnabled}, FacialHair: {profile.FacialHairGradientEnabled}, AllMarkings: {profile.AllMarkingsGradientEnabled}");
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
@ -285,15 +288,21 @@ 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
),
)
{
// Sunrise: Load gradient settings from database
HairGradientEnabled = profile.HairGradientEnabled,
HairGradientSecondaryColor = Color.FromHex(profile.HairGradientSecondaryColor ?? "#FFFFFF"),
HairGradientDirection = profile.HairGradientDirection,
FacialHairGradientEnabled = profile.FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = Color.FromHex(profile.FacialHairGradientSecondaryColor ?? "#FFFFFF"),
FacialHairGradientDirection = profile.FacialHairGradientDirection,
AllMarkingsGradientEnabled = profile.AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = Color.FromHex(profile.AllMarkingsGradientSecondaryColor ?? "#FFFFFF"),
AllMarkingsGradientDirection = profile.AllMarkingsGradientDirection
},
spawnPriority,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
@ -328,14 +337,22 @@ 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();
// Sunrise: Save gradient settings to database
profile.HairGradientEnabled = appearance.HairGradientEnabled;
profile.HairGradientSecondaryColor = appearance.HairGradientSecondaryColor.ToHex();
profile.HairGradientDirection = appearance.HairGradientDirection;
profile.FacialHairGradientEnabled = appearance.FacialHairGradientEnabled;
profile.FacialHairGradientSecondaryColor = appearance.FacialHairGradientSecondaryColor.ToHex();
profile.FacialHairGradientDirection = appearance.FacialHairGradientDirection;
profile.AllMarkingsGradientEnabled = appearance.AllMarkingsGradientEnabled;
profile.AllMarkingsGradientSecondaryColor = appearance.AllMarkingsGradientSecondaryColor.ToHex();
profile.AllMarkingsGradientDirection = appearance.AllMarkingsGradientDirection;
// Debug logging for gradient save
Console.WriteLine($"Sunrise: Saving gradient settings - Hair: {appearance.HairGradientEnabled}, FacialHair: {appearance.FacialHairGradientEnabled}, AllMarkings: {appearance.AllMarkingsGradientEnabled}");
profile.SpawnPriority = (int) humanoid.SpawnPriority;
profile.Markings = markings;
profile.Slot = slot;

View file

@ -131,6 +131,45 @@ public sealed partial class HumanoidAppearanceComponent : Component
/// </summary>
[DataField]
public Dictionary<string, Dictionary<Sex, Dictionary<HumanoidVisualLayers, DisplacementData>>> BodyTypeSexMarkingsDisplacement = new();
//Sunrise start Hair/Fur gradient settings (client-applied shader parameters)
[DataField, AutoNetworkedField]
public bool HairGradientEnabled = false;
[DataField, AutoNetworkedField]
public Color HairGradientSecondaryColor = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField, AutoNetworkedField]
public int HairGradientDirection = 0;
[DataField, AutoNetworkedField]
public bool FacialHairGradientEnabled = false;
[DataField, AutoNetworkedField]
public Color FacialHairGradientSecondaryColor = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField, AutoNetworkedField]
public int FacialHairGradientDirection = 0; //Sunrise end
// Sunrise start Global gradient for all markings (except skin/base layers)
[DataField, AutoNetworkedField]
public bool AllMarkingsGradientEnabled = false;
[DataField, AutoNetworkedField]
public Color AllMarkingsGradientSecondaryColor = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField, AutoNetworkedField]
public int AllMarkingsGradientDirection = 0;
// Sunrise end
}
[DataDefinition]

View file

@ -1,5 +1,4 @@
using System.Linq;
using Content.Shared._Sunrise.MarkingEffects;
using System.Numerics;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
@ -25,22 +24,6 @@ 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;
@ -50,6 +33,45 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
[DataField]
public List<Marking> Markings { get; set; } = new();
//Sunrise start Hair/Fur gradient settings
[DataField]
public bool HairGradientEnabled { get; set; } = false;
[DataField]
public Color HairGradientSecondaryColor { get; set; } = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField]
public int HairGradientDirection { get; set; } = 0;
[DataField]
public bool FacialHairGradientEnabled { get; set; } = false;
[DataField]
public Color FacialHairGradientSecondaryColor { get; set; } = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField]
public int FacialHairGradientDirection { get; set; } = 0; //Sunrise end
// Sunrise start Global gradient for all markings (except skin)
[DataField]
public bool AllMarkingsGradientEnabled { get; set; } = false;
[DataField]
public Color AllMarkingsGradientSecondaryColor { get; set; } = Color.White;
/// <summary>
/// 0 = bottom->top, 1 = top->bottom, 2 = left->right, 3 = right->left
/// </summary>
[DataField]
public int AllMarkingsGradientDirection { get; set; } = 0;
// Sunrise end
[DataField]
public float Width { get; set; } = 1f; //Sunrise
@ -63,12 +85,6 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
Color eyeColor,
Color skinColor,
List<Marking> markings,
//sunrise gradient start
MarkingEffectType hairMarkingEffectType,
MarkingEffect? hairMarkingEffect,
MarkingEffectType facialHairMarkingEffectType,
MarkingEffect? facialHairMarkingEffect,
//sunrise gradient end
float width, //Sunrise
float height) //Sunrise
{
@ -79,89 +95,313 @@ 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
this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings), other.Width, other.Height)
{
HairGradientEnabled = other.HairGradientEnabled; //Sunrise start
HairGradientSecondaryColor = ClampColor(other.HairGradientSecondaryColor);
HairGradientDirection = other.HairGradientDirection;
FacialHairGradientEnabled = other.FacialHairGradientEnabled;
FacialHairGradientSecondaryColor = ClampColor(other.FacialHairGradientSecondaryColor);
FacialHairGradientDirection = other.FacialHairGradientDirection;
AllMarkingsGradientEnabled = other.AllMarkingsGradientEnabled;
AllMarkingsGradientSecondaryColor = ClampColor(other.AllMarkingsGradientSecondaryColor);
AllMarkingsGradientDirection = other.AllMarkingsGradientDirection; //Sunrise end
}
public HumanoidCharacterAppearance WithHairStyleName(string newName)
{
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithHairColor(Color newColor, MarkingEffect? newExtendedColor = null)
public HumanoidCharacterAppearance WithHairColor(Color newColor)
{
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? HairMarkingEffectType, newExtendedColor ?? HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithFacialHairStyleName(string newName)
{
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height);
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithFacialHairColor(Color newColor, MarkingEffect? newFacialExtendedColor = null)
public HumanoidCharacterAppearance WithFacialHairColor(Color newColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? FacialHairMarkingEffectType, newFacialExtendedColor ?? FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithEyeColor(Color newColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithSkinColor(Color newColor)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
public HumanoidCharacterAppearance WithMarkings(List<Marking> newMarkings)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, Width, Height) //Sunrise start
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
}; //Sunrise end
}
// sunrise gradient edit start
public HumanoidCharacterAppearance WithHairExtendedColor(MarkingEffect? newExtendedColor)
//Sunrise start Hair gradient methods
public HumanoidCharacterAppearance WithHairGradientEnabled(bool enabled)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? MarkingEffectType.Color, newExtendedColor, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = enabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithFacialHairExtendedColor(MarkingEffect? newFacialExtendedColor)
public HumanoidCharacterAppearance WithHairGradientSecondaryColor(Color color)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? MarkingEffectType.Color, newFacialExtendedColor, Width, Height); // sunrise gradient edit
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = ClampColor(color),
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithHairGradientDirection(int direction)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = direction,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithFacialHairGradientEnabled(bool enabled)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = enabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithFacialHairGradientSecondaryColor(Color color)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = ClampColor(color),
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithFacialHairGradientDirection(int direction)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = direction,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithAllMarkingsGradientEnabled(bool enabled)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = enabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithAllMarkingsGradientSecondaryColor(Color color)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = ClampColor(color),
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithAllMarkingsGradientDirection(int direction)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = direction
};
}
// sunrise gradient edit end
public HumanoidCharacterAppearance WithWidth(float newWidth)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, newWidth, Height);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newWidth, Height)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
public HumanoidCharacterAppearance WithHeight(float newHeight)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, newHeight);
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, newHeight)
{
HairGradientEnabled = HairGradientEnabled,
HairGradientSecondaryColor = HairGradientSecondaryColor,
HairGradientDirection = HairGradientDirection,
FacialHairGradientEnabled = FacialHairGradientEnabled,
FacialHairGradientSecondaryColor = FacialHairGradientSecondaryColor,
FacialHairGradientDirection = FacialHairGradientDirection,
AllMarkingsGradientEnabled = AllMarkingsGradientEnabled,
AllMarkingsGradientSecondaryColor = AllMarkingsGradientSecondaryColor,
AllMarkingsGradientDirection = AllMarkingsGradientDirection
};
}
//Sunrise end
public static HumanoidCharacterAppearance DefaultWithSpecies(string species)
{
@ -185,12 +425,6 @@ 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
);
@ -209,40 +443,76 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
{
var random = IoCManager.Resolve<IRobustRandom>();
var markingManager = IoCManager.Resolve<MarkingManager>();
var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList();
var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList();
var newHairStyle = hairStyles.Count > 0
? random.Pick(hairStyles)
: HairStyles.DefaultHairStyle.Id;
var newFacialHairStyle = HairStyles.DefaultFacialHairStyle;
var newHairStyle = HairStyles.DefaultHairStyle;
List<Marking> newMarkings = [];
var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female
? HairStyles.DefaultFacialHairStyle.Id
: random.Pick(facialHairStyles);
// grab a completely random color.
var baseColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1);
var newHairColor = random.Pick(HairStyles.RealisticHairColors);
newHairColor = newHairColor
.WithRed(RandomizeColor(newHairColor.R))
.WithGreen(RandomizeColor(newHairColor.G))
.WithBlue(RandomizeColor(newHairColor.B));
// TODO: Add random markings
var newEyeColor = random.Pick(RealisticEyeColors);
// create a new color palette based on BaseColor. roll to determine what type of palette it is.
// personally I think this should be weighted, but I can't be bothered to implement that.
List<Color> colorPalette = [];
switch (random.Next(3))
{
case 0:
colorPalette = GetSplitComplementaries(baseColor);
break;
case 1:
colorPalette = GetTriadicComplementaries(baseColor);
break;
case 2:
colorPalette = GetOneComplementary(baseColor);
break;
}
// grab the species skin coloration type.
var skinType = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species).SkinColoration;
var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1);
// declare some defaults. ensures that the hair and eyes on hues-colored species don't match the skin or one another.
var newSkinColor = colorPalette[0];
var newHairColor = colorPalette[1];
var newEyeColor = colorPalette[2];
// now we do some color logic.
switch (skinType)
{
// if the species is HumanToned:
case HumanoidSkinColor.HumanToned:
newSkinColor = Humanoid.SkinColor.HumanSkinTone(random.Next(0, 101));
// quantize the randomized skin color to the nearest acceptable HumanToned color.
var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor));
newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone);
// pick a random realistic hair color from the list and randomize it juuuuust a little bit.
newHairColor = random.Pick(HairStyles.RealisticHairColors);
newHairColor = newHairColor
.WithRed(RandomizeColor(newHairColor.R))
.WithGreen(RandomizeColor(newHairColor.G))
.WithBlue(RandomizeColor(newHairColor.B));
// and pick a random realistic eye color from the list.
newEyeColor = random.Pick(RealisticEyeColors);
// we're also going to crush the other colors down to the skin's luminosity so markings don't appear too bright on darker skin.
colorPalette[1] = SquashToSkinLuminosity(newSkinColor, colorPalette[1]);
colorPalette[2] = SquashToSkinLuminosity(newSkinColor, colorPalette[2]);
break;
// if the species is Hues toned: it's fine the way it is.
case HumanoidSkinColor.Hues:
break;
// if the species is TintedHues toned: tint them hues.
case HumanoidSkinColor.TintedHues:
newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor);
// we're also going to crush the other colors down to valid TintedHues skin colors.
colorPalette[1] = Humanoid.SkinColor.ValidTintedHuesSkinTone(colorPalette[1]);
colorPalette[2] = Humanoid.SkinColor.ValidTintedHuesSkinTone(colorPalette[2]);
break;
// if the species is VoxFeathers toned: confine the skin color to vox limits. Bright colors are otherwise fine, so leave the marking colors alone.
case HumanoidSkinColor.VoxFeathers:
newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor);
break;
@ -251,18 +521,151 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
break;
}
// now we loop through every extant marking category,
foreach (var category in Enum.GetValues<MarkingCategories>())
{
// grab a list of markings in that category for that species,
var markings = markingManager.MarkingsByCategoryAndSpecies(category, species).Keys.ToList();
var markingProtos = markingManager.MarkingsByCategoryAndSpecies(category, species).Values.ToList();
// if it's facial hair, there are entries in the category, and the character is not female, assign a random one. else bald
if (category == MarkingCategories.FacialHair)
{
newFacialHairStyle = markings.Count == 0 || sex == Sex.Female ? HairStyles.DefaultFacialHairStyle : random.Pick(markings);
}
// if it's hair, and there are hair styles, roll one. else bald
else if (category == MarkingCategories.Hair)
{
newHairStyle = markings.Count > 0 ? random.Pick(markings) : HairStyles.DefaultHairStyle;
}
// for every other category,
else if (markings.Count > 0)
{
// roll a die. currently a 1 in 3 chance per category, except Tails, which are 1 in 2 (because of the effect they have on the silhouettes of spiders and moths.)
int diceRoll;
if (category == MarkingCategories.Tail)
diceRoll = random.Next(2);
else
diceRoll = random.Next(3);
if (diceRoll == 0)
{
MarkingPrototype? lastMarking = null;
// roll to see how many markings from that category will be added. currently a maximum of 2.
var loops = random.Next(2) + 1;
// add a marking (loops) times
for (var i = 0; i < loops; i++)
{
// pick a random marking from the list
var protoToAdd = random.Pick(markingProtos);
var markingToAdd = protoToAdd.AsMarking();
Color markingColor;
// prevent duplicates:
if (lastMarking != null && lastMarking == protoToAdd)
continue;
// set gauze to white.
// side note, I really hate that gauze isn't its own category. please fix that so that i can make this not suck as much.
// or, like, give it its own color rules. or something.
if (markingToAdd.MarkingId.Contains("gauze", StringComparison.OrdinalIgnoreCase))
{
markingToAdd.SetColor(Color.White);
newMarkings.Add(markingToAdd);
lastMarking = protoToAdd;
continue;
}
// select a random color from our two secondary colors. if our marking is a Tail, add the skin color as well, otherwise lizards always look a little odd.
// this will also make moths and spiders look less interesting on average, but I don't want a hardcoded exception for lizards.
if (category == MarkingCategories.Tail)
markingColor = random.Pick(colorPalette);
else
markingColor = random.Pick(colorPalette.Skip(0).ToList());
// set the marking to that color
markingToAdd.SetColor(markingColor);
// otherwise, add it to the final list.
newMarkings.Add(markingToAdd);
lastMarking = protoToAdd;
}
}
}
}
//Sunrise start
var speciesPrototype = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species);
var newWidth = random.NextFloat(speciesPrototype.MinWidth, speciesPrototype.MaxWidth);
var newHeight = random.NextFloat(speciesPrototype.MinHeight, speciesPrototype.MaxHeight);
//Sunrise end
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new (), MarkingEffectType.Color, null, MarkingEffectType.Color, null, newWidth, newHeight);
// 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
// helper functions:
float RandomizeColor(float channel)
{
return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f);
}
List<Color> GetComplementaryColors(Color color, double angle)
{
var hsl = Color.ToHsl(color);
var hVal = hsl.X + angle;
hVal = hVal >= 0.360 ? hVal - 0.360 : hVal;
var positiveHSL = new Vector4((float)hVal, hsl.Y, hsl.Z, hsl.W);
var hVal1 = hsl.X - angle;
hVal1 = hVal1 <= 0 ? hVal1 + 0.360 : hVal1;
var negativeHSL = new Vector4((float)hVal1, hsl.Y, hsl.Z, hsl.W);
var c0 = Color.FromHsl(positiveHSL);
var c1 = Color.FromHsl(negativeHSL);
var palette = new List<Color> { color, c0, c1 };
return palette;
}
// return a list of triadic complementary colors
List<Color> GetTriadicComplementaries(Color color)
{
return GetComplementaryColors(color, 0.120);
}
// return a list of split complementary colors
List<Color> GetSplitComplementaries(Color color)
{
return GetComplementaryColors(color, 0.150);
}
// return a list containing the base color and two copies of a single complemenary color
List<Color> GetOneComplementary(Color color)
{
return GetComplementaryColors(color, 0.180);
}
Color SquashToSkinLuminosity(Color skinColor, Color toSquash)
{
var skinColorHSL = Color.ToHsl(skinColor);
var toSquashHSL = Color.ToHsl(toSquash);
// check if the skin color is as dark as or darker than the marking color:
if (toSquashHSL.Z <= skinColorHSL.Z)
{
// if it is, don't fuck with it
return toSquash;
}
// otherwise, create a new color with the H, S, and A of toSquash, but the L of skinColor
var newColor = new Vector4(toSquashHSL.X, toSquashHSL.Y, skinColorHSL.Z, toSquashHSL.W);
return Color.FromHsl(newColor);
}
}
public static Color ClampColor(Color color)
@ -333,24 +736,6 @@ 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,
@ -359,13 +744,10 @@ 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;
@ -376,12 +758,15 @@ 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 (HairGradientEnabled != other.HairGradientEnabled) return false; //Sunrise start
if (!HairGradientSecondaryColor.Equals(other.HairGradientSecondaryColor)) return false;
if (HairGradientDirection != other.HairGradientDirection) return false;
if (FacialHairGradientEnabled != other.FacialHairGradientEnabled) return false;
if (!FacialHairGradientSecondaryColor.Equals(other.FacialHairGradientSecondaryColor)) return false;
if (FacialHairGradientDirection != other.FacialHairGradientDirection) return false;
if (AllMarkingsGradientEnabled != other.AllMarkingsGradientEnabled) return false;
if (!AllMarkingsGradientSecondaryColor.Equals(other.AllMarkingsGradientSecondaryColor)) return false;
if (AllMarkingsGradientDirection != other.AllMarkingsGradientDirection) return false; //Sunrise end
if (Width != other.Width) return false; //Sunrise
if (Height != other.Height) return false; //Sunrise
return true;
@ -398,11 +783,15 @@ 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) &&
HairGradientEnabled == other.HairGradientEnabled && //Sunrise start
HairGradientSecondaryColor.Equals(other.HairGradientSecondaryColor) &&
HairGradientDirection == other.HairGradientDirection &&
FacialHairGradientEnabled == other.FacialHairGradientEnabled &&
FacialHairGradientSecondaryColor.Equals(other.FacialHairGradientSecondaryColor) &&
FacialHairGradientDirection == other.FacialHairGradientDirection &&
AllMarkingsGradientEnabled == other.AllMarkingsGradientEnabled &&
AllMarkingsGradientSecondaryColor.Equals(other.AllMarkingsGradientSecondaryColor) &&
AllMarkingsGradientDirection == other.AllMarkingsGradientDirection && //Sunrise end
Width == other.Width && //starlight
Height == other.Height;
}
@ -414,7 +803,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
public override int GetHashCode()
{
return HashCode.Combine(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, new Vector2(Width, Height));
var h1 = HashCode.Combine(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); //Sunrise start
var h2 = HashCode.Combine(HairGradientEnabled, HairGradientSecondaryColor, HairGradientDirection);
var h3 = HashCode.Combine(FacialHairGradientEnabled, FacialHairGradientSecondaryColor, FacialHairGradientDirection);
var h4 = HashCode.Combine(AllMarkingsGradientEnabled, AllMarkingsGradientSecondaryColor, AllMarkingsGradientDirection);
var h5 = HashCode.Combine(Width, Height);
return HashCode.Combine(h1, h2, h3, h4, h5); //Sunrise end
}
public HumanoidCharacterAppearance Clone()

View file

@ -1,5 +1,4 @@
using System.Linq;
using Content.Shared._Sunrise.MarkingEffects;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@ -13,32 +12,20 @@ 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<MarkingEffect>? markingEffects = null)
List<Color> markingColors)
{
MarkingId = markingId;
_markingColors = markingColors;
MarkingEffects = markingEffects ?? new(); // sunrise gradient edit
}
public Marking(string markingId,
IReadOnlyList<Color> markingColors,
IReadOnlyList<MarkingEffect>? markingEffects = null)
: this(
markingId,
new List<Color>(markingColors),
markingEffects is not null ? new List<MarkingEffect>(markingEffects) : new List<MarkingEffect>())
IReadOnlyList<Color> markingColors)
: this(markingId, new List<Color>(markingColors))
{
}
@ -47,11 +34,7 @@ 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;
}
@ -59,7 +42,6 @@ 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;
}
@ -99,20 +81,6 @@ namespace Content.Shared.Humanoid.Markings
}
}
public void SetMarkingEffect(int colorIndex, MarkingEffect effect)
{
if(MarkingEffects.Count > colorIndex && colorIndex >= 0)
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)
@ -154,53 +122,28 @@ 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
public new string ToString()
new public string ToString()
{
// reserved character
string sanitizedName = this.MarkingId.Replace('@', '_');
List<string> colorStringList = new();
foreach (Color color in _markingColors)
colorStringList.Add(color.ToHex());
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}";
return $"{sanitizedName}@{String.Join(',', colorStringList)}";
}
public static Marking? ParseFromDbString(string input)
{
if (string.IsNullOrWhiteSpace(input))
if (input.Length == 0)
return null;
var split = input.Split('@');
if (split.Length < 2)
return null;
if (split.Length != 2) return null;
List<Color> colorList = new();
foreach (string color in split[1].Split(','))
colorList.Add(Color.FromHex(color));
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);
return new Marking(split[0], colorList);
}
}
}

View file

@ -3,7 +3,6 @@ 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;
@ -199,6 +198,20 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
targetHumanoid.Width = sourceHumanoid.Width; //Sunrise
targetHumanoid.Height = sourceHumanoid.Height; //Sunrise
//Sunrise start: Copy gradient settings
targetHumanoid.HairGradientEnabled = sourceHumanoid.HairGradientEnabled;
targetHumanoid.HairGradientSecondaryColor = sourceHumanoid.HairGradientSecondaryColor;
targetHumanoid.HairGradientDirection = sourceHumanoid.HairGradientDirection;
targetHumanoid.FacialHairGradientEnabled = sourceHumanoid.FacialHairGradientEnabled;
targetHumanoid.FacialHairGradientSecondaryColor = sourceHumanoid.FacialHairGradientSecondaryColor;
targetHumanoid.FacialHairGradientDirection = sourceHumanoid.FacialHairGradientDirection;
targetHumanoid.AllMarkingsGradientEnabled = sourceHumanoid.AllMarkingsGradientEnabled;
targetHumanoid.AllMarkingsGradientSecondaryColor = sourceHumanoid.AllMarkingsGradientSecondaryColor;
targetHumanoid.AllMarkingsGradientDirection = sourceHumanoid.AllMarkingsGradientDirection;
targetHumanoid.CachedHairColor = sourceHumanoid.CachedHairColor;
targetHumanoid.CachedFacialHairColor = sourceHumanoid.CachedFacialHairColor;
//Sunrise end
SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
SetGender((target, targetHumanoid), sourceHumanoid.Gender);
@ -470,13 +483,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, markingEffect: profile.Appearance.HairMarkingEffect);
AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false);
}
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, markingEffect: profile.Appearance.FacialHairMarkingEffect);
AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false);
}
humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _proto);
@ -508,6 +521,20 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
humanoid.Width = profile.Appearance.Width; //Sunrise
humanoid.Height = profile.Appearance.Height; //Sunrise
//Sunrise start: Load gradient settings from profile
humanoid.HairGradientEnabled = profile.Appearance.HairGradientEnabled;
humanoid.HairGradientSecondaryColor = profile.Appearance.HairGradientSecondaryColor;
humanoid.HairGradientDirection = profile.Appearance.HairGradientDirection;
humanoid.FacialHairGradientEnabled = profile.Appearance.FacialHairGradientEnabled;
humanoid.FacialHairGradientSecondaryColor = profile.Appearance.FacialHairGradientSecondaryColor;
humanoid.FacialHairGradientDirection = profile.Appearance.FacialHairGradientDirection;
humanoid.AllMarkingsGradientEnabled = profile.Appearance.AllMarkingsGradientEnabled;
humanoid.AllMarkingsGradientSecondaryColor = profile.Appearance.AllMarkingsGradientSecondaryColor;
humanoid.AllMarkingsGradientDirection = profile.Appearance.AllMarkingsGradientDirection;
// Set cached hair colors for gradient shader
humanoid.CachedHairColor = hairColor;
humanoid.CachedFacialHairColor = facialHairColor; //Sunrise end
Dirty(uid, humanoid);
}
@ -520,7 +547,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, MarkingEffect? markingEffect = null)
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid)
|| !_markingManager.Markings.TryGetValue(marking, out var prototype))
@ -535,8 +562,6 @@ 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

@ -1,62 +0,0 @@
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

@ -1,147 +0,0 @@
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;
}
}
}
// Ensure both base and gradient colors are present for compatibility
if (!colors.ContainsKey("base"))
colors["base"] = Color.White;
if (!colors.ContainsKey("gradient"))
colors["gradient"] = colors["base"]; // Use base color as fallback
return new GradientMarkingEffect(colors, offset, size, rotation, speed, pixelated, mirrored);
}
#endregion
#region Constructors
public GradientMarkingEffect()
{
Colors = new Dictionary<string, Color>()
{
{ "base", Color.White },
{ "gradient", Color.White }
};
}
public GradientMarkingEffect(Color color)
{
Colors = new Dictionary<string, Color>
{
{"base", color },
{"gradient", 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

@ -1,150 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,101 +0,0 @@
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;
}
}
}
// Ensure both base and gradient colors are present for compatibility
if (!colors.ContainsKey("base"))
colors["base"] = Color.White;
if (!colors.ContainsKey("gradient"))
colors["gradient"] = colors["base"]; // Use base color as fallback
return new RoughGradientMarkingEffect(colors, horizontal);
}
#endregion
#region Constructors
public RoughGradientMarkingEffect()
{
Colors = new Dictionary<string, Color>()
{
{ "base", Color.White },
{ "gradient", Color.White }
};
}
public RoughGradientMarkingEffect(Color color)
{
Colors = new Dictionary<string, Color>
{
{"base", color },
{"gradient", 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,13 @@
# Sunrise Hair gradient UI
humanoid-profile-editor-hair-gradient-label = Hair gradient
humanoid-profile-editor-hair-gradient-enable = Enable gradient
humanoid-profile-editor-hair-gradient-direction = Direction
humanoid-profile-editor-hair-gradient-dir-bottom-top = Bottom to top
humanoid-profile-editor-hair-gradient-dir-top-bottom = Top to bottom
humanoid-profile-editor-hair-gradient-dir-left-right = Left to right
humanoid-profile-editor-hair-gradient-dir-right-left = Right to left
humanoid-profile-editor-facialhair-gradient-label = Facial hair gradient
# Sunrise All markings gradient
humanoid-profile-editor-allmarkings-gradient-label = All markings gradient

View file

@ -0,0 +1,13 @@
# Sunrise Hair gradient UI
humanoid-profile-editor-hair-gradient-label = Градиент волос
humanoid-profile-editor-hair-gradient-enable = Включить градиент
humanoid-profile-editor-hair-gradient-direction = Направление
humanoid-profile-editor-hair-gradient-dir-bottom-top = Снизу вверх
humanoid-profile-editor-hair-gradient-dir-top-bottom = Сверху вниз
humanoid-profile-editor-hair-gradient-dir-left-right = Слева направо
humanoid-profile-editor-hair-gradient-dir-right-left = Справа налево
humanoid-profile-editor-facialhair-gradient-label = Градиент растительности на лице
# Sunrise All markings gradient
humanoid-profile-editor-allmarkings-gradient-label = Градиент всех маркингов

View file

@ -105,4 +105,4 @@ marking-HairAppleJack = Эппл Джек (NEW)
marking-HairPinkiePie = Пинки Пай (NEW)
marking-HairRarity = Рарити (NEW)
marking-HairAqua = Аква (NEW)
marking-HairBairdAndBangs = Коса с чёлкой (NEW)
marking-HairBairdAndBangs = Коса с чёлкой (NEW)

View file

@ -1,4 +1,4 @@
marking-effect-type-color = Без эффекта
marking-effect-type-color = Стандартный
marking-effect-color-color-base = Цвет
marking-effect-type-gradient = Градиент
marking-effect-gradient-color-base = Начало

View file

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

View file

@ -0,0 +1,13 @@
# © SUNRISE, Contributor: UnilTan
# Licensed under SUNRISE CLA
# Full text: https://github.com/space-sunrise/space-station-14/blob/master/CLA.txt
# Hair/fur linear gradient used in customization
- type: shader
id: HairGradient
kind: source
path: "/Textures/_Sunrise/Shaders/hair_linear_gradient.swsl"
params:
color1: 1, 0, 0
color2: 1, 1, 1
direction: 0
light_mode: shaded

View file

@ -1,4 +1,4 @@
- type: shader
- type: shader
id: NVDDisplay
kind: source
path: "/Textures/_Sunrise/Shaders/NVDDisplay.swsl"
@ -34,13 +34,3 @@
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

@ -1,66 +0,0 @@
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

@ -1,69 +0,0 @@
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);
}

View file

@ -0,0 +1,43 @@
// Linear gradient tint shader for sprite layers (hair/fur).
// Blends between two colors along a chosen axis and multiplies original texture RGB (no grayscale), preserving alpha.
//
// direction:
// 0 = bottom -> top (vertical)
// 1 = top -> bottom (vertical)
// 2 = left -> right (horizontal)
// 3 = right -> left (horizontal)
uniform highp vec3 color1;
uniform highp vec3 color2;
uniform highp float direction;
void fragment()
{
// Sample the sprite normally (texture atlas UV)
highp vec4 base = zTexture(UV);
// Compute gradient coordinate t in [0,1] using quad-local UV (UV2),
// so it spans exactly across the drawn sprite region, not the whole atlas.
highp float t = 0.0;
if (direction < 0.5) // bottom -> top
t = UV2.y;
else if (direction < 1.5) // top -> bottom
t = 1.0 - UV2.y;
else if (direction < 2.5) // left -> right
t = UV2.x;
else // right -> left
t = 1.0 - UV2.x;
highp float tt = clamp(t, 0.0, 1.0);
// Создаем градиентный цвет: переходим от color1 к color2
highp vec3 gradColor = mix(color1, color2, tt);
// Применяем градиентный цвет к оригинальной текстуре
// Текстура волос умножается на градиентный цвет напрямую
highp vec3 finalRgb = base.rgb * gradColor;
COLOR = vec4(finalRgb, base.a);
}

7
runall.bat Normal file
View file

@ -0,0 +1,7 @@
set PDIR=%~dp0
cd %PDIR%Bin\Content.Server
start Content.Server.exe
cd %PDIR%Bin\Content.Client
start Content.Client.exe
cd %PDIR%
set PDIR=