Markings (#7072)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
@@ -22,13 +22,13 @@ namespace Content.Client.CharacterAppearance.Systems
|
||||
SubscribeLocalEvent<HumanoidAppearanceBodyPartRemovedEvent>(BodyPartRemoved);
|
||||
}
|
||||
|
||||
private readonly HumanoidVisualLayers[] _bodyPartLayers = {
|
||||
public readonly static HumanoidVisualLayers[] BodyPartLayers = {
|
||||
HumanoidVisualLayers.Chest,
|
||||
HumanoidVisualLayers.Head,
|
||||
HumanoidVisualLayers.Snout,
|
||||
HumanoidVisualLayers.Frills,
|
||||
HumanoidVisualLayers.TailBehind,
|
||||
HumanoidVisualLayers.TailFront,
|
||||
HumanoidVisualLayers.HeadTop,
|
||||
HumanoidVisualLayers.HeadSide,
|
||||
HumanoidVisualLayers.Tail,
|
||||
HumanoidVisualLayers.Eyes,
|
||||
HumanoidVisualLayers.RArm,
|
||||
HumanoidVisualLayers.LArm,
|
||||
@@ -94,10 +94,11 @@ namespace Content.Client.CharacterAppearance.Systems
|
||||
sprite.LayerSetSprite(facialLayer, facialHairPrototype.Sprite);
|
||||
}
|
||||
|
||||
foreach (var layer in _bodyPartLayers)
|
||||
foreach (var layer in BodyPartLayers)
|
||||
{
|
||||
// Not every mob may have the furry layers hence we just skip it.
|
||||
if (!sprite.LayerMapTryGet(layer, out var actualLayer)) continue;
|
||||
if (!sprite[actualLayer].Visible) continue;
|
||||
|
||||
sprite.LayerSetColor(actualLayer, component.Appearance.SkinColor);
|
||||
}
|
||||
|
||||
28
Content.Client/Markings/MarkingPicker.xaml
Normal file
@@ -0,0 +1,28 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<!-- Primary container -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<!-- Marking lists -->
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="5" HorizontalExpand="True">
|
||||
<!-- Unused markings -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'markings-unused'}" />
|
||||
<OptionButton Name="CMarkingCategoryButton" />
|
||||
<ItemList Name="CMarkingsUnused" VerticalExpand="True" MinSize="300 250" />
|
||||
<Label Name="CMarkingPoints" Text="uwu" />
|
||||
<Button Name="CMarkingAdd" Text="{Loc 'markings-add'}" />
|
||||
</BoxContainer>
|
||||
<!-- Used markings -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'markings-used'}" />
|
||||
<ItemList Name="CMarkingsUsed" VerticalExpand="True" MinSize="300 250" />
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="5">
|
||||
<Button Name="CMarkingRankUp" Text="{Loc 'markings-rank-up'}" HorizontalExpand="True" />
|
||||
<Button Name="CMarkingRankDown" Text="{Loc 'markings-rank-down'}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="CMarkingRemove" Text="{Loc 'markings-remove'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Colors -->
|
||||
<BoxContainer Name="CMarkingColors" Orientation="Vertical" Visible="False" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
439
Content.Client/Markings/MarkingPicker.xaml.cs
Normal file
@@ -0,0 +1,439 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.CharacterAppearance;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.Markings;
|
||||
using Content.Shared.Species;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Markings
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MarkingPicker : Control
|
||||
{
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public Action<MarkingsSet>? OnMarkingAdded;
|
||||
public Action<MarkingsSet>? OnMarkingRemoved;
|
||||
public Action<MarkingsSet>? OnMarkingColorChange;
|
||||
public Action<MarkingsSet>? OnMarkingRankChange;
|
||||
|
||||
private List<Color> _currentMarkingColors = new();
|
||||
|
||||
private Dictionary<MarkingCategories, MarkingPoints> PointLimits = new();
|
||||
private Dictionary<MarkingCategories, MarkingPoints> PointsUsed = new();
|
||||
|
||||
private ItemList.Item? _selectedMarking;
|
||||
private ItemList.Item? _selectedUnusedMarking;
|
||||
private MarkingCategories _selectedMarkingCategory = MarkingCategories.Chest;
|
||||
|
||||
private MarkingsSet _currentMarkings = new();
|
||||
|
||||
private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList();
|
||||
|
||||
private string _currentSpecies = SpeciesManager.DefaultSpecies;
|
||||
|
||||
public void SetData(MarkingsSet newMarkings, string species)
|
||||
{
|
||||
_currentMarkings = newMarkings;
|
||||
_currentSpecies = species;
|
||||
|
||||
// Should marking limits be dependent on species prototypes,
|
||||
// or should it be dependent on the entity the
|
||||
// species contains? Having marking points as a part of
|
||||
// the component allows for any arbitrary thing to have
|
||||
// a marking (at this point, it's practically a sprite decoration),
|
||||
// but having it as a part of a species makes markings instead
|
||||
// be dependent on humanoid variants for constraints
|
||||
SpeciesPrototype speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(species);
|
||||
EntityPrototype body = _prototypeManager.Index<EntityPrototype>(speciesPrototype.Prototype);
|
||||
|
||||
body.TryGetComponent("Markings", out MarkingsComponent? markingsComponent);
|
||||
|
||||
PointLimits = markingsComponent!.LayerPoints;
|
||||
PointsUsed = MarkingPoints.CloneMarkingPointDictionary(PointLimits);
|
||||
|
||||
Populate();
|
||||
PopulateUsed();
|
||||
}
|
||||
|
||||
public MarkingPicker()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
for (int i = 0; i < _markingCategories.Count; i++)
|
||||
{
|
||||
CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{_markingCategories[i].ToString()}"), i);
|
||||
}
|
||||
CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(MarkingCategories.Chest));
|
||||
CMarkingCategoryButton.OnItemSelected += OnCategoryChange;
|
||||
CMarkingsUnused.OnItemSelected += item =>
|
||||
_selectedUnusedMarking = CMarkingsUnused[item.ItemIndex];
|
||||
|
||||
CMarkingAdd.OnPressed += args =>
|
||||
MarkingAdd();
|
||||
|
||||
CMarkingsUsed.OnItemSelected += OnUsedMarkingSelected;
|
||||
|
||||
CMarkingRemove.OnPressed += args =>
|
||||
MarkingRemove();
|
||||
|
||||
CMarkingRankUp.OnPressed += _ => SwapMarkingUp();
|
||||
CMarkingRankDown.OnPressed += _ => SwapMarkingDown();
|
||||
}
|
||||
|
||||
private string GetMarkingName(MarkingPrototype marking) => Loc.GetString($"marking-{marking.ID}");
|
||||
private List<string> GetMarkingStateNames(MarkingPrototype marking)
|
||||
{
|
||||
List<string> result = new();
|
||||
foreach (var markingState in marking.Sprites)
|
||||
{
|
||||
switch (markingState)
|
||||
{
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
result.Add(Loc.GetString($"marking-{marking.ID}-{rsi.RsiState}"));
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
result.Add(Loc.GetString($"marking-{marking.ID}-{texture.TexturePath.Filename}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Populate()
|
||||
{
|
||||
CMarkingsUnused.Clear();
|
||||
_selectedUnusedMarking = null;
|
||||
|
||||
var markings = _markingManager.CategorizedMarkings();
|
||||
foreach (var marking in markings[_selectedMarkingCategory])
|
||||
{
|
||||
if (_currentMarkings.Contains(marking.AsMarking())) continue;
|
||||
if (marking.SpeciesRestrictions != null && !marking.SpeciesRestrictions.Contains(_currentSpecies)) continue;
|
||||
var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
|
||||
item.Metadata = marking;
|
||||
}
|
||||
|
||||
if (PointsUsed.ContainsKey(_selectedMarkingCategory))
|
||||
{
|
||||
CMarkingPoints.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CMarkingPoints.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the used marking list. Returns a list of markings that weren't
|
||||
// valid to add to the marking list.
|
||||
public void PopulateUsed()
|
||||
{
|
||||
CMarkingsUsed.Clear();
|
||||
CMarkingColors.Visible = false;
|
||||
_selectedMarking = null;
|
||||
|
||||
// a little slower than the original process
|
||||
// (the original method here had the logic
|
||||
// tied with the presentation, which did it all
|
||||
// in one go including display)
|
||||
//
|
||||
// BUT
|
||||
//
|
||||
// it's all client side
|
||||
// so does it really matter???
|
||||
//
|
||||
// actual validation/filtering occurs server side, but
|
||||
// it might be better to just have a Process function
|
||||
// that just iterates through all the markings with
|
||||
// a species and points dict to ensure that all markings
|
||||
// that were given are valid?
|
||||
//
|
||||
// one of the larger issues is that this doesn't
|
||||
// necessarily use the existing backing list, but rather it
|
||||
// allocates entirely new lists instead to perform
|
||||
// their functions, making a 'Process' function
|
||||
// more desirable imo, since this isn't *really* used
|
||||
// outside of this specific niche
|
||||
|
||||
var markings = new MarkingsSet(_currentMarkings);
|
||||
|
||||
// ensures all markings are valid
|
||||
markings = MarkingsSet.EnsureValid(_currentMarkings, _markingManager);
|
||||
|
||||
// filters out all non-valid species markings
|
||||
markings = MarkingsSet.FilterSpecies(_currentMarkings, _currentSpecies);
|
||||
|
||||
// processes all the points currently available
|
||||
markings = MarkingsSet.ProcessPoints(_currentMarkings, PointsUsed);
|
||||
|
||||
// if the marking set has changed, invoke the event that involves changed marking sets
|
||||
if (markings != _currentMarkings)
|
||||
{
|
||||
Logger.DebugS("Markings", "Marking set is different, resetting markings on dummy now");
|
||||
_currentMarkings = markings;
|
||||
OnMarkingRemoved?.Invoke(_currentMarkings);
|
||||
}
|
||||
|
||||
IEnumerator markingEnumerator = _currentMarkings.GetReverseEnumerator();
|
||||
|
||||
// walk backwards through the list for visual purposes
|
||||
while (markingEnumerator.MoveNext())
|
||||
{
|
||||
Marking marking = (Marking) markingEnumerator.Current;
|
||||
var newMarking = _markingManager.Markings()[marking.MarkingId];
|
||||
var _item = new ItemList.Item(CMarkingsUsed)
|
||||
{
|
||||
Text = Loc.GetString("marking-used", ("marking-name", $"{GetMarkingName(newMarking)}"), ("marking-category", Loc.GetString($"markings-category-{newMarking.MarkingCategory}"))),
|
||||
Icon = newMarking.Sprites[0].Frame0(),
|
||||
Selectable = true,
|
||||
Metadata = newMarking,
|
||||
IconModulate = marking.MarkingColors[0]
|
||||
};
|
||||
CMarkingsUsed.Add(_item);
|
||||
}
|
||||
|
||||
// since all the points have been processed, update the points visually
|
||||
UpdatePoints();
|
||||
}
|
||||
|
||||
private void SwapMarkingUp()
|
||||
{
|
||||
if (_selectedMarking == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var i = CMarkingsUsed.IndexOf(_selectedMarking);
|
||||
if (ShiftMarkingRank(i, -1))
|
||||
{
|
||||
OnMarkingRankChange?.Invoke(_currentMarkings);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwapMarkingDown()
|
||||
{
|
||||
if (_selectedMarking == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var i = CMarkingsUsed.IndexOf(_selectedMarking);
|
||||
if (ShiftMarkingRank(i, 1))
|
||||
{
|
||||
OnMarkingRankChange?.Invoke(_currentMarkings);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShiftMarkingRank(int src, int places)
|
||||
{
|
||||
if (src + places >= CMarkingsUsed.Count || src + places < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var visualDest = src + places; // what it would visually look like
|
||||
var visualTemp = CMarkingsUsed[visualDest];
|
||||
CMarkingsUsed[visualDest] = CMarkingsUsed[src];
|
||||
CMarkingsUsed[src] = visualTemp;
|
||||
|
||||
switch (places)
|
||||
{
|
||||
// i.e., we're going down in rank
|
||||
case < 0:
|
||||
_currentMarkings.ShiftRankDownFromEnd(src);
|
||||
break;
|
||||
// i.e., we're going up in rank
|
||||
case > 0:
|
||||
_currentMarkings.ShiftRankUpFromEnd(src);
|
||||
break;
|
||||
// do nothing?
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// repopulate in case markings are restricted,
|
||||
// and also filter out any markings that are now invalid
|
||||
// attempt to preserve any existing markings as well:
|
||||
// it would be frustrating to otherwise have all markings
|
||||
// cleared, imo
|
||||
public void SetSpecies(string species)
|
||||
{
|
||||
_currentSpecies = species;
|
||||
var markingCount = _currentMarkings.Count;
|
||||
|
||||
SpeciesPrototype speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(species);
|
||||
EntityPrototype body = _prototypeManager.Index<EntityPrototype>(speciesPrototype.Prototype);
|
||||
|
||||
body.TryGetComponent("Markings", out MarkingsComponent? markingsComponent);
|
||||
|
||||
PointLimits = markingsComponent!.LayerPoints;
|
||||
PointsUsed = MarkingPoints.CloneMarkingPointDictionary(PointLimits);
|
||||
|
||||
Populate();
|
||||
PopulateUsed();
|
||||
}
|
||||
|
||||
private void UpdatePoints()
|
||||
{
|
||||
if (PointsUsed.TryGetValue(_selectedMarkingCategory, out var pointsRemaining))
|
||||
{
|
||||
CMarkingPoints.Text = Loc.GetString("marking-points-remaining", ("points", pointsRemaining.Points));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCategoryChange(OptionButton.ItemSelectedEventArgs category)
|
||||
{
|
||||
CMarkingCategoryButton.SelectId(category.Id);
|
||||
_selectedMarkingCategory = _markingCategories[category.Id];
|
||||
Populate();
|
||||
UpdatePoints();
|
||||
}
|
||||
|
||||
// TODO: This should be using ColorSelectorSliders once that's merged, so
|
||||
private void OnUsedMarkingSelected(ItemList.ItemListSelectedEventArgs item)
|
||||
{
|
||||
_selectedMarking = CMarkingsUsed[item.ItemIndex];
|
||||
var prototype = (MarkingPrototype) _selectedMarking.Metadata!;
|
||||
|
||||
if (prototype.FollowSkinColor)
|
||||
{
|
||||
CMarkingColors.Visible = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var stateNames = GetMarkingStateNames(prototype);
|
||||
_currentMarkingColors.Clear();
|
||||
CMarkingColors.DisposeAllChildren();
|
||||
List<ColorSelectorSliders> colorSliders = new();
|
||||
for (int i = 0; i < prototype.Sprites.Count; i++)
|
||||
{
|
||||
var colorContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
};
|
||||
|
||||
CMarkingColors.AddChild(colorContainer);
|
||||
|
||||
ColorSelectorSliders colorSelector = new ColorSelectorSliders();
|
||||
colorSliders.Add(colorSelector);
|
||||
|
||||
colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });
|
||||
colorContainer.AddChild(colorSelector);
|
||||
|
||||
var currentColor = new Color(
|
||||
_currentMarkings[_currentMarkings.Count - 1 - item.ItemIndex].MarkingColors[i].RByte,
|
||||
_currentMarkings[_currentMarkings.Count - 1 - item.ItemIndex].MarkingColors[i].GByte,
|
||||
_currentMarkings[_currentMarkings.Count - 1 - item.ItemIndex].MarkingColors[i].BByte
|
||||
);
|
||||
colorSelector.Color = currentColor;
|
||||
_currentMarkingColors.Add(currentColor);
|
||||
int colorIndex = _currentMarkingColors.IndexOf(currentColor);
|
||||
|
||||
Action<Color> colorChanged = delegate(Color color)
|
||||
{
|
||||
_currentMarkingColors[colorIndex] = colorSelector.Color;
|
||||
|
||||
ColorChanged(colorIndex);
|
||||
};
|
||||
colorSelector.OnColorChanged += colorChanged;
|
||||
}
|
||||
|
||||
CMarkingColors.Visible = true;
|
||||
}
|
||||
|
||||
private void ColorChanged(int colorIndex)
|
||||
{
|
||||
if (_selectedMarking is null) return;
|
||||
var markingPrototype = (MarkingPrototype) _selectedMarking.Metadata!;
|
||||
int markingIndex = _currentMarkings.FindIndexOf(markingPrototype.ID);
|
||||
|
||||
if (markingIndex < 0) return;
|
||||
|
||||
_selectedMarking.IconModulate = _currentMarkingColors[colorIndex];
|
||||
_currentMarkings[markingIndex].SetColor(colorIndex, _currentMarkingColors[colorIndex]);
|
||||
OnMarkingColorChange?.Invoke(_currentMarkings);
|
||||
}
|
||||
|
||||
private void MarkingAdd()
|
||||
{
|
||||
if (_selectedUnusedMarking is null) return;
|
||||
|
||||
MarkingPrototype marking = (MarkingPrototype) _selectedUnusedMarking.Metadata!;
|
||||
|
||||
if (PointsUsed.TryGetValue(marking.MarkingCategory, out var points))
|
||||
{
|
||||
if (points.Points == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
points.Points--;
|
||||
}
|
||||
|
||||
UpdatePoints();
|
||||
|
||||
_currentMarkings.AddBack(marking.AsMarking());
|
||||
|
||||
CMarkingsUnused.Remove(_selectedUnusedMarking);
|
||||
var item = new ItemList.Item(CMarkingsUsed)
|
||||
{
|
||||
Text = Loc.GetString("marking-used", ("marking-name", $"{GetMarkingName(marking)}"), ("marking-category", Loc.GetString($"markings-category-{marking.MarkingCategory}"))),
|
||||
Icon = marking.Sprites[0].Frame0(),
|
||||
Selectable = true,
|
||||
Metadata = marking,
|
||||
};
|
||||
CMarkingsUsed.Insert(0, item);
|
||||
|
||||
_selectedUnusedMarking = null;
|
||||
OnMarkingAdded?.Invoke(_currentMarkings);
|
||||
}
|
||||
|
||||
private void MarkingRemove()
|
||||
{
|
||||
if (_selectedMarking is null) return;
|
||||
|
||||
MarkingPrototype marking = (MarkingPrototype) _selectedMarking.Metadata!;
|
||||
|
||||
if (PointsUsed.TryGetValue(marking.MarkingCategory, out var points))
|
||||
{
|
||||
points.Points++;
|
||||
}
|
||||
|
||||
UpdatePoints();
|
||||
|
||||
_currentMarkings.Remove(marking.AsMarking());
|
||||
CMarkingsUsed.Remove(_selectedMarking);
|
||||
|
||||
if (marking.MarkingCategory == _selectedMarkingCategory)
|
||||
{
|
||||
var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
|
||||
item.Metadata = marking;
|
||||
}
|
||||
_selectedMarking = null;
|
||||
CMarkingColors.Visible = false;
|
||||
OnMarkingRemoved?.Invoke(_currentMarkings);
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Content.Client/Markings/MarkingsSystem.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.CharacterAppearance.Systems;
|
||||
using Content.Shared.Markings;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.CharacterAppearance.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Markings
|
||||
{
|
||||
public sealed class MarkingsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MarkingsComponent, SharedHumanoidAppearanceSystem.ChangedHumanoidAppearanceEvent>(UpdateMarkings);
|
||||
}
|
||||
|
||||
public void ToggleMarkingVisibility(EntityUid uid, SpriteComponent body, HumanoidVisualLayers layer, bool toggle)
|
||||
{
|
||||
if(!EntityManager.TryGetComponent(uid, out MarkingsComponent? markings)) return;
|
||||
|
||||
if (markings.ActiveMarkings.TryGetValue(layer, out List<Marking>? layerMarkings))
|
||||
foreach (Marking activeMarking in layerMarkings)
|
||||
body.LayerSetVisible(activeMarking.MarkingId, toggle);
|
||||
}
|
||||
|
||||
public void SetActiveMarkings(EntityUid uid, MarkingsSet markingList, MarkingsComponent? markings = null)
|
||||
{
|
||||
if (!Resolve(uid, ref markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
markings.ActiveMarkings.Clear();
|
||||
|
||||
foreach (HumanoidVisualLayers layer in HumanoidAppearanceSystem.BodyPartLayers)
|
||||
{
|
||||
markings.ActiveMarkings.Add(layer, new List<Marking>());
|
||||
}
|
||||
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
markings.ActiveMarkings[_markingManager.Markings()[marking.MarkingId].BodyPart].Add(marking);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMarkings(EntityUid uid, MarkingsComponent markings, SharedHumanoidAppearanceSystem.ChangedHumanoidAppearanceEvent args)
|
||||
{
|
||||
var appearance = args.Appearance;
|
||||
if (!EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) return;
|
||||
MarkingsSet totalMarkings = new MarkingsSet(appearance.Markings);
|
||||
|
||||
Dictionary<MarkingCategories, MarkingPoints> usedPoints = MarkingPoints.CloneMarkingPointDictionary(markings.LayerPoints);
|
||||
|
||||
var markingsEnumerator = appearance.Markings.GetReverseEnumerator();
|
||||
// Reverse ordering
|
||||
while (markingsEnumerator.MoveNext())
|
||||
{
|
||||
var marking = (Marking) markingsEnumerator.Current;
|
||||
if (!_markingManager.IsValidMarking(marking, out MarkingPrototype? markingPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (usedPoints.TryGetValue(markingPrototype.MarkingCategory, out MarkingPoints? points))
|
||||
{
|
||||
if (points.Points == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
points.Points--;
|
||||
}
|
||||
|
||||
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < markingPrototype.Sprites.Count(); j++)
|
||||
{
|
||||
var rsi = (SpriteSpecifier.Rsi) markingPrototype.Sprites[j];
|
||||
string layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (sprite.LayerMapTryGet(layerId, out var existingLayer))
|
||||
{
|
||||
sprite.RemoveLayer(existingLayer);
|
||||
sprite.LayerMapRemove(marking.MarkingId);
|
||||
}
|
||||
|
||||
int layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
if (markingPrototype.FollowSkinColor)
|
||||
{
|
||||
sprite.LayerSetColor(layerId, appearance.SkinColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetColor(layerId, marking.MarkingColors[j]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for each layer, check if it's required and
|
||||
// if the points are greater than zero
|
||||
//
|
||||
// if so, then we start applying default markings
|
||||
// until the point requirement is satisfied -
|
||||
// this can also mean that a specific set of markings
|
||||
// is applied on top of existing markings
|
||||
//
|
||||
// All default markings will follow the skin color of
|
||||
// the current body.
|
||||
foreach (var (layerType, points) in usedPoints)
|
||||
{
|
||||
if (points.Required && points.Points > 0)
|
||||
{
|
||||
while (points.Points > 0)
|
||||
{
|
||||
// this all has to be checked, continues shouldn't occur because
|
||||
// points.Points needs to be subtracted
|
||||
if (points.DefaultMarkings.TryGetValue(points.Points - 1, out var marking)
|
||||
&& _markingManager.Markings().TryGetValue(marking, out var markingPrototype)
|
||||
&& markingPrototype.MarkingCategory == layerType // check if this actually belongs on this layer, too
|
||||
&& sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
for (int j = 0; j < markingPrototype.Sprites.Count(); j++)
|
||||
{
|
||||
var rsi = (SpriteSpecifier.Rsi) markingPrototype.Sprites[j];
|
||||
string layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (sprite.LayerMapTryGet(layerId, out var existingLayer))
|
||||
{
|
||||
sprite.RemoveLayer(existingLayer);
|
||||
sprite.LayerMapRemove(markingPrototype.ID);
|
||||
}
|
||||
|
||||
int layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
sprite.LayerSetColor(layerId, appearance.SkinColor);
|
||||
}
|
||||
|
||||
totalMarkings.AddBack(markingPrototype.AsMarking());
|
||||
}
|
||||
|
||||
points.Points--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetActiveMarkings(uid, totalMarkings, markings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:magicmirror="clr-namespace:Content.Client.CharacterAppearance"
|
||||
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI">
|
||||
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
|
||||
xmlns:markings="clr-namespace:Content.Client.Markings">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<!-- Left side -->
|
||||
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
|
||||
@@ -87,9 +88,10 @@
|
||||
</BoxContainer>
|
||||
<!-- Skin -->
|
||||
<prefUi:HighlightedContainer>
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Vertical">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-skin-color-label'}" />
|
||||
<Slider HorizontalExpand="True" Name="CSkin" MinValue="0" MaxValue="100" Value="20" />
|
||||
<BoxContainer Name="CRgbSkinColorContainer" Visible="False" Orientation="Vertical" HorizontalExpand="True"></BoxContainer>
|
||||
</BoxContainer>
|
||||
</prefUi:HighlightedContainer>
|
||||
<!-- Hair -->
|
||||
@@ -136,6 +138,12 @@
|
||||
<BoxContainer Name="CAntagList" Orientation="Vertical" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="CMarkingsTab" Orientation="Vertical">
|
||||
<!-- Markings -->
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<markings:MarkingPicker Name="CMarkings" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</TabContainer>
|
||||
</BoxContainer>
|
||||
<!-- Right side -->
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Client.Stylesheets;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.CharacterAppearance.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Species;
|
||||
@@ -91,8 +92,12 @@ namespace Content.Client.Preferences.UI
|
||||
private SpriteView? _previewSprite;
|
||||
private SpriteView? _previewSpriteSide;
|
||||
|
||||
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
|
||||
private ColorSelectorSliders _rgbSkinColorSelector;
|
||||
|
||||
private bool _isDirty;
|
||||
private bool _needUpdatePreview;
|
||||
private bool _needsDummyRebuild;
|
||||
public int CharacterSlot;
|
||||
public HumanoidCharacterProfile? Profile;
|
||||
|
||||
@@ -194,7 +199,7 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
CSpeciesButton.SelectId(args.Id);
|
||||
SetSpecies(_speciesList[args.Id].ID);
|
||||
OnSkinColorOnValueChanged(CSkin);
|
||||
OnSkinColorOnValueChanged();
|
||||
};
|
||||
|
||||
#endregion Species
|
||||
@@ -209,7 +214,16 @@ namespace Content.Client.Preferences.UI
|
||||
// 0 is 45 - 20 - 100
|
||||
// 20 is 25 - 20 - 100
|
||||
// 100 is 25 - 100 - 20
|
||||
_skinColor.OnValueChanged += OnSkinColorOnValueChanged;
|
||||
_skinColor.OnValueChanged += _ =>
|
||||
{
|
||||
OnSkinColorOnValueChanged();
|
||||
};
|
||||
|
||||
_rgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
|
||||
_rgbSkinColorSelector.OnColorChanged += _ =>
|
||||
{
|
||||
OnSkinColorOnValueChanged();
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -434,6 +448,16 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
#endregion Save
|
||||
|
||||
#region Markings
|
||||
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-markings-tab"));
|
||||
|
||||
CMarkings.OnMarkingAdded += OnMarkingChange;
|
||||
CMarkings.OnMarkingRemoved += OnMarkingChange;
|
||||
CMarkings.OnMarkingColorChange += OnMarkingChange;
|
||||
CMarkings.OnMarkingRankChange += OnMarkingChange;
|
||||
|
||||
#endregion Markings
|
||||
|
||||
#endregion Left
|
||||
|
||||
if (preferencesManager.ServerDataLoaded)
|
||||
@@ -443,10 +467,31 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
preferencesManager.OnServerDataLoaded += LoadServerData;
|
||||
|
||||
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
private void OnSkinColorOnValueChanged(Range range)
|
||||
private void OnMarkingChange(MarkingsSet markings)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
|
||||
NeedsDummyRebuild = true;
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void OnMarkingColorChange(MarkingsSet markings)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
|
||||
private void OnSkinColorOnValueChanged()
|
||||
{
|
||||
if (Profile is null) return;
|
||||
|
||||
@@ -456,7 +501,14 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
case SpeciesSkinColor.HumanToned:
|
||||
{
|
||||
var rangeOffset = (int) range.Value - 20;
|
||||
var range = _skinColor.Value;
|
||||
if (!_skinColor.Visible)
|
||||
{
|
||||
_skinColor.Visible = true;
|
||||
_rgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
var rangeOffset = (int) range - 20;
|
||||
|
||||
float hue = 25;
|
||||
float sat = 20;
|
||||
@@ -479,13 +531,20 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
case SpeciesSkinColor.Hues:
|
||||
{
|
||||
var color = Color.FromHsv(new Vector4(range.Value / 100.0f, 1.0f, 1.0f, 1.0f));
|
||||
if (!_rgbSkinColorContainer.Visible)
|
||||
{
|
||||
_skinColor.Visible = false;
|
||||
_rgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = new Color(_rgbSkinColorSelector.Color.R, _rgbSkinColorSelector.Color.G, _rgbSkinColorSelector.Color.B);
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsDirty = true;
|
||||
NeedsDummyRebuild = true; // ugh - fix this asap
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -557,6 +616,8 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
|
||||
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
|
||||
|
||||
NeedsDummyRebuild = true;
|
||||
UpdateControls();
|
||||
}
|
||||
|
||||
@@ -581,7 +642,9 @@ namespace Content.Client.Preferences.UI
|
||||
private void SetSpecies(string newSpecies)
|
||||
{
|
||||
Profile = Profile?.WithSpecies(newSpecies);
|
||||
OnSkinColorOnValueChanged(CSkin); // Species may have special color prefs, make sure to update it.
|
||||
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
|
||||
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
|
||||
NeedsDummyRebuild = true;
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
@@ -610,6 +673,7 @@ namespace Content.Client.Preferences.UI
|
||||
if (Profile != null)
|
||||
{
|
||||
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
|
||||
NeedsDummyRebuild = true;
|
||||
OnProfileChanged?.Invoke(Profile, CharacterSlot);
|
||||
}
|
||||
}
|
||||
@@ -625,6 +689,15 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
}
|
||||
|
||||
private bool NeedsDummyRebuild
|
||||
{
|
||||
get => _needsDummyRebuild;
|
||||
set
|
||||
{
|
||||
_needsDummyRebuild = value;
|
||||
_needUpdatePreview = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNameEdit()
|
||||
{
|
||||
@@ -650,12 +723,18 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var color = Color.ToHsv(Profile.Appearance.SkinColor);
|
||||
|
||||
switch (skin)
|
||||
{
|
||||
case SpeciesSkinColor.HumanToned:
|
||||
{
|
||||
if (!_skinColor.Visible)
|
||||
{
|
||||
_skinColor.Visible = true;
|
||||
_rgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
var color = Color.ToHsv(Profile.Appearance.SkinColor);
|
||||
// check for hue/value first, if hue is lower than this percentage
|
||||
// and value is 1.0
|
||||
// then it'll be hue
|
||||
@@ -673,13 +752,30 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
case SpeciesSkinColor.Hues:
|
||||
{
|
||||
_skinColor.Value = color.X * 100;
|
||||
if (!_rgbSkinColorContainer.Visible)
|
||||
{
|
||||
_skinColor.Visible = false;
|
||||
_rgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
// set the RGB values to the direct values otherwise
|
||||
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateMarkings()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CMarkings.SetData(Profile.Appearance.Markings, Profile.Species);
|
||||
}
|
||||
|
||||
private void UpdateSpecies()
|
||||
{
|
||||
if (Profile == null)
|
||||
@@ -758,7 +854,12 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
RebuildSpriteView();
|
||||
|
||||
if (_needsDummyRebuild)
|
||||
{
|
||||
RebuildSpriteView(); // Species change also requires sprite rebuild, so we'll do that now.
|
||||
_needsDummyRebuild = false;
|
||||
}
|
||||
|
||||
EntitySystem.Get<SharedHumanoidAppearanceSystem>().UpdateFromProfile(_previewDummy!.Value, Profile);
|
||||
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
|
||||
@@ -780,6 +881,7 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateSaveButton();
|
||||
UpdateJobPriorities();
|
||||
UpdateAntagPreferences();
|
||||
UpdateMarkings();
|
||||
|
||||
_needUpdatePreview = true;
|
||||
|
||||
|
||||
1040
Content.Server.Database/Migrations/Postgres/20220310173734_SpeciesMarkings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
public partial class SpeciesMarkings : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "markings",
|
||||
table: "profile",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "markings",
|
||||
table: "profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,6 +595,11 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("hair_name");
|
||||
|
||||
b.Property<string>("Markings")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("markings");
|
||||
|
||||
b.Property<int>("PreferenceId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("preference_id");
|
||||
|
||||
989
Content.Server.Database/Migrations/Sqlite/20220310173728_SpeciesMarkings.Designer.cs
generated
Normal file
@@ -0,0 +1,989 @@
|
||||
// <auto-generated />
|
||||
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("20220310173728_SpeciesMarkings")]
|
||||
partial class SpeciesMarkings
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<int?>("AdminRankId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_rank_id");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_flag_id");
|
||||
|
||||
b.Property<Guid>("AdminId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("admin_id");
|
||||
|
||||
b.Property<string>("Flag")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("flag");
|
||||
|
||||
b.Property<bool>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_log_id");
|
||||
|
||||
b.Property<int>("RoundId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("round_id");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<sbyte>("Impact")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("impact");
|
||||
|
||||
b.Property<string>("Json")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("json");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("message");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.HasKey("Id", "RoundId")
|
||||
.HasName("PK_admin_log");
|
||||
|
||||
b.HasIndex("RoundId")
|
||||
.HasDatabaseName("IX_admin_log_round_id");
|
||||
|
||||
b.HasIndex("Type")
|
||||
.HasDatabaseName("IX_admin_log_type");
|
||||
|
||||
b.ToTable("admin_log", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b =>
|
||||
{
|
||||
b.Property<int>("Uid")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("uid");
|
||||
|
||||
b.Property<int?>("AdminLogId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_log_id");
|
||||
|
||||
b.Property<int?>("AdminLogRoundId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_log_round_id");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Uid")
|
||||
.HasName("PK_admin_log_entity");
|
||||
|
||||
b.HasIndex("AdminLogId", "AdminLogRoundId")
|
||||
.HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id");
|
||||
|
||||
b.ToTable("admin_log_entity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerUserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_user_id");
|
||||
|
||||
b.Property<int>("LogId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("log_id");
|
||||
|
||||
b.Property<int>("RoundId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("round_id");
|
||||
|
||||
b.HasKey("PlayerUserId", "LogId", "RoundId")
|
||||
.HasName("PK_admin_log_player");
|
||||
|
||||
b.HasIndex("LogId", "RoundId");
|
||||
|
||||
b.ToTable("admin_log_player", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_rank_id");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_rank_flag_id");
|
||||
|
||||
b.Property<int>("AdminRankId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_rank_id");
|
||||
|
||||
b.Property<string>("Flag")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("flag");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_admin_rank_flag");
|
||||
|
||||
b.HasIndex("AdminRankId")
|
||||
.HasDatabaseName("IX_admin_rank_flag_admin_rank_id");
|
||||
|
||||
b.HasIndex("Flag", "AdminRankId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("admin_rank_flag", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Antag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("antag_id");
|
||||
|
||||
b.Property<string>("AntagName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("antag_name");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("assigned_user_id_id");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<string>("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.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("connection_log_id");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("address");
|
||||
|
||||
b.Property<byte?>("Denied")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("denied");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<DateTime>("Time")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("time");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_connection_log");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("connection_log", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("job_id");
|
||||
|
||||
b.Property<string>("JobName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("job_name");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_job");
|
||||
|
||||
b.HasIndex("ProfileId")
|
||||
.HasDatabaseName("IX_job_profile_id");
|
||||
|
||||
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.Player", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<DateTime>("FirstSeenTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("first_seen_time");
|
||||
|
||||
b.Property<string>("LastSeenAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_seen_address");
|
||||
|
||||
b.Property<byte[]>("LastSeenHWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
b.Property<DateTime>("LastSeenTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_seen_time");
|
||||
|
||||
b.Property<string>("LastSeenUserName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_seen_user_name");
|
||||
|
||||
b.Property<Guid>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("preference_id");
|
||||
|
||||
b.Property<string>("AdminOOCColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("admin_ooc_color");
|
||||
|
||||
b.Property<int>("SelectedCharacterSlot")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("selected_character_slot");
|
||||
|
||||
b.Property<Guid>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.Property<int>("Age")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("age");
|
||||
|
||||
b.Property<string>("Backpack")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("backpack");
|
||||
|
||||
b.Property<string>("CharacterName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("char_name");
|
||||
|
||||
b.Property<string>("Clothing")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("clothing");
|
||||
|
||||
b.Property<string>("EyeColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("eye_color");
|
||||
|
||||
b.Property<string>("FacialHairColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("facial_hair_color");
|
||||
|
||||
b.Property<string>("FacialHairName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("facial_hair_name");
|
||||
|
||||
b.Property<string>("Gender")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("gender");
|
||||
|
||||
b.Property<string>("HairColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("hair_color");
|
||||
|
||||
b.Property<string>("HairName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("hair_name");
|
||||
|
||||
b.Property<string>("Markings")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("markings");
|
||||
|
||||
b.Property<int>("PreferenceId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("preference_id");
|
||||
|
||||
b.Property<int>("PreferenceUnavailable")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("pref_unavailable");
|
||||
|
||||
b.Property<string>("Sex")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("sex");
|
||||
|
||||
b.Property<string>("SkinColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("skin_color");
|
||||
|
||||
b.Property<int>("Slot")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("slot");
|
||||
|
||||
b.Property<string>("Species")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("species");
|
||||
|
||||
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.Round", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("round_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_round");
|
||||
|
||||
b.ToTable("round", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_ban_id");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("address");
|
||||
|
||||
b.Property<DateTime>("BanTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("ban_time");
|
||||
|
||||
b.Property<Guid?>("BanningAdmin")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("banning_admin");
|
||||
|
||||
b.Property<DateTime?>("ExpirationTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_server_ban");
|
||||
|
||||
b.HasIndex("Address");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("server_ban", (string)null);
|
||||
|
||||
b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_ban_hit_id");
|
||||
|
||||
b.Property<int>("BanId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("ban_id");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_role_ban_id");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("address");
|
||||
|
||||
b.Property<DateTime>("BanTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("ban_time");
|
||||
|
||||
b.Property<Guid?>("BanningAdmin")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("banning_admin");
|
||||
|
||||
b.Property<DateTime?>("ExpirationTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("role_id");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_server_role_ban");
|
||||
|
||||
b.HasIndex("Address");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("server_role_ban", (string)null);
|
||||
|
||||
b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("role_unban_id");
|
||||
|
||||
b.Property<int>("BanId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("ban_id");
|
||||
|
||||
b.Property<DateTime>("UnbanTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("unban_time");
|
||||
|
||||
b.Property<Guid?>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("unban_id");
|
||||
|
||||
b.Property<int>("BanId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("ban_id");
|
||||
|
||||
b.Property<DateTime>("UnbanTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("unban_time");
|
||||
|
||||
b.Property<Guid?>("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.Whitelist", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("PK_whitelist");
|
||||
|
||||
b.ToTable("whitelist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlayerRound", b =>
|
||||
{
|
||||
b.Property<int>("PlayersId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("players_id");
|
||||
|
||||
b.Property<int>("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.AdminLogEntity", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.AdminLog", null)
|
||||
.WithMany("Entities")
|
||||
.HasForeignKey("AdminLogId", "AdminLogRoundId")
|
||||
.HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id");
|
||||
});
|
||||
|
||||
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("LogId", "RoundId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id");
|
||||
|
||||
b.Navigation("Log");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
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.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.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.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.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.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("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("Entities");
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
{
|
||||
b.Navigation("Profiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Profile", b =>
|
||||
{
|
||||
b.Navigation("Antags");
|
||||
|
||||
b.Navigation("Jobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
public partial class SpeciesMarkings : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "markings",
|
||||
table: "profile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "markings",
|
||||
table: "profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,6 +553,11 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("hair_name");
|
||||
|
||||
b.Property<string>("Markings")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("markings");
|
||||
|
||||
b.Property<int>("PreferenceId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("preference_id");
|
||||
|
||||
@@ -199,6 +199,7 @@ namespace Content.Server.Database
|
||||
public string Sex { get; set; } = null!;
|
||||
public string Gender { get; set; } = null!;
|
||||
public string Species { get; set; } = null!;
|
||||
public string Markings { get; set; } = null!;
|
||||
public string HairName { get; set; } = null!;
|
||||
public string HairColor { get; set; } = null!;
|
||||
public string FacialHairName { get; set; } = null!;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -172,6 +173,24 @@ namespace Content.Server.Database
|
||||
if (Enum.TryParse<Gender>(profile.Gender, true, out var genderVal))
|
||||
gender = genderVal;
|
||||
|
||||
List<Marking> markings = new();
|
||||
if (profile.Markings != null && profile.Markings.Length != 0)
|
||||
{
|
||||
List<string>? markingsRaw = JsonSerializer.Deserialize<List<string>>(profile.Markings);
|
||||
if (markingsRaw != null)
|
||||
{
|
||||
foreach (var marking in markingsRaw)
|
||||
{
|
||||
var parsed = Marking.ParseFromDbString(marking);
|
||||
|
||||
if (parsed is null) continue;
|
||||
|
||||
markings.Add(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
var markingsSet = new MarkingsSet(markings);
|
||||
|
||||
return new HumanoidCharacterProfile(
|
||||
profile.CharacterName,
|
||||
profile.Species,
|
||||
@@ -185,7 +204,8 @@ namespace Content.Server.Database
|
||||
profile.FacialHairName,
|
||||
Color.FromHex(profile.FacialHairColor),
|
||||
Color.FromHex(profile.EyeColor),
|
||||
Color.FromHex(profile.SkinColor)
|
||||
Color.FromHex(profile.SkinColor),
|
||||
markingsSet
|
||||
),
|
||||
clothing,
|
||||
backpack,
|
||||
@@ -198,6 +218,12 @@ namespace Content.Server.Database
|
||||
private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int slot)
|
||||
{
|
||||
var appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance;
|
||||
List<string> markingStrings = new();
|
||||
foreach (var marking in appearance.Markings)
|
||||
{
|
||||
markingStrings.Add(marking.ToString());
|
||||
}
|
||||
var markings = JsonSerializer.Serialize(markingStrings);
|
||||
|
||||
var entity = new Profile
|
||||
{
|
||||
@@ -214,6 +240,7 @@ namespace Content.Server.Database
|
||||
SkinColor = appearance.SkinColor.ToHex(),
|
||||
Clothing = humanoid.Clothing.ToString(),
|
||||
Backpack = humanoid.Backpack.ToString(),
|
||||
Markings = markings,
|
||||
Slot = slot,
|
||||
PreferenceUnavailable = (DbPreferenceUnavailableMode) humanoid.PreferenceUnavailable
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Markings;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.CharacterAppearance
|
||||
{
|
||||
@@ -11,7 +13,8 @@ namespace Content.Shared.CharacterAppearance
|
||||
string facialHairStyleId,
|
||||
Color facialHairColor,
|
||||
Color eyeColor,
|
||||
Color skinColor)
|
||||
Color skinColor,
|
||||
MarkingsSet markings)
|
||||
{
|
||||
HairStyleId = hairStyleId;
|
||||
HairColor = ClampColor(hairColor);
|
||||
@@ -19,6 +22,7 @@ namespace Content.Shared.CharacterAppearance
|
||||
FacialHairColor = ClampColor(facialHairColor);
|
||||
EyeColor = ClampColor(eyeColor);
|
||||
SkinColor = ClampColor(skinColor);
|
||||
Markings = markings;
|
||||
}
|
||||
|
||||
public string HairStyleId { get; }
|
||||
@@ -27,35 +31,41 @@ namespace Content.Shared.CharacterAppearance
|
||||
public Color FacialHairColor { get; }
|
||||
public Color EyeColor { get; }
|
||||
public Color SkinColor { get; }
|
||||
public MarkingsSet Markings { get; }
|
||||
|
||||
public HumanoidCharacterAppearance WithHairStyleName(string newName)
|
||||
{
|
||||
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor);
|
||||
return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithHairColor(Color newColor)
|
||||
{
|
||||
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor);
|
||||
return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithFacialHairStyleName(string newName)
|
||||
{
|
||||
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor);
|
||||
return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithFacialHairColor(Color newColor)
|
||||
{
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor);
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithEyeColor(Color newColor)
|
||||
{
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor);
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithSkinColor(Color newColor)
|
||||
{
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor);
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings);
|
||||
}
|
||||
|
||||
public HumanoidCharacterAppearance WithMarkings(MarkingsSet newMarkings)
|
||||
{
|
||||
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings);
|
||||
}
|
||||
|
||||
public static HumanoidCharacterAppearance Default()
|
||||
@@ -66,7 +76,8 @@ namespace Content.Shared.CharacterAppearance
|
||||
HairStyles.DefaultFacialHairStyle,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
Color.FromHex("#C0967F")
|
||||
Color.FromHex("#C0967F"),
|
||||
new MarkingsSet()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,7 +101,7 @@ namespace Content.Shared.CharacterAppearance
|
||||
.WithBlue(RandomizeColor(newHairColor.B));
|
||||
|
||||
// TODO: Add random eye and skin color
|
||||
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, Color.Black, Color.FromHex("#C0967F"));
|
||||
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, Color.Black, Color.FromHex("#C0967F"), new MarkingsSet());
|
||||
|
||||
float RandomizeColor(float channel)
|
||||
{
|
||||
@@ -103,7 +114,7 @@ namespace Content.Shared.CharacterAppearance
|
||||
return new(color.RByte, color.GByte, color.BByte);
|
||||
}
|
||||
|
||||
public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance)
|
||||
public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<SpriteAccessoryManager>();
|
||||
var hairStyleId = appearance.HairStyleId;
|
||||
@@ -123,13 +134,17 @@ namespace Content.Shared.CharacterAppearance
|
||||
var eyeColor = ClampColor(appearance.EyeColor);
|
||||
var skinColor = ClampColor(appearance.SkinColor);
|
||||
|
||||
var validMarkingsSet = MarkingsSet.EnsureValid(appearance.Markings);
|
||||
validMarkingsSet = MarkingsSet.FilterSpecies(validMarkingsSet, species);
|
||||
|
||||
return new HumanoidCharacterAppearance(
|
||||
hairStyleId,
|
||||
hairColor,
|
||||
facialHairStyleId,
|
||||
facialHairColor,
|
||||
eyeColor,
|
||||
skinColor);
|
||||
skinColor,
|
||||
validMarkingsSet);
|
||||
}
|
||||
|
||||
public bool MemberwiseEquals(ICharacterAppearance maybeOther)
|
||||
@@ -141,6 +156,7 @@ namespace Content.Shared.CharacterAppearance
|
||||
if (!FacialHairColor.Equals(other.FacialHairColor)) return false;
|
||||
if (!EyeColor.Equals(other.EyeColor)) return false;
|
||||
if (!SkinColor.Equals(other.SkinColor)) return false;
|
||||
if (!Markings.Equals(other.Markings)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ namespace Content.Shared.CharacterAppearance
|
||||
[Serializable, NetSerializable]
|
||||
public enum HumanoidVisualLayers : byte
|
||||
{
|
||||
TailBehind,
|
||||
Tail,
|
||||
Hair,
|
||||
FacialHair,
|
||||
Chest,
|
||||
Head,
|
||||
Snout,
|
||||
Frills,
|
||||
Horns,
|
||||
HeadSide, // side parts (i.e., frills)
|
||||
HeadTop, // top parts (i.e., ears)
|
||||
Eyes,
|
||||
RArm,
|
||||
LArm,
|
||||
@@ -22,7 +22,6 @@ namespace Content.Shared.CharacterAppearance
|
||||
LLeg,
|
||||
RFoot,
|
||||
LFoot,
|
||||
TailFront,
|
||||
Handcuffs,
|
||||
StencilMask,
|
||||
Fire,
|
||||
|
||||
@@ -17,14 +17,13 @@ namespace Content.Shared.CharacterAppearance
|
||||
yield return HumanoidVisualLayers.Chest;
|
||||
break;
|
||||
case BodyPartType.Tail:
|
||||
yield return HumanoidVisualLayers.TailFront;
|
||||
yield return HumanoidVisualLayers.TailBehind;
|
||||
yield return HumanoidVisualLayers.Tail;
|
||||
break;
|
||||
case BodyPartType.Head:
|
||||
yield return HumanoidVisualLayers.Head;
|
||||
yield return HumanoidVisualLayers.Snout;
|
||||
yield return HumanoidVisualLayers.Frills;
|
||||
yield return HumanoidVisualLayers.Horns;
|
||||
yield return HumanoidVisualLayers.HeadSide;
|
||||
yield return HumanoidVisualLayers.HeadTop;
|
||||
yield return HumanoidVisualLayers.Eyes;
|
||||
yield return HumanoidVisualLayers.FacialHair;
|
||||
yield return HumanoidVisualLayers.Hair;
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.IoC;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Markings;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -38,6 +39,7 @@ namespace Content.Shared.Entry
|
||||
_initTileDefinitions();
|
||||
CheckReactions();
|
||||
IoCManager.Resolve<SpriteAccessoryManager>().Initialize();
|
||||
IoCManager.Resolve<MarkingManager>().Initialize();
|
||||
}
|
||||
|
||||
private void CheckReactions()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.Markings;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Shared.IoC
|
||||
@@ -8,6 +9,7 @@ namespace Content.Shared.IoC
|
||||
public static void Register()
|
||||
{
|
||||
IoCManager.Register<SpriteAccessoryManager, SpriteAccessoryManager>();
|
||||
IoCManager.Register<MarkingManager, MarkingManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
Content.Shared/Markings/Marking.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string>
|
||||
{
|
||||
private List<Color> _markingColors = new();
|
||||
|
||||
private Marking(string markingId,
|
||||
List<Color> markingColors)
|
||||
{
|
||||
MarkingId = markingId;
|
||||
_markingColors = markingColors;
|
||||
}
|
||||
|
||||
public Marking(string markingId,
|
||||
IReadOnlyList<Color> markingColors)
|
||||
: this(markingId, new List<Color>(markingColors))
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
public Marking(string markingId)
|
||||
: this(markingId, new List<Color>())
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
public Marking(string markingId, int colorCount)
|
||||
{
|
||||
MarkingId = markingId;
|
||||
List<Color> colors = new();
|
||||
for (int i = 0; i < colorCount; i++)
|
||||
colors.Add(Color.White);
|
||||
_markingColors = colors;
|
||||
}
|
||||
|
||||
[DataField("markingId")]
|
||||
[ViewVariables]
|
||||
public string MarkingId { get; } = default!;
|
||||
|
||||
[DataField("markingColor")]
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<Color> MarkingColors => _markingColors;
|
||||
|
||||
public void SetColor(int colorIndex, Color color) =>
|
||||
_markingColors[colorIndex] = color;
|
||||
|
||||
public int CompareTo(Marking? marking)
|
||||
{
|
||||
if (marking == null) return 1;
|
||||
else return this.MarkingId.CompareTo(marking.MarkingId);
|
||||
}
|
||||
|
||||
public int CompareTo(string? markingId)
|
||||
{
|
||||
if (markingId == null) return 1;
|
||||
return this.MarkingId.CompareTo(markingId);
|
||||
}
|
||||
|
||||
public bool Equals(Marking? other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
return (this.MarkingId.Equals(other.MarkingId));
|
||||
}
|
||||
|
||||
|
||||
// look this could be better but I don't think serializing
|
||||
// colors is the correct thing to do
|
||||
//
|
||||
// this is still janky imo but serializing a color and feeding
|
||||
// it into the default JSON serializer (which is just *fine*)
|
||||
// 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()
|
||||
{
|
||||
// reserved character
|
||||
string sanitizedName = this.MarkingId.Replace('@', '_');
|
||||
List<string> colorStringList = new();
|
||||
foreach (Color color in _markingColors)
|
||||
colorStringList.Add(color.ToHex());
|
||||
|
||||
return $"{sanitizedName}@{String.Join(',', colorStringList)}";
|
||||
}
|
||||
|
||||
public static Marking? ParseFromDbString(string input)
|
||||
{
|
||||
if (input.Length == 0) return null;
|
||||
var split = input.Split('@');
|
||||
if (split.Length != 2) return null;
|
||||
List<Color> colorList = new();
|
||||
foreach (string color in split[1].Split(','))
|
||||
colorList.Add(Color.FromHex(color));
|
||||
|
||||
return new Marking(split[0], colorList);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Content.Shared/Markings/MarkingCategories.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum MarkingCategories : byte
|
||||
{
|
||||
Head,
|
||||
HeadTop,
|
||||
HeadSide,
|
||||
Snout,
|
||||
Chest,
|
||||
Arms,
|
||||
Legs,
|
||||
Tail,
|
||||
Overlay
|
||||
}
|
||||
}
|
||||
68
Content.Shared/Markings/MarkingManager.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
public sealed class MarkingManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly List<MarkingPrototype> _index = new();
|
||||
private readonly Dictionary<MarkingCategories, List<MarkingPrototype>> _markingDict = new();
|
||||
private readonly Dictionary<string, MarkingPrototype> _markings = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypeReload;
|
||||
|
||||
foreach (var category in Enum.GetValues<MarkingCategories>())
|
||||
_markingDict.Add(category, new List<MarkingPrototype>());
|
||||
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<MarkingPrototype>())
|
||||
{
|
||||
_index.Add(prototype);
|
||||
_markingDict[prototype.MarkingCategory].Add(prototype);
|
||||
_markings.Add(prototype.ID, prototype);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, MarkingPrototype> Markings() => _markings;
|
||||
public IReadOnlyDictionary<MarkingCategories, List<MarkingPrototype>> CategorizedMarkings() => _markingDict;
|
||||
|
||||
public IReadOnlyDictionary<MarkingCategories, List<MarkingPrototype>> MarkingsBySpecies(string species)
|
||||
{
|
||||
var result = new Dictionary<MarkingCategories, List<MarkingPrototype>>(_markingDict);
|
||||
|
||||
foreach (var list in result.Values)
|
||||
{
|
||||
list.RemoveAll(marking => marking.SpeciesRestrictions != null && marking.SpeciesRestrictions.Contains(species));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsValidMarking(Marking marking, [NotNullWhen(true)] out MarkingPrototype? markingResult)
|
||||
{
|
||||
return _markings.TryGetValue(marking.MarkingId, out markingResult);
|
||||
}
|
||||
|
||||
private void OnPrototypeReload(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if(!args.ByType.TryGetValue(typeof(MarkingPrototype), out var set))
|
||||
return;
|
||||
|
||||
|
||||
_index.RemoveAll(i => set.Modified.ContainsKey(i.ID));
|
||||
|
||||
foreach (var prototype in set.Modified.Values)
|
||||
{
|
||||
var markingPrototype = (MarkingPrototype) prototype;
|
||||
_index.Add(markingPrototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Content.Shared/Markings/MarkingPrototype.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Prototype("marking")]
|
||||
public sealed class MarkingPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = "uwu";
|
||||
|
||||
public string Name { get; private set; } = default!;
|
||||
|
||||
[DataField("bodyPart", required: true)]
|
||||
public HumanoidVisualLayers BodyPart { get; } = default!;
|
||||
|
||||
[DataField("markingCategory", required: true)]
|
||||
public MarkingCategories MarkingCategory { get; } = default!;
|
||||
|
||||
[DataField("speciesRestriction")]
|
||||
public List<string>? SpeciesRestrictions { get; }
|
||||
|
||||
[DataField("followSkinColor")]
|
||||
public bool FollowSkinColor { get; } = false;
|
||||
|
||||
[DataField("sprites", required: true)]
|
||||
public List<SpriteSpecifier> Sprites { get; private set; } = default!;
|
||||
|
||||
public Marking AsMarking()
|
||||
{
|
||||
return new Marking(ID, Sprites.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Content.Shared/Markings/MarkingsComponent.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class MarkingsComponent : Component
|
||||
{
|
||||
public Dictionary<HumanoidVisualLayers, List<Marking>> ActiveMarkings = new();
|
||||
|
||||
// Layer points for the attached mob. This is verified client side (but should be verified server side, eventually as well),
|
||||
// but upon render for the given entity with this component, it will start subtracting
|
||||
// points from this set. Upon depletion, no more sprites in this layer will be
|
||||
// rendered. If an entry is null, however, it is considered 'unlimited points' for
|
||||
// that layer.
|
||||
//
|
||||
// Layer points are useful for restricting the amount of markings a specific layer can use
|
||||
// for specific mobs (i.e., a lizard should only use one set of horns and maybe two frills),
|
||||
// and all species with selectable tails should have exactly one tail)
|
||||
//
|
||||
// If something is required, then something must be selected in that category. Otherwise,
|
||||
// the first instance of a marking in that category will be added to a character
|
||||
// upon round start.
|
||||
[DataField("layerPoints")]
|
||||
public Dictionary<MarkingCategories, MarkingPoints> LayerPoints = new();
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class MarkingPoints
|
||||
{
|
||||
[DataField("points", required: true)]
|
||||
public int Points = 0;
|
||||
[DataField("required", required: true)]
|
||||
public bool Required = false;
|
||||
// Default markings for this layer.
|
||||
[DataField("defaultMarkings")]
|
||||
public List<string> DefaultMarkings = new();
|
||||
|
||||
public static Dictionary<MarkingCategories, MarkingPoints> CloneMarkingPointDictionary(Dictionary<MarkingCategories, MarkingPoints> self)
|
||||
{
|
||||
var clone = new Dictionary<MarkingCategories, MarkingPoints>();
|
||||
|
||||
foreach (var (category, points) in self)
|
||||
{
|
||||
clone[category] = new MarkingPoints()
|
||||
{
|
||||
Points = points.Points,
|
||||
Required = points.Required,
|
||||
DefaultMarkings = points.DefaultMarkings
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
283
Content.Shared/Markings/MarkingsSet.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Markings;
|
||||
|
||||
// TODO: Maybe put points logic into here too? It would make some sense
|
||||
// but it would have to be a template loaded in, otherwise clients could
|
||||
// send really invalid points sets that would have to be verified on the
|
||||
// server/client every time
|
||||
//
|
||||
// currently marking point constraints are just validated upon data send
|
||||
// from the client's UI (which might be a nono, means that connections
|
||||
// can just send garbage marking sets and it will save on the server)
|
||||
// and when an entity is rendered, (which is OK enough)
|
||||
//
|
||||
// equally, we'd need to access references every time we wanted to get
|
||||
// the managers required, which... not *terrible* if we do null default
|
||||
// params
|
||||
[Serializable, NetSerializable]
|
||||
public class MarkingsSet : IEnumerable, IEquatable<MarkingsSet>
|
||||
{
|
||||
// if you want a rust style VecDeque, you're looking at
|
||||
// the wrong place, i just wanted a similar API + some
|
||||
// markings specific functions
|
||||
private List<Marking> _markings = new();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => _markings.Count;
|
||||
}
|
||||
|
||||
public MarkingsSet()
|
||||
{
|
||||
}
|
||||
|
||||
public MarkingsSet(List<Marking> markings)
|
||||
{
|
||||
_markings = markings;
|
||||
}
|
||||
|
||||
public MarkingsSet(MarkingsSet other)
|
||||
{
|
||||
_markings = new(other._markings);
|
||||
}
|
||||
|
||||
public Marking this[int idx] => Index(idx);
|
||||
|
||||
public Marking Index(int idx)
|
||||
{
|
||||
return _markings[idx];
|
||||
}
|
||||
|
||||
// Gets a marking idx spaces from the back of the list.
|
||||
public Marking IndexReverse(int idx)
|
||||
{
|
||||
return _markings[_markings.Count - 1 - idx];
|
||||
}
|
||||
|
||||
public void AddFront(Marking marking)
|
||||
{
|
||||
_markings.Insert(0, marking);
|
||||
}
|
||||
|
||||
public void AddBack(Marking marking)
|
||||
{
|
||||
_markings.Add(marking);
|
||||
}
|
||||
|
||||
public bool Remove(Marking marking)
|
||||
{
|
||||
return _markings.Remove(marking);
|
||||
}
|
||||
|
||||
public bool Contains(Marking marking)
|
||||
{
|
||||
return _markings.Contains(marking);
|
||||
}
|
||||
|
||||
public int FindIndexOf(string id)
|
||||
{
|
||||
return _markings.FindIndex(m => m.MarkingId == id);
|
||||
}
|
||||
|
||||
// Shifts a marking's rank upwards (i.e., towards the front of the list)
|
||||
public void ShiftRankUp(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _markings.Count || idx - 1 < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = _markings[idx - 1];
|
||||
_markings[idx - 1] = _markings[idx];
|
||||
_markings[idx] = temp;
|
||||
}
|
||||
|
||||
// Shifts up from the back (i.e., 2nd position from end)
|
||||
public void ShiftRankUpFromEnd(int idx)
|
||||
{
|
||||
ShiftRankUp(Count - idx - 1);
|
||||
}
|
||||
|
||||
// Ditto, but the opposite direction.
|
||||
public void ShiftRankDown(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _markings.Count || idx + 1 >= _markings.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = _markings[idx + 1];
|
||||
_markings[idx + 1] = _markings[idx];
|
||||
_markings[idx] = temp;
|
||||
}
|
||||
|
||||
// Ditto as above.
|
||||
public void ShiftRankDownFromEnd(int idx)
|
||||
{
|
||||
ShiftRankDown(Count - idx - 1);
|
||||
}
|
||||
|
||||
// Ensures that all markings in a set are valid.
|
||||
public static MarkingsSet EnsureValid(MarkingsSet set, MarkingManager? manager = null)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
manager = IoCManager.Resolve<MarkingManager>();
|
||||
}
|
||||
|
||||
var newList = set._markings.Where(marking => manager.Markings().ContainsKey(marking.MarkingId)).ToList();
|
||||
|
||||
set._markings = newList;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
// Filters out markings based on species.
|
||||
public static MarkingsSet FilterSpecies(MarkingsSet set, string species)
|
||||
{
|
||||
var _markingsManager = IoCManager.Resolve<MarkingManager>();
|
||||
var newList = set._markings.Where(marking =>
|
||||
{
|
||||
if (!_markingsManager.Markings().TryGetValue(marking.MarkingId, out MarkingPrototype? prototype))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prototype.SpeciesRestrictions != null)
|
||||
{
|
||||
if (!prototype.SpeciesRestrictions.Contains(species))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}).ToList();
|
||||
|
||||
set._markings = newList;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
// Processes a MarkingsSet using the given dictionary of MarkingPoints.
|
||||
public static MarkingsSet ProcessPoints(MarkingsSet set, Dictionary<MarkingCategories, MarkingPoints> points)
|
||||
{
|
||||
var finalSet = new List<Marking>();
|
||||
var _markingsManager = IoCManager.Resolve<MarkingManager>();
|
||||
|
||||
foreach (var marking in set)
|
||||
{
|
||||
if (_markingsManager.Markings().TryGetValue(marking.MarkingId, out MarkingPrototype? markingPrototype))
|
||||
{
|
||||
if (points.TryGetValue(markingPrototype.MarkingCategory, out var pointsRemaining))
|
||||
{
|
||||
if (pointsRemaining.Points == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pointsRemaining.Points--;
|
||||
|
||||
finalSet.Add(marking);
|
||||
}
|
||||
else
|
||||
{
|
||||
// points don't exist otherwise
|
||||
finalSet.Add(marking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set._markings = finalSet;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (IEnumerator) GetEnumerator();
|
||||
}
|
||||
|
||||
public MarkingsEnumerator GetEnumerator()
|
||||
{
|
||||
return new MarkingsEnumerator(_markings, false);
|
||||
}
|
||||
|
||||
public IEnumerator GetReverseEnumerator()
|
||||
{
|
||||
return (IEnumerator) new MarkingsEnumerator(_markings, true);
|
||||
}
|
||||
|
||||
public bool Equals(MarkingsSet? set)
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _markings.SequenceEqual(set._markings);
|
||||
}
|
||||
}
|
||||
|
||||
public class MarkingsEnumerator : IEnumerator
|
||||
{
|
||||
private List<Marking> _markings;
|
||||
private bool _reverse;
|
||||
|
||||
int position;
|
||||
|
||||
public MarkingsEnumerator(List<Marking> markings, bool reverse)
|
||||
{
|
||||
_markings = markings;
|
||||
_reverse = reverse;
|
||||
|
||||
if (_reverse)
|
||||
{
|
||||
position = _markings.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_reverse)
|
||||
{
|
||||
position--;
|
||||
return (position >= 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
position++;
|
||||
return (position < _markings.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (_reverse)
|
||||
{
|
||||
position = _markings.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = -1;
|
||||
}
|
||||
}
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get => _markings[position];
|
||||
}
|
||||
|
||||
public Marking Current
|
||||
{
|
||||
get => _markings[position];
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ namespace Content.Shared.Preferences
|
||||
name = RandomName();
|
||||
}
|
||||
|
||||
var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance);
|
||||
var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species);
|
||||
|
||||
var prefsUnavailableMode = PreferenceUnavailable switch
|
||||
{
|
||||
|
||||
@@ -43,8 +43,6 @@ public sealed class SpeciesPrototype : IPrototype
|
||||
/// </summary>
|
||||
[DataField("skinColoration", required: true)]
|
||||
public SpeciesSkinColor SkinColoration { get; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
public enum SpeciesSkinColor
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -52,7 +53,8 @@ namespace Content.Tests.Server.Preferences
|
||||
"Shaved",
|
||||
Color.Aquamarine,
|
||||
Color.Azure,
|
||||
Color.Beige
|
||||
Color.Beige,
|
||||
new MarkingsSet()
|
||||
),
|
||||
ClothingPreference.Jumpskirt,
|
||||
BackpackPreference.Backpack,
|
||||
|
||||
2
Resources/Locale/en-US/markings/cat.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
marking-CatEars = Cat Ears
|
||||
marking-CatTail = Cat Tail
|
||||
41
Resources/Locale/en-US/markings/reptilian.ftl
Normal file
@@ -0,0 +1,41 @@
|
||||
marking-LizardFrillsShort-frills_short = Lizard Frills (Short)
|
||||
marking-LizardFrillsShort = Lizard Frills (Short)
|
||||
|
||||
marking-LizardFrillsSimple-frills_simple = Lizard Frills (Simple)
|
||||
marking-LizardFrillsSimple = Lizard Frills (Simple)
|
||||
|
||||
marking-LizardFrillsAquatic-frills_aquatic = Lizard Frills (Aquatic)
|
||||
marking-LizardFrillsAquatic = Lizard Frills (Aquatic)
|
||||
|
||||
marking-LizardHornsAngler-horns_angler = Lizard Horns (Angler)
|
||||
marking-LizardHornsAngler = Lizard Horns (Angler)
|
||||
|
||||
marking-LizardHornsCurled-horns_curled = Lizard Horns (Curled)
|
||||
marking-LizardHornsCurled = Lizard Horns (Curled)
|
||||
|
||||
marking-LizardHornsRam-horns_ram = Lizard Horns (Ram)
|
||||
marking-LizardHornsRam = Lizard Horns (Ram)
|
||||
|
||||
marking-LizardHornsShort-horns_short = Lizard Horns (Short)
|
||||
marking-LizardHornsShort = Lizard Horns (Short)
|
||||
|
||||
marking-LizardHornsSimple-horns_simple = Lizard Horns
|
||||
marking-LizardHornsSimple = Lizard Horns
|
||||
|
||||
marking-LizardTailSmooth-tail_smooth = Lizard Tail (Smooth)
|
||||
marking-LizardTailSmooth = Lizard Tail (Smooth)
|
||||
|
||||
marking-LizardTailSpikes-tail_spikes = Lizard Tail (Spiky)
|
||||
marking-LizardTailSpikes = Lizard Tail (Spiky)
|
||||
|
||||
marking-LizardTailLTiger-tail_ltiger = Lizard Tail (Light Tiger Stripes)
|
||||
marking-LizardTailLTiger = Lizard Tail (Light Tiger Stripes)
|
||||
|
||||
marking-LizardTailDTiger-tail_dtiger = Lizard Tail (Dark Tiger Stripes)
|
||||
marking-LizardTailDTiger = Lizard Tail (Dark Tiger Stripes)
|
||||
|
||||
marking-LizardSnoutRound-snout_round = Lizard Snout (Round)
|
||||
marking-LizardSnoutRound = Lizard Snout (Round)
|
||||
|
||||
marking-LizardSnoutSharp-snout_sharp = Lizard Snout (Sharp)
|
||||
marking-LizardSnoutSharp = Lizard Snout (Sharp)
|
||||
@@ -35,3 +35,4 @@ humanoid-profile-editor-job-priority-medium-button = Medium
|
||||
humanoid-profile-editor-job-priority-low-button = Low
|
||||
humanoid-profile-editor-job-priority-never-button = Never
|
||||
humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC names will lead to admin intervention on this server. Read our \[Rules\] for more.
|
||||
humanoid-profile-editor-markings-tab = Markings
|
||||
|
||||
20
Resources/Locale/en-US/preferences/ui/markings-picker.ftl
Normal file
@@ -0,0 +1,20 @@
|
||||
markings-used = Used Markings
|
||||
markings-unused = Unused Markings
|
||||
markings-add = Add Marking
|
||||
markings-remove = Remove Marking
|
||||
markings-rank-up = Up
|
||||
markings-rank-down = Down
|
||||
marking-points-remaining = Points remaining: {$points}
|
||||
marking-used = {$marking-name} ({$marking-category})
|
||||
|
||||
# Categories
|
||||
|
||||
markings-category-Head = Head
|
||||
markings-category-HeadTop = Head (Top)
|
||||
markings-category-HeadSide = Head (Side)
|
||||
markings-category-Snout = Snout
|
||||
markings-category-Chest = Chest
|
||||
markings-category-Arms = Arms
|
||||
markings-category-Legs = Legs
|
||||
markings-category-Tail = Tail
|
||||
markings-category-Overlay = Overlay
|
||||
@@ -0,0 +1,19 @@
|
||||
- type: marking
|
||||
id: CatEars
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Human]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/cat_parts.rsi/
|
||||
state: ears_cat_outer
|
||||
- sprite: Mobs/Customization/cat_parts.rsi/
|
||||
state: ears_cat_inner
|
||||
|
||||
- type: marking
|
||||
id: CatTail
|
||||
bodyPart: Tail
|
||||
markingCategory: Tail
|
||||
speciesRestriction: [Human]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/cat_parts.rsi/
|
||||
state: tail_cat
|
||||
@@ -0,0 +1,130 @@
|
||||
- type: marking
|
||||
id: LizardFrillsAquatic
|
||||
bodyPart: HeadSide
|
||||
markingCategory: HeadSide
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: frills_aquatic
|
||||
|
||||
- type: marking
|
||||
id: LizardFrillsShort
|
||||
bodyPart: HeadSide
|
||||
markingCategory: HeadSide
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: frills_short
|
||||
|
||||
- type: marking
|
||||
id: LizardFrillsSimple
|
||||
bodyPart: HeadSide
|
||||
markingCategory: HeadSide
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: frills_simple
|
||||
|
||||
- type: marking
|
||||
id: LizardHornsAngler
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: horns_angler
|
||||
|
||||
- type: marking
|
||||
id: LizardHornsCurled
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: horns_curled
|
||||
|
||||
- type: marking
|
||||
id: LizardHornsRam
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: horns_ram
|
||||
|
||||
- type: marking
|
||||
id: LizardHornsShort
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: horns_short
|
||||
|
||||
- type: marking
|
||||
id: LizardHornsSimple
|
||||
bodyPart: HeadTop
|
||||
markingCategory: HeadTop
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: horns_simple
|
||||
|
||||
- type: marking
|
||||
id: LizardTailSmooth
|
||||
bodyPart: Tail
|
||||
markingCategory: Tail
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: tail_smooth
|
||||
|
||||
- type: marking
|
||||
id: LizardTailSpikes
|
||||
bodyPart: Tail
|
||||
markingCategory: Tail
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: tail_spikes
|
||||
|
||||
- type: marking
|
||||
id: LizardTailLTiger
|
||||
bodyPart: Tail
|
||||
markingCategory: Tail
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: tail_ltiger
|
||||
|
||||
- type: marking
|
||||
id: LizardTailDTiger
|
||||
bodyPart: Tail
|
||||
markingCategory: Tail
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: tail_dtiger
|
||||
|
||||
- type: marking
|
||||
id: LizardSnoutRound
|
||||
bodyPart: Snout
|
||||
markingCategory: Snout
|
||||
followSkinColor: true
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: snout_round
|
||||
|
||||
- type: marking
|
||||
id: LizardSnoutSharp
|
||||
bodyPart: Snout
|
||||
markingCategory: Snout
|
||||
followSkinColor: true
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi/
|
||||
state: snout_sharp
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +162,14 @@
|
||||
- map: [ "head" ]
|
||||
- map: [ "pocket1" ]
|
||||
- map: [ "pocket2" ]
|
||||
- type: Markings
|
||||
layerPoints:
|
||||
Tail:
|
||||
points: 0
|
||||
required: false
|
||||
HeadTop:
|
||||
points: 0
|
||||
required: false
|
||||
- type: Physics
|
||||
bodyType: KinematicController
|
||||
- type: Fixtures
|
||||
@@ -395,6 +403,7 @@
|
||||
- map: [ "pocket2" ]
|
||||
- map: ["hand-left"]
|
||||
- map: ["hand-right"]
|
||||
- type: Markings
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: Fixtures
|
||||
|
||||
@@ -9,16 +9,32 @@
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: full
|
||||
- type: Markings
|
||||
layerPoints:
|
||||
Tail:
|
||||
points: 1
|
||||
required: true
|
||||
defaultMarkings: [LizardTailSmooth]
|
||||
Snout:
|
||||
points: 1
|
||||
required: true
|
||||
defaultMarkings: [LizardSnoutRound]
|
||||
HeadTop:
|
||||
points: 1
|
||||
required: false
|
||||
HeadSide:
|
||||
points: 1
|
||||
required: false
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
noRot: true
|
||||
drawdepth: Mobs
|
||||
scale: 1, 1
|
||||
layers:
|
||||
- map: [ "enum.HumanoidVisualLayers.TailBehind" ]
|
||||
color: "#34a223"
|
||||
state: tail_smooth_behind
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.Tail" ]
|
||||
sprite: Mobs/Customization/masking_helpers.rsi
|
||||
state: none
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.Chest" ]
|
||||
color: "#34a223"
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
@@ -28,9 +44,9 @@
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: head_m
|
||||
- map: [ "enum.HumanoidVisualLayers.Snout" ]
|
||||
color: "#34a223"
|
||||
state: snout_round
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
sprite: Mobs/Customization/masking_helpers.rsi
|
||||
state: none
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.Eyes" ]
|
||||
color: "#008800"
|
||||
sprite: Mobs/Customization/eyes.rsi
|
||||
@@ -88,20 +104,17 @@
|
||||
- map: [ "ears" ]
|
||||
- map: [ "outerClothing" ]
|
||||
- map: [ "eyes" ]
|
||||
- map: [ "enum.HumanoidVisualLayers.TailFront" ]
|
||||
color: "#34a223"
|
||||
state: tail_smooth_front
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "belt" ]
|
||||
- map: [ "neck" ]
|
||||
- map: [ "back" ]
|
||||
- map: [ "enum.HumanoidVisualLayers.Frills" ]
|
||||
state: frills_simple
|
||||
color: "#34a223"
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.Horns" ]
|
||||
state: horns_simple
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.HeadSide" ]
|
||||
sprite: Mobs/Customization/masking_helpers.rsi
|
||||
state: none
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.HeadTop" ]
|
||||
sprite: Mobs/Customization/masking_helpers.rsi
|
||||
state: none
|
||||
visible: false
|
||||
- map: [ "mask" ]
|
||||
- map: [ "head" ]
|
||||
- map: [ "pocket1" ]
|
||||
@@ -138,16 +151,30 @@
|
||||
noSpawn: true
|
||||
description: A dummy reptilian meant to be used in character setup.
|
||||
components:
|
||||
- type: Markings
|
||||
layerPoints:
|
||||
Tail:
|
||||
points: 1
|
||||
required: true
|
||||
defaultMarkings: [LizardTailSmooth]
|
||||
Snout:
|
||||
points: 1
|
||||
required: true
|
||||
defaultMarkings: [LizardSnoutRound]
|
||||
HeadTop:
|
||||
points: 1
|
||||
required: false
|
||||
HeadSide:
|
||||
points: 1
|
||||
required: false
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
noRot: true
|
||||
drawdepth: Mobs
|
||||
scale: 1, 1
|
||||
layers:
|
||||
- map: [ "enum.HumanoidVisualLayers.TailBehind" ]
|
||||
color: "#34a223"
|
||||
state: tail_smooth_behind
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.Tail" ]
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.Chest" ]
|
||||
color: "#34a223"
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
@@ -157,9 +184,7 @@
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: head_m
|
||||
- map: [ "enum.HumanoidVisualLayers.Snout" ]
|
||||
color: "#34a223"
|
||||
state: snout_round
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.Eyes" ]
|
||||
color: "#008800"
|
||||
sprite: Mobs/Customization/eyes.rsi
|
||||
@@ -217,19 +242,13 @@
|
||||
- map: [ "ears" ]
|
||||
- map: [ "outerClothing" ]
|
||||
- map: [ "eyes" ]
|
||||
- map: [ "enum.HumanoidVisualLayers.TailFront" ]
|
||||
color: "#34a223"
|
||||
state: tail_smooth_front
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "belt" ]
|
||||
- map: [ "neck" ]
|
||||
- map: [ "back" ]
|
||||
- map: [ "enum.HumanoidVisualLayers.Frills" ]
|
||||
state: frills_simple
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.Horns" ]
|
||||
state: horns_simple
|
||||
sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
- map: [ "enum.HumanoidVisualLayers.HeadSide" ]
|
||||
visible: false
|
||||
- map: [ "enum.HumanoidVisualLayers.HeadTop" ]
|
||||
visible: false
|
||||
- map: [ "mask" ]
|
||||
- map: [ "head" ]
|
||||
- map: [ "pocket1" ]
|
||||
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,2 @@
|
||||
{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/tgstation/tgstation/blob/master/icons/mob/mutant_bodyparts.dmi", "states": [{"name":"ears_cat_inner","directions":4},{"name":"ears_cat_outer","directions":4},{"name":"tail_cat","directions":4}]}
|
||||
|
||||
BIN
Resources/Textures/Mobs/Customization/cat_parts.rsi/tail_cat.png
Normal file
|
After Width: | Height: | Size: 247 B |
@@ -1 +1 @@
|
||||
{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "female_full", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "female_top", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]}
|
||||
{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "female_full", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "female_top", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "none"}]}
|
||||
|
||||
|
After Width: | Height: | Size: 83 B |
@@ -39,6 +39,22 @@
|
||||
"name": "tail_ltiger_behind",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "tail_smooth",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "tail_spikes",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "tail_dtiger",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "tail_ltiger",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "snout_round",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.7 KiB |