From d1ed5109e27a869844bc74932b213c882fa66a4e Mon Sep 17 00:00:00 2001 From: Ligyb <65973111+Lgibb18@users.noreply.github.com> Date: Fri, 1 Aug 2025 00:32:37 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=B4=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B2=D0=BE=D0=BB=D0=BE=D1=81=20(#2652)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DisplacementMap/DisplacementMapSystem.cs | 18 +- .../Humanoid/HumanoidAppearanceSystem.cs | 73 +- .../Humanoid/SingleMarkingPicker.xaml | 8 +- .../Humanoid/SingleMarkingPicker.xaml.cs | 32 +- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 66 +- .../ColorMarkingEffectUiBuilder.cs | 16 + .../GradientMarkingEffectUiBuilder.cs | 45 + .../IMarkingEffectUiBuilder.cs | 14 + .../MarkingEffectShaders.cs | 44 + .../RoughGradientMarkingEffectUiBuilder.cs | 21 + .../Controls/CustomColorSelectorSliders.cs | 445 ++++ .../Controls/MarkingEffectSelectorSliders.cs | 238 ++ .../20250724212329_Gradient.Designer.cs | 2193 +++++++++++++++++ .../Postgres/20250724212329_Gradient.cs | 62 + .../PostgresServerDbContextModelSnapshot.cs | 18 + .../20250724212307_Gradient.Designer.cs | 2114 ++++++++++++++++ .../Sqlite/20250724212307_Gradient.cs | 62 + .../SqliteServerDbContextModelSnapshot.cs | 18 + Content.Server.Database/Model.cs | 6 + Content.Server/Database/ServerDbBase.cs | 13 + .../Humanoid/HumanoidCharacterAppearance.cs | 105 +- Content.Shared/Humanoid/Markings/Marking.cs | 87 +- .../SharedHumanoidAppearanceSystem.cs | 9 +- .../MarkingEffects/ColorMarkingEffect.cs | 62 + .../MarkingEffects/GradientMarkingEffect.cs | 139 ++ .../_Sunrise/MarkingEffects/MarkingEffect.cs | 150 ++ .../MarkingEffects/MarkingEffectTypes.cs | 20 + .../RoughGradientMarkingEffect.cs | 93 + .../markingeffects/markingeffects.ftl | 19 + .../Entities/Mobs/Species/human.yml | 6 +- .../Prototypes/_Sunrise/Shaders/sharders.yml | 12 +- .../Textures/_Sunrise/Shaders/gradient.swsl | 66 + .../_Sunrise/Shaders/gradient_rough.swsl | 69 + 33 files changed, 6274 insertions(+), 69 deletions(-) create mode 100644 Content.Client/_Sunrise/MarkingEffectsClient/ColorMarkingEffectUiBuilder.cs create mode 100644 Content.Client/_Sunrise/MarkingEffectsClient/GradientMarkingEffectUiBuilder.cs create mode 100644 Content.Client/_Sunrise/MarkingEffectsClient/IMarkingEffectUiBuilder.cs create mode 100644 Content.Client/_Sunrise/MarkingEffectsClient/MarkingEffectShaders.cs create mode 100644 Content.Client/_Sunrise/MarkingEffectsClient/RoughGradientMarkingEffectUiBuilder.cs create mode 100644 Content.Client/_Sunrise/UserInterface/Controls/CustomColorSelectorSliders.cs create mode 100644 Content.Client/_Sunrise/UserInterface/Controls/MarkingEffectSelectorSliders.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.cs create mode 100644 Content.Shared/_Sunrise/MarkingEffects/ColorMarkingEffect.cs create mode 100644 Content.Shared/_Sunrise/MarkingEffects/GradientMarkingEffect.cs create mode 100644 Content.Shared/_Sunrise/MarkingEffects/MarkingEffect.cs create mode 100644 Content.Shared/_Sunrise/MarkingEffects/MarkingEffectTypes.cs create mode 100644 Content.Shared/_Sunrise/MarkingEffects/RoughGradientMarkingEffect.cs create mode 100644 Resources/Locale/ru-RU/_strings/_sunrise/markingeffects/markingeffects.ftl create mode 100644 Resources/Textures/_Sunrise/Shaders/gradient.swsl create mode 100644 Resources/Textures/_Sunrise/Shaders/gradient_rough.swsl diff --git a/Content.Client/DisplacementMap/DisplacementMapSystem.cs b/Content.Client/DisplacementMap/DisplacementMapSystem.cs index 94dbc7f00c..23e9e8b117 100644 --- a/Content.Client/DisplacementMap/DisplacementMapSystem.cs +++ b/Content.Client/DisplacementMap/DisplacementMapSystem.cs @@ -23,15 +23,29 @@ public sealed class DisplacementMapSystem : EntitySystem Entity sprite, int index, object key, - out string displacementKey) + out string displacementKey, + ShaderInstance? shaderOverride = null) // Sunrise-Edit { displacementKey = $"{key}-displacement"; if (key.ToString() is null) return false; + // Sunrise-Start + // sprite.Comp.LayerSetShader(index, data.ShaderOverride); + // TODO: костыль пиздец, когда появится возможность устанавливать 2 шейдера на один леер - удалить эту хуйню if (data.ShaderOverride != null) - sprite.Comp.LayerSetShader(index, data.ShaderOverride); + { + if (shaderOverride != null) + { + shaderOverride.SetParameter("useDisplacement", true); + shaderOverride.SetParameter("displacementSize", 127); + sprite.Comp.LayerSetShader(index, shaderOverride); + } + else + sprite.Comp.LayerSetShader(index, data.ShaderOverride); + } + // Sunrise-End _sprite.RemoveLayer(sprite.AsNullable(), displacementKey, false); diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 5748c8165b..571944ffaa 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -1,15 +1,19 @@ +using System.Linq; using System.Numerics; +using Content.Client._Sunrise.MarkingEffectsClient; using Content.Client.DisplacementMap; using Content.Shared.CCVar; using Content.Shared.Humanoid; using Content.Shared.CCVar; using Content.Shared._Sunrise; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared.DisplacementMap; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Inventory; using Content.Shared.Preferences; using Robust.Client.GameObjects; +using Robust.Client.Graphics; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -181,14 +185,25 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager) ? profile.Appearance.SkinColor.WithAlpha(hairAlpha) : profile.Appearance.HairColor; + + var hairMarkingEffects = profile.Appearance.HairMarkingEffect != null + ? new List { profile.Appearance.HairMarkingEffect } + : new List(); + var hair = new Marking(profile.Appearance.HairStyleId, - new[] { hairColor }); + new[] { hairColor }, + hairMarkingEffects); + + var facialHairMarkingEffects = profile.Appearance.FacialHairMarkingEffect != null + ? new List { profile.Appearance.FacialHairMarkingEffect } + : new List(); var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager) ? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha) : profile.Appearance.FacialHairColor; var facialHair = new Marking(profile.Appearance.FacialHairStyleId, - new[] { facialHairColor }); + new[] { facialHairColor }, + facialHairMarkingEffects); if (_markingManager.CanBeApplied(profile.Species, profile.Sex, hair, _prototypeManager)) { @@ -258,7 +273,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem { if (_markingManager.TryGetMarking(marking, out var markingPrototype)) { - ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity); + ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity, marking.MarkingEffects); // Sunrise-Edit //if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop) // applyUndergarmentTop = false; //else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom) @@ -350,7 +365,8 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem public void ApplyMarking(MarkingPrototype markingPrototype, // Sunrise-Edit IReadOnlyList? colors, bool visible, - Entity entity) + Entity entity, + IReadOnlyList? markingEffects = null) { var humanoid = entity.Comp1; var sprite = entity.Comp2; @@ -389,22 +405,55 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem continue; } - // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid - // and we need to check the index is correct. - // So if that happens just default to white? - if (colors != null && j < colors.Count) + // Sunrise-Edit-Start + // // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid + // // and we need to check the index is correct. + // // So if that happens just default to white? + // if (colors != null && j < colors.Count) + // { + // _sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]); + // } + // else + // { + // _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White); + // } + + ShaderInstance? shaderOverride = null; + + + if (markingEffects != null && j < markingEffects.Count && markingEffects[j].Type != MarkingEffectType.Color) { - _sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]); + float texWidth = sprite.AllLayers.Max(x => x.PixelSize.X); + float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y); + var shaderName = markingEffects[j].Type.ToString(); + var instance = _prototypeManager.Index(shaderName).InstanceUnique(); + shaderOverride = instance; + + instance.ApplyShaderParams(markingEffects[j], new Vector2(texWidth, texHeight)); + + sprite.LayerSetShader(layerId, instance); + _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White); } else { - _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White); + if (colors != null && j < colors.Count) + { + _sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]); + } + else + { + _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White); + } } + //Sunrise-Edit-End var displacementData = GetMarkingDisplacement(entity.Owner, markingPrototype.BodyPart, humanoid); if (displacementData != null && markingPrototype.CanBeDisplaced) { - _displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _); + // TODO: в шейдер нужно ещё вставлять displacementSize, сейчас в нём хардкод 127 + + // TODO: костыль пиздец, когда появится возможность устанавливать 2 шейдера на один леер - удалить эту хуйню (shaderOverride) + _displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _, shaderOverride); // Sunrise-Edit } } } @@ -498,7 +547,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem foreach (var marking in markingList) { if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer) - ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite)); + ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite), marking.MarkingEffects); // Sunrise-Edit } } } diff --git a/Content.Client/Humanoid/SingleMarkingPicker.xaml b/Content.Client/Humanoid/SingleMarkingPicker.xaml index c816c52e9c..4a499dc993 100644 --- a/Content.Client/Humanoid/SingleMarkingPicker.xaml +++ b/Content.Client/Humanoid/SingleMarkingPicker.xaml @@ -17,9 +17,11 @@ + - - - + + + + diff --git a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs index b9bb4525ff..c6528d49f2 100644 --- a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs @@ -1,4 +1,6 @@ using System.Linq; +using Content.Client._Sunrise.UserInterface.Controls; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared.Humanoid.Markings; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; @@ -41,6 +43,10 @@ public sealed partial class SingleMarkingPicker : BoxContainer /// public Action<(int slot, Marking marking)>? OnColorChanged; + // sunrise gradient edit start + public Action<(int slot, Marking marking)>? OnExtendedColorChanged; + // sunrise gradient edit end + // current selected slot private int _slot = -1; private int Slot @@ -229,23 +235,40 @@ public sealed partial class SingleMarkingPicker : BoxContainer ColorSelectorContainer.DisposeAllChildren(); ColorSelectorContainer.RemoveAllChildren(); - if (marking.MarkingColors.Count != proto.Sprites.Count) + if (marking.MarkingColors.Count != proto.Sprites.Count || + marking.MarkingEffects.Count != proto.Sprites.Count) { marking = new Marking(marking.MarkingId, proto.Sprites.Count); } for (var i = 0; i < marking.MarkingColors.Count; i++) { - var selector = new ColorSelectorSliders + MarkingEffect selectorColor; + var selectorType = MarkingEffectType.Color; + if(marking.MarkingEffects == null) + selectorColor = new ColorMarkingEffect(marking.MarkingColors[i]); + else + { + selectorColor = marking.MarkingEffects[i]; + selectorType = marking.MarkingEffects[i].Type; + } + + var selector = new MarkingEffectSelectorSliders(selectorColor) { HorizontalExpand = true }; - selector.Color = marking.MarkingColors[i]; + selector.CurrentType = selectorType; var colorIndex = i; selector.OnColorChanged += color => { - marking.SetColor(colorIndex, color); + var newCol = color.Colors["base"]; + if (marking.MarkingColors[colorIndex] != newCol) + marking.SetColor(colorIndex, newCol); + + if(marking.MarkingEffects?[colorIndex].Equals(color) != true) + marking.SetMarkingEffect(colorIndex, color.Clone()); + OnColorChanged!((_slot, marking)); }; @@ -272,6 +295,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer for (var i = 0; i < _markings[Slot].MarkingColors.Count && i < oldMarking.MarkingColors.Count; i++) { _markings[Slot].SetColor(i, oldMarking.MarkingColors[i]); + _markings[Slot].SetMarkingEffect(i, oldMarking.MarkingEffects[i]); // Sunrise-Edit } PopulateColors(); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 9fd73196a9..6f003e81ee 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -10,6 +10,7 @@ using Content.Client.Sprite; using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Guidebook; using Content.Shared._Sunrise; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared._Sunrise.SunriseCCVars; using Content.Shared.CCVar; using Content.Shared.Clothing; @@ -332,6 +333,30 @@ namespace Content.Client.Lobby.UI #region Hair + // sunrise gradient edit start + + HairStylePicker.OnExtendedColorChanged += newColor => + { + if (Profile is null) + return; + Profile = Profile.WithCharacterAppearance( + Profile.Appearance.WithHairExtendedColor(newColor.marking.MarkingEffects[0])); + UpdateCMarkingsHair(); + ReloadPreview(); + }; + + FacialHairPicker.OnExtendedColorChanged += newColor => + { + if (Profile is null) + return; + Profile = Profile.WithCharacterAppearance( + Profile.Appearance.WithFacialHairExtendedColor(newColor.marking.MarkingEffects[0])); + UpdateCMarkingsFacialHair(); + ReloadPreview(); + }; + + // sunrise gradient edit end + HairStylePicker.OnMarkingSelect += newStyle => { if (Profile is null) @@ -345,8 +370,9 @@ namespace Content.Client.Lobby.UI { if (Profile is null) return; + var newExtended = newColor.marking.MarkingEffects[0].Clone(); Profile = Profile.WithCharacterAppearance( - Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0])); + Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0], newExtended)); // sunrise gradient edit UpdateCMarkingsHair(); ReloadPreview(); }; @@ -364,8 +390,9 @@ namespace Content.Client.Lobby.UI { if (Profile is null) return; + var newExtended = newColor.marking.MarkingEffects[0].Clone(); Profile = Profile.WithCharacterAppearance( - Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0])); + Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0], newExtended)); // sunrise gradient edit UpdateCMarkingsFacialHair(); ReloadPreview(); }; @@ -1657,13 +1684,29 @@ namespace Content.Client.Lobby.UI var hairMarking = Profile.Appearance.HairStyleId switch { HairStyles.DefaultHairStyle => new List(), - _ => new() { new(Profile.Appearance.HairStyleId, new List() { Profile.Appearance.HairColor }) }, + _ => new List + { + new( + Profile.Appearance.HairStyleId, + new[] { Profile.Appearance.HairColor }, + Profile.Appearance.HairMarkingEffect is { } hairExt + ? new List { hairExt.Clone() } + : null) + } }; var facialHairMarking = Profile.Appearance.FacialHairStyleId switch { HairStyles.DefaultFacialHairStyle => new List(), - _ => new() { new(Profile.Appearance.FacialHairStyleId, new List() { Profile.Appearance.FacialHairColor }) }, + _ => new List + { + new( + Profile.Appearance.FacialHairStyleId, + new[] { Profile.Appearance.FacialHairColor }, + Profile.Appearance.FacialHairMarkingEffect is { } facialExt + ? new List { facialExt.Clone() } + : null) + } }; HairStylePicker.UpdateData( @@ -1703,7 +1746,12 @@ namespace Content.Client.Lobby.UI } if (hairColor != null) { - Markings.HairMarking = new (Profile.Appearance.HairStyleId, new List() { hairColor.Value }); + Markings.HairMarking = new ( + Profile.Appearance.HairStyleId, + new List() { hairColor.Value }, + Profile.Appearance.HairMarkingEffect is { } hairExt + ? new List { hairExt.Clone() } + : null); } else { @@ -1737,7 +1785,12 @@ namespace Content.Client.Lobby.UI } if (facialHairColor != null) { - Markings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List() { facialHairColor.Value }); + Markings.FacialHairMarking = new( + Profile.Appearance.FacialHairStyleId, + new List() { facialHairColor.Value }, + Profile.Appearance.FacialHairMarkingEffect is { } facialExt + ? new List { facialExt.Clone() } + : null); } else { @@ -1912,7 +1965,6 @@ namespace Content.Client.Lobby.UI } CBodyTypesButton.Select(_bodyTypes.FindIndex(x => x.ID == Profile.BodyType)); - IsDirty = true; } } } diff --git a/Content.Client/_Sunrise/MarkingEffectsClient/ColorMarkingEffectUiBuilder.cs b/Content.Client/_Sunrise/MarkingEffectsClient/ColorMarkingEffectUiBuilder.cs new file mode 100644 index 0000000000..ea275c8d85 --- /dev/null +++ b/Content.Client/_Sunrise/MarkingEffectsClient/ColorMarkingEffectUiBuilder.cs @@ -0,0 +1,16 @@ +using Content.Client._Sunrise.UserInterface.Controls; +using Content.Shared._Sunrise.MarkingEffects; +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.MarkingEffectsClient; + +public sealed class ColorMarkingEffectUiBuilder : IMarkingEffectUiBuilder +{ + public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent) + { + if (effect is not ColorMarkingEffect) + return; + + parent.CreateSelector(type: MarkingEffectType.Color); + } +} diff --git a/Content.Client/_Sunrise/MarkingEffectsClient/GradientMarkingEffectUiBuilder.cs b/Content.Client/_Sunrise/MarkingEffectsClient/GradientMarkingEffectUiBuilder.cs new file mode 100644 index 0000000000..275e45b7fe --- /dev/null +++ b/Content.Client/_Sunrise/MarkingEffectsClient/GradientMarkingEffectUiBuilder.cs @@ -0,0 +1,45 @@ +using Content.Client._Sunrise.UserInterface.Controls; +using Content.Shared._Sunrise.MarkingEffects; + +namespace Content.Client._Sunrise.MarkingEffectsClient; + +public sealed class GradientMarkingEffectUiBuilder : IMarkingEffectUiBuilder +{ + private const float ToIntScaling = 100; + + private const int OffsetMin = -200; + private const int OffsetMax = 100; + + private const int SizeMin = 30; + private const int SizeMax = 500; + + private const int RotationMin = 0; + private const int RotationMax = 360; + + public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent) + { + if (effect is not GradientMarkingEffect gradient) + return; + + parent.CreateSelector(type: MarkingEffectType.Gradient); + parent.CreateSelector("gradient", MarkingEffectType.Gradient); + + parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-offset"), + (int)(gradient.Offset.Y * ToIntScaling), OffsetMin, OffsetMax, + v => gradient.Offset.Y = v / ToIntScaling + ); + parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-size"), + (int)(gradient.Size.Y * ToIntScaling), SizeMin, SizeMax, + v => gradient.Size.Y = v / ToIntScaling); + parent.CreateSlider(Loc.GetString("marking-effect-gradient-parameter-rotation"), + (int)gradient.Rotation, RotationMin, RotationMax, + v => gradient.Rotation = v); + + parent.CreateToggle(Loc.GetString("marking-effect-gradient-parameter-pixelation"), + gradient.Pixelated, + v => gradient.Pixelated = v); + parent.CreateToggle(Loc.GetString("marking-effect-gradient-parameter-mirror"), + gradient.Mirrored, + v => gradient.Mirrored = v); + } +} diff --git a/Content.Client/_Sunrise/MarkingEffectsClient/IMarkingEffectUiBuilder.cs b/Content.Client/_Sunrise/MarkingEffectsClient/IMarkingEffectUiBuilder.cs new file mode 100644 index 0000000000..e6ed81d9ee --- /dev/null +++ b/Content.Client/_Sunrise/MarkingEffectsClient/IMarkingEffectUiBuilder.cs @@ -0,0 +1,14 @@ +using Content.Client._Sunrise.UserInterface.Controls; +using Content.Shared._Sunrise.MarkingEffects; +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.MarkingEffectsClient; + +public interface IMarkingEffectUiBuilder +{ + /// + /// Builds UI elements to customize + /// + void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent); +} + diff --git a/Content.Client/_Sunrise/MarkingEffectsClient/MarkingEffectShaders.cs b/Content.Client/_Sunrise/MarkingEffectsClient/MarkingEffectShaders.cs new file mode 100644 index 0000000000..0d38eb46f1 --- /dev/null +++ b/Content.Client/_Sunrise/MarkingEffectsClient/MarkingEffectShaders.cs @@ -0,0 +1,44 @@ +using System.Numerics; +using Content.Shared._Sunrise.MarkingEffects; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; + +namespace Content.Client._Sunrise.MarkingEffectsClient; + +public static class MarkingEffectShaders +{ + + public static Robust.Shared.Maths.Vector3 ColorToVec(Color col) + { + return new Robust.Shared.Maths.Vector3(col.R, col.G, col.B); + } + + public static void ApplyShaderParams(this ShaderInstance instance, MarkingEffect color, Vector2 texScale) + { + instance.SetParameter("useDisplacement", false); + + switch (color.Type) + { + case MarkingEffectType.Gradient: + if (color is not GradientMarkingEffect gradient) + return; + + instance.SetParameter("color1", ColorToVec(gradient.Colors["base"])); + instance.SetParameter("color2", ColorToVec(gradient.Colors["gradient"])); + instance.SetParameter("texScale", texScale); + instance.SetParameter("offset", gradient.Offset); + instance.SetParameter("size", gradient.Size); + instance.SetParameter("rotation", gradient.Rotation); + instance.SetParameter("pixelated", gradient.Pixelated); + instance.SetParameter("mirrored", gradient.Mirrored); + break; + case MarkingEffectType.RoughGradient: + if (color is not RoughGradientMarkingEffect roughGradient) + return; + instance.SetParameter("color1", ColorToVec(roughGradient.Colors["base"])); + instance.SetParameter("color2", ColorToVec(roughGradient.Colors["gradient"])); + instance.SetParameter("horizontal", roughGradient.Horizontal); + break; + } + } +} diff --git a/Content.Client/_Sunrise/MarkingEffectsClient/RoughGradientMarkingEffectUiBuilder.cs b/Content.Client/_Sunrise/MarkingEffectsClient/RoughGradientMarkingEffectUiBuilder.cs new file mode 100644 index 0000000000..d7afde48ec --- /dev/null +++ b/Content.Client/_Sunrise/MarkingEffectsClient/RoughGradientMarkingEffectUiBuilder.cs @@ -0,0 +1,21 @@ +using Content.Client._Sunrise.UserInterface.Controls; +using Content.Shared._Sunrise.MarkingEffects; +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.MarkingEffectsClient; + +public sealed class RoughGradientMarkingEffectUiBuilder : IMarkingEffectUiBuilder +{ + public void BuildUI(MarkingEffect effect, MarkingEffectSelectorSliders parent) + { + if (effect is not RoughGradientMarkingEffect rough) + return; + + parent.CreateSelector(type: MarkingEffectType.RoughGradient); + parent.CreateSelector("gradient", type: MarkingEffectType.RoughGradient); + + parent.CreateToggle(Loc.GetString("marking-effect-roughgradient-parameter-horizontal"), + rough.Horizontal, + val => rough.Horizontal = val); + } +} diff --git a/Content.Client/_Sunrise/UserInterface/Controls/CustomColorSelectorSliders.cs b/Content.Client/_Sunrise/UserInterface/Controls/CustomColorSelectorSliders.cs new file mode 100644 index 0000000000..a7951d9b57 --- /dev/null +++ b/Content.Client/_Sunrise/UserInterface/Controls/CustomColorSelectorSliders.cs @@ -0,0 +1,445 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client._Sunrise.UserInterface.Controls; +using System; +using System.Collections.Generic; +using Robust.Shared.Localization; +using Robust.Shared.Maths; + +// condensed version of the original ColorSlider set +public sealed class CustomColorSelectorSliders : Control +{ + public Color Color + { + get => _currentColor; + set + { + _currentColor = value; + switch (SelectorType) + { + case ColorSelectorType.Rgb: + _colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A); + break; + case ColorSelectorType.Hsv: + _colorData = Color.ToHsv(value); + break; + } + Update(); + } + } + + public ColorSelectorType SelectorType + { + get => _currentType; + set + { + switch ((_currentType, value)) + { + case (ColorSelectorType.Rgb, ColorSelectorType.Hsv): + _colorData = Color.ToHsv(Color); + break; + case (ColorSelectorType.Hsv, ColorSelectorType.Rgb): + _colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A); + break; + } + _currentType = value; + UpdateType(); + Update(); + } + } + + public bool IsAlphaVisible + { + get => _isAlphaVisible; + set + { + _isAlphaVisible = value; + + _alphaSliderBox.Visible = _isAlphaVisible; + } + } + + public Action? OnColorChanged; + public Action? OnColorReleased; + + private bool _updating = false; + private Color _currentColor = Color.White; + private Vector4 _colorData; + private ColorSelectorType _currentType = ColorSelectorType.Rgb; + private bool _isAlphaVisible = false; + + private ColorableSlider _topColorSlider; + private ColorableSlider _middleColorSlider; + private ColorableSlider _bottomColorSlider; + private Slider _alphaSlider; + + private BoxContainer _alphaSliderBox = new(); + + private SpinBox _topInputBox; + private SpinBox _middleInputBox; + private SpinBox _bottomInputBox; + private SpinBox _alphaInputBox; + + private Label _topSliderLabel = new(); + private Label _middleSliderLabel = new(); + private Label _bottomSliderLabel = new(); + private Label _alphaSliderLabel = new(); + + private OptionButton _typeSelector; + private List _types = new(); + + private ColorSelectorStyleBox _topStyle; + private ColorSelectorStyleBox _middleStyle; + private ColorSelectorStyleBox _bottomStyle; + + public CustomColorSelectorSliders() : this(ColorSelectorType.Rgb){} // Sunrise-Edit + + + public CustomColorSelectorSliders(ColorSelectorType type = ColorSelectorType.Rgb, string? label = null) // Sunrise-Edit + { + _topColorSlider = new ColorableSlider + { + HorizontalExpand = true, + VerticalAlignment = VAlignment.Center, + BackgroundStyleBoxOverride = _topStyle = new(), + MaxValue = 1.0f + }; + + _middleColorSlider = new ColorableSlider + { + HorizontalExpand = true, + VerticalAlignment = VAlignment.Center, + BackgroundStyleBoxOverride = _middleStyle = new(), + MaxValue = 1.0f + }; + + _bottomColorSlider = new ColorableSlider + { + HorizontalExpand = true, + VerticalAlignment = VAlignment.Center, + BackgroundStyleBoxOverride = _bottomStyle = new(), + MaxValue = 1.0f + }; + + _alphaSlider = new Slider + { + HorizontalExpand = true, + VerticalAlignment = VAlignment.Center, + MaxValue = 1.0f, + }; + + _topColorSlider.OnValueChanged += _ => { OnColorSet(); }; + _middleColorSlider.OnValueChanged += _ => { OnColorSet(); }; + _bottomColorSlider.OnValueChanged += _ => { OnColorSet(); }; + _alphaSlider.OnValueChanged += _ => { OnColorSet(); }; + + _topColorSlider.OnReleased += _ => { OnColorSet(true); }; + _middleColorSlider.OnReleased += _ => { OnColorSet(true); }; + _bottomColorSlider.OnReleased += _ => { OnColorSet(true); }; + _alphaSlider.OnReleased += _ => { OnColorSet(true); }; + + _topInputBox = new SpinBox + { + IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Top) + }; + _topInputBox.InitDefaultButtons(); + + _middleInputBox = new SpinBox + { + IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Middle) + }; + _middleInputBox.InitDefaultButtons(); + + _bottomInputBox = new SpinBox + { + IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Bottom) + }; + _bottomInputBox.InitDefaultButtons(); + + _alphaInputBox = new SpinBox + { + IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Alpha) + }; + _alphaInputBox.InitDefaultButtons(); + + _topInputBox.ValueChanged += value => + { + _topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top); + }; + + _middleInputBox.ValueChanged += value => + { + _middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle); + }; + + _bottomInputBox.ValueChanged += value => + { + _bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom); + }; + + _alphaInputBox.ValueChanged += value => + { + _alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha); + }; + + _alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha"); + + _typeSelector = new OptionButton(); + foreach (var ty in Enum.GetValues()) + { + _typeSelector.AddItem(Loc.GetString($"color-selector-sliders-{ty.ToString().ToLower()}")); + _types.Add(ty); + } + + _typeSelector.OnItemSelected += args => + { + SelectorType = _types[args.Id]; + _typeSelector.Select(args.Id); + }; + + // TODO: Maybe some engine widgets could be laid out in XAML? + + var rootBox = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical + }; + AddChild(rootBox); + + var headerBox = new BoxContainer(); + rootBox.AddChild(headerBox); + + headerBox.AddChild(_typeSelector); + + //Sunrise-Edit-Start + if (label != null) + { + var textLabel = new Label + { + Text = label, + }; + headerBox.AddChild(textLabel); + } + //Sunrise-Edit-End + + var bodyBox = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical + }; + + // pita + var topSliderBox = new BoxContainer(); + + topSliderBox.AddChild(_topSliderLabel); + topSliderBox.AddChild(_topColorSlider); + topSliderBox.AddChild(_topInputBox); + + var middleSliderBox = new BoxContainer(); + + middleSliderBox.AddChild(_middleSliderLabel); + middleSliderBox.AddChild(_middleColorSlider); + middleSliderBox.AddChild(_middleInputBox); + + var bottomSliderBox = new BoxContainer(); + + bottomSliderBox.AddChild(_bottomSliderLabel); + bottomSliderBox.AddChild(_bottomColorSlider); + bottomSliderBox.AddChild(_bottomInputBox); + + _alphaSliderBox.Visible = IsAlphaVisible; + _alphaSliderBox.AddChild(_alphaSliderLabel); + _alphaSliderBox.AddChild(_alphaSlider); + _alphaSliderBox.AddChild(_alphaInputBox); + + bodyBox.AddChild(topSliderBox); + bodyBox.AddChild(middleSliderBox); + bodyBox.AddChild(bottomSliderBox); + bodyBox.AddChild(_alphaSliderBox); + + rootBox.AddChild(bodyBox); + + SelectorType = type; // Sunrise-Edit + _typeSelector.TrySelect(_types.IndexOf(_currentType)); // Sunrise-Edit + + UpdateType(); + Color = _currentColor; + } + + private void UpdateType() + { + (string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels(); + + _topSliderLabel.Text = labels.topLabel; + _middleSliderLabel.Text = labels.middleLabel; + _bottomSliderLabel.Text = labels.bottomLabel; + + bool hsv = SelectorType == ColorSelectorType.Hsv; + _topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red); + _middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green); + _bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue); + } + + private void Update() + { + // This code is a mess of UI events causing stack overflows. Also, updating one slider triggers all sliders to + // update, which due to rounding errors causes them to actually change values, specifically for HSV sliders. + if (_updating) + return; + + _updating = true; + _topStyle.SetBaseColor(_colorData); + _middleStyle.SetBaseColor(_colorData); + _bottomStyle.SetBaseColor(_colorData); + + switch (SelectorType) + { + case ColorSelectorType.Rgb: + _topColorSlider.Value = _colorData.X; + _middleColorSlider.Value = _colorData.Y; + _bottomColorSlider.Value = _colorData.Z; + + _topInputBox.Value = (int)(_colorData.X * 255.0f); + _middleInputBox.Value = (int)(_colorData.Y * 255.0f); + _bottomInputBox.Value = (int)(_colorData.Z * 255.0f); + + break; + case ColorSelectorType.Hsv: + // dumb workaround because the formula for + // HSV calculation results in a negative + // number in any value past 300 degrees + if (_colorData.X > 0) + { + _topColorSlider.Value = _colorData.X; + _topInputBox.Value = (int)(_colorData.X * 360.0f); + } + else + { + _topInputBox.Value = (int)(_topColorSlider.Value * 360.0f); + } + + _middleColorSlider.Value = _colorData.Y; + _bottomColorSlider.Value = _colorData.Z; + + _middleInputBox.Value = (int)(_colorData.Y * 100.0f); + _bottomInputBox.Value = (int)(_colorData.Z * 100.0f); + + + break; + } + + _alphaSlider.Value = Color.A; + _alphaInputBox.Value = (int)(Color.A * 100.0f); + _updating = false; + } + + private bool IsSpinBoxValid(int value, ColorSliderOrder ordering) + { + if (value < 0) + { + return false; + } + + if (ordering == ColorSliderOrder.Alpha) + { + return value <= 100; + } + + switch (SelectorType) + { + case ColorSelectorType.Rgb: + return value <= byte.MaxValue; + case ColorSelectorType.Hsv: + switch (ordering) + { + case ColorSliderOrder.Top: + return value <= 360; + default: + return value <= 100; + } + } + + return false; + } + + private (string, string, string) GetSliderLabels() + { + switch (SelectorType) + { + case ColorSelectorType.Rgb: + return ( + Loc.GetString("color-selector-sliders-red"), + Loc.GetString("color-selector-sliders-green"), + Loc.GetString("color-selector-sliders-blue") + ); + case ColorSelectorType.Hsv: + return ( + Loc.GetString("color-selector-sliders-hue"), + Loc.GetString("color-selector-sliders-saturation"), + Loc.GetString("color-selector-sliders-value") + ); + } + + return ("ERR", "ERR", "ERR"); + } + + private float GetColorValueDivisor(ColorSliderOrder order) + { + if (order == ColorSliderOrder.Alpha) + { + return 100.0f; + } + + switch (SelectorType) + { + case ColorSelectorType.Rgb: + return 255.0f; + case ColorSelectorType.Hsv: + switch (order) + { + case ColorSliderOrder.Top: + return 360.0f; + default: + return 100.0f; + } + } + + return 0.0f; + } + + private void OnColorSet(bool release = false) + { + // stack overflow otherwise due to value sets + if (_updating) + { + return; + } + + _colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value); + + _currentColor = SelectorType switch + { + ColorSelectorType.Hsv => Color.FromHsv(_colorData), + _ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W) + }; + + Update(); + if (release) + OnColorReleased?.Invoke(_currentColor); + else + OnColorChanged?.Invoke(_currentColor); + } + + private enum ColorSliderOrder + { + Top, + Middle, + Bottom, + Alpha + } + + public enum ColorSelectorType + { + Rgb, + Hsv, + } +} diff --git a/Content.Client/_Sunrise/UserInterface/Controls/MarkingEffectSelectorSliders.cs b/Content.Client/_Sunrise/UserInterface/Controls/MarkingEffectSelectorSliders.cs new file mode 100644 index 0000000000..d9a8e879de --- /dev/null +++ b/Content.Client/_Sunrise/UserInterface/Controls/MarkingEffectSelectorSliders.cs @@ -0,0 +1,238 @@ +using Content.Client._Sunrise.MarkingEffectsClient; +using Content.Shared._Sunrise.MarkingEffects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client._Sunrise.UserInterface.Controls; + +public sealed class MarkingEffectSelectorSliders : Control +{ + private MarkingEffect Effect { get; set; } + + private static readonly Dictionary UiBuilders = new() + { + { MarkingEffectType.Color, new ColorMarkingEffectUiBuilder() }, + { MarkingEffectType.Gradient, new GradientMarkingEffectUiBuilder() }, + { MarkingEffectType.RoughGradient, new RoughGradientMarkingEffectUiBuilder() }, + }; + + private readonly Dictionary _colorSelectors = new(); + + private readonly OptionButton _typeSelector; + private readonly List _types = new(); + + private MarkingEffectType _currentType; + + private readonly BoxContainer _selectorsContainer; + private readonly BoxContainer _slidersContainer; + private readonly BoxContainer _toggleContainer; + + public Action? OnColorChanged; + + public MarkingEffectType CurrentType + { + get => _currentType; + set + { + if (_currentType == value) + return; + + _currentType = value; + Populate(_currentType); + } + } + + public MarkingEffectSelectorSliders(MarkingEffect? defaultEffect = null) + { + defaultEffect ??= ColorMarkingEffect.White; + + _typeSelector = new OptionButton(); + foreach (var type in Enum.GetValues()) + { + _typeSelector.AddItem(Loc.GetString($"marking-effect-type-{type.ToString().ToLower()}")); + _types.Add(type); + } + + _typeSelector.OnItemSelected += args => + { + CurrentType = _types[args.Id]; + _typeSelector.Select(args.Id); + OnColorsChanged(); + }; + + var rootBox = new BoxContainer + { Orientation = BoxContainer.LayoutOrientation.Vertical }; + AddChild(rootBox); + + var headerBox = new BoxContainer(); + rootBox.AddChild(headerBox); + headerBox.AddChild(_typeSelector); + + var bodyBox = new BoxContainer + { Orientation = BoxContainer.LayoutOrientation.Vertical }; + rootBox.AddChild(bodyBox); + + _selectorsContainer = new BoxContainer(); + bodyBox.AddChild(_selectorsContainer); + + _slidersContainer = new BoxContainer + { Orientation = BoxContainer.LayoutOrientation.Vertical }; + bodyBox.AddChild(_slidersContainer); + + _toggleContainer = new BoxContainer(); + bodyBox.AddChild(_toggleContainer); + + + _currentType = defaultEffect.Type; + _typeSelector.TrySelect(_types.IndexOf(_currentType)); + Effect = defaultEffect; + Populate(_currentType, defaultEffect); + } + + public CustomColorSelectorSliders CreateSelector(string key = "base", MarkingEffectType type = MarkingEffectType.Color) + { + var colorSelector = new CustomColorSelectorSliders( + CustomColorSelectorSliders.ColorSelectorType.Hsv, + Loc.GetString($"marking-effect-{type.ToString().ToLower()}-color-{key}")); + + colorSelector.HorizontalExpand = true; + colorSelector.HorizontalAlignment = HAlignment.Stretch; + + if (Effect.Colors.TryGetValue(key, out var defaultColor)) + colorSelector.Color = defaultColor; + + colorSelector.OnColorReleased += _ => OnColorsChanged(); + + _colorSelectors.Add(key, colorSelector); + + var selectorContainer = new BoxContainer + { + HorizontalExpand = true, + HorizontalAlignment = HAlignment.Stretch, + }; + + _selectorsContainer.AddChild(selectorContainer); + selectorContainer.AddChild(colorSelector); + + return colorSelector; + } + + public void CreateSlider(string label, + int defaultValue, + int minValue, + int maxValue, + Action 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 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 onValueChanged) + { + var button = new Button + { + Text = label, + ToggleMode = true, + Pressed = defaultValue, + }; + + button.OnToggled += _ => OnColorsChanged(); + + _toggleContainer.AddChild(button); + + BindToggle(button, onValueChanged); + } + + private void BindToggle(Button toggle, Action setValue) + { + toggle.OnToggled += val => + { + setValue(val.Pressed); + OnColorsChanged(); + }; + } + + private bool IsSpinBoxValid(int value, float min, float max) + { + return (value >= min) && (value <= max); + } + + private void OnColorsChanged() + { + foreach (var (key, selector) in _colorSelectors) + { + Effect.Colors[key] = selector.Color; + } + + OnColorChanged?.Invoke(Effect); + } + + private void Populate(MarkingEffectType type, MarkingEffect? defaultEffect = null) + { + _colorSelectors.Clear(); + _selectorsContainer.DisposeAllChildren(); + _slidersContainer.DisposeAllChildren(); + _toggleContainer.DisposeAllChildren(); + + defaultEffect ??= type switch + { + MarkingEffectType.Color => ColorMarkingEffect.White, + MarkingEffectType.Gradient => new GradientMarkingEffect(), + MarkingEffectType.RoughGradient => new RoughGradientMarkingEffect(), + _ => ColorMarkingEffect.White, + }; + + Effect = defaultEffect; + + if (UiBuilders.TryGetValue(type, out var builder)) + builder.BuildUI(Effect, this); + else + Logger.Warning($"No UI builder for marking effect: {type}"); + } +} + diff --git a/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.Designer.cs b/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.Designer.cs new file mode 100644 index 0000000000..7dd24d9d24 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.Designer.cs @@ -0,0 +1,2193 @@ +// +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20250724212329_Gradient")] + partial class Gradient + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.AHelpMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ahelp_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOnly") + .HasColumnType("boolean") + .HasColumnName("admin_only"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlaySound") + .HasColumnType("boolean") + .HasColumnName("play_sound"); + + b.Property("ReceiverUserId") + .HasColumnType("uuid") + .HasColumnName("receiver_user_id"); + + b.Property("SenderUserId") + .HasColumnType("uuid") + .HasColumnName("sender_user_id"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("sent_at"); + + b.HasKey("Id") + .HasName("PK_ahelp_messages"); + + b.HasIndex("ReceiverUserId") + .HasDatabaseName("IX_ahelp_messages_receiver_user_id"); + + b.ToTable("ahelp_messages", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Deadminned") + .HasColumnType("boolean") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("boolean") + .HasColumnName("suspended"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("real") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.IPIntelCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ipintel_cache_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Score") + .HasColumnType("real") + .HasColumnName("score"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("PK_ipintel_cache"); + + b.ToTable("ipintel_cache", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.PrimitiveCollection>("ConstructionFavorites") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("construction_favorites"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("body_type"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairColorType") + .HasColumnType("integer") + .HasColumnName("facial_hair_color_type"); + + b.Property("FacialHairExtendedColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_extended_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairColorType") + .HasColumnType("integer") + .HasColumnName("hair_color_type"); + + b.Property("HairExtendedColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_extended_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.Property("Voice") + .IsRequired() + .HasColumnType("text") + .HasColumnName("voice"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EntityName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("entity_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("integer") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.cs b/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.cs new file mode 100644 index 0000000000..4f84bd9fcf --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20250724212329_Gradient.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class Gradient : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "facial_hair_color_type", + table: "profile", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "facial_hair_extended_color", + table: "profile", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "hair_color_type", + table: "profile", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hair_extended_color", + table: "profile", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + 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"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 308e67f502..0670ab80ab 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -895,6 +895,15 @@ namespace Content.Server.Database.Migrations.Postgres .HasColumnType("text") .HasColumnName("facial_hair_color"); + b.Property("FacialHairColorType") + .HasColumnType("integer") + .HasColumnName("facial_hair_color_type"); + + b.Property("FacialHairExtendedColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_extended_color"); + b.Property("FacialHairName") .IsRequired() .HasColumnType("text") @@ -915,6 +924,15 @@ namespace Content.Server.Database.Migrations.Postgres .HasColumnType("text") .HasColumnName("hair_color"); + b.Property("HairColorType") + .HasColumnType("integer") + .HasColumnName("hair_color_type"); + + b.Property("HairExtendedColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_extended_color"); + b.Property("HairName") .IsRequired() .HasColumnType("text") diff --git a/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.Designer.cs new file mode 100644 index 0000000000..66c693aa52 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.Designer.cs @@ -0,0 +1,2114 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20250724212307_Gradient")] + partial class Gradient + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.1"); + + modelBuilder.Entity("Content.Server.Database.AHelpMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ahelp_messages_id"); + + b.Property("AdminOnly") + .HasColumnType("INTEGER") + .HasColumnName("admin_only"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlaySound") + .HasColumnType("INTEGER") + .HasColumnName("play_sound"); + + b.Property("ReceiverUserId") + .HasColumnType("TEXT") + .HasColumnName("receiver_user_id"); + + b.Property("SenderUserId") + .HasColumnType("TEXT") + .HasColumnName("sender_user_id"); + + b.Property("SentAt") + .HasColumnType("TEXT") + .HasColumnName("sent_at"); + + b.HasKey("Id") + .HasName("PK_ahelp_messages"); + + b.HasIndex("ReceiverUserId") + .HasDatabaseName("IX_ahelp_messages_receiver_user_id"); + + b.ToTable("ahelp_messages", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Deadminned") + .HasColumnType("INTEGER") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("INTEGER") + .HasColumnName("suspended"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("REAL") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.IPIntelCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ipintel_cache_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Score") + .HasColumnType("REAL") + .HasColumnName("score"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("PK_ipintel_cache"); + + b.HasIndex("Address") + .IsUnique(); + + b.ToTable("ipintel_cache", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.PrimitiveCollection("ConstructionFavorites") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("construction_favorites"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("body_type"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairColorType") + .HasColumnType("INTEGER") + .HasColumnName("facial_hair_color_type"); + + b.Property("FacialHairExtendedColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_extended_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairColorType") + .HasColumnType("INTEGER") + .HasColumnName("hair_color_type"); + + b.Property("HairExtendedColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_extended_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.Property("Voice") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("voice"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("EntityName") + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("entity_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.cs b/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.cs new file mode 100644 index 0000000000..723127268b --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20250724212307_Gradient.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class Gradient : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "facial_hair_color_type", + table: "profile", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "facial_hair_extended_color", + table: "profile", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "hair_color_type", + table: "profile", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hair_extended_color", + table: "profile", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + + /// + 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"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index c2e77a6abf..c9fa5ee282 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -844,6 +844,15 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("facial_hair_color"); + b.Property("FacialHairColorType") + .HasColumnType("INTEGER") + .HasColumnName("facial_hair_color_type"); + + b.Property("FacialHairExtendedColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_extended_color"); + b.Property("FacialHairName") .IsRequired() .HasColumnType("TEXT") @@ -864,6 +873,15 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("hair_color"); + b.Property("HairColorType") + .HasColumnType("INTEGER") + .HasColumnName("hair_color_type"); + + b.Property("HairExtendedColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_extended_color"); + b.Property("HairName") .IsRequired() .HasColumnType("TEXT") diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index f94d33a140..07b8ca6509 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -420,6 +420,12 @@ namespace Content.Server.Database public string HairColor { get; set; } = null!; public string FacialHairName { get; set; } = null!; public string FacialHairColor { get; set; } = null!; + // sunrise gradient start + public int HairColorType { get; set; } = 0; + public string HairExtendedColor { get; set; } = null!; + public int FacialHairColorType { get; set; } = 0; + public string FacialHairExtendedColor { get; set; } = null!; + // sunrise gradient end public string EyeColor { get; set; } = null!; public string SkinColor { get; set; } = null!; public int SpawnPriority { get; set; } = 0; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index cdec692261..7fcd69458d 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared.Administration.Logs; using Content.Shared.Construction.Prototypes; using Content.Shared.Database; @@ -283,6 +284,12 @@ namespace Content.Server.Database Color.FromHex(profile.EyeColor), Color.FromHex(profile.SkinColor), markings, + //sunrise gradient start + (MarkingEffectType)profile.HairColorType, + MarkingEffect.Parse(profile.HairExtendedColor), + (MarkingEffectType)profile.FacialHairColorType, + MarkingEffect.Parse(profile.FacialHairExtendedColor), + //sunrise gradient end profile.Width, profile.Height ), @@ -320,6 +327,12 @@ namespace Content.Server.Database profile.HairColor = appearance.HairColor.ToHex(); profile.FacialHairName = appearance.FacialHairStyleId; profile.FacialHairColor = appearance.FacialHairColor.ToHex(); + // sunrise gradient start + profile.HairColorType = (int)appearance.HairMarkingEffectType; + profile.HairExtendedColor = appearance.HairMarkingEffect?.ToString() ?? ""; + profile.FacialHairColorType = (int)appearance.FacialHairMarkingEffectType; + profile.FacialHairExtendedColor = appearance.FacialHairMarkingEffect?.ToString() ?? ""; + // sunrise gradient end profile.EyeColor = appearance.EyeColor.ToHex(); profile.SkinColor = appearance.SkinColor.ToHex(); profile.SpawnPriority = (int) humanoid.SpawnPriority; diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index d3f5c1ff80..fa6fcfcb4a 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._Sunrise.MarkingEffects; using System.Numerics; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; @@ -24,6 +25,22 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, [DataField] public Color FacialHairColor { get; set; } = Color.Black; + // sunrise gradient edit start + + [DataField] + public MarkingEffectType HairMarkingEffectType { get; set; } = MarkingEffectType.Color; + + [DataField] + public MarkingEffect? HairMarkingEffect { get; set; } + + [DataField] + public MarkingEffectType FacialHairMarkingEffectType { get; set; } = MarkingEffectType.Color; + + [DataField] + public MarkingEffect? FacialHairMarkingEffect { get; set; } + + // sunrise gradient edit end + [DataField] public Color EyeColor { get; set; } = Color.Black; @@ -46,6 +63,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, Color eyeColor, Color skinColor, List markings, + //sunrise gradient start + MarkingEffectType hairMarkingEffectType = MarkingEffectType.Color, + MarkingEffect? hairMarkingEffect = null, + MarkingEffectType facialHairMarkingEffectType = MarkingEffectType.Color, + MarkingEffect? facialHairMarkingEffect = null, + //sunrise gradient end float width, //Sunrise float height) //Sunrise { @@ -56,10 +79,19 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, EyeColor = ClampColor(eyeColor); SkinColor = ClampColor(skinColor); Markings = markings; + //sunrise gradient start + HairMarkingEffectType = hairMarkingEffectType; + HairMarkingEffect = hairMarkingEffect; + FacialHairMarkingEffectType = facialHairMarkingEffectType; + FacialHairMarkingEffect = facialHairMarkingEffect; Width = width; //Sunrise Height = height; //Sunrise + //sunrise gradient end } + public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) : + this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings), other.HairMarkingEffectType, other.HairMarkingEffect, other.FacialHairMarkingEffectType, other.FacialHairMarkingEffect, other.Width, other.Height); // sunrise gradient edit + public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) : this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings), other.Width, other.Height) { @@ -68,50 +100,49 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, public HumanoidCharacterAppearance WithHairStyleName(string newName) { - return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height); + return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } - public HumanoidCharacterAppearance WithHairColor(Color newColor) + public HumanoidCharacterAppearance WithHairColor(Color newColor, MarkingEffect? newExtendedColor = null) { - return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height); + return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? HairMarkingEffectType, newExtendedColor ?? HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } public HumanoidCharacterAppearance WithFacialHairStyleName(string newName) { - return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, Width, Height); + return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); } - public HumanoidCharacterAppearance WithFacialHairColor(Color newColor) + public HumanoidCharacterAppearance WithFacialHairColor(Color newColor, MarkingEffect? newFacialExtendedColor = null) { - return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, Width, Height); + return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? FacialHairMarkingEffectType, newFacialExtendedColor ?? FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } public HumanoidCharacterAppearance WithEyeColor(Color newColor) { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, Width, Height); + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } public HumanoidCharacterAppearance WithSkinColor(Color newColor) { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, Width, Height); + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } public HumanoidCharacterAppearance WithMarkings(List newMarkings) { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, Width, Height); + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings, HairMarkingEffectType, HairMarkingEffect, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } - //Sunrise start - public HumanoidCharacterAppearance WithWidth(float newWidth) + // sunrise gradient edit start + public HumanoidCharacterAppearance WithHairExtendedColor(MarkingEffect? newExtendedColor) { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newWidth, Height); + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, newExtendedColor?.Type ?? MarkingEffectType.Color, newExtendedColor, FacialHairMarkingEffectType, FacialHairMarkingEffect, Width, Height); // sunrise gradient edit } - - public HumanoidCharacterAppearance WithHeight(float newHeight) + public HumanoidCharacterAppearance WithFacialHairExtendedColor(MarkingEffect? newFacialExtendedColor) { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, Width, newHeight); + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings, HairMarkingEffectType, HairMarkingEffect, newFacialExtendedColor?.Type ?? MarkingEffectType.Color, newFacialExtendedColor, Width, Height); // sunrise gradient edit } - //Sunrise end + // sunrise gradient edit end public static HumanoidCharacterAppearance DefaultWithSpecies(string species) { @@ -135,6 +166,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, Color.Black, skinColor, new (), + // sunrise gradient edit start + MarkingEffectType.Color, + null, + MarkingEffectType.Color, + null, + // sunrise gradient edit end speciesPrototype.DefaultWidth, //Sunrise speciesPrototype.DefaultHeight //Sunrise ); @@ -315,7 +352,7 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, //Sunrise end // at the end of all that, we should have new values for each of these, so we set the character appearance to these new values. - return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, newMarkings, newWidth, newHeight); // Sunrise edit + return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, newMarkings, MarkingEffectType.Color, null, MarkingEffectType.Color, null, newWidth, newHeight); // helper functions: float RandomizeColor(float channel) @@ -446,6 +483,24 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, markingSet.FilterSponsor(sponsorPrototypes, markingManager); // Sunrise-Sponsors } + // sunrise gradient start + MarkingEffect? hairExtendedColor = null; + if (appearance.HairMarkingEffect != null) + { + hairExtendedColor = appearance.HairMarkingEffect; + foreach (var (key, value) in hairExtendedColor.Colors) + hairExtendedColor.Colors[key] = ClampColor(value); + } + + MarkingEffect? facialHairExtendedColor = null; + if (appearance.FacialHairMarkingEffect != null) + { + facialHairExtendedColor = appearance.FacialHairMarkingEffect; + foreach (var (key, value) in facialHairExtendedColor.Colors) + facialHairExtendedColor.Colors[key] = ClampColor(value); + } + // sunrise gradient end + return new HumanoidCharacterAppearance( hairStyleId, hairColor, @@ -454,10 +509,13 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, eyeColor, skinColor, markingSet.GetForwardEnumerator().ToList(), + appearance.HairMarkingEffectType, + hairExtendedColor, + appearance.FacialHairMarkingEffectType, + facialHairExtendedColor, width, height); } - public bool MemberwiseEquals(ICharacterAppearance maybeOther) { if (maybeOther is not HumanoidCharacterAppearance other) return false; @@ -468,6 +526,12 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, if (!EyeColor.Equals(other.EyeColor)) return false; if (!SkinColor.Equals(other.SkinColor)) return false; if (!Markings.SequenceEqual(other.Markings)) return false; + // sunrise gradient edit start + if (!HairMarkingEffectType.Equals(other.HairMarkingEffectType)) return false; + if (!Equals(HairMarkingEffect, other.HairMarkingEffect)) return false; + if (!FacialHairMarkingEffectType.Equals(other.FacialHairMarkingEffectType)) return false; + if (!Equals(FacialHairMarkingEffect, other.FacialHairMarkingEffect)) return false; + // sunrise gradient edit end if (Width != other.Width) return false; //Sunrise if (Height != other.Height) return false; //Sunrise return true; @@ -484,6 +548,11 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, EyeColor.Equals(other.EyeColor) && SkinColor.Equals(other.SkinColor) && Markings.SequenceEqual(other.Markings) && + // sunrise gradient edit start + HairMarkingEffectType.Equals(other.HairMarkingEffectType) && + Equals(HairMarkingEffect, other.HairMarkingEffect) && + FacialHairMarkingEffectType.Equals(other.FacialHairMarkingEffectType) && + Equals(FacialHairMarkingEffect, other.FacialHairMarkingEffect) && Width == other.Width && //starlight Height == other.Height; } diff --git a/Content.Shared/Humanoid/Markings/Marking.cs b/Content.Shared/Humanoid/Markings/Marking.cs index 767d4a6482..fcec7c6147 100644 --- a/Content.Shared/Humanoid/Markings/Marking.cs +++ b/Content.Shared/Humanoid/Markings/Marking.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -12,20 +13,32 @@ namespace Content.Shared.Humanoid.Markings [DataField("markingColor")] private List _markingColors = new(); + // sunrise gradient edit start + [DataField("markingEffects")] + public List MarkingEffects = new(); + // sunrise gradient edit end + + private Marking() { } public Marking(string markingId, - List markingColors) + List markingColors, + List? markingEffects = null) { MarkingId = markingId; _markingColors = markingColors; + MarkingEffects = markingEffects ?? new(); // sunrise gradient edit } public Marking(string markingId, - IReadOnlyList markingColors) - : this(markingId, new List(markingColors)) + IReadOnlyList markingColors, + IReadOnlyList? markingEffects = null) + : this( + markingId, + new List(markingColors), + markingEffects is not null ? new List(markingEffects) : new List()) { } @@ -34,7 +47,11 @@ namespace Content.Shared.Humanoid.Markings MarkingId = markingId; List colors = new(); for (int i = 0; i < colorCount; i++) + { colors.Add(Color.White); + MarkingEffects.Add(ColorMarkingEffect.White); + } + _markingColors = colors; } @@ -42,6 +59,7 @@ namespace Content.Shared.Humanoid.Markings { MarkingId = other.MarkingId; _markingColors = new(other.MarkingColors); + MarkingEffects = other.MarkingEffects.Select(e => e.Clone()).ToList(); Visible = other.Visible; Forced = other.Forced; } @@ -81,6 +99,17 @@ namespace Content.Shared.Humanoid.Markings } } + public void SetMarkingEffect(int colorIndex, MarkingEffect effect) => + MarkingEffects[colorIndex] = effect; + + public void SetMarkingEffect(MarkingEffect effect) + { + for (int i = 0; i < MarkingEffects.Count; i++) + { + MarkingEffects[i] = effect; + } + } + public int CompareTo(Marking? marking) { if (marking == null) @@ -122,27 +151,53 @@ namespace Content.Shared.Humanoid.Markings // doesn't seem to have compatible interfaces? this 'works' // for now but should eventually be improved so that this can, // in fact just be serialized through a convenient interface - new public string ToString() + public new string ToString() { - // reserved character string sanitizedName = this.MarkingId.Replace('@', '_'); - List colorStringList = new(); - foreach (Color color in _markingColors) - colorStringList.Add(color.ToHex()); - return $"{sanitizedName}@{String.Join(',', colorStringList)}"; + var colorStringList = _markingColors.Select(c => c.ToHex()).ToList(); + if (MarkingEffects == null || MarkingEffects.Count == 0) + return $"{sanitizedName}@{string.Join(',', colorStringList)}"; + + var extColorsList = MarkingEffects.Select(ext => ext.ToString()); + + var extColorsString = string.Join(";", extColorsList); + return $"{sanitizedName}@{string.Join(',', colorStringList)}@{extColorsString}"; } public static Marking? ParseFromDbString(string input) { - if (input.Length == 0) return null; - var split = input.Split('@'); - if (split.Length != 2) return null; - List colorList = new(); - foreach (string color in split[1].Split(',')) - colorList.Add(Color.FromHex(color)); + if (string.IsNullOrWhiteSpace(input)) + return null; - return new Marking(split[0], colorList); + var split = input.Split('@'); + if (split.Length < 2) + return null; + + var name = split[0]; + var colorsRaw = split[1]; + + var colorList = new List(); + 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(); + + foreach (var extColorStr in extColorsRaw.Split(';')) + { + var parsed = MarkingEffect.Parse(extColorStr); + if (parsed != null) + markingEffects.Add(parsed); + } + + return new Marking(name, colorList, markingEffects); } + } } diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index c03786e877..f2946b8944 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -3,6 +3,7 @@ using System.Linq; using Content.Shared._Sunrise; using Content.Shared._Sunrise.TTS; using System.Numerics; +using Content.Shared._Sunrise.MarkingEffects; using Content.Shared.CCVar; using Content.Shared.Decals; using Content.Shared.Examine; @@ -456,13 +457,13 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) && _markingManager.CanBeApplied(profile.Species, profile.Sex, hairPrototype, _proto)) { - AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false); + AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false, markingEffect: profile.Appearance.HairMarkingEffect); } if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) && _markingManager.CanBeApplied(profile.Species, profile.Sex, facialHairPrototype, _proto)) { - AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false); + AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false, markingEffect: profile.Appearance.FacialHairMarkingEffect); } humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _proto); @@ -506,7 +507,7 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem /// Whether to immediately sync this marking or not /// If this marking was forced (ignores marking points) /// Humanoid component of the entity - public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) + public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null, MarkingEffect? markingEffect = null) { if (!Resolve(uid, ref humanoid) || !_markingManager.Markings.TryGetValue(marking, out var prototype)) @@ -521,6 +522,8 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem for (var i = 0; i < prototype.Sprites.Count; i++) { markingObject.SetColor(i, color.Value); + if(markingEffect != null) + markingObject.SetMarkingEffect(i, markingEffect); } } diff --git a/Content.Shared/_Sunrise/MarkingEffects/ColorMarkingEffect.cs b/Content.Shared/_Sunrise/MarkingEffects/ColorMarkingEffect.cs new file mode 100644 index 0000000000..8fe1b0fd71 --- /dev/null +++ b/Content.Shared/_Sunrise/MarkingEffects/ColorMarkingEffect.cs @@ -0,0 +1,62 @@ +using System.Linq; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.MarkingEffects; + +[Serializable, NetSerializable] +public sealed partial class ColorMarkingEffect : MarkingEffect +{ + public override MarkingEffectType Type => MarkingEffectType.Color; + + public Color GetColor() + => Colors.TryGetValue("base", out var col) ? col : Color.White; + + #region Constructors + + public ColorMarkingEffect(Color color) : base(color) { } + public static ColorMarkingEffect White => new(Color.White); + + #endregion + + #region Parsers + + public override string ToString() + { + Dictionary 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 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 +} diff --git a/Content.Shared/_Sunrise/MarkingEffects/GradientMarkingEffect.cs b/Content.Shared/_Sunrise/MarkingEffects/GradientMarkingEffect.cs new file mode 100644 index 0000000000..25529a865f --- /dev/null +++ b/Content.Shared/_Sunrise/MarkingEffects/GradientMarkingEffect.cs @@ -0,0 +1,139 @@ +using System.Linq; +using System.Numerics; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.MarkingEffects; + +[Serializable, NetSerializable] +public sealed partial class GradientMarkingEffect : MarkingEffect +{ + public override MarkingEffectType Type => MarkingEffectType.Gradient; + + public Vector2 Offset = new(0, -190/100f); + public Vector2 Size = new(1, 33/100f); + public float Rotation = 0; + public float Speed = 1; + public bool Pixelated = true; + public bool Mirrored = false; + + #region Parsing + public override string ToString() + { + var dict = new Dictionary(); + + 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 dict) + { + var colors = new Dictionary(); + + var offset = new Vector2(0, -190/100f); + var size = new Vector2(1, 33/100f); + var rotation = 0f; + var speed = 1f; + var pixelated = true; + var mirrored = false; + + foreach (var (type, value) in dict) + { + switch (type) + { + case "offset": + TryParseParam(value, out offset); + break; + case "size": + TryParseParam(value, out size); + break; + case "rotation": + TryParseParam(value, out rotation); + break; + case "speed": + TryParseParam(value, out speed); + break; + case "pixelated": + TryParseParam(value, out pixelated); + break; + case "mirrored": + TryParseParam(value, out mirrored); + break; + default: + { + if (type.StartsWith("color.")) + colors[type["color.".Length..]] = Color.TryFromHex(value) ?? Color.White; + break; + } + } + } + + return new GradientMarkingEffect(colors, offset, size, rotation, speed, pixelated, mirrored); + } + #endregion + + #region Constructors + public GradientMarkingEffect() + { + Colors = new Dictionary() + { + { "base", Color.White } + }; + } + + public GradientMarkingEffect(Color color) + { + Colors = new Dictionary + { + {"base", color } + }; + } + + public GradientMarkingEffect(Dictionary 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 +} diff --git a/Content.Shared/_Sunrise/MarkingEffects/MarkingEffect.cs b/Content.Shared/_Sunrise/MarkingEffects/MarkingEffect.cs new file mode 100644 index 0000000000..73f04efa0a --- /dev/null +++ b/Content.Shared/_Sunrise/MarkingEffects/MarkingEffect.cs @@ -0,0 +1,150 @@ +using System.Globalization; +using System.Linq; +using System.Numerics; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.MarkingEffects; + +[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable] +public abstract partial class MarkingEffect +{ + public abstract MarkingEffectType Type { get; } + public Dictionary Colors; + + public abstract override string ToString(); + public abstract MarkingEffect Clone(); + public abstract bool Equals(MarkingEffect? other); + + #region Constructors + + protected MarkingEffect() + { + Colors = new Dictionary + { + { "base", Color.White } + }; + } + + protected MarkingEffect(Color color) + { + Colors = new Dictionary + { + { "base", color } + }; + } + + #endregion + + #region Parsers + + protected static Dictionary? 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(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 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(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( + Dictionary? a, + Dictionary? 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 +} diff --git a/Content.Shared/_Sunrise/MarkingEffects/MarkingEffectTypes.cs b/Content.Shared/_Sunrise/MarkingEffects/MarkingEffectTypes.cs new file mode 100644 index 0000000000..a38bf83fc2 --- /dev/null +++ b/Content.Shared/_Sunrise/MarkingEffects/MarkingEffectTypes.cs @@ -0,0 +1,20 @@ +namespace Content.Shared._Sunrise.MarkingEffects; + +// какой-то прям костыль, надо как-то по-другому парсер реализовать + +public enum MarkingEffectType +{ + Color, + Gradient, + RoughGradient, +} + +public static class MarkingEffectTypes +{ + public static readonly Dictionary, MarkingEffect?>> TypeParsers = new() + { + { MarkingEffectType.Color, ColorMarkingEffect.Parse }, + { MarkingEffectType.Gradient, GradientMarkingEffect.Parse }, + { MarkingEffectType.RoughGradient, RoughGradientMarkingEffect.Parse }, + }; +} diff --git a/Content.Shared/_Sunrise/MarkingEffects/RoughGradientMarkingEffect.cs b/Content.Shared/_Sunrise/MarkingEffects/RoughGradientMarkingEffect.cs new file mode 100644 index 0000000000..8e482ce878 --- /dev/null +++ b/Content.Shared/_Sunrise/MarkingEffects/RoughGradientMarkingEffect.cs @@ -0,0 +1,93 @@ +using System.Linq; +using System.Numerics; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.MarkingEffects; + +[Serializable, NetSerializable] +public sealed partial class RoughGradientMarkingEffect : MarkingEffect +{ + public override MarkingEffectType Type => MarkingEffectType.RoughGradient; + + public bool Horizontal = false; + + #region Parsing + public override string ToString() + { + var dict = new Dictionary(); + + 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 dict) + { + var colors = new Dictionary(); + + var horizontal = false; + + foreach (var (type, value) in dict) + { + switch (type) + { + case "horizontal": + TryParseParam(value, out horizontal); + break; + default: + { + if (type.StartsWith("color.")) + colors[type["color.".Length..]] = Color.TryFromHex(value) ?? Color.White; + break; + } + } + } + + return new RoughGradientMarkingEffect(colors, horizontal); + } + #endregion + + #region Constructors + public RoughGradientMarkingEffect() + { + Colors = new Dictionary() + { + { "base", Color.White } + }; + } + + public RoughGradientMarkingEffect(Color color) + { + Colors = new Dictionary + { + {"base", color } + }; + } + + public RoughGradientMarkingEffect(Dictionary 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 +} diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/markingeffects/markingeffects.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/markingeffects/markingeffects.ftl new file mode 100644 index 0000000000..15face0cdf --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/_sunrise/markingeffects/markingeffects.ftl @@ -0,0 +1,19 @@ +marking-effect-type-color = Стандартный +marking-effect-color-color-base = Цвет + + +marking-effect-type-gradient = Градиент +marking-effect-gradient-color-base = Начало +marking-effect-gradient-color-gradient = Конец + +marking-effect-gradient-parameter-offset = Позиция +marking-effect-gradient-parameter-size = Размер +marking-effect-gradient-parameter-rotation = Поворот +marking-effect-gradient-parameter-pixelation = Пикселизация +marking-effect-gradient-parameter-mirror = Отражение эффекта + + +marking-effect-type-roughgradient = Неровный градиент +marking-effect-roughgradient-color-base = Начало +marking-effect-roughgradient-color-gradient = Конец +marking-effect-roughgradient-parameter-horizontal = Горизонтальный diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 61c26f6607..3514e1f1fb 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -28,7 +28,7 @@ hideLayersOnEquip: - Hair - Snout -# MARKINGS START + # MARKINGS START bodyTypeMarkingsDisplacement: body-fat-m: Hair: @@ -557,7 +557,7 @@ categories: [ HideSpawnMenu ] components: - type: HumanoidAppearance -# MARKINGS START + # MARKINGS START bodyTypeMarkingsDisplacement: body-fat-m: Hair: @@ -1071,4 +1071,4 @@ sizeMaps: 32: sprite: _Sunrise/Mobs/Species/Human/Distrofik/displacement.rsi - state: back \ No newline at end of file + state: back diff --git a/Resources/Prototypes/_Sunrise/Shaders/sharders.yml b/Resources/Prototypes/_Sunrise/Shaders/sharders.yml index 8b35b54078..3d4a342fac 100644 --- a/Resources/Prototypes/_Sunrise/Shaders/sharders.yml +++ b/Resources/Prototypes/_Sunrise/Shaders/sharders.yml @@ -1,4 +1,4 @@ -- type: shader +- type: shader id: NVDDisplay kind: source path: "/Textures/_Sunrise/Shaders/NVDDisplay.swsl" @@ -34,3 +34,13 @@ NightVisionBoost: 2.0 NightVisionThreshold: 0.3 BlueTintIntensity: 0.6 + +- type: shader + id: Gradient + kind: source + path: "/Textures/_Sunrise/Shaders/gradient.swsl" + +- type: shader + id: RoughGradient + kind: source + path: "/Textures/_Sunrise/Shaders/gradient_rough.swsl" diff --git a/Resources/Textures/_Sunrise/Shaders/gradient.swsl b/Resources/Textures/_Sunrise/Shaders/gradient.swsl new file mode 100644 index 0000000000..2b7cc86910 --- /dev/null +++ b/Resources/Textures/_Sunrise/Shaders/gradient.swsl @@ -0,0 +1,66 @@ +uniform highp vec3 color1; +uniform highp vec3 color2; +uniform highp vec2 texScale; +uniform highp vec2 offset; +uniform highp vec2 size; +uniform highp float rotation; +uniform bool pixelated; +uniform bool mirrored; + +// DISPLACEMENT START +uniform sampler2D displacementMap; +uniform highp float displacementSize; +uniform highp vec4 displacementUV; +varying highp vec2 displacementUVOut; + +void vertex() { + if(useDisplacement == true) { + displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2); + } +} + +uniform bool useDisplacement; +// DISPLACEMENT END + +highp float mirror(highp float x) { + return mix(x, abs(fract(x) * 2.0 - 1.0), float(mirrored)); +} + +void fragment() { + highp vec4 color = texture2D(TEXTURE, UV); + + highp vec2 uv = UV2; + highp float rad = radians(rotation); + highp vec2 center = vec2(0.5); + uv -= center; + + highp float cos_a = cos(rad); + highp float sin_a = sin(rad); + highp mat2 rot = mat2( + cos_a, -sin_a, + sin_a, cos_a + ); + + if (pixelated) { + uv = floor(uv * texScale) / texScale; + } + + uv = rot * uv; + uv += center; + + highp float t = mirror(uv.y / size.y + offset.y); + t = clamp(t, 0.0, 1.0); + + highp vec3 gradient = mix(color2, color1, t); + + // DISPLACEMENT START + if(useDisplacement == true) { + highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut); + highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0); + color = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0)); + color.a *= displacementSample.a; + } + // DISPLACEMENT END + + COLOR = color * vec4(gradient, 1); +} diff --git a/Resources/Textures/_Sunrise/Shaders/gradient_rough.swsl b/Resources/Textures/_Sunrise/Shaders/gradient_rough.swsl new file mode 100644 index 0000000000..d0b602826a --- /dev/null +++ b/Resources/Textures/_Sunrise/Shaders/gradient_rough.swsl @@ -0,0 +1,69 @@ +uniform highp vec3 color1; +uniform highp vec3 color2; +uniform bool horizontal; + +// DISPLACEMENT START +uniform sampler2D displacementMap; +uniform highp float displacementSize; +uniform highp vec4 displacementUV; +varying highp vec2 displacementUVOut; + +void vertex() { + if(useDisplacement == true) { + displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2); + } +} + +uniform bool useDisplacement; +// DISPLACEMENT END + +highp float find_bound(bool forward, bool horizontal) { + highp float step = horizontal ? TEXTURE_PIXEL_SIZE.x : TEXTURE_PIXEL_SIZE.y; + highp float current = horizontal ? UV.x : UV.y; + + const highp int MAX_ITERATIONS = 256; + highp int iterations = 0; + + highp vec4 result = texture2D(TEXTURE, UV); + + while ((forward ? current < 1.0 : current > 0.0) && result.a > 0.0 && iterations < MAX_ITERATIONS) { + current += forward ? step : -step; + iterations++; + + highp vec2 probeUV = UV; + if (horizontal) + probeUV.x = current; + else + probeUV.y = current; + + result = texture2D(TEXTURE, probeUV); + } + + return current; +} + + +void fragment() { + highp vec4 color = texture2D(TEXTURE, UV); + + highp float a = find_bound(false, horizontal); + highp float b = find_bound(true, horizontal); + + highp float diff = max(0.001, b - a); + highp float pos = horizontal ? UV.x : UV.y; + + highp float norm = clamp((pos - a) / diff, 0.0, 1.0); + highp vec3 gradient = mix(color2, color1, norm); + + // DISPLACEMENT START + if(useDisplacement == true) { + highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut); + highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0); + color = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0)); + color.a *= displacementSample.a; + } + // DISPLACEMENT END + + COLOR = vec4(color.rgb * gradient, color.a); +} +