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 ;
Changeling devour and transform (#34002)
* Initial:
Create Devour componentry, preliminary identity storage and the systems
for Devouring
* I have genuinely no idea what i'm doing
- added the radial menu, it has nothing in it.
- trying to get it to populate. the event under the event is broken,
i don't know why, but apparently it's not typed right
- Added a placeholder transform
- oh also fixed up some devour stuff and moved some things around.
* Holey moley, Transform, better devour, oh my!
- Move DnaComponent into Shared because we need it for the DNA cloning
- Make Transform MOSTLY work on the LAST identity devoured.
- Fix some issues on devour that involved prediction, canceling and
Damage exeucting (Thanks Plykiya for pointing out AttemptFrequency!)
* Proper tail stealing and Damage modifier attempt
Add check to add a wagging component on the Changeling if the victim's
species Prototype had one.
attempt to add the Damage mitigation check
* MAJOR CLEANUP AND FIXES AUGH 3 DAYS!!!
- Nullspaced a clone of a victim
- fix audio using server virtualized Pvs (i hate this)
- fix the mispredicted doafters
- Clean up a wholelotta code
- utilize clone systems to clone appearances
- Move CloneAppearance from server to shared So we can actually access
it
* Examine stuff, more cleanup, Jumpsuit ripping
- make rotting prevent the action
- Add ripping of clothing (guh why is it also server)
- add some System stuff for pushing husked corpse inspection
- clean up more badcode
* Doing things properly, UI sorta kinda works.
- Utilize Relayed events for Devour checking
- Get a UI that partially works, Says the name of identities, doesn't
show their looks
- Make use of the New Dynamic BUI assignment
- commit the sin of no client prediction cause nullspace entities aren't
networked
* Got an entity for the Frontend transform
Issue with the looks
* Stick a camera into a fake mobs forehead
- Get the UI to see the net entity in pause space by using a
ViewSubscriber to get the Pvs from the initially stored identity entity
- Remove all the other parts used to try to get this to work before hand
* Raaaaadiallllllls also fix protection coefficents
- Change FancyWindow to Radial
- Fix Issue where coeffeient checking was the wrong way round
* absolutely massive cleanup, no more camera in mobs
- cleaned up event variables that are not needed
- Removed the use of a Pause space and go back to Nullspace usage
- use a PvsOverride rather than ViewSharing
- Remove old commented out code and Lots of unused code
* Fix "Ui elements" dying on the screen
- some minor cleanup
- don't start the entities that get cloned
* ftl, cleanup, and fixing missing transform details
- add replace functionality to TypingIndicatorSystem and
BodyEmotesSystem
- add placeholder sounds and functions to TransformBodyEmotes
- add extra Pvs handling for later use
- attributions for the funny straw sound
- Sound collections for all of the sounds
- various cleanups
* Some extra cleanup
* Fix some false assumptions about TypingIndicator
- Bubbles now transfer on spawned humans rather than used humans
- Clean up YET MORE CODE
- make it so you can't eat yourself
* Oooprs, forgot to add a Husked Corpse Loc
* Missing period in the husked corpse loc
* bad devour windup placeholder
* Husking and WIP Lungs
- Husking now will be prevented from Revival fully and will change
the appearance of players
* Add finalized Sprites for actions and final meta
- add devour on and off sprites
- add transform action sprite
- Add Armblade sprite for future use
- Credit obscenelytinyshark for the sprites <3
* Remove ling lungs, Entity<> everything
- Remove the ling lungs stuff for now... body system is overly
complicated, makes my head hurt
- Switch every method to use Entity<> from Uid, Comp format
* cleanup, admin logging, WIP Roles
* Admin verb, Roundstart, gamerule stuff
- add a Admin verb to make Changelingification easy!
- Add game rule stuff for admin verb and to tell the hapless
goober how to be a changeling... sorta
- clean up parts to make VV easy... USE THE VERB!!
* Armor Coefficent Check
- Remove bespoke changeling armor check and replace it
with a generic armor coefficient query.
* move to UnrevivableComponent instead of husked
- Move UnrevivableComponent to shared
- add Analyzable and ReasonMessage to UnrevivableComponent
to give granular control of the message and whether or not it shows up
in the analyzer
- remove the check for HuskedComponent in DefibrillatorSystem
* aaaaaaa CopyComp
- Some cleanup
- make Vocal system shared
- make VocalSystem Not make more Actions than it needs
- Use some code from ChameleonProjector so we can copy components
- partially ungod method the Transform system
* Cleanup, Moving more things to CopyComp
- TransformBodyEmotes now uses CopyComp (it's a server component so i
need to tell the server to deal with it
- TypingIndicatorComponent also now uses CopyComp
- cleaned up old, now unused "replace" methods in favor of CopyComp
- BodyEmotesSystem now has a publically accessable LoadSounds to deal
with the same problem Screaming had
* WIP
* Devour Windup noise, ForensicsSystem cleanup
* Revert VocalSystem Changes
- Reverted Moving VocalSystem to shared, copy comp acomplishes it
- added component.ScreamActionEntity = null; for copy comp
* cleanup unneeded comments
* revert an accidental line removal
* Remove duplicate SharedHumanoidAppearanceSystem
* Cleanup Typo's and import Forensics components for Dna
* Some more forensics calls
* cleanup use CopyComp for now until CopyComps
* CR cleanup
* Undo some SharedHumanoidAppearanceSystem changes
* Confound these spaces
* Some Copycomp stuff and fixing some PVS override
* use the proper TryCopyComps that are merged
* Change TransformMenu with RadialWithSector
* All sounds done, Fix lack of typing indicator issue
* Updated attributions to include used sound authors
* some ftl typos and mind_role text issue
* DNA, Screaming, appearance, grammar, wagging
- reduced all of the above using ApplyComponentChanges
- Issue still remains with bodyEmotes sticking around in the UI
* Fix UI stuff, partials, entprotoid, good practices
- bunch of partials added
- UI now has a predicted message
- EntProtoID in the admin verb
- RipClothing now uses Entity<ButcherableComponent>
- husking is now optional (off by default) for testing/till we have
hivemind/when we figure out what were doing with devour
- remove TransformGrammarSet
* More CR stuff and documentation
- Make TargetIsProtected less of a meme, with a prototype
set of DamageTypes to check
- Documenation everywhere
- Move DevourEvents into its own file
* Predicted sounds and fix the comp clone list
- Made all start and stop sounds shared
- Split out the rest of the events and UI stuff into subfiles
- Fixed some Clone comp list issues where comments had -'s causing them
to be read incorrectly
* Damage cap check, Identity Shutdown cleanup, cleanup
* Sound stuff (but actually this time)
* Missed documentation
* Missed Documentation and a EntProtoId
* Remove unused dependency
* Remove a nullcheck
* Some dummy minplayers
* CR - Husked now uses a rem/ensure
* Update Actions in the Prototype
* Fixup mindswap handover
- cleanup and handover PVS on mindswap
* Fixup Missing meta from accidental "Take-theirs"
* Add the Armblade to the roundstart-role
* Cleanup, CR (everything but the UI and renames)
* missed a spot
* missed some more whitespace
* Renames
* Primary constructor and a space in these trying times
* User interface stuff for Slime transformation
* popup prediction
* Ling devour no longer makes duplicate identities
- added a key to identities to the original victim
- Add some extra clone settings
* add guard statements to OnClones
* SentOnlyToOwner additions
* fix for sound stoppage error
* Move Organ deleter into soon to be atomized husk
* clone event inventory
* mono sounds
* lower sound volume
* Fix networked sound warning
* Clone comps thing
* review
* attributions
* Fix clobbered changes
* I'm gonna weh out
- whole bunch of CR changes
* fix some very buggy git
* okay its fixed
* address most review points
* fix inventory
* we hate entityuids
* fix test and more cleanup
* move this
* fix more stuff
* fix validation and rootable
* Remove Quickswitch due to some UI quirks
* oops left out some better explanation
* remove dangling LastConsumed component fields
* fix test fail
* try this
* cleanup cloning subscriptions, add movement speed modifier
* fix slime storage
* fix cloning setting inheritance
* Add session information to transform admin logs
* slay the integration test hydra
* dwarf size
* more volume tweaks
* comments
* improve comments and unpredict deletion due to errors when shutting down the server
* fix displancement cloning
---------
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-08-06 16:55:49 -03:00
using Robust.Shared.Enums ;
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
2025-07-07 15:57:05 -04:00
public static readonly ProtoId < SpeciesPrototype > DefaultSpecies = "Human" ;
2023-01-24 13:38:19 +13:00
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 )
2025-09-09 18:17:56 +02:00
| | ! _proto . Resolve ( 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 )
{
2025-06-17 22:16:24 +02:00
if ( ! Resolve ( source , ref sourceHumanoid , false ) | | ! Resolve ( target , ref targetHumanoid , false ) )
2025-02-09 23:13:27 -04:00
return ;
targetHumanoid . Species = sourceHumanoid . Species ;
targetHumanoid . SkinColor = sourceHumanoid . SkinColor ;
targetHumanoid . EyeColor = sourceHumanoid . EyeColor ;
targetHumanoid . Age = sourceHumanoid . Age ;
targetHumanoid . CustomBaseLayers = new ( sourceHumanoid . CustomBaseLayers ) ;
targetHumanoid . MarkingSet = new ( sourceHumanoid . MarkingSet ) ;
Changeling devour and transform (#34002)
* Initial:
Create Devour componentry, preliminary identity storage and the systems
for Devouring
* I have genuinely no idea what i'm doing
- added the radial menu, it has nothing in it.
- trying to get it to populate. the event under the event is broken,
i don't know why, but apparently it's not typed right
- Added a placeholder transform
- oh also fixed up some devour stuff and moved some things around.
* Holey moley, Transform, better devour, oh my!
- Move DnaComponent into Shared because we need it for the DNA cloning
- Make Transform MOSTLY work on the LAST identity devoured.
- Fix some issues on devour that involved prediction, canceling and
Damage exeucting (Thanks Plykiya for pointing out AttemptFrequency!)
* Proper tail stealing and Damage modifier attempt
Add check to add a wagging component on the Changeling if the victim's
species Prototype had one.
attempt to add the Damage mitigation check
* MAJOR CLEANUP AND FIXES AUGH 3 DAYS!!!
- Nullspaced a clone of a victim
- fix audio using server virtualized Pvs (i hate this)
- fix the mispredicted doafters
- Clean up a wholelotta code
- utilize clone systems to clone appearances
- Move CloneAppearance from server to shared So we can actually access
it
* Examine stuff, more cleanup, Jumpsuit ripping
- make rotting prevent the action
- Add ripping of clothing (guh why is it also server)
- add some System stuff for pushing husked corpse inspection
- clean up more badcode
* Doing things properly, UI sorta kinda works.
- Utilize Relayed events for Devour checking
- Get a UI that partially works, Says the name of identities, doesn't
show their looks
- Make use of the New Dynamic BUI assignment
- commit the sin of no client prediction cause nullspace entities aren't
networked
* Got an entity for the Frontend transform
Issue with the looks
* Stick a camera into a fake mobs forehead
- Get the UI to see the net entity in pause space by using a
ViewSubscriber to get the Pvs from the initially stored identity entity
- Remove all the other parts used to try to get this to work before hand
* Raaaaadiallllllls also fix protection coefficents
- Change FancyWindow to Radial
- Fix Issue where coeffeient checking was the wrong way round
* absolutely massive cleanup, no more camera in mobs
- cleaned up event variables that are not needed
- Removed the use of a Pause space and go back to Nullspace usage
- use a PvsOverride rather than ViewSharing
- Remove old commented out code and Lots of unused code
* Fix "Ui elements" dying on the screen
- some minor cleanup
- don't start the entities that get cloned
* ftl, cleanup, and fixing missing transform details
- add replace functionality to TypingIndicatorSystem and
BodyEmotesSystem
- add placeholder sounds and functions to TransformBodyEmotes
- add extra Pvs handling for later use
- attributions for the funny straw sound
- Sound collections for all of the sounds
- various cleanups
* Some extra cleanup
* Fix some false assumptions about TypingIndicator
- Bubbles now transfer on spawned humans rather than used humans
- Clean up YET MORE CODE
- make it so you can't eat yourself
* Oooprs, forgot to add a Husked Corpse Loc
* Missing period in the husked corpse loc
* bad devour windup placeholder
* Husking and WIP Lungs
- Husking now will be prevented from Revival fully and will change
the appearance of players
* Add finalized Sprites for actions and final meta
- add devour on and off sprites
- add transform action sprite
- Add Armblade sprite for future use
- Credit obscenelytinyshark for the sprites <3
* Remove ling lungs, Entity<> everything
- Remove the ling lungs stuff for now... body system is overly
complicated, makes my head hurt
- Switch every method to use Entity<> from Uid, Comp format
* cleanup, admin logging, WIP Roles
* Admin verb, Roundstart, gamerule stuff
- add a Admin verb to make Changelingification easy!
- Add game rule stuff for admin verb and to tell the hapless
goober how to be a changeling... sorta
- clean up parts to make VV easy... USE THE VERB!!
* Armor Coefficent Check
- Remove bespoke changeling armor check and replace it
with a generic armor coefficient query.
* move to UnrevivableComponent instead of husked
- Move UnrevivableComponent to shared
- add Analyzable and ReasonMessage to UnrevivableComponent
to give granular control of the message and whether or not it shows up
in the analyzer
- remove the check for HuskedComponent in DefibrillatorSystem
* aaaaaaa CopyComp
- Some cleanup
- make Vocal system shared
- make VocalSystem Not make more Actions than it needs
- Use some code from ChameleonProjector so we can copy components
- partially ungod method the Transform system
* Cleanup, Moving more things to CopyComp
- TransformBodyEmotes now uses CopyComp (it's a server component so i
need to tell the server to deal with it
- TypingIndicatorComponent also now uses CopyComp
- cleaned up old, now unused "replace" methods in favor of CopyComp
- BodyEmotesSystem now has a publically accessable LoadSounds to deal
with the same problem Screaming had
* WIP
* Devour Windup noise, ForensicsSystem cleanup
* Revert VocalSystem Changes
- Reverted Moving VocalSystem to shared, copy comp acomplishes it
- added component.ScreamActionEntity = null; for copy comp
* cleanup unneeded comments
* revert an accidental line removal
* Remove duplicate SharedHumanoidAppearanceSystem
* Cleanup Typo's and import Forensics components for Dna
* Some more forensics calls
* cleanup use CopyComp for now until CopyComps
* CR cleanup
* Undo some SharedHumanoidAppearanceSystem changes
* Confound these spaces
* Some Copycomp stuff and fixing some PVS override
* use the proper TryCopyComps that are merged
* Change TransformMenu with RadialWithSector
* All sounds done, Fix lack of typing indicator issue
* Updated attributions to include used sound authors
* some ftl typos and mind_role text issue
* DNA, Screaming, appearance, grammar, wagging
- reduced all of the above using ApplyComponentChanges
- Issue still remains with bodyEmotes sticking around in the UI
* Fix UI stuff, partials, entprotoid, good practices
- bunch of partials added
- UI now has a predicted message
- EntProtoID in the admin verb
- RipClothing now uses Entity<ButcherableComponent>
- husking is now optional (off by default) for testing/till we have
hivemind/when we figure out what were doing with devour
- remove TransformGrammarSet
* More CR stuff and documentation
- Make TargetIsProtected less of a meme, with a prototype
set of DamageTypes to check
- Documenation everywhere
- Move DevourEvents into its own file
* Predicted sounds and fix the comp clone list
- Made all start and stop sounds shared
- Split out the rest of the events and UI stuff into subfiles
- Fixed some Clone comp list issues where comments had -'s causing them
to be read incorrectly
* Damage cap check, Identity Shutdown cleanup, cleanup
* Sound stuff (but actually this time)
* Missed documentation
* Missed Documentation and a EntProtoId
* Remove unused dependency
* Remove a nullcheck
* Some dummy minplayers
* CR - Husked now uses a rem/ensure
* Update Actions in the Prototype
* Fixup mindswap handover
- cleanup and handover PVS on mindswap
* Fixup Missing meta from accidental "Take-theirs"
* Add the Armblade to the roundstart-role
* Cleanup, CR (everything but the UI and renames)
* missed a spot
* missed some more whitespace
* Renames
* Primary constructor and a space in these trying times
* User interface stuff for Slime transformation
* popup prediction
* Ling devour no longer makes duplicate identities
- added a key to identities to the original victim
- Add some extra clone settings
* add guard statements to OnClones
* SentOnlyToOwner additions
* fix for sound stoppage error
* Move Organ deleter into soon to be atomized husk
* clone event inventory
* mono sounds
* lower sound volume
* Fix networked sound warning
* Clone comps thing
* review
* attributions
* Fix clobbered changes
* I'm gonna weh out
- whole bunch of CR changes
* fix some very buggy git
* okay its fixed
* address most review points
* fix inventory
* we hate entityuids
* fix test and more cleanup
* move this
* fix more stuff
* fix validation and rootable
* Remove Quickswitch due to some UI quirks
* oops left out some better explanation
* remove dangling LastConsumed component fields
* fix test fail
* try this
* cleanup cloning subscriptions, add movement speed modifier
* fix slime storage
* fix cloning setting inheritance
* Add session information to transform admin logs
* slay the integration test hydra
* dwarf size
* more volume tweaks
* comments
* improve comments and unpredict deletion due to errors when shutting down the server
* fix displancement cloning
---------
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-08-06 16:55:49 -03:00
SetSex ( target , sourceHumanoid . Sex , false , targetHumanoid ) ;
SetGender ( ( target , targetHumanoid ) , sourceHumanoid . Gender ) ;
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
}
Changeling devour and transform (#34002)
* Initial:
Create Devour componentry, preliminary identity storage and the systems
for Devouring
* I have genuinely no idea what i'm doing
- added the radial menu, it has nothing in it.
- trying to get it to populate. the event under the event is broken,
i don't know why, but apparently it's not typed right
- Added a placeholder transform
- oh also fixed up some devour stuff and moved some things around.
* Holey moley, Transform, better devour, oh my!
- Move DnaComponent into Shared because we need it for the DNA cloning
- Make Transform MOSTLY work on the LAST identity devoured.
- Fix some issues on devour that involved prediction, canceling and
Damage exeucting (Thanks Plykiya for pointing out AttemptFrequency!)
* Proper tail stealing and Damage modifier attempt
Add check to add a wagging component on the Changeling if the victim's
species Prototype had one.
attempt to add the Damage mitigation check
* MAJOR CLEANUP AND FIXES AUGH 3 DAYS!!!
- Nullspaced a clone of a victim
- fix audio using server virtualized Pvs (i hate this)
- fix the mispredicted doafters
- Clean up a wholelotta code
- utilize clone systems to clone appearances
- Move CloneAppearance from server to shared So we can actually access
it
* Examine stuff, more cleanup, Jumpsuit ripping
- make rotting prevent the action
- Add ripping of clothing (guh why is it also server)
- add some System stuff for pushing husked corpse inspection
- clean up more badcode
* Doing things properly, UI sorta kinda works.
- Utilize Relayed events for Devour checking
- Get a UI that partially works, Says the name of identities, doesn't
show their looks
- Make use of the New Dynamic BUI assignment
- commit the sin of no client prediction cause nullspace entities aren't
networked
* Got an entity for the Frontend transform
Issue with the looks
* Stick a camera into a fake mobs forehead
- Get the UI to see the net entity in pause space by using a
ViewSubscriber to get the Pvs from the initially stored identity entity
- Remove all the other parts used to try to get this to work before hand
* Raaaaadiallllllls also fix protection coefficents
- Change FancyWindow to Radial
- Fix Issue where coeffeient checking was the wrong way round
* absolutely massive cleanup, no more camera in mobs
- cleaned up event variables that are not needed
- Removed the use of a Pause space and go back to Nullspace usage
- use a PvsOverride rather than ViewSharing
- Remove old commented out code and Lots of unused code
* Fix "Ui elements" dying on the screen
- some minor cleanup
- don't start the entities that get cloned
* ftl, cleanup, and fixing missing transform details
- add replace functionality to TypingIndicatorSystem and
BodyEmotesSystem
- add placeholder sounds and functions to TransformBodyEmotes
- add extra Pvs handling for later use
- attributions for the funny straw sound
- Sound collections for all of the sounds
- various cleanups
* Some extra cleanup
* Fix some false assumptions about TypingIndicator
- Bubbles now transfer on spawned humans rather than used humans
- Clean up YET MORE CODE
- make it so you can't eat yourself
* Oooprs, forgot to add a Husked Corpse Loc
* Missing period in the husked corpse loc
* bad devour windup placeholder
* Husking and WIP Lungs
- Husking now will be prevented from Revival fully and will change
the appearance of players
* Add finalized Sprites for actions and final meta
- add devour on and off sprites
- add transform action sprite
- Add Armblade sprite for future use
- Credit obscenelytinyshark for the sprites <3
* Remove ling lungs, Entity<> everything
- Remove the ling lungs stuff for now... body system is overly
complicated, makes my head hurt
- Switch every method to use Entity<> from Uid, Comp format
* cleanup, admin logging, WIP Roles
* Admin verb, Roundstart, gamerule stuff
- add a Admin verb to make Changelingification easy!
- Add game rule stuff for admin verb and to tell the hapless
goober how to be a changeling... sorta
- clean up parts to make VV easy... USE THE VERB!!
* Armor Coefficent Check
- Remove bespoke changeling armor check and replace it
with a generic armor coefficient query.
* move to UnrevivableComponent instead of husked
- Move UnrevivableComponent to shared
- add Analyzable and ReasonMessage to UnrevivableComponent
to give granular control of the message and whether or not it shows up
in the analyzer
- remove the check for HuskedComponent in DefibrillatorSystem
* aaaaaaa CopyComp
- Some cleanup
- make Vocal system shared
- make VocalSystem Not make more Actions than it needs
- Use some code from ChameleonProjector so we can copy components
- partially ungod method the Transform system
* Cleanup, Moving more things to CopyComp
- TransformBodyEmotes now uses CopyComp (it's a server component so i
need to tell the server to deal with it
- TypingIndicatorComponent also now uses CopyComp
- cleaned up old, now unused "replace" methods in favor of CopyComp
- BodyEmotesSystem now has a publically accessable LoadSounds to deal
with the same problem Screaming had
* WIP
* Devour Windup noise, ForensicsSystem cleanup
* Revert VocalSystem Changes
- Reverted Moving VocalSystem to shared, copy comp acomplishes it
- added component.ScreamActionEntity = null; for copy comp
* cleanup unneeded comments
* revert an accidental line removal
* Remove duplicate SharedHumanoidAppearanceSystem
* Cleanup Typo's and import Forensics components for Dna
* Some more forensics calls
* cleanup use CopyComp for now until CopyComps
* CR cleanup
* Undo some SharedHumanoidAppearanceSystem changes
* Confound these spaces
* Some Copycomp stuff and fixing some PVS override
* use the proper TryCopyComps that are merged
* Change TransformMenu with RadialWithSector
* All sounds done, Fix lack of typing indicator issue
* Updated attributions to include used sound authors
* some ftl typos and mind_role text issue
* DNA, Screaming, appearance, grammar, wagging
- reduced all of the above using ApplyComponentChanges
- Issue still remains with bodyEmotes sticking around in the UI
* Fix UI stuff, partials, entprotoid, good practices
- bunch of partials added
- UI now has a predicted message
- EntProtoID in the admin verb
- RipClothing now uses Entity<ButcherableComponent>
- husking is now optional (off by default) for testing/till we have
hivemind/when we figure out what were doing with devour
- remove TransformGrammarSet
* More CR stuff and documentation
- Make TargetIsProtected less of a meme, with a prototype
set of DamageTypes to check
- Documenation everywhere
- Move DevourEvents into its own file
* Predicted sounds and fix the comp clone list
- Made all start and stop sounds shared
- Split out the rest of the events and UI stuff into subfiles
- Fixed some Clone comp list issues where comments had -'s causing them
to be read incorrectly
* Damage cap check, Identity Shutdown cleanup, cleanup
* Sound stuff (but actually this time)
* Missed documentation
* Missed Documentation and a EntProtoId
* Remove unused dependency
* Remove a nullcheck
* Some dummy minplayers
* CR - Husked now uses a rem/ensure
* Update Actions in the Prototype
* Fixup mindswap handover
- cleanup and handover PVS on mindswap
* Fixup Missing meta from accidental "Take-theirs"
* Add the Armblade to the roundstart-role
* Cleanup, CR (everything but the UI and renames)
* missed a spot
* missed some more whitespace
* Renames
* Primary constructor and a space in these trying times
* User interface stuff for Slime transformation
* popup prediction
* Ling devour no longer makes duplicate identities
- added a key to identities to the original victim
- Add some extra clone settings
* add guard statements to OnClones
* SentOnlyToOwner additions
* fix for sound stoppage error
* Move Organ deleter into soon to be atomized husk
* clone event inventory
* mono sounds
* lower sound volume
* Fix networked sound warning
* Clone comps thing
* review
* attributions
* Fix clobbered changes
* I'm gonna weh out
- whole bunch of CR changes
* fix some very buggy git
* okay its fixed
* address most review points
* fix inventory
* we hate entityuids
* fix test and more cleanup
* move this
* fix more stuff
* fix validation and rootable
* Remove Quickswitch due to some UI quirks
* oops left out some better explanation
* remove dangling LastConsumed component fields
* fix test fail
* try this
* cleanup cloning subscriptions, add movement speed modifier
* fix slime storage
* fix cloning setting inheritance
* Add session information to transform admin logs
* slay the integration test hydra
* dwarf size
* more volume tweaks
* comments
* improve comments and unpredict deletion due to errors when shutting down the server
* fix displancement cloning
---------
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-08-06 16:55:49 -03:00
/// <summary>
/// Sets the gender in the entity's HumanoidAppearanceComponent and GrammarComponent.
/// </summary>
public void SetGender ( Entity < HumanoidAppearanceComponent ? > ent , Gender gender )
{
if ( ! Resolve ( ent , ref ent . Comp ) )
return ;
ent . Comp . Gender = gender ;
Dirty ( ent ) ;
if ( TryComp < GrammarComponent > ( ent , out var grammar ) )
_grammarSystem . SetGender ( ( ent , grammar ) , gender ) ;
_identity . QueueIdentityUpdate ( ent ) ;
}
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 ;
2025-09-09 18:17:56 +02:00
if ( ! _proto . Resolve < SpeciesPrototype > ( humanoid . Species , out var species ) )
2023-04-05 16:41:11 -07:00
{
return ;
}
2025-09-14 01:30:17 -04:00
if ( verify & & _proto . Resolve ( species . SkinColoration , out var index ) )
2023-04-05 16:41:11 -07:00
{
2025-09-14 01:30:17 -04:00
var strategy = index . Strategy ;
skinColor = strategy . EnsureVerified ( skinColor ) ;
2023-04-05 16:41:11 -07:00
}
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
}