diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index 5db7209d22..f01a170850 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -334,8 +334,11 @@ public sealed class ClientClothingSystem : ClothingSystem if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId)) continue; - if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers)) + if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, out var displacementKey)) + { + revealedLayers.Add(displacementKey); index++; + } } } diff --git a/Content.Client/DisplacementMap/DisplacementMapSystem.cs b/Content.Client/DisplacementMap/DisplacementMapSystem.cs index 6db164a09f..9e9ad6135b 100644 --- a/Content.Client/DisplacementMap/DisplacementMapSystem.cs +++ b/Content.Client/DisplacementMap/DisplacementMapSystem.cs @@ -9,22 +9,36 @@ public sealed class DisplacementMapSystem : EntitySystem { [Dependency] private readonly ISerializationManager _serialization = default!; - public bool TryAddDisplacement(DisplacementData data, SpriteComponent sprite, int index, string key, HashSet revealedLayers) + /// + /// Attempting to apply a displacement map to a specific layer of SpriteComponent + /// + /// Information package for applying the displacement map + /// SpriteComponent + /// Index of the layer where the new map layer will be added + /// Unique layer key, which will determine which layer to apply displacement map to + /// The key of the new displacement map layer added by this function. + /// + public bool TryAddDisplacement(DisplacementData data, + SpriteComponent sprite, + int index, + object key, + out string displacementKey) { + displacementKey = $"{key}-displacement"; + + if (key.ToString() is null) + return false; + if (data.ShaderOverride != null) sprite.LayerSetShader(index, data.ShaderOverride); - var displacementKey = $"{key}-displacement"; - if (!revealedLayers.Add(displacementKey)) - { - Log.Warning($"Duplicate key for DISPLACEMENT: {displacementKey}."); - return false; - } + if (sprite.LayerMapTryGet(displacementKey, out var oldIndex)) + sprite.RemoveLayer(oldIndex); //allows you not to write it every time in the YML foreach (var pair in data.SizeMaps) { - pair.Value.CopyToShaderParameters??= new() + pair.Value.CopyToShaderParameters ??= new() { LayerKey = "dummy", ParameterTexture = "displacementMap", @@ -45,21 +59,22 @@ public sealed class DisplacementMapSystem : EntitySystem if (actualRSI is not null) { if (actualRSI.Size.X != actualRSI.Size.Y) - Log.Warning($"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked"); + { + Log.Warning( + $"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked"); + } var layerSize = actualRSI.Size.X; - if (data.SizeMaps.ContainsKey(layerSize)) - displacementDataLayer = data.SizeMaps[layerSize]; + if (data.SizeMaps.TryGetValue(layerSize, out var map)) + displacementDataLayer = map; } var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true); - displacementLayer.CopyToShaderParameters!.LayerKey = key; + displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString() ?? "this is impossible"; sprite.AddLayer(displacementLayer, index); sprite.LayerMapSet(displacementKey, index); - revealedLayers.Add(displacementKey); - return true; } } diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index f879e06a20..7e4484fde6 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -348,14 +348,16 @@ namespace Content.Client.Hands.Systems sprite.LayerSetData(index, layerData); - //Add displacement maps - if (hand.Location == HandLocation.Left && handComp.LeftHandDisplacement is not null) - _displacement.TryAddDisplacement(handComp.LeftHandDisplacement, sprite, index, key, revealedLayers); - else if (hand.Location == HandLocation.Right && handComp.RightHandDisplacement is not null) - _displacement.TryAddDisplacement(handComp.RightHandDisplacement, sprite, index, key, revealedLayers); - //Fallback to default displacement map - else if (handComp.HandDisplacement is not null) - _displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers); + // Add displacement maps + var displacement = hand.Location switch + { + HandLocation.Left => handComp.LeftHandDisplacement, + HandLocation.Right => handComp.RightHandDisplacement, + _ => handComp.HandDisplacement + }; + + if (displacement is not null && _displacement.TryAddDisplacement(displacement, sprite, index, key, out var displacementKey)) + revealedLayers.Add(displacementKey); } RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true); diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index aa9c4ccce7..c63112243e 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -1,3 +1,4 @@ +using Content.Client.DisplacementMap; using Content.Shared.CCVar; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -16,6 +17,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly MarkingManager _markingManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly DisplacementMapSystem _displacement = default!; public override void Initialize() { @@ -369,6 +371,11 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem { sprite.LayerSetColor(layerId, Color.White); } + + if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced) + { + _displacement.TryAddDisplacement(displacementData, sprite, targetLayer + j + 1, layerId, out _); + } } } diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs index 4a741074c9..aeb8d6716f 100644 --- a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs +++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.DisplacementMap; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Inventory; @@ -99,6 +100,12 @@ public sealed partial class HumanoidAppearanceComponent : Component [DataField] public ProtoId? UndergarmentBottom = new ProtoId("UndergarmentBottomBoxers"); + + /// + /// The displacement maps that will be applied to specific layers of the humanoid. + /// + [DataField] + public Dictionary MarkingsDisplacement = new(); } [DataDefinition] diff --git a/Content.Shared/Humanoid/Markings/MarkingManager.cs b/Content.Shared/Humanoid/Markings/MarkingManager.cs index f45e6cf060..e844dc2280 100644 --- a/Content.Shared/Humanoid/Markings/MarkingManager.cs +++ b/Content.Shared/Humanoid/Markings/MarkingManager.cs @@ -62,12 +62,12 @@ namespace Content.Shared.Humanoid.Markings string species) { var speciesProto = _prototypeManager.Index(species); - var onlyWhitelisted = _prototypeManager.Index(speciesProto.MarkingPoints).OnlyWhitelisted; + var markingPoints = _prototypeManager.Index(speciesProto.MarkingPoints); var res = new Dictionary(); foreach (var (key, marking) in MarkingsByCategory(category)) { - if (onlyWhitelisted && marking.SpeciesRestrictions == null) + if ((markingPoints.OnlyWhitelisted || markingPoints.Points[category].OnlyWhitelisted) && marking.SpeciesRestrictions == null) { continue; } diff --git a/Content.Shared/Humanoid/Markings/MarkingPoints.cs b/Content.Shared/Humanoid/Markings/MarkingPoints.cs index a5bcca9706..6b1696849f 100644 --- a/Content.Shared/Humanoid/Markings/MarkingPoints.cs +++ b/Content.Shared/Humanoid/Markings/MarkingPoints.cs @@ -1,6 +1,5 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Humanoid.Markings; @@ -8,13 +7,23 @@ namespace Content.Shared.Humanoid.Markings; [Serializable, NetSerializable] public sealed partial class MarkingPoints { - [DataField("points", required: true)] + [DataField(required: true)] public int Points = 0; - [DataField("required", required: true)] - public bool Required = false; + + [DataField(required: true)] + public bool Required; + + /// + /// If the user of this marking point set is only allowed to + /// use whitelisted markings, and not globally usable markings. + /// Only used for validation and profile construction. Ignored anywhere else. + /// + [DataField] + public bool OnlyWhitelisted; + // Default markings for this layer. - [DataField("defaultMarkings", customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List DefaultMarkings = new(); + [DataField] + public List> DefaultMarkings = new(); public static Dictionary CloneMarkingPointDictionary(Dictionary self) { @@ -26,6 +35,7 @@ public sealed partial class MarkingPoints { Points = points.Points, Required = points.Required, + OnlyWhitelisted = points.OnlyWhitelisted, DefaultMarkings = points.DefaultMarkings }; } @@ -44,8 +54,9 @@ public sealed partial class MarkingPointsPrototype : IPrototype /// use whitelisted markings, and not globally usable markings. /// Only used for validation and profile construction. Ignored anywhere else. /// - [DataField("onlyWhitelisted")] public bool OnlyWhitelisted; + [DataField] + public bool OnlyWhitelisted; - [DataField("points", required: true)] + [DataField(required: true)] public Dictionary Points { get; private set; } = default!; } diff --git a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs index ead061dd39..a6c578015a 100644 --- a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs +++ b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs @@ -32,6 +32,13 @@ namespace Content.Shared.Humanoid.Markings [DataField("coloring")] public MarkingColors Coloring { get; private set; } = new(); + /// + /// Do we need to apply any displacement maps to this marking? Set to false if your marking is incompatible + /// with a standard human doll, and is used for some special races with unusual shapes + /// + [DataField] + public bool CanBeDisplaced { get; private set; } = true; + [DataField("sprites", required: true)] public List Sprites { get; private set; } = default!; diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml index 066d197223..67e686999a 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml @@ -2,6 +2,7 @@ id: VoxFacialHairBeard bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -11,6 +12,7 @@ id: VoxFacialHairColonel bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -20,6 +22,7 @@ id: VoxFacialHairFu bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -29,6 +32,7 @@ id: VoxFacialHairMane bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -38,6 +42,7 @@ id: VoxFacialHairManeSmall bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -47,6 +52,7 @@ id: VoxFacialHairNeck bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi @@ -56,6 +62,7 @@ id: VoxFacialHairTufts bodyPart: FacialHair markingCategory: FacialHair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml index 8748b3a093..7fc3fedaa2 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml @@ -2,6 +2,7 @@ id: VoxHairAfro bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -11,6 +12,7 @@ id: VoxHairBraids bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -20,6 +22,7 @@ id: VoxHairCrestedQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -29,6 +32,7 @@ id: VoxHairEmperorQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -38,6 +42,7 @@ id: VoxHairFlowing bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -47,6 +52,7 @@ id: VoxHairHawk bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -56,6 +62,7 @@ id: VoxHairHorns bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -65,6 +72,7 @@ id: VoxHairKeelQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -74,6 +82,7 @@ id: VoxHairKeetQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -83,6 +92,7 @@ id: VoxHairKingly bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -92,6 +102,7 @@ id: VoxHairLongBraid bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -101,6 +112,7 @@ id: VoxHairMange bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -110,6 +122,7 @@ id: VoxHairMohawk bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -119,6 +132,7 @@ id: VoxHairNights bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -128,6 +142,7 @@ id: VoxHairPony bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -137,6 +152,7 @@ id: VoxHairRazorClipped bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -146,6 +162,7 @@ id: VoxHairRazor bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -155,6 +172,7 @@ id: VoxHairSortBraid bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -164,6 +182,7 @@ id: VoxHairShortQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -173,6 +192,7 @@ id: VoxHairSpotty bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -182,6 +202,7 @@ id: VoxHairSurf bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -191,6 +212,7 @@ id: VoxHairTielQuills bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -200,6 +222,7 @@ id: VoxHairWiseBraid bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi @@ -209,6 +232,7 @@ id: VoxHairYasu bodyPart: Hair markingCategory: Hair + canBeDisplaced: false speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index 4703bfd06b..94b5cebf26 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -11,10 +11,6 @@ - type: Body prototype: Vox requiredLegs: 2 - - type: HumanoidAppearance - species: Vox - undergarmentTop: UndergarmentTopTanktopVox - undergarmentBottom: UndergarmentBottomBoxersVox #- type: VoxAccent # Not yet coded - type: Speech speechVerb: Vox @@ -108,6 +104,16 @@ sprite: "Effects/creampie.rsi" state: "creampie_vox" # Not default visible: false + - type: HumanoidAppearance + species: Vox + undergarmentTop: UndergarmentTopTanktopVox + undergarmentBottom: UndergarmentBottomBoxersVox + markingsDisplacement: + Hair: + sizeMaps: + 32: + sprite: Mobs/Species/Vox/displacement.rsi + state: hair - type: Inventory speciesId: vox displacements: @@ -167,6 +173,12 @@ species: Vox undergarmentTop: UndergarmentTopTanktopVox undergarmentBottom: UndergarmentBottomBoxersVox + markingsDisplacement: + Hair: + sizeMaps: + 32: + sprite: Mobs/Species/Vox/displacement.rsi + state: hair - type: Body prototype: Vox - type: Inventory diff --git a/Resources/Prototypes/Species/vox.yml b/Resources/Prototypes/Species/vox.yml index 8501784b89..fd458b40f8 100644 --- a/Resources/Prototypes/Species/vox.yml +++ b/Resources/Prototypes/Species/vox.yml @@ -37,7 +37,6 @@ - type: markingPoints id: MobVoxMarkingLimits - onlyWhitelisted: true points: Hair: points: 1 @@ -45,34 +44,43 @@ FacialHair: points: 1 required: false + onlyWhitelisted: true Head: points: 1 required: true + onlyWhitelisted: true Snout: points: 1 required: true defaultMarkings: [ VoxBeak ] + onlyWhitelisted: true Arms: points: 4 required: true defaultMarkings: [ VoxLArmScales, VoxRArmScales, VoxRHandScales, VoxLHandScales ] + onlyWhitelisted: true Legs: points: 4 required: true defaultMarkings: [ VoxLLegScales, VoxRLegScales, VoxRFootScales, VoxLFootScales ] + onlyWhitelisted: true UndergarmentTop: points: 1 required: false + onlyWhitelisted: true UndergarmentBottom: points: 1 required: false + onlyWhitelisted: true Chest: points: 1 required: false + onlyWhitelisted: true Tail: points: 1 required: true defaultMarkings: [ VoxTail ] + onlyWhitelisted: true - type: humanoidBaseSprite id: MobVoxEyes diff --git a/Resources/Textures/Mobs/Species/Vox/displacement.rsi/hair.png b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/hair.png new file mode 100644 index 0000000000..d52d859ebe Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/hair.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json index 228f934114..0c910f85e5 100644 --- a/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "jumpsuit state made by PJB3005. back, hand, head, and eyes states made by Flareguy, ears, hand_l, hand_r and shoes made by TheShuEd", + "copyright": "jumpsuit state made by PJB3005. back, hand, head, and eyes states made by Flareguy, ears, hand_l, hand_r, hair and shoes made by TheShuEd", "size": { "x": 32, "y": 32 @@ -38,6 +38,10 @@ "name": "shoes", "directions": 4 }, + { + "name": "hair", + "directions": 4 + }, { "name": "hand_l", "directions": 4