2024-05-12 09:18:21 +10:00
using System.IO ;
2023-09-28 16:20:29 -07:00
using System.Linq ;
2025-03-20 09:30:47 -04:00
using System.Numerics ;
2024-05-12 09:18:21 +10:00
using Content.Shared.CCVar ;
2024-01-25 01:39:00 -07:00
using Content.Shared.Decals ;
2024-04-08 15:16:21 +00:00
using Content.Shared.Examine ;
2023-01-24 13:38:19 +13:00
using Content.Shared.Humanoid.Markings ;
using Content.Shared.Humanoid.Prototypes ;
2024-04-08 15:16:21 +00:00
using Content.Shared.IdentityManagement ;
2025-03-20 09:30:47 -04:00
using Content.Shared.Inventory ;
2023-08-05 14:25:47 +10:00
using Content.Shared.Preferences ;
2024-05-12 09:18:21 +10:00
using Robust.Shared ;
using Robust.Shared.Configuration ;
2023-08-05 14:25:47 +10:00
using Robust.Shared.GameObjects.Components.Localization ;
using Robust.Shared.Network ;
2024-05-12 09:18:21 +10:00
using Robust.Shared.Player ;
2023-09-28 16:20:29 -07:00
using Robust.Shared.Prototypes ;
2024-05-12 09:18:21 +10:00
using Robust.Shared.Serialization.Manager ;
using Robust.Shared.Serialization.Markdown ;
using Robust.Shared.Utility ;
using YamlDotNet.RepresentationModel ;
2023-01-24 13:38:19 +13:00
namespace Content.Shared.Humanoid ;
/// <summary>
/// HumanoidSystem. Primarily deals with the appearance and visual data
/// of a humanoid entity. HumanoidVisualizer is what deals with actually
/// organizing the sprites and setting up the sprite component's layers.
///
/// This is a shared system, because while it is server authoritative,
/// you still need a local copy so that players can set up their
/// characters.
/// </summary>
public abstract class SharedHumanoidAppearanceSystem : EntitySystem
{
2024-05-12 09:18:21 +10:00
[Dependency] private readonly IConfigurationManager _cfgManager = default ! ;
2023-08-05 14:25:47 +10:00
[Dependency] private readonly INetManager _netManager = default ! ;
2024-04-08 15:16:21 +00:00
[Dependency] private readonly IPrototypeManager _proto = default ! ;
2024-05-12 09:18:21 +10:00
[Dependency] private readonly ISerializationManager _serManager = default ! ;
2023-01-24 13:38:19 +13:00
[Dependency] private readonly MarkingManager _markingManager = default ! ;
2025-04-21 18:40:31 +00:00
[Dependency] private readonly GrammarSystem _grammarSystem = default ! ;
2025-05-03 03:26:12 +02:00
[Dependency] private readonly SharedIdentitySystem _identity = default ! ;
2023-01-24 13:38:19 +13:00
2023-08-13 20:26:59 -04:00
[ValidatePrototypeId<SpeciesPrototype>]
2023-01-24 13:38:19 +13:00
public const string DefaultSpecies = "Human" ;
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-04-08 15:16:21 +00:00
2023-08-05 14:25:47 +10:00
SubscribeLocalEvent < HumanoidAppearanceComponent , ComponentInit > ( OnInit ) ;
2024-04-08 15:16:21 +00:00
SubscribeLocalEvent < HumanoidAppearanceComponent , ExaminedEvent > ( OnExamined ) ;
2023-01-24 13:38:19 +13:00
}
2024-05-12 09:18:21 +10:00
public DataNode ToDataNode ( HumanoidCharacterProfile profile )
{
var export = new HumanoidProfileExport ( )
{
ForkId = _cfgManager . GetCVar ( CVars . BuildForkId ) ,
Profile = profile ,
} ;
var dataNode = _serManager . WriteValue ( export , alwaysWrite : true , notNullableOverride : true ) ;
return dataNode ;
}
public HumanoidCharacterProfile FromStream ( Stream stream , ICommonSession session )
{
using var reader = new StreamReader ( stream , EncodingHelpers . UTF8 ) ;
var yamlStream = new YamlStream ( ) ;
yamlStream . Load ( reader ) ;
var root = yamlStream . Documents [ 0 ] . RootNode ;
var export = _serManager . Read < HumanoidProfileExport > ( root . ToDataNode ( ) , notNullableOverride : true ) ;
/ *
* Add custom handling here for forks / version numbers if you care .
* /
var profile = export . Profile ;
var collection = IoCManager . Instance ;
profile . EnsureValid ( session , collection ! ) ;
return profile ;
}
2023-08-05 14:25:47 +10:00
private void OnInit ( EntityUid uid , HumanoidAppearanceComponent humanoid , ComponentInit args )
{
2023-09-11 09:42:41 +10:00
if ( string . IsNullOrEmpty ( humanoid . Species ) | | _netManager . IsClient & & ! IsClientSide ( uid ) )
2023-08-05 14:25:47 +10:00
{
return ;
}
if ( string . IsNullOrEmpty ( humanoid . Initial )
2024-04-08 15:16:21 +00:00
| | ! _proto . TryIndex ( humanoid . Initial , out HumanoidProfilePrototype ? startingSet ) )
2023-08-05 14:25:47 +10:00
{
LoadProfile ( uid , HumanoidCharacterProfile . DefaultWithSpecies ( humanoid . Species ) , humanoid ) ;
return ;
}
// Do this first, because profiles currently do not support custom base layers
foreach ( var ( layer , info ) in startingSet . CustomBaseLayers )
{
humanoid . CustomBaseLayers . Add ( layer , info ) ;
}
LoadProfile ( uid , startingSet . Profile , humanoid ) ;
}
2024-04-08 15:16:21 +00:00
private void OnExamined ( EntityUid uid , HumanoidAppearanceComponent component , ExaminedEvent args )
{
var identity = Identity . Entity ( uid , EntityManager ) ;
var species = GetSpeciesRepresentation ( component . Species ) . ToLower ( ) ;
var age = GetAgeRepresentation ( component . Species , component . Age ) ;
args . PushText ( Loc . GetString ( "humanoid-appearance-component-examine" , ( "user" , identity ) , ( "age" , age ) , ( "species" , species ) ) ) ;
}
2023-01-24 13:38:19 +13:00
/// <summary>
/// Toggles a humanoid's sprite layer visibility.
/// </summary>
2025-03-20 09:30:47 -04:00
/// <param name="ent">Humanoid entity</param>
2023-01-24 13:38:19 +13:00
/// <param name="layer">Layer to toggle visibility for</param>
2025-03-20 09:30:47 -04:00
/// <param name="visible">Whether to hide or show the layer. If more than once piece of clothing is hiding the layer, it may remain hidden.</param>
/// <param name="source">Equipment slot that has the clothing that is (or was) hiding the layer. If not specified, the change is "permanent" (i.e., see <see cref="HumanoidAppearanceComponent.PermanentlyHidden"/>)</param>
public void SetLayerVisibility ( Entity < HumanoidAppearanceComponent ? > ent ,
2023-01-24 13:38:19 +13:00
HumanoidVisualLayers layer ,
bool visible ,
2025-03-20 09:30:47 -04:00
SlotFlags ? source = null )
2023-01-24 13:38:19 +13:00
{
2025-03-20 09:30:47 -04:00
if ( ! Resolve ( ent . Owner , ref ent . Comp , false ) )
2023-01-24 13:38:19 +13:00
return ;
var dirty = false ;
2025-03-20 09:30:47 -04:00
SetLayerVisibility ( ent ! , layer , visible , source , ref dirty ) ;
2023-01-24 13:38:19 +13:00
if ( dirty )
2025-03-20 09:30:47 -04:00
Dirty ( ent ) ;
2023-01-24 13:38:19 +13:00
}
2025-02-09 23:13:27 -04:00
/// <summary>
/// Clones a humanoid's appearance to a target mob, provided they both have humanoid components.
/// </summary>
/// <param name="source">Source entity to fetch the original appearance from.</param>
/// <param name="target">Target entity to apply the source entity's appearance to.</param>
/// <param name="sourceHumanoid">Source entity's humanoid component.</param>
/// <param name="targetHumanoid">Target entity's humanoid component.</param>
public void CloneAppearance ( EntityUid source , EntityUid target , HumanoidAppearanceComponent ? sourceHumanoid = null ,
HumanoidAppearanceComponent ? targetHumanoid = null )
{
if ( ! Resolve ( source , ref sourceHumanoid ) | | ! Resolve ( target , ref targetHumanoid ) )
return ;
targetHumanoid . Species = sourceHumanoid . Species ;
targetHumanoid . SkinColor = sourceHumanoid . SkinColor ;
targetHumanoid . EyeColor = sourceHumanoid . EyeColor ;
targetHumanoid . Age = sourceHumanoid . Age ;
SetSex ( target , sourceHumanoid . Sex , false , targetHumanoid ) ;
targetHumanoid . CustomBaseLayers = new ( sourceHumanoid . CustomBaseLayers ) ;
targetHumanoid . MarkingSet = new ( sourceHumanoid . MarkingSet ) ;
targetHumanoid . Gender = sourceHumanoid . Gender ;
2025-04-21 18:40:31 +00:00
2025-02-09 23:13:27 -04:00
if ( TryComp < GrammarComponent > ( target , out var grammar ) )
2025-04-21 18:40:31 +00:00
_grammarSystem . SetGender ( ( target , grammar ) , sourceHumanoid . Gender ) ;
2025-02-09 23:13:27 -04:00
2025-05-03 03:26:12 +02:00
_identity . QueueIdentityUpdate ( target ) ;
2025-02-09 23:13:27 -04:00
Dirty ( target , targetHumanoid ) ;
}
2023-01-24 13:38:19 +13:00
/// <summary>
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
/// </summary>
2025-03-20 09:30:47 -04:00
/// <param name="ent">Humanoid entity</param>
2023-01-24 13:38:19 +13:00
/// <param name="layers">An enumerable of all sprite layers that are going to have their visibility set</param>
/// <param name="visible">The visibility state of the layers given</param>
2025-03-20 09:30:47 -04:00
public void SetLayersVisibility ( Entity < HumanoidAppearanceComponent ? > ent ,
IEnumerable < HumanoidVisualLayers > layers ,
bool visible )
2023-01-24 13:38:19 +13:00
{
2025-03-20 09:30:47 -04:00
if ( ! Resolve ( ent . Owner , ref ent . Comp , false ) )
2023-01-24 13:38:19 +13:00
return ;
var dirty = false ;
foreach ( var layer in layers )
{
2025-03-20 09:30:47 -04:00
SetLayerVisibility ( ent ! , layer , visible , null , ref dirty ) ;
2023-01-24 13:38:19 +13:00
}
if ( dirty )
2025-03-20 09:30:47 -04:00
Dirty ( ent ) ;
2023-01-24 13:38:19 +13:00
}
2025-03-20 09:30:47 -04:00
/// <inheritdoc cref="SetLayerVisibility(Entity{HumanoidAppearanceComponent?},HumanoidVisualLayers,bool,Nullable{SlotFlags})"/>
public virtual void SetLayerVisibility (
Entity < HumanoidAppearanceComponent > ent ,
2023-01-24 13:38:19 +13:00
HumanoidVisualLayers layer ,
bool visible ,
2025-03-20 09:30:47 -04:00
SlotFlags ? source ,
2023-01-24 13:38:19 +13:00
ref bool dirty )
{
2025-03-20 09:30:47 -04:00
#if DEBUG
if ( source is { } s )
2023-01-24 13:38:19 +13:00
{
2025-03-20 09:30:47 -04:00
DebugTools . AssertNotEqual ( s , SlotFlags . NONE ) ;
// Check that only a single bit in the bitflag is set
var powerOfTwo = BitOperations . RoundUpToPowerOf2 ( ( uint ) s ) ;
DebugTools . AssertEqual ( ( uint ) s , powerOfTwo ) ;
}
#endif
2023-01-24 13:38:19 +13:00
2025-03-20 09:30:47 -04:00
if ( visible )
{
if ( source is not { } slot )
{
dirty | = ent . Comp . PermanentlyHidden . Remove ( layer ) ;
}
else if ( ent . Comp . HiddenLayers . TryGetValue ( layer , out var oldSlots ) )
{
// This layer might be getting hidden by more than one piece of equipped clothing.
// remove slot flag from the set of slots hiding this layer, then check if there are any left.
ent . Comp . HiddenLayers [ layer ] = ~ slot & oldSlots ;
if ( ent . Comp . HiddenLayers [ layer ] = = SlotFlags . NONE )
ent . Comp . HiddenLayers . Remove ( layer ) ;
dirty | = ( oldSlots & slot ) ! = 0 ;
}
2023-01-24 13:38:19 +13:00
}
else
{
2025-03-20 09:30:47 -04:00
if ( source is not { } slot )
{
dirty | = ent . Comp . PermanentlyHidden . Add ( layer ) ;
}
else
{
var oldSlots = ent . Comp . HiddenLayers . GetValueOrDefault ( layer ) ;
ent . Comp . HiddenLayers [ layer ] = slot | oldSlots ;
dirty | = ( oldSlots & slot ) ! = slot ;
}
2023-01-24 13:38:19 +13:00
}
}
/// <summary>
/// Set a humanoid mob's species. This will change their base sprites, as well as their current
/// set of markings to fit against the mob's new species.
/// </summary>
/// <param name="uid">The humanoid mob's UID.</param>
/// <param name="species">The species to set the mob to. Will return if the species prototype was invalid.</param>
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void SetSpecies ( EntityUid uid , string species , bool sync = true , HumanoidAppearanceComponent ? humanoid = null )
{
2024-04-08 15:16:21 +00:00
if ( ! Resolve ( uid , ref humanoid ) | | ! _proto . TryIndex < SpeciesPrototype > ( species , out var prototype ) )
2023-01-24 13:38:19 +13:00
{
return ;
}
humanoid . Species = species ;
2023-03-05 08:59:07 +06:00
humanoid . MarkingSet . EnsureSpecies ( species , humanoid . SkinColor , _markingManager ) ;
2023-01-24 13:38:19 +13:00
var oldMarkings = humanoid . MarkingSet . GetForwardEnumerator ( ) . ToList ( ) ;
2024-04-08 15:16:21 +00:00
humanoid . MarkingSet = new ( oldMarkings , prototype . MarkingPoints , _markingManager , _proto ) ;
2023-01-24 13:38:19 +13:00
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-01-24 13:38:19 +13:00
}
/// <summary>
/// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom,
/// custom base layers should use <see cref="SetBaseLayerColor"/> instead.
/// </summary>
/// <param name="uid">The humanoid mob's UID.</param>
/// <param name="skinColor">Skin color to set on the humanoid mob.</param>
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
2023-04-23 02:16:59 -07:00
/// <param name="verify">Whether to verify the skin color can be set on this humanoid or not</param>
2023-01-24 13:38:19 +13:00
/// <param name="humanoid">Humanoid component of the entity</param>
2023-04-23 02:16:59 -07:00
public virtual void SetSkinColor ( EntityUid uid , Color skinColor , bool sync = true , bool verify = true , HumanoidAppearanceComponent ? humanoid = null )
2023-01-24 13:38:19 +13:00
{
if ( ! Resolve ( uid , ref humanoid ) )
return ;
2024-04-08 15:16:21 +00:00
if ( ! _proto . TryIndex < SpeciesPrototype > ( humanoid . Species , out var species ) )
2023-04-05 16:41:11 -07:00
{
return ;
}
2023-04-23 02:16:59 -07:00
if ( verify & & ! SkinColor . VerifySkinColor ( species . SkinColoration , skinColor ) )
2023-04-05 16:41:11 -07:00
{
skinColor = SkinColor . ValidSkinTone ( species . SkinColoration , skinColor ) ;
}
2023-01-24 13:38:19 +13:00
humanoid . SkinColor = skinColor ;
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-01-24 13:38:19 +13:00
}
/// <summary>
/// Sets the base layer ID of this humanoid mob. A humanoid mob's 'base layer' is
/// the skin sprite that is applied to the mob's sprite upon appearance refresh.
/// </summary>
/// <param name="uid">The humanoid mob's UID.</param>
/// <param name="layer">The layer to target on this humanoid mob.</param>
/// <param name="id">The ID of the sprite to use. See <see cref="HumanoidSpeciesSpriteLayer"/>.</param>
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
/// <param name="humanoid">Humanoid component of the entity</param>
2023-01-27 11:04:58 +13:00
public void SetBaseLayerId ( EntityUid uid , HumanoidVisualLayers layer , string? id , bool sync = true ,
2023-01-24 13:38:19 +13:00
HumanoidAppearanceComponent ? humanoid = null )
{
if ( ! Resolve ( uid , ref humanoid ) )
return ;
if ( humanoid . CustomBaseLayers . TryGetValue ( layer , out var info ) )
2023-09-28 16:20:29 -07:00
humanoid . CustomBaseLayers [ layer ] = info with { Id = id } ;
2023-01-24 13:38:19 +13:00
else
humanoid . CustomBaseLayers [ layer ] = new ( id ) ;
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-01-24 13:38:19 +13:00
}
/// <summary>
/// Sets the color of this humanoid mob's base layer. See <see cref="SetBaseLayerId"/> for a
/// description of how base layers work.
/// </summary>
/// <param name="uid">The humanoid mob's UID.</param>
/// <param name="layer">The layer to target on this humanoid mob.</param>
/// <param name="color">The color to set this base layer to.</param>
public void SetBaseLayerColor ( EntityUid uid , HumanoidVisualLayers layer , Color ? color , bool sync = true , HumanoidAppearanceComponent ? humanoid = null )
{
if ( ! Resolve ( uid , ref humanoid ) )
return ;
2023-01-27 11:04:58 +13:00
if ( humanoid . CustomBaseLayers . TryGetValue ( layer , out var info ) )
humanoid . CustomBaseLayers [ layer ] = info with { Color = color } ;
else
humanoid . CustomBaseLayers [ layer ] = new ( null , color ) ;
2023-01-24 13:38:19 +13:00
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-01-24 13:38:19 +13:00
}
2023-01-25 17:29:41 +01:00
/// <summary>
/// Set a humanoid mob's sex. This will not change their gender.
/// </summary>
/// <param name="uid">The humanoid mob's UID.</param>
/// <param name="sex">The sex to set the mob to.</param>
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void SetSex ( EntityUid uid , Sex sex , bool sync = true , HumanoidAppearanceComponent ? humanoid = null )
{
if ( ! Resolve ( uid , ref humanoid ) | | humanoid . Sex = = sex )
return ;
var oldSex = humanoid . Sex ;
humanoid . Sex = sex ;
2023-09-19 23:56:10 +03:00
humanoid . MarkingSet . EnsureSexes ( sex , _markingManager ) ;
2023-01-25 17:29:41 +01:00
RaiseLocalEvent ( uid , new SexChangedEvent ( oldSex , sex ) ) ;
if ( sync )
{
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-01-25 17:29:41 +01:00
}
}
2023-08-05 14:25:47 +10:00
/// <summary>
/// Loads a humanoid character profile directly onto this humanoid mob.
/// </summary>
/// <param name="uid">The mob's entity UID.</param>
/// <param name="profile">The character profile to load.</param>
/// <param name="humanoid">Humanoid component of the entity</param>
2024-04-24 21:31:45 -04:00
public virtual void LoadProfile ( EntityUid uid , HumanoidCharacterProfile ? profile , HumanoidAppearanceComponent ? humanoid = null )
2023-08-05 14:25:47 +10:00
{
2024-04-24 21:31:45 -04:00
if ( profile = = null )
return ;
2023-08-05 14:25:47 +10:00
if ( ! Resolve ( uid , ref humanoid ) )
{
return ;
}
SetSpecies ( uid , profile . Species , false , humanoid ) ;
SetSex ( uid , profile . Sex , false , humanoid ) ;
humanoid . EyeColor = profile . Appearance . EyeColor ;
SetSkinColor ( uid , profile . Appearance . SkinColor , false ) ;
humanoid . MarkingSet . Clear ( ) ;
// Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it.
var markingFColored = new Dictionary < Marking , MarkingPrototype > ( ) ;
foreach ( var marking in profile . Appearance . Markings )
{
if ( _markingManager . TryGetMarking ( marking , out var prototype ) )
{
if ( ! prototype . ForcedColoring )
{
AddMarking ( uid , marking . MarkingId , marking . MarkingColors , false ) ;
}
else
{
markingFColored . Add ( marking , prototype ) ;
}
}
}
// Hair/facial hair - this may eventually be deprecated.
// We need to ensure hair before applying it or coloring can try depend on markings that can be invalid
2024-04-08 15:16:21 +00:00
var hairColor = _markingManager . MustMatchSkin ( profile . Species , HumanoidVisualLayers . Hair , out var hairAlpha , _proto )
2023-08-05 14:25:47 +10:00
? profile . Appearance . SkinColor . WithAlpha ( hairAlpha ) : profile . Appearance . HairColor ;
2024-04-08 15:16:21 +00:00
var facialHairColor = _markingManager . MustMatchSkin ( profile . Species , HumanoidVisualLayers . FacialHair , out var facialHairAlpha , _proto )
2023-08-05 14:25:47 +10:00
? profile . Appearance . SkinColor . WithAlpha ( facialHairAlpha ) : profile . Appearance . FacialHairColor ;
if ( _markingManager . Markings . TryGetValue ( profile . Appearance . HairStyleId , out var hairPrototype ) & &
2024-04-08 15:16:21 +00:00
_markingManager . CanBeApplied ( profile . Species , profile . Sex , hairPrototype , _proto ) )
2023-08-05 14:25:47 +10:00
{
AddMarking ( uid , profile . Appearance . HairStyleId , hairColor , false ) ;
}
if ( _markingManager . Markings . TryGetValue ( profile . Appearance . FacialHairStyleId , out var facialHairPrototype ) & &
2024-04-08 15:16:21 +00:00
_markingManager . CanBeApplied ( profile . Species , profile . Sex , facialHairPrototype , _proto ) )
2023-08-05 14:25:47 +10:00
{
AddMarking ( uid , profile . Appearance . FacialHairStyleId , facialHairColor , false ) ;
}
2024-04-08 15:16:21 +00:00
humanoid . MarkingSet . EnsureSpecies ( profile . Species , profile . Appearance . SkinColor , _markingManager , _proto ) ;
2023-08-05 14:25:47 +10:00
// Finally adding marking with forced colors
foreach ( var ( marking , prototype ) in markingFColored )
{
var markingColors = MarkingColoring . GetMarkingLayerColors (
prototype ,
profile . Appearance . SkinColor ,
profile . Appearance . EyeColor ,
humanoid . MarkingSet
) ;
AddMarking ( uid , marking . MarkingId , markingColors , false ) ;
}
EnsureDefaultMarkings ( uid , humanoid ) ;
humanoid . Gender = profile . Gender ;
if ( TryComp < GrammarComponent > ( uid , out var grammar ) )
{
2025-04-21 18:40:31 +00:00
_grammarSystem . SetGender ( ( uid , grammar ) , profile . Gender ) ;
2023-08-05 14:25:47 +10:00
}
humanoid . Age = profile . Age ;
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-08-05 14:25:47 +10:00
}
/// <summary>
/// Adds a marking to this humanoid.
/// </summary>
/// <param name="uid">Humanoid mob's UID</param>
/// <param name="marking">Marking ID to use</param>
/// <param name="color">Color to apply to all marking layers of this marking</param>
/// <param name="sync">Whether to immediately sync this marking or not</param>
/// <param name="forced">If this marking was forced (ignores marking points)</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void AddMarking ( EntityUid uid , string marking , Color ? color = null , bool sync = true , bool forced = false , HumanoidAppearanceComponent ? humanoid = null )
{
if ( ! Resolve ( uid , ref humanoid )
| | ! _markingManager . Markings . TryGetValue ( marking , out var prototype ) )
{
return ;
}
var markingObject = prototype . AsMarking ( ) ;
markingObject . Forced = forced ;
if ( color ! = null )
{
for ( var i = 0 ; i < prototype . Sprites . Count ; i + + )
{
markingObject . SetColor ( i , color . Value ) ;
}
}
humanoid . MarkingSet . AddBack ( prototype . MarkingCategory , markingObject ) ;
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-08-05 14:25:47 +10:00
}
private void EnsureDefaultMarkings ( EntityUid uid , HumanoidAppearanceComponent ? humanoid )
{
if ( ! Resolve ( uid , ref humanoid ) )
{
return ;
}
humanoid . MarkingSet . EnsureDefault ( humanoid . SkinColor , humanoid . EyeColor , _markingManager ) ;
}
/// <summary>
///
/// </summary>
/// <param name="uid">Humanoid mob's UID</param>
/// <param name="marking">Marking ID to use</param>
/// <param name="colors">Colors to apply against this marking's set of sprites.</param>
/// <param name="sync">Whether to immediately sync this marking or not</param>
/// <param name="forced">If this marking was forced (ignores marking points)</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void AddMarking ( EntityUid uid , string marking , IReadOnlyList < Color > colors , bool sync = true , bool forced = false , HumanoidAppearanceComponent ? humanoid = null )
{
if ( ! Resolve ( uid , ref humanoid )
| | ! _markingManager . Markings . TryGetValue ( marking , out var prototype ) )
{
return ;
}
var markingObject = new Marking ( marking , colors ) ;
markingObject . Forced = forced ;
humanoid . MarkingSet . AddBack ( prototype . MarkingCategory , markingObject ) ;
if ( sync )
2024-03-19 23:27:02 -04:00
Dirty ( uid , humanoid ) ;
2023-08-05 14:25:47 +10:00
}
2024-04-08 15:16:21 +00:00
/// <summary>
/// Takes ID of the species prototype, returns UI-friendly name of the species.
/// </summary>
public string GetSpeciesRepresentation ( string speciesId )
{
if ( _proto . TryIndex < SpeciesPrototype > ( speciesId , out var species ) )
{
return Loc . GetString ( species . Name ) ;
}
Log . Error ( "Tried to get representation of unknown species: {speciesId}" ) ;
return Loc . GetString ( "humanoid-appearance-component-unknown-species" ) ;
}
public string GetAgeRepresentation ( string species , int age )
{
if ( ! _proto . TryIndex < SpeciesPrototype > ( species , out var speciesPrototype ) )
{
Log . Error ( "Tried to get age representation of species that couldn't be indexed: " + species ) ;
return Loc . GetString ( "identity-age-young" ) ;
}
if ( age < speciesPrototype . YoungAge )
{
return Loc . GetString ( "identity-age-young" ) ;
}
if ( age < speciesPrototype . OldAge )
{
return Loc . GetString ( "identity-age-middle-aged" ) ;
}
return Loc . GetString ( "identity-age-old" ) ;
}
2023-01-24 13:38:19 +13:00
}