diff --git a/Content.Client/Access/IdCardSystem.cs b/Content.Client/Access/IdCardSystem.cs new file mode 100644 index 0000000000..fcf2bf57de --- /dev/null +++ b/Content.Client/Access/IdCardSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Access.Systems; + +namespace Content.Client.Access; + +public sealed class IdCardSystem : SharedIdCardSystem +{ +} diff --git a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs index a3e7485497..7306715512 100644 --- a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs @@ -170,9 +170,12 @@ namespace Content.Client.Administration.UI if (pl.Antag) sb.Append(new Rune(0x1F5E1)); // 🗡 - sb.AppendFormat("\"{0}\"", pl.CharacterName) - .Append(' ') - .Append(pl.Username); + sb.AppendFormat("\"{0}\"", pl.CharacterName); + + if (pl.IdentityName != pl.CharacterName && pl.IdentityName != string.Empty) + sb.Append(' ').AppendFormat("[{0}]", pl.IdentityName); + + sb.Append(' ').Append(pl.Username); return sb.ToString(); } diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs index 02c87dc778..48f58a6efa 100644 --- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs +++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs @@ -71,6 +71,8 @@ namespace Content.Client.Administration.UI.CustomControls foreach (var info in _adminSystem.PlayerList) { var displayName = $"{info.CharacterName} ({info.Username})"; + if (info.IdentityName != info.CharacterName) + displayName += $" [{info.IdentityName}]"; if (!string.IsNullOrEmpty(FilterLineEdit.Text) && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant())) { diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index 9e92dc125b..84a4817da6 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -100,6 +100,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab { var entry = new PlayerTabEntry(player.Username, player.CharacterName, + player.IdentityName, player.StartingJob, player.Antag ? "YES" : "NO", new StyleBoxFlat(useAltColor ? _altColor : _defaultColor), diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs index 1ab6071e03..45cb4b7703 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs @@ -10,7 +10,7 @@ public sealed partial class PlayerTabEntry : ContainerButton { public EntityUid? PlayerUid; - public PlayerTabEntry(string username, string character, string job, string antagonist, StyleBox styleBox, bool connected) + public PlayerTabEntry(string username, string character, string identity, string job, string antagonist, StyleBox styleBox, bool connected) { RobustXamlLoader.Load(this); @@ -19,6 +19,8 @@ public sealed partial class PlayerTabEntry : ContainerButton UsernameLabel.StyleClasses.Add("Disabled"); JobLabel.Text = job; CharacterLabel.Text = character; + if (identity != character) + CharacterLabel.Text += $" [{identity}]"; AntagonistLabel.Text = antagonist; BackgroundColorPanel.PanelOverride = styleBox; } diff --git a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs index 756a7e5b62..95f7e84291 100644 --- a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs +++ b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs @@ -3,6 +3,7 @@ using Content.Client.Cargo.UI; using Content.Shared.Cargo.BUI; using Content.Shared.Cargo.Events; using Content.Shared.Cargo.Prototypes; +using Content.Shared.IdentityManagement; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.Prototypes; @@ -55,7 +56,7 @@ namespace Content.Client.Cargo.BUI string orderRequester; if (entityManager.TryGetComponent(localPlayer, out var metadata)) - orderRequester = metadata.EntityName; + orderRequester = Identity.Name(localPlayer.Value, entityManager); else orderRequester = string.Empty; diff --git a/Content.Client/ContextMenu/UI/EntityMenuElement.cs b/Content.Client/ContextMenu/UI/EntityMenuElement.cs index 00ce165a06..c371418c07 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuElement.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuElement.cs @@ -1,4 +1,8 @@ +using Content.Client.Administration.Managers; +using Content.Shared.Administration; +using Content.Shared.IdentityManagement; using Robust.Client.GameObjects; +using Robust.Client.Player; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -11,6 +15,7 @@ namespace Content.Client.ContextMenu.UI public const string StyleClassEntityMenuCountText = "contextMenuCount"; [Dependency] private IEntityManager _entityManager = default!; + [Dependency] private IPlayerManager _playerManager = default!; /// /// The entity that can be accessed by interacting with this element. @@ -74,10 +79,12 @@ namespace Content.Client.ContextMenu.UI EntityIcon.Sprite = _entityManager.GetComponentOrNull(entity); - if (UserInterfaceManager.DebugMonitors.Visible) + var admin = IoCManager.Resolve(); + + if (admin.HasFlag(AdminFlags.Admin | AdminFlags.Debug)) Text = _entityManager.ToPrettyString(entity.Value); else - Text = _entityManager.GetComponent(entity.Value).EntityName; + Text = Identity.Name(entity.Value, _entityManager, _playerManager.LocalPlayer!.ControlledEntity!); } } } diff --git a/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs b/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs index 71e1b8fdfa..2609527a03 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Shared.IdentityManagement; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -21,7 +22,7 @@ namespace Content.Client.ContextMenu.UI { if (GroupingContextMenuType == 0) { - var newEntities = entities.GroupBy(e => _entityManager.GetComponent(e).EntityName + (_entityManager.GetComponent(e).EntityPrototype?.ID ?? string.Empty)).ToList(); + var newEntities = entities.GroupBy(e => Identity.Name(e, _entityManager) + (_entityManager.GetComponent(e).EntityPrototype?.ID ?? string.Empty)).ToList(); return newEntities.Select(grp => grp.ToList()).ToList(); } else diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 1504621899..1ffa008559 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using Content.Client.Verbs; using Content.Shared.Examine; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Verbs; using JetBrains.Annotations; @@ -192,7 +193,7 @@ namespace Content.Client.Examine hBox.AddChild(new Label { - Text = EntityManager.GetComponent(target).EntityName, + Text = Identity.Name(target, EntityManager, player), HorizontalExpand = true, }); diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs index 25165ae0de..ca2e25b94d 100644 --- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs +++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs @@ -7,7 +7,7 @@ using Content.Shared.Disease.Components; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; using Content.Shared.Damage; - +using Content.Shared.IdentityManagement; using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent; namespace Content.Client.HealthAnalyzer.UI @@ -28,8 +28,9 @@ namespace Content.Client.HealthAnalyzer.UI if (msg.TargetEntity != null && entities.TryGetComponent(msg.TargetEntity, out var damageable)) { string entityName = "Unknown"; - if (msg.TargetEntity != null && entities.TryGetComponent(msg.TargetEntity.Value, out var metaData)) - entityName = metaData.EntityName; + if (msg.TargetEntity != null && + entities.TryGetComponent(msg.TargetEntity.Value, out var metaData)) + entityName = Identity.Name(msg.TargetEntity.Value, entities); IReadOnlyDictionary DamagePerGroup = damageable.DamagePerGroup; IReadOnlyDictionary DamagePerType = damageable.Damage.DamageDict; diff --git a/Content.Client/IdentityManagement/IdentitySystem.cs b/Content.Client/IdentityManagement/IdentitySystem.cs new file mode 100644 index 0000000000..7a7c96010d --- /dev/null +++ b/Content.Client/IdentityManagement/IdentitySystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.IdentityManagement; + +namespace Content.Client.IdentityManagement; + +public class IdentitySystem : SharedIdentitySystem +{ +} diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs index 9edc87339f..a2009d661e 100644 --- a/Content.Client/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Client.Strip; +using Content.Shared.IdentityManagement; using Content.Shared.Strip.Components; using JetBrains.Annotations; using Robust.Client.GameObjects; @@ -28,7 +29,8 @@ namespace Content.Client.Inventory { base.Open(); - _strippingMenu = new StrippingMenu($"{Loc.GetString("strippable-bound-user-interface-stripping-menu-title",("ownerName", IoCManager.Resolve().GetComponent(Owner.Owner).EntityName))}"); + var entMan = IoCManager.Resolve(); + _strippingMenu = new StrippingMenu($"{Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner.Owner, entMan)))}"); _strippingMenu.OnClose += Close; _strippingMenu.OpenCentered(); diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs index 90b1ff8770..3cd1e01110 100644 --- a/Content.Client/Items/UI/ItemStatusPanel.cs +++ b/Content.Client/Items/UI/ItemStatusPanel.cs @@ -4,6 +4,7 @@ using Content.Client.Items.Components; using Content.Client.Resources; using Content.Client.Stylesheets; using Content.Shared.Hands.Components; +using Content.Shared.IdentityManagement; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -159,11 +160,12 @@ namespace Content.Client.Items.UI if (_entityManager.TryGetComponent(_entity, out HandVirtualItemComponent? virtualItem) && _entityManager.EntityExists(virtualItem.BlockingEntity)) { - _itemNameLabel.Text = _entityManager.GetComponent(virtualItem.BlockingEntity).EntityName; + // Uses identity because we can be blocked by pulling someone + _itemNameLabel.Text = Identity.Name(virtualItem.BlockingEntity, _entityManager); } else { - _itemNameLabel.Text = _entityManager.GetComponent(_entity.Value).EntityName; + _itemNameLabel.Text = Identity.Name(_entity.Value, _entityManager); } } diff --git a/Content.Server/Access/Systems/IdCardSystem.cs b/Content.Server/Access/Systems/IdCardSystem.cs index de769790a8..9ed0398645 100644 --- a/Content.Server/Access/Systems/IdCardSystem.cs +++ b/Content.Server/Access/Systems/IdCardSystem.cs @@ -83,6 +83,7 @@ namespace Content.Server.Access.Systems jobTitle = jobTitle[..SharedIdCardConsoleComponent.MaxJobTitleLength]; id.JobTitle = jobTitle; + Dirty(id); UpdateEntityName(uid, id); return true; } @@ -96,6 +97,7 @@ namespace Content.Server.Access.Systems fullName = fullName[..SharedIdCardConsoleComponent.MaxFullNameLength]; id.FullName = fullName; + Dirty(id); UpdateEntityName(uid, id); return true; } @@ -129,50 +131,5 @@ namespace Content.Server.Access.Systems ("jobSuffix", jobSuffix)); EntityManager.GetComponent(id.Owner).EntityName = val; } - - /// - /// Attempt to find an ID card on an entity. This will look in the entity itself, in the entity's hands, and - /// in the entity's inventory. - /// - public bool TryFindIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard) - { - // check held item? - if (EntityManager.TryGetComponent(uid, out SharedHandsComponent? hands) && - hands.ActiveHandEntity is EntityUid heldItem && - TryGetIdCard(heldItem, out idCard)) - { - return true; - } - - // check entity itself - if (TryGetIdCard(uid, out idCard)) - return true; - - // check inventory slot? - if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid) && TryGetIdCard(idUid.Value, out idCard)) - { - return true; - } - - return false; - } - - /// - /// Attempt to get an id card component from an entity, either by getting it directly from the entity, or by - /// getting the contained id from a . - /// - public bool TryGetIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard) - { - if (EntityManager.TryGetComponent(uid, out idCard)) - return true; - - if (EntityManager.TryGetComponent(uid, out PDAComponent? pda) && pda.ContainedID != null) - { - idCard = pda.ContainedID; - return true; - } - - return false; - } } } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index b9e2f5a633..84e8962b18 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -1,10 +1,12 @@ using System.Globalization; using System.Linq; using Content.Server.Administration.Managers; +using Content.Server.IdentityManagement; using Content.Server.Players; using Content.Server.Roles; using Content.Shared.Administration; using Content.Shared.Administration.Events; +using Content.Shared.IdentityManagement; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Enums; @@ -25,6 +27,7 @@ namespace Content.Server.Administration.Systems _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _adminManager.OnPermsChanged += OnAdminPermsChanged; + SubscribeLocalEvent(OnIdentityChanged); SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnRoleEvent); @@ -46,6 +49,14 @@ namespace Content.Server.Administration.Systems } } + private void OnIdentityChanged(IdentityChangedEvent ev) + { + if (!TryComp(ev.CharacterEntity, out var actor)) + return; + + UpdatePlayerList(actor.PlayerSession); + } + private void OnRoleEvent(RoleEvent ev) { if (!ev.Role.Antagonist || ev.Role.Mind.Session == null) @@ -106,9 +117,13 @@ namespace Content.Server.Administration.Systems { var name = session.Name; var username = string.Empty; + var identityName = string.Empty; if (session.AttachedEntity != null) + { username = EntityManager.GetComponent(session.AttachedEntity.Value).EntityName; + identityName = Identity.Name(session.AttachedEntity.Value, EntityManager); + } var mind = session.ContentData()?.Mind; @@ -119,7 +134,7 @@ namespace Content.Server.Administration.Systems var connected = session.Status is SessionStatus.Connected or SessionStatus.InGame; - return new PlayerInfo(name, username, startingRole, antag, session.AttachedEntity.GetValueOrDefault(), session.UserId, + return new PlayerInfo(name, username, identityName, startingRole, antag, session.AttachedEntity.GetValueOrDefault(), session.UserId, connected); } } diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Server/Animals/Systems/UdderSystem.cs index c1dfa4caa6..c0e5cbf469 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Server/Animals/Systems/UdderSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Server.Nutrition.Components; using Content.Server.Popups; +using Content.Shared.IdentityManagement; using Content.Shared.Nutrition.Components; using Content.Shared.Popups; using Content.Shared.Verbs; @@ -110,7 +111,7 @@ namespace Content.Server.Animals.Systems var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity); _solutionContainerSystem.TryAddSolution(ev.ContainerUid, targetSolution, split); - _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", ev.ContainerUid)), uid, + _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(ev.ContainerUid, EntityManager))), uid, Filter.Entities(ev.UserUid), PopupType.Medium); } diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index 2db56b7c32..e64aad7581 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -10,6 +10,7 @@ using Content.Server.Cooldown; using Content.Server.Bible.Components; using Content.Server.MobState; using Content.Server.Popups; +using Content.Shared.IdentityManagement; using Content.Shared.Popups; using Robust.Shared.Random; using Robust.Shared.Audio; @@ -121,10 +122,10 @@ namespace Content.Server.Bible { if (_random.Prob(component.FailChance)) { - var othersFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-others", ("user", args.User),("target", args.Target),("bible", uid)); + var othersFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-others", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid)); _popupSystem.PopupEntity(othersFailMessage, args.User, Filter.PvsExcept(args.User), PopupType.SmallCaution); - var selfFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-self", ("target", args.Target),("bible", uid)); + var selfFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-self", ("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid)); _popupSystem.PopupEntity(selfFailMessage, args.User, Filter.Entities(args.User), PopupType.MediumCaution); SoundSystem.Play("/Audio/Effects/hit_kick.ogg", Filter.Pvs(args.Target.Value), args.User); @@ -133,10 +134,10 @@ namespace Content.Server.Bible } } - var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-others", ("user", args.User),("target", args.Target),("bible", uid)); + var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-others", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid)); _popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), PopupType.Medium); - var selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-self", ("target", args.Target),("bible", uid)); + var selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-self", ("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid)); _popupSystem.PopupEntity(selfMessage, args.User, Filter.Entities(args.User), PopupType.Large); SoundSystem.Play(component.HealSoundPath.GetSound(), Filter.Pvs(args.Target.Value), args.User); diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index d2eb1fcb90..89e4dc8f59 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Chemistry.Reaction; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Robust.Shared.Audio; @@ -174,18 +175,18 @@ public sealed class BloodstreamSystem : EntitySystem if (component.BleedAmount > 10) { args.Message.PushNewline(); - args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", uid))); + args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", Identity.Entity(uid, EntityManager)))); } else if (component.BleedAmount > 0) { args.Message.PushNewline(); - args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", uid))); + args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", Identity.Entity(uid, EntityManager)))); } if (GetBloodLevelPercentage(uid, component) < component.BloodlossThreshold) { args.Message.PushNewline(); - args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", uid))); + args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", Identity.Entity(uid, EntityManager)))); } } diff --git a/Content.Server/Body/Systems/BodyReassembleSystem.cs b/Content.Server/Body/Systems/BodyReassembleSystem.cs index 03fab94e40..91a0f715d4 100644 --- a/Content.Server/Body/Systems/BodyReassembleSystem.cs +++ b/Content.Server/Body/Systems/BodyReassembleSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Popups; using Content.Server.Preferences.Managers; using Content.Shared.Actions; using Content.Shared.CharacterAppearance.Systems; +using Content.Shared.IdentityManagement; using Content.Shared.Preferences; using Content.Shared.Species; using Content.Shared.Verbs; @@ -176,7 +177,7 @@ namespace Content.Server.Body.Systems EntityManager.DeleteEntity(entity); } - _popupSystem.PopupEntity(Loc.GetString("reassemble-success", ("user", mob)), mob, Filter.Entities(mob)); + _popupSystem.PopupEntity(Loc.GetString("reassemble-success", ("user", Identity.Entity(mob, EntityManager))), mob, Filter.Entities(mob)); } /// diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index a735d5ef6a..35d96c4b9a 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Examine; using Content.Shared.Tag; using Content.Shared.FixedPoint; using Content.Shared.Audio; +using Content.Shared.IdentityManagement; using Content.Shared.Popups; using Content.Shared.Random.Helpers; using Robust.Shared.Player; @@ -241,7 +242,7 @@ namespace Content.Server.Botany.Systems ("owner", uid), ("usingItem", args.Used)), Filter.Entities(args.User), PopupType.Medium); _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message", - ("user", args.User), + ("user", Identity.Entity(args.User, EntityManager)), ("usingItem", args.Used), ("owner", uid)), uid, Filter.PvsExcept(args.User)); diff --git a/Content.Server/Chemistry/Components/HyposprayComponent.cs b/Content.Server/Chemistry/Components/HyposprayComponent.cs index 5680d89f01..b87233f918 100644 --- a/Content.Server/Chemistry/Components/HyposprayComponent.cs +++ b/Content.Server/Chemistry/Components/HyposprayComponent.cs @@ -6,6 +6,7 @@ using Content.Server.Weapon.Melee; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Content.Shared.Sound; @@ -66,7 +67,7 @@ namespace Content.Server.Chemistry.Components if (!solutionsSys.TryGetInjectableSolution(target.Value, out var targetSolution)) { user.PopupMessage(user, - Loc.GetString("hypospray-cant-inject", ("target", target))); + Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target.Value, _entMan)))); return false; } diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs index a6add96c94..ad5499ea2c 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs @@ -9,6 +9,7 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Hands; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.MobState.Components; @@ -63,7 +64,7 @@ public sealed partial class ChemistrySystem else { _popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message", - ("target", target)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(target, EntityManager))), component.Owner, Filter.Entities(user)); } } else if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Draw) @@ -82,7 +83,7 @@ public sealed partial class ChemistrySystem else { _popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message", - ("target", target)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(target, EntityManager))), component.Owner, Filter.Entities(user)); } } } @@ -194,7 +195,7 @@ public sealed partial class ChemistrySystem if (user != target) { // Create a pop-up for the target - var userName = MetaData(user).EntityName; + var userName = Identity.Name(user, EntityManager); _popup.PopupEntity(Loc.GetString("injector-component-injecting-target", ("user", userName)), user, Filter.Entities(target)); @@ -256,7 +257,7 @@ public sealed partial class ChemistrySystem if (realTransferAmount <= 0) { - _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", targetBloodstream.Owner)), + _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, Filter.Entities(user)); return; } @@ -270,7 +271,7 @@ public sealed partial class ChemistrySystem _popup.PopupEntity(Loc.GetString("injector-component-inject-success-message", ("amount", removedSolution.TotalVolume), - ("target", targetBloodstream.Owner)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, Filter.Entities(user)); Dirty(component); AfterInject(component); @@ -289,7 +290,7 @@ public sealed partial class ChemistrySystem if (realTransferAmount <= 0) { - _popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", targetEntity)), + _popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, Filter.Entities(user)); return; } @@ -310,7 +311,7 @@ public sealed partial class ChemistrySystem _popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message", ("amount", removedSolution.TotalVolume), - ("target", targetEntity)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, Filter.Entities(user)); Dirty(component); AfterInject(component); @@ -349,7 +350,7 @@ public sealed partial class ChemistrySystem if (realTransferAmount <= 0) { - _popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", targetEntity)), + _popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, Filter.Entities(user)); return; } @@ -371,7 +372,7 @@ public sealed partial class ChemistrySystem _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", ("amount", removedSolution.TotalVolume), - ("target", targetEntity)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, Filter.Entities(user)); Dirty(component); AfterDraw(component); @@ -395,7 +396,7 @@ public sealed partial class ChemistrySystem _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", ("amount", drawAmount), - ("target", target)), component.Owner, Filter.Entities(user)); + ("target", Identity.Entity(target, EntityManager))), component.Owner, Filter.Entities(user)); Dirty(component); AfterDraw(component); diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index 71e5d465ac..2f60968750 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Climbing; using Content.Shared.Damage; using Content.Shared.DragDrop; using Content.Shared.GameTicking; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; @@ -138,7 +139,7 @@ public sealed class ClimbSystem : SharedClimbSystem return; if (user == uid) { - var othersMessage = Loc.GetString("comp-climbable-user-climbs-other", ("user", uid), + var othersMessage = Loc.GetString("comp-climbable-user-climbs-other", ("user", Identity.Entity(uid, EntityManager)), ("climbable", climbable)); uid.PopupMessageOtherClients(othersMessage); @@ -147,11 +148,11 @@ public sealed class ClimbSystem : SharedClimbSystem } else { - var othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other", ("user", user), - ("moved-user", uid), ("climbable", climbable)); + var othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other", ("user", Identity.Entity(user, EntityManager)), + ("moved-user", Identity.Entity(uid, EntityManager)), ("climbable", climbable)); user.PopupMessageOtherClients(othersMessage); - var selfMessage = Loc.GetString("comp-climbable-user-climbs-force", ("moved-user", uid), + var selfMessage = Loc.GetString("comp-climbable-user-climbs-force", ("moved-user", Identity.Entity(uid, EntityManager)), ("climbable", climbable)); user.PopupMessage(selfMessage); } @@ -428,4 +429,4 @@ public sealed class StartClimbEvent : EntityEventArgs { Climbable = climbable; } -} \ No newline at end of file +} diff --git a/Content.Server/CombatMode/CombatModeSystem.cs b/Content.Server/CombatMode/CombatModeSystem.cs index 1179f22717..8432744bba 100644 --- a/Content.Server/CombatMode/CombatModeSystem.cs +++ b/Content.Server/CombatMode/CombatModeSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Audio; using Content.Shared.CombatMode; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.IdentityManagement; using Content.Shared.Stunnable; using JetBrains.Annotations; using Robust.Shared.Audio; @@ -81,14 +82,12 @@ namespace Content.Server.CombatMode { SoundSystem.Play(component.DisarmFailSound.GetSound(), Filter.Pvs(args.Performer), args.Performer, AudioHelpers.WithVariation(0.025f)); - var targetName = Name(args.Target); - var msgOther = Loc.GetString( "disarm-action-popup-message-other-clients", - ("performerName", Name(args.Performer)), - ("targetName", targetName)); + ("performerName", Identity.Entity(args.Performer, EntityManager)), + ("targetName", Identity.Entity(args.Target, EntityManager))); - var msgUser = Loc.GetString("disarm-action-popup-message-cursor", ("targetName", targetName )); + var msgUser = Loc.GetString("disarm-action-popup-message-cursor", ("targetName", Identity.Entity(args.Target, EntityManager))); _popupSystem.PopupEntity(msgOther, args.Performer, filterOther); _popupSystem.PopupEntity(msgUser, args.Performer, Filter.Entities(args.Performer)); diff --git a/Content.Server/Disease/DiseaseDiagnosisSystem.cs b/Content.Server/Disease/DiseaseDiagnosisSystem.cs index 7bf08e9b17..879240b733 100644 --- a/Content.Server/Disease/DiseaseDiagnosisSystem.cs +++ b/Content.Server/Disease/DiseaseDiagnosisSystem.cs @@ -17,6 +17,7 @@ using Robust.Shared.Audio; using Robust.Shared.Utility; using Content.Shared.Tools.Components; using Content.Server.Station.Systems; +using Content.Shared.IdentityManagement; namespace Content.Server.Disease { @@ -120,7 +121,7 @@ namespace Content.Server.Disease EntityManager.TryGetComponent(maskUid, out var blocker) && blocker.Enabled) { - _popupSystem.PopupEntity(Loc.GetString("swab-mask-blocked", ("target", args.Target), ("mask", maskUid)), args.User, Filter.Entities(args.User)); + _popupSystem.PopupEntity(Loc.GetString("swab-mask-blocked", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("mask", maskUid)), args.User, Filter.Entities(args.User)); return; } @@ -320,7 +321,7 @@ namespace Content.Server.Disease return; args.Swab.Used = true; - _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", args.Target)), args.Target.Value, Filter.Entities(args.User)); + _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, Filter.Entities(args.User)); if (args.Swab.Disease != null || args.Carrier.Diseases.Count == 0) return; diff --git a/Content.Server/Emag/EmagSystem.cs b/Content.Server/Emag/EmagSystem.cs index 194e5b5014..124bbd515f 100644 --- a/Content.Server/Emag/EmagSystem.cs +++ b/Content.Server/Emag/EmagSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Tag; @@ -65,7 +66,7 @@ namespace Content.Server.Emag RaiseLocalEvent(args.Target.Value, emaggedEvent, false); if (emaggedEvent.Handled) { - _popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", args.Target)), args.User, + _popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User, Filter.Entities(args.User), PopupType.Medium); _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(args.User):player} emagged {ToPrettyString(args.Target.Value):target}"); component.Charges--; diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index db79e8ab75..3f585b4403 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Stunnable; using Content.Server.Weapon.Melee; using Content.Shared.Examine; using Content.Shared.Flash; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; @@ -130,7 +131,7 @@ namespace Content.Server.Flash if (displayPopup && user != null && target != user && EntityManager.EntityExists(user.Value)) { user.Value.PopupMessage(target, Loc.GetString("flash-component-user-blinds-you", - ("user", user.Value))); + ("user", Identity.Entity(user.Value, EntityManager)))); } } diff --git a/Content.Server/Forensics/Systems/ForensicPadSystem.cs b/Content.Server/Forensics/Systems/ForensicPadSystem.cs index b9e1eb7b5b..4fe3c97c94 100644 --- a/Content.Server/Forensics/Systems/ForensicPadSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicPadSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Server.DoAfter; using Content.Server.Popups; +using Content.Shared.IdentityManagement; using Robust.Shared.Player; namespace Content.Server.Forensics @@ -59,7 +60,7 @@ namespace Content.Server.Forensics if (_inventory.TryGetSlotEntity(args.Target.Value, "gloves", out var gloves)) { - _popupSystem.PopupEntity(Loc.GetString("forensic-pad-gloves", ("target", args.Target.Value)), args.Target.Value, Filter.Entities(args.User)); + _popupSystem.PopupEntity(Loc.GetString("forensic-pad-gloves", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, Filter.Entities(args.User)); return; } @@ -67,8 +68,8 @@ namespace Content.Server.Forensics { if (args.User != args.Target) { - _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", args.Target.Value)), args.Target.Value, Filter.Entities(args.User)); - _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", args.User)), args.Target.Value, Filter.Entities(args.Target.Value)); + _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, Filter.Entities(args.User)); + _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager))), args.Target.Value, Filter.Entities(args.Target.Value)); } StartScan(args.User, args.Target.Value, component, fingerprint.Fingerprint); return; diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 3619b65e7b..b528355643 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Inventory; using Content.Shared.Physics.Pull; @@ -92,10 +93,10 @@ namespace Content.Server.Hands.Systems if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false)) return; - var targetName = Name(args.Target); - - var msgOther = Loc.GetString("hands-component-disarm-success-others-message", ("disarmer", Name(args.Source)), ("disarmed", targetName)); - var msgUser = Loc.GetString("hands-component-disarm-success-message", ("disarmed", targetName)); + var targEnt = Identity.Entity(args.Target, EntityManager); + var msgOther = Loc.GetString("hands-component-disarm-success-others-message", + ("disarmer", Identity.Entity(args.Source, EntityManager)), ("disarmed", targEnt)); + var msgUser = Loc.GetString("hands-component-disarm-success-message", ("disarmed", targEnt)); var filter = Filter.Pvs(args.Source).RemoveWhereAttachedEntity(e => e == args.Source); _popupSystem.PopupEntity(msgOther, args.Source, filter); diff --git a/Content.Server/HealthExaminable/HealthExaminableSystem.cs b/Content.Server/HealthExaminable/HealthExaminableSystem.cs index 55b0dae1f4..b571821529 100644 --- a/Content.Server/HealthExaminable/HealthExaminableSystem.cs +++ b/Content.Server/HealthExaminable/HealthExaminableSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Content.Shared.Examine; using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; using Content.Shared.Verbs; using Robust.Shared.Utility; @@ -60,7 +61,7 @@ public sealed class HealthExaminableSystem : EntitySystem foreach (var threshold in component.Thresholds) { var str = $"health-examinable-{component.LocPrefix}-{type}-{threshold}"; - var tempLocStr = Loc.GetString($"health-examinable-{component.LocPrefix}-{type}-{threshold}", ("target", uid)); + var tempLocStr = Loc.GetString($"health-examinable-{component.LocPrefix}-{type}-{threshold}", ("target", Identity.Entity(uid, EntityManager))); // i.e., this string doesn't exist, because theres nothing for that threshold if (tempLocStr == str) diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs new file mode 100644 index 0000000000..bc50d62e02 --- /dev/null +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -0,0 +1,163 @@ +using Content.Server.Access.Systems; +using Content.Server.Administration.Logs; +using Content.Shared.CharacterAppearance.Components; +using Content.Shared.Database; +using Content.Shared.Hands; +using Content.Shared.IdentityManagement; +using Content.Shared.IdentityManagement.Components; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.Preferences; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects.Components.Localization; + +namespace Content.Server.IdentityManagement; + +/// +/// Responsible for updating the identity of an entity on init or clothing equip/unequip. +/// +public class IdentitySystem : SharedIdentitySystem +{ + [Dependency] private readonly IdCardSystem _idCard = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + + private Queue _queuedIdentityUpdates = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + while (_queuedIdentityUpdates.TryDequeue(out var ent)) + { + if (!TryComp(ent, out var identity)) + continue; + + UpdateIdentityInfo(ent, identity); + } + } + + // This is where the magic happens + protected override void OnComponentInit(EntityUid uid, IdentityComponent component, ComponentInit args) + { + base.OnComponentInit(uid, component, args); + + var ident = Spawn(null, Transform(uid).Coordinates); + + QueueIdentityUpdate(uid); + component.IdentityEntitySlot.Insert(ident); + } + + /// + /// Queues an identity update to the start of the next tick. + /// + public void QueueIdentityUpdate(EntityUid uid) + { + _queuedIdentityUpdates.Enqueue(uid); + } + + #region Private API + + /// + /// Updates the metadata name for the id(entity) from the current state of the character. + /// + private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity) + { + if (identity.IdentityEntitySlot.ContainedEntity is not { } ident) + return; + + var representation = GetIdentityRepresentation(uid); + var name = GetIdentityName(uid, representation); + + // Clone the old entity's grammar to the identity entity, for loc purposes. + if (TryComp(uid, out var grammar)) + { + var identityGrammar = EnsureComp(ident); + identityGrammar.Attributes.Clear(); + + foreach (var (k, v) in grammar.Attributes) + { + identityGrammar.Attributes.Add(k, v); + } + + // If presumed name is null and we're using that, we set proper noun to be false ("the old woman") + if (name != representation.TrueName && representation.PresumedName == null) + identityGrammar.ProperNoun = false; + } + + if (name == Name(ident)) + return; + + MetaData(ident).EntityName = name; + + _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}"); + RaiseLocalEvent(new IdentityChangedEvent(uid, ident)); + } + + private string GetIdentityName(EntityUid target, IdentityRepresentation representation) + { + var ev = new SeeIdentityAttemptEvent(); + + RaiseLocalEvent(target, ev); + return representation.ToStringKnown(!ev.Cancelled); + } + + /// + /// Gets an 'identity representation' of an entity, with their true name being the entity name + /// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one. + /// + private IdentityRepresentation GetIdentityRepresentation(EntityUid target, + InventoryComponent? inventory=null, + HumanoidAppearanceComponent? appearance=null) + { + int age = HumanoidCharacterProfile.MinimumAge; + Gender gender = Gender.Neuter; + + // Always use their actual age and gender, since that can't really be changed by an ID. + if (Resolve(target, ref appearance, false)) + { + gender = appearance.Gender; + age = appearance.Age; + } + + var trueName = Name(target); + if (!Resolve(target, ref inventory, false)) + return new(trueName, age, gender, string.Empty); + + string? presumedJob = null; + string? presumedName = null; + + // Get their name and job from their ID for their presumed name. + if (_idCard.TryFindIdCard(target, out var id)) + { + presumedName = id.FullName; + presumedJob = id.JobTitle?.ToLowerInvariant(); + } + + // If it didn't find a job, that's fine. + return new(trueName, age, gender, presumedName, presumedJob); + } + + #endregion +} + +public sealed class IdentityChangedEvent : EntityEventArgs +{ + public EntityUid CharacterEntity; + public EntityUid IdentityEntity; + + public IdentityChangedEvent(EntityUid characterEntity, EntityUid identityEntity) + { + CharacterEntity = characterEntity; + IdentityEntity = identityEntity; + } +} diff --git a/Content.Server/Interaction/InteractionPopupSystem.cs b/Content.Server/Interaction/InteractionPopupSystem.cs index 21683444a2..8aca8656bc 100644 --- a/Content.Server/Interaction/InteractionPopupSystem.cs +++ b/Content.Server/Interaction/InteractionPopupSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Popups; using Content.Server.Interaction.Components; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.MobState.Components; using Robust.Shared.Audio; @@ -42,7 +43,7 @@ public sealed class InteractionPopupSystem : EntitySystem if (_random.Prob(component.SuccessChance)) { if (component.InteractSuccessString != null) - msg = Loc.GetString(component.InteractSuccessString, ("target", uid)); // Success message (localized). + msg = Loc.GetString(component.InteractSuccessString, ("target", Identity.Entity(uid, EntityManager))); // Success message (localized). if (component.InteractSuccessSound != null) sfx = component.InteractSuccessSound.GetSound(); @@ -50,7 +51,7 @@ public sealed class InteractionPopupSystem : EntitySystem else { if (component.InteractFailureString != null) - msg = Loc.GetString(component.InteractFailureString, ("target", uid)); // Failure message (localized). + msg = Loc.GetString(component.InteractFailureString, ("target", Identity.Entity(uid, EntityManager))); // Failure message (localized). if (component.InteractFailureSound != null) sfx = component.InteractFailureSound.GetSound(); @@ -58,7 +59,8 @@ public sealed class InteractionPopupSystem : EntitySystem if (component.MessagePerceivedByOthers != null) { - string msgOthers = Loc.GetString(component.MessagePerceivedByOthers,("user", args.User), ("target", uid)); + string msgOthers = Loc.GetString(component.MessagePerceivedByOthers, + ("user", Identity.Entity(args.User, EntityManager)), ("target", Identity.Entity(uid, EntityManager))); _popupSystem.PopupEntity(msg, uid, Filter.Entities(args.User)); _popupSystem.PopupEntity(msgOthers, uid, Filter.Pvs(uid, 2F, EntityManager).RemoveWhereAttachedEntity(puid => puid == args.User)); } diff --git a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs index cf708044b4..e8c5cb0921 100644 --- a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Popups; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.DragDrop; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.MobState.Components; using Content.Shared.Nutrition.Components; @@ -123,7 +124,7 @@ namespace Content.Server.Kitchen.EntitySystems UpdateAppearance(uid, null, component); - _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-kill", ("user", userUid), ("victim", victimUid)), uid, + _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-kill", ("user", Identity.Entity(userUid, EntityManager)), ("victim", victimUid)), uid, Filter.Pvs(userUid), PopupType.LargeCaution); // THE WHAT? diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index ed75f10b8d..0f02306edd 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Medical.Components; using Content.Server.Disease; using Content.Server.Popups; using Content.Shared.Damage; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.MobState.Components; using Robust.Server.GameObjects; @@ -83,7 +84,7 @@ namespace Content.Server.Medical args.User, Filter.Entities(args.User)); return; } - _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", args.Target), ("disease", args.Component.Disease)), + _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("disease", args.Component.Disease)), args.User, Filter.Entities(args.User)); } diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 1a6dea6443..bfe8038d17 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.MobState.Components; @@ -249,8 +250,7 @@ namespace Content.Server.Nutrition.EntitySystems if (forceDrink) { - EntityManager.TryGetComponent(user, out MetaDataComponent? meta); - var userName = meta?.EntityName ?? string.Empty; + var userName = Identity.Name(user, EntityManager); _popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, Filter.Entities(target)); @@ -334,11 +334,8 @@ namespace Content.Server.Nutrition.EntitySystems if (forceDrink) { - EntityManager.TryGetComponent(uid, out MetaDataComponent? targetMeta); - var targetName = targetMeta?.EntityName ?? string.Empty; - - EntityManager.TryGetComponent(args.User, out MetaDataComponent? userMeta); - var userName = userMeta?.EntityName ?? string.Empty; + var targetName = Identity.Name(uid, EntityManager); + var userName = Identity.Name(args.User, EntityManager); _popupSystem.PopupEntity( Loc.GetString("drink-component-force-feed-success", ("user", userName)), uid, Filter.Entities(uid)); diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 30d49a8154..f0c800c019 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -19,6 +19,7 @@ using Robust.Shared.Player; using Robust.Shared.Utility; using Content.Shared.Inventory; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; namespace Content.Server.Nutrition.EntitySystems @@ -114,9 +115,7 @@ namespace Content.Server.Nutrition.EntitySystems if (forceFeed) { - EntityManager.TryGetComponent(user, out MetaDataComponent? meta); - var userName = meta?.EntityName ?? string.Empty; - + var userName = Identity.Name(user, EntityManager); _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)), user, Filter.Entities(target)); @@ -180,12 +179,8 @@ namespace Content.Server.Nutrition.EntitySystems if (forceFeed) { - EntityManager.TryGetComponent(uid, out MetaDataComponent? targetMeta); - var targetName = targetMeta?.EntityName ?? string.Empty; - - EntityManager.TryGetComponent(args.User, out MetaDataComponent? userMeta); - var userName = userMeta?.EntityName ?? string.Empty; - + var targetName = Identity.Name(uid, EntityManager); + var userName = Identity.Name(args.User, EntityManager); _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName)), uid, Filter.Entities(uid)); diff --git a/Content.Server/Paper/PaperSystem.cs b/Content.Server/Paper/PaperSystem.cs index cfcd2ff392..80ae4650b3 100644 --- a/Content.Server/Paper/PaperSystem.cs +++ b/Content.Server/Paper/PaperSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Interaction; using Content.Shared.Tag; using Robust.Server.GameObjects; using Content.Server.Popups; +using Content.Shared.IdentityManagement; using Robust.Shared.Player; using static Content.Shared.Paper.SharedPaperComponent; @@ -89,9 +90,9 @@ namespace Content.Server.Paper if (TryComp(args.Used, out var stampComp) && TryStamp(uid, stampComp.StampedName, stampComp.StampState, paperComp)) { // successfully stamped, play popup - var stampPaperOtherMessage = Loc.GetString("paper-component-action-stamp-paper-other", ("user", args.User),("target", args.Target),("stamp", args.Used)); + var stampPaperOtherMessage = Loc.GetString("paper-component-action-stamp-paper-other", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target, EntityManager)),("stamp", args.Used)); _popupSystem.PopupEntity(stampPaperOtherMessage, args.User, Filter.Pvs(args.User, entityManager: EntityManager).RemoveWhereAttachedEntity(puid => puid == args.User)); - var stampPaperSelfMessage = Loc.GetString("paper-component-action-stamp-paper-self", ("target", args.Target),("stamp", args.Used)); + var stampPaperSelfMessage = Loc.GetString("paper-component-action-stamp-paper-self", ("target", Identity.Entity(args.Target, EntityManager)),("stamp", args.Used)); _popupSystem.PopupEntity(stampPaperSelfMessage, args.User, Filter.Entities(args.User)); } } diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index bf8d522941..5b2eb1cda4 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Ghost.Components; using Content.Server.Players; using Content.Server.Pointing.Components; using Content.Server.Visible; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; @@ -155,11 +156,11 @@ namespace Content.Server.Pointing.EntitySystems string selfMessage; string viewerMessage; string? viewerPointedAtMessage = null; - var playerName = Name(player); + var playerName = Identity.Entity(player, EntityManager); if (Exists(pointed)) { - var pointedName = Name(pointed); + var pointedName = Identity.Entity(pointed, EntityManager); selfMessage = player == pointed ? Loc.GetString("pointing-system-point-at-self") diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index c116630ba1..2de0866f3d 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -3,6 +3,7 @@ using Content.Server.CharacterAppearance.Systems; using Content.Server.DetailExaminable; using Content.Server.Hands.Components; using Content.Server.Hands.Systems; +using Content.Server.IdentityManagement; using Content.Server.PDA; using Content.Server.Roles; using Content.Server.Station.Components; @@ -38,6 +39,7 @@ public sealed class StationSpawningSystem : EntitySystem [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly PDASystem _pdaSystem = default!; [Dependency] private readonly AccessSystem _accessSystem = default!; + [Dependency] private readonly IdentitySystem _identity = default!; /// public override void Initialize() @@ -99,6 +101,7 @@ public sealed class StationSpawningSystem : EntitySystem var jobEntity = EntityManager.SpawnEntity(job.JobEntity, coordinates); MakeSentientCommand.MakeSentient(jobEntity, EntityManager); DoJobSpecials(job, jobEntity); + _identity.QueueIdentityUpdate(jobEntity); return jobEntity; } @@ -125,7 +128,7 @@ public sealed class StationSpawningSystem : EntitySystem } DoJobSpecials(job, entity); - + _identity.QueueIdentityUpdate(entity); return entity; } diff --git a/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs b/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs index 5629ebfe68..6b69810131 100644 --- a/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs @@ -36,6 +36,9 @@ public sealed class StorageFillVisualizerSystem : EntitySystem if (!Resolve(uid, ref storage, ref appearance, ref component, false)) return; + if (component.MaxFillLevels < 1) + return; + var level = ContentHelpers.RoundToEqualLevels(storage.StorageUsed, storage.StorageCapacityMax, component.MaxFillLevels); appearance.SetData(StorageFillVisuals.FillLevel, level); } diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index e90de452a4..8b6dfca3cd 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Inventory; using Content.Server.UserInterface; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; @@ -306,7 +307,7 @@ namespace Content.Server.Strip { if (userHands.ActiveHandEntity != null) { - _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", user), ("item", userHands.ActiveHandEntity)), component.Owner, + _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", Identity.Entity(user, EntityManager)), ("item", userHands.ActiveHandEntity)), component.Owner, Filter.Entities(component.Owner), PopupType.Large); } } @@ -369,7 +370,7 @@ namespace Content.Server.Strip { if (handSlot.HeldEntity != null) { - _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", user), ("item", handSlot.HeldEntity)), component.Owner, + _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", Identity.Entity(user, EntityManager)), ("item", handSlot.HeldEntity)), component.Owner, Filter.Entities(component.Owner), PopupType.Large); } } @@ -437,7 +438,7 @@ namespace Content.Server.Strip else { if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var slotItem)) - _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", user), ("item", slotItem)), component.Owner, + _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", slotItem)), component.Owner, Filter.Entities(component.Owner), PopupType.Large); } } @@ -501,7 +502,7 @@ namespace Content.Server.Strip { if (handSlot.HeldEntity != null) { - _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", user), ("item", handSlot.HeldEntity)), component.Owner, Filter.Entities(component.Owner)); + _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", handSlot.HeldEntity)), component.Owner, Filter.Entities(component.Owner)); } } diff --git a/Content.Server/Stunnable/Systems/StunSystem.cs b/Content.Server/Stunnable/Systems/StunSystem.cs index 8701cdac8a..b341eb2021 100644 --- a/Content.Server/Stunnable/Systems/StunSystem.cs +++ b/Content.Server/Stunnable/Systems/StunSystem.cs @@ -3,6 +3,7 @@ using Content.Server.CombatMode; using Content.Server.Popups; using Content.Shared.Audio; using Content.Shared.Database; +using Content.Shared.IdentityManagement; using Content.Shared.Popups; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; @@ -38,9 +39,11 @@ namespace Content.Server.Stunnable var knock = EntityManager.GetComponent(uid); SoundSystem.Play(knock.StunAttemptSound.GetSound(), Filter.Pvs(source), source, AudioHelpers.WithVariation(0.025f)); + var targetEnt = Identity.Entity(target, EntityManager); + var sourceEnt = Identity.Entity(source, EntityManager); // TODO: Use PopupSystem - source.PopupMessageOtherClients(Loc.GetString("stunned-component-disarm-success-others", ("source", Name(source)), ("target", Name(target)))); - source.PopupMessageCursor(Loc.GetString("stunned-component-disarm-success", ("target", Name(target)))); + source.PopupMessageOtherClients(Loc.GetString("stunned-component-disarm-success-others", ("source", sourceEnt), ("target", targetEnt))); + source.PopupMessageCursor(Loc.GetString("stunned-component-disarm-success", ("target", targetEnt))); _adminLogger.Add(LogType.DisarmedKnockdown, LogImpact.Medium, $"{ToPrettyString(args.Source):user} knocked down {ToPrettyString(args.Target):target}"); diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index dd0e6a27fd..6ca7c7eaa2 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -73,4 +73,5 @@ public enum LogType // haha so funny Emag = 69, Gib = 70, + Identity = 71 } diff --git a/Content.Shared/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs index 27fbeed81d..43395a7a5c 100644 --- a/Content.Shared/Access/Components/IdCardComponent.cs +++ b/Content.Shared/Access/Components/IdCardComponent.cs @@ -1,11 +1,11 @@ using Content.Shared.Access.Systems; using Content.Shared.PDA; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Access.Components { - // TODO BUI NETWORKING if ever clients can open their own BUI's (id card console, pda), then this data should be - // networked. - [RegisterComponent] + [RegisterComponent, NetworkedComponent] [Access(typeof(SharedIdCardSystem), typeof(SharedPDASystem), typeof(SharedAgentIdCardSystem))] public sealed class IdCardComponent : Component { @@ -20,4 +20,17 @@ namespace Content.Shared.Access.Components [DataField("jobTitle")] public string? JobTitle; } + + [Serializable, NetSerializable] + public sealed class IdCardComponentState : ComponentState + { + public string? FullName; + public string? JobTitle; + + public IdCardComponentState(string? fullName, string? jobTitle) + { + FullName = fullName; + JobTitle = jobTitle; + } + } } diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs index b5f31b3dda..52b8cc4358 100644 --- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs +++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs @@ -1,6 +1,80 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Access.Components; +using Content.Shared.Hands.Components; +using Content.Shared.Inventory; +using Content.Shared.PDA; +using Robust.Shared.GameStates; + namespace Content.Shared.Access.Systems; public abstract class SharedIdCardSystem : EntitySystem { - // this class just exists to make friends. Will you be its friend? + [Dependency] private readonly InventorySystem _inventorySystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentGetState); + SubscribeLocalEvent(OnComponentHandleState); + } + + private void OnComponentGetState(EntityUid uid, IdCardComponent component, ref ComponentGetState args) + { + args.State = new IdCardComponentState(component.FullName, component.JobTitle); + } + + private void OnComponentHandleState(EntityUid uid, IdCardComponent component, ref ComponentHandleState args) + { + if (args.Current is IdCardComponentState state) + { + component.FullName = state.FullName; + component.JobTitle = state.JobTitle; + } + } + + /// + /// Attempt to find an ID card on an entity. This will look in the entity itself, in the entity's hands, and + /// in the entity's inventory. + /// + public bool TryFindIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard) + { + // check held item? + if (EntityManager.TryGetComponent(uid, out SharedHandsComponent? hands) && + hands.ActiveHandEntity is EntityUid heldItem && + TryGetIdCard(heldItem, out idCard)) + { + return true; + } + + // check entity itself + if (TryGetIdCard(uid, out idCard)) + return true; + + // check inventory slot? + if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid) && TryGetIdCard(idUid.Value, out idCard)) + { + return true; + } + + return false; + } + + /// + /// Attempt to get an id card component from an entity, either by getting it directly from the entity, or by + /// getting the contained id from a . + /// + public bool TryGetIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard) + { + if (EntityManager.TryGetComponent(uid, out idCard)) + return true; + + if (EntityManager.TryGetComponent(uid, out PDAComponent? pda) && pda.ContainedID != null) + { + idCard = pda.ContainedID; + return true; + } + + return false; + } } diff --git a/Content.Shared/Administration/PlayerInfo.cs b/Content.Shared/Administration/PlayerInfo.cs index d1634e313a..f09cd5f77d 100644 --- a/Content.Shared/Administration/PlayerInfo.cs +++ b/Content.Shared/Administration/PlayerInfo.cs @@ -4,5 +4,5 @@ using Robust.Shared.Serialization; namespace Content.Shared.Administration { [Serializable, NetSerializable] - public record PlayerInfo(string Username, string CharacterName, string StartingJob, bool Antag, EntityUid EntityUid, NetUserId SessionId, bool Connected); + public record PlayerInfo(string Username, string CharacterName, string IdentityName, string StartingJob, bool Antag, EntityUid EntityUid, NetUserId SessionId, bool Connected); } diff --git a/Content.Shared/Blocking/BlockingSystem.cs b/Content.Shared/Blocking/BlockingSystem.cs index 813fbdf306..d70f12430e 100644 --- a/Content.Shared/Blocking/BlockingSystem.cs +++ b/Content.Shared/Blocking/BlockingSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Actions.ActionTypes; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Toggleable; @@ -105,8 +106,9 @@ public sealed class BlockingSystem : EntitySystem var shieldName = Name(item); + var blockerName = Identity.Entity(user, EntityManager); var msgUser = Loc.GetString("action-popup-blocking-user", ("shield", shieldName)); - var msgOther = Loc.GetString("action-popup-blocking-other", ("blockerName", Name(user)), ("shield", shieldName)); + var msgOther = Loc.GetString("action-popup-blocking-other", ("blockerName", blockerName), ("shield", shieldName)); if (component.BlockingToggleAction != null) { @@ -154,8 +156,9 @@ public sealed class BlockingSystem : EntitySystem var shieldName = Name(item); + var blockerName = Identity.Entity(user, EntityManager); var msgUser = Loc.GetString("action-popup-blocking-disabling-user", ("shield", shieldName)); - var msgOther = Loc.GetString("action-popup-blocking-disabling-other", ("blockerName", Name(user)), ("shield", shieldName)); + var msgOther = Loc.GetString("action-popup-blocking-disabling-other", ("blockerName", blockerName), ("shield", shieldName)); //If the component blocking toggle isn't null, grab the users SharedBlockingUserComponent and PhysicsComponent //then toggle the action to false, unanchor the user, remove the hard fixture diff --git a/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs b/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs index 5cca464186..fe3d51ede0 100644 --- a/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs +++ b/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.CharacterAppearance.Systems; +using Content.Shared.Preferences; using Content.Shared.Species; using Robust.Shared.Enums; using Robust.Shared.GameStates; @@ -23,6 +24,9 @@ namespace Content.Shared.CharacterAppearance.Components [ViewVariables] public string Species { get; set; } = SpeciesManager.DefaultSpecies; + [ViewVariables(VVAccess.ReadWrite)] + public int Age { get; set; } = HumanoidCharacterProfile.MinimumAge; + [DataField("categoriesHair")] [ViewVariables] public SpriteAccessoryCategories CategoriesHair { get; set; } = SpriteAccessoryCategories.HumanHair; @@ -55,16 +59,19 @@ namespace Content.Shared.CharacterAppearance.Components public Sex Sex { get; } public Gender Gender { get; } public string Species { get; } + public int Age { get; } public HumanoidAppearanceComponentState(HumanoidCharacterAppearance appearance, Sex sex, Gender gender, - string species) + string species, + int age) { Appearance = appearance; Sex = sex; Gender = gender; Species = species; + Age = age; } } } diff --git a/Content.Shared/CharacterAppearance/Systems/SharedHumanoidAppearanceSystem.cs b/Content.Shared/CharacterAppearance/Systems/SharedHumanoidAppearanceSystem.cs index 3d0f6a03a9..67ce557eba 100644 --- a/Content.Shared/CharacterAppearance/Systems/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/CharacterAppearance/Systems/SharedHumanoidAppearanceSystem.cs @@ -19,7 +19,7 @@ namespace Content.Shared.CharacterAppearance.Systems public void UpdateFromProfile(EntityUid uid, ICharacterProfile profile, HumanoidAppearanceComponent? appearance=null) { var humanoid = (HumanoidCharacterProfile) profile; - UpdateAppearance(uid, humanoid.Appearance, humanoid.Sex, humanoid.Gender, humanoid.Species, appearance); + UpdateAppearance(uid, humanoid.Appearance, humanoid.Sex, humanoid.Gender, humanoid.Species, humanoid.Age, appearance); } // The magic mirror otherwise wouldn't work. (it directly modifies the component server-side) @@ -29,7 +29,7 @@ namespace Content.Shared.CharacterAppearance.Systems component.Dirty(); } - private void UpdateAppearance(EntityUid uid, HumanoidCharacterAppearance appearance, Sex sex, Gender gender, string species, HumanoidAppearanceComponent? component = null) + private void UpdateAppearance(EntityUid uid, HumanoidCharacterAppearance appearance, Sex sex, Gender gender, string species, int age, HumanoidAppearanceComponent? component = null) { if (!Resolve(uid, ref component)) return; @@ -37,6 +37,7 @@ namespace Content.Shared.CharacterAppearance.Systems component.Sex = sex; component.Gender = gender; component.Species = species; + component.Age = age; if (EntityManager.TryGetComponent(uid, out GrammarComponent? g)) g.Gender = gender; @@ -59,7 +60,7 @@ namespace Content.Shared.CharacterAppearance.Systems private void OnAppearanceGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args) { - args.State = new HumanoidAppearanceComponentState(component.Appearance, component.Sex, component.Gender, component.Species); + args.State = new HumanoidAppearanceComponentState(component.Appearance, component.Sex, component.Gender, component.Species, component.Age); } private void OnAppearanceHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentHandleState args) @@ -67,7 +68,7 @@ namespace Content.Shared.CharacterAppearance.Systems if (args.Current is not HumanoidAppearanceComponentState state) return; - UpdateAppearance(uid, state.Appearance, state.Sex, state.Gender, state.Species); + UpdateAppearance(uid, state.Appearance, state.Sex, state.Gender, state.Species, state.Age); } // Scaffolding until Body is moved to ECS. @@ -106,7 +107,6 @@ namespace Content.Shared.CharacterAppearance.Systems Uid = uid; Args = args; } - } [Serializable, NetSerializable] diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index 81307ccf26..4cf780f09f 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -1,5 +1,6 @@ using Content.Shared.Examine; using Content.Shared.Hands.Components; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Map; @@ -166,7 +167,7 @@ public abstract partial class SharedHandsSystem : EntitySystem if (HasComp(inhand)) continue; - args.PushText(Loc.GetString("comp-hands-examine", ("user", handsComp.Owner), ("item", inhand))); + args.PushText(Loc.GetString("comp-hands-examine", ("user", Identity.Entity(handsComp.Owner, EntityManager)), ("item", inhand))); } } } diff --git a/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs b/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs new file mode 100644 index 0000000000..a898ab7dce --- /dev/null +++ b/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Inventory; +using Robust.Shared.GameStates; + +namespace Content.Shared.IdentityManagement.Components; + +[RegisterComponent, NetworkedComponent] +public sealed class IdentityBlockerComponent : Component +{ +} + +/// +/// Raised on an entity and relayed to inventory to determine if its identity should be knowable. +/// +public sealed class SeeIdentityAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent +{ + // i.e. masks or helmets. + public SlotFlags TargetSlots => SlotFlags.MASK | SlotFlags.HEAD; +} diff --git a/Content.Shared/IdentityManagement/Components/IdentityComponent.cs b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs new file mode 100644 index 0000000000..8c7a357cb5 --- /dev/null +++ b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs @@ -0,0 +1,76 @@ +using Robust.Shared.Containers; +using Robust.Shared.Enums; + +namespace Content.Shared.IdentityManagement.Components; + +/// +/// Stores the identity entity (whose name is the users 'identity', etc) +/// for a given entity, and marks that it can have an identity at all. +/// +/// +/// This is a and not just a datum entity because we do sort of care that it gets deleted and sent with the user. +/// +[RegisterComponent] +public sealed class IdentityComponent : Component +{ + [ViewVariables] + public ContainerSlot IdentityEntitySlot = default!; +} + +/// +/// A data structure representing the 'identity' of an entity as presented to +/// other players. +/// +public sealed class IdentityRepresentation +{ + public string TrueName; + public int TrueAge; + public Gender TrueGender; + + public string? PresumedName; + public string? PresumedJob; + + public IdentityRepresentation(string trueName, int trueAge, Gender trueGender, string? presumedName=null, string? presumedJob=null) + { + TrueName = trueName; + TrueAge = trueAge; + TrueGender = trueGender; + + PresumedJob = presumedJob; + PresumedName = presumedName; + } + + public string ToStringKnown(bool trueName) + { + return trueName + ? TrueName + : PresumedName ?? ToStringUnknown(); + } + + /// + /// Returns a string representing their identity where it is 'unknown' by a viewer. + /// Used for cases where the viewer is not necessarily able to accurately assess + /// the identity of the person being viewed. + /// + public string ToStringUnknown() + { + var ageString = TrueAge switch + { + <= 30 => Loc.GetString("identity-age-young"), + > 30 and <= 60 => Loc.GetString("identity-age-middle-aged"), + > 60 => Loc.GetString("identity-age-old") + }; + + var genderString = TrueGender switch + { + Gender.Female => Loc.GetString("identity-gender-feminine"), + Gender.Male => Loc.GetString("identity-gender-masculine"), + Gender.Epicene or Gender.Neuter or _ => Loc.GetString("identity-gender-person") + }; + + // i.e. 'young assistant man' or 'old cargo technician person' or 'middle-aged captain' + return PresumedJob is null + ? $"{ageString} {genderString}" + : $"{ageString} {PresumedJob} {genderString}"; + } +} diff --git a/Content.Shared/IdentityManagement/Identity.cs b/Content.Shared/IdentityManagement/Identity.cs new file mode 100644 index 0000000000..f3f3f1f121 --- /dev/null +++ b/Content.Shared/IdentityManagement/Identity.cs @@ -0,0 +1,60 @@ +using Content.Shared.Ghost; +using Content.Shared.IdentityManagement.Components; + +namespace Content.Shared.IdentityManagement; + +/// +/// Static content API for getting the identity entities/names for a given entity. +/// This should almost always be used in favor of metadata name, if the entity in question is a human player that +/// can have identity. +/// +public static class Identity +{ + /// + /// Returns the name that should be used for this entity for identity purposes. + /// + public static string Name(EntityUid uid, IEntityManager ent, EntityUid? viewer=null) + { + var uidName = ent.GetComponent(uid).EntityName; + + if (!ent.TryGetComponent(uid, out var identity)) + return uidName; + + var ident = identity.IdentityEntitySlot.ContainedEntity; + if (ident is null) + return uidName; + + var identName = ent.GetComponent(ident.Value).EntityName; + if (viewer == null || !CanSeeThroughIdentity(uid, viewer.Value, ent)) + { + return identName; + } + if (uidName == identName) + { + return uidName; + } + + return uidName + $" ({identName})"; + } + + /// + /// Returns the entity that should be used for identity purposes, for example to pass into localization. + /// This is an extension method because of its simplicity, and if it was any harder to call it might not + /// be used enough for loc. + /// + public static EntityUid Entity(EntityUid uid, IEntityManager ent) + { + if (!ent.TryGetComponent(uid, out var identity)) + return uid; + + return identity.IdentityEntitySlot.ContainedEntity ?? uid; + } + + public static bool CanSeeThroughIdentity(EntityUid uid, EntityUid viewer, IEntityManager ent) + { + // Would check for uid == viewer here but I think it's better for you to see yourself + // how everyone else will see you, otherwise people will probably get confused and think they aren't disguised + return ent.HasComponent(viewer); + } + +} diff --git a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs new file mode 100644 index 0000000000..99c85c5ba0 --- /dev/null +++ b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.IdentityManagement.Components; +using Robust.Shared.Containers; + +namespace Content.Shared.IdentityManagement; + +public abstract class SharedIdentitySystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + private static string SlotName = "identity"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnSeeIdentity); + } + + private void OnSeeIdentity(EntityUid uid, IdentityBlockerComponent component, SeeIdentityAttemptEvent args) + { + args.Cancel(); + } + + protected virtual void OnComponentInit(EntityUid uid, IdentityComponent component, ComponentInit args) + { + component.IdentityEntitySlot = _container.EnsureContainer(uid, SlotName); + } +} diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 1179ded8c6..bd38228e9c 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Content.Shared.Electrocution; using Content.Shared.Explosion; +using Content.Shared.IdentityManagement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Slippery; using Content.Shared.Strip.Components; @@ -17,6 +18,7 @@ public partial class InventorySystem SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + SubscribeLocalEvent(RelayInventoryEvent); } protected void RelayInventoryEvent(EntityUid uid, InventoryComponent component, T args) where T : EntityEventArgs, IInventoryRelayEvent diff --git a/Resources/Locale/en-US/actions/actions/blocking.ftl b/Resources/Locale/en-US/actions/actions/blocking.ftl index 48bbe7d83b..6cfe9314a9 100644 --- a/Resources/Locale/en-US/actions/actions/blocking.ftl +++ b/Resources/Locale/en-US/actions/actions/blocking.ftl @@ -4,7 +4,7 @@ action-description-blocking = Raise or lower your shield. action-popup-blocking-user = You raise your {$shield}! action-popup-blocking-disabling-user = You lower your {$shield}! -action-popup-blocking-other = {$blockerName} raises their {$shield}! -action-popup-blocking-disabling-other = {$blockerName} lowers their {$shield}! +action-popup-blocking-other = {CAPITALIZE(THE($blockerName))} raises {POSS-ADJ($blockerName)} {$shield}! +action-popup-blocking-disabling-other = {CAPITALIZE(THE($blockerName))} lowers {POSS-ADJ($blockerName)} {$shield}! action-popup-blocking-user-cant-block = The gravity here prevents you from blocking. diff --git a/Resources/Locale/en-US/actions/actions/disarm-action.ftl b/Resources/Locale/en-US/actions/actions/disarm-action.ftl index 4f97b4f766..7dded67bc0 100644 --- a/Resources/Locale/en-US/actions/actions/disarm-action.ftl +++ b/Resources/Locale/en-US/actions/actions/disarm-action.ftl @@ -1,7 +1,7 @@ disarm-action-free-hand = You need to use a free hand to disarm! -disarm-action-popup-message-other-clients = {$performerName} fails to disarm {$targetName}! -disarm-action-popup-message-cursor = You fail to disarm {$targetName}! +disarm-action-popup-message-other-clients = {CAPITALIZE(THE($performerName))} fails to disarm {THE($targetName)}! +disarm-action-popup-message-cursor = You fail to disarm {THE($targetName)}! action-name-disarm = [color=red]Disarm[/color] action-description-disarm = Attempt to [color=red]disarm[/color] someone. diff --git a/Resources/Locale/en-US/chemistry/components/injector-component.ftl b/Resources/Locale/en-US/chemistry/components/injector-component.ftl index 4249c14e87..e137130290 100644 --- a/Resources/Locale/en-US/chemistry/components/injector-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/injector-component.ftl @@ -21,4 +21,4 @@ injector-component-target-is-empty-message = {$target} is empty! ## mob-inject doafter messages injector-component-injecting-user = You start inserting the needle. -injector-component-injecting-target = {$user} is trying to stick a needle into you! +injector-component-injecting-target = {CAPITALIZE(THE($user))} is trying to stick a needle into you! diff --git a/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl b/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl index 6031b8ba91..a310fd9b69 100644 --- a/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl +++ b/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl @@ -2,9 +2,9 @@ pointing-system-try-point-cannot-reach = You can't reach there! pointing-system-point-at-self = You point at yourself. -pointing-system-point-at-other = You point at {$other}. -pointing-system-point-at-self-others = {$otherName} points at {$other}. -pointing-system-point-at-other-others = {$otherName} points at {$other}. +pointing-system-point-at-other = You point at {THE($other)}. +pointing-system-point-at-self-others = {CAPITALIZE(THE($otherName))} points at {THE($other)}. +pointing-system-point-at-other-others = {CAPITALIZE(THE($otherName))} points at {THE($other)}. pointing-system-point-at-you-other = {$otherName} points at you. pointing-system-point-at-tile = You point at the {$tileName}. -pointing-system-other-point-at-tile = {$otherName} points at the {$tileName}. +pointing-system-other-point-at-tile = {CAPITALIZE(THE($otherName))} points at the {$tileName}. diff --git a/Resources/Locale/en-US/hands/components/hands-component.ftl b/Resources/Locale/en-US/hands/components/hands-component.ftl index 6782bb403f..6f85ae7e02 100644 --- a/Resources/Locale/en-US/hands/components/hands-component.ftl +++ b/Resources/Locale/en-US/hands/components/hands-component.ftl @@ -1,4 +1,4 @@ -hands-component-disarm-success-others-message = {$disarmer} disarms {$disarmed}! -hands-component-disarm-success-message = You disarm {$disarmed}! -hands-component-shove-success-others-message = {$shover} shoves {$shoved}! -hands-component-shove-success-message = You shove {$shoved}! \ No newline at end of file +hands-component-disarm-success-others-message = {CAPITALIZE(THE($disarmer))} disarms {THE($disarmed)}! +hands-component-disarm-success-message = You disarm {THE($disarmed)}! +hands-component-shove-success-others-message = {CAPITALIZE(THE($shover))} shoves {THE($shoved)}! +hands-component-shove-success-message = You shove {THE($shoved)}! diff --git a/Resources/Locale/en-US/identity/identity-system.ftl b/Resources/Locale/en-US/identity/identity-system.ftl new file mode 100644 index 0000000000..efaadb8188 --- /dev/null +++ b/Resources/Locale/en-US/identity/identity-system.ftl @@ -0,0 +1,9 @@ +identity-unknown-name = ??? + +identity-age-young = young +identity-age-middle-aged = middle-aged +identity-age-old = old + +identity-gender-feminine = woman +identity-gender-masculine = man +identity-gender-person = person diff --git a/Resources/Locale/en-US/stunnable/components/stunnable-component.ftl b/Resources/Locale/en-US/stunnable/components/stunnable-component.ftl index 671aee2cef..71d275504d 100644 --- a/Resources/Locale/en-US/stunnable/components/stunnable-component.ftl +++ b/Resources/Locale/en-US/stunnable/components/stunnable-component.ftl @@ -1,2 +1,2 @@ -stunnable-component-disarm-success-others = {$source} pushes {$target}! -stunnable-component-disarm-success = You push {$target}! \ No newline at end of file +stunnable-component-disarm-success-others = {CAPITALIZE(THE($source))} pushes {THE($target)}! +stunnable-component-disarm-success = You push {THE($target)}! diff --git a/Resources/Locale/en-US/stunnable/stun-system.ftl b/Resources/Locale/en-US/stunnable/stun-system.ftl index 7bdbc71895..6121a7e833 100644 --- a/Resources/Locale/en-US/stunnable/stun-system.ftl +++ b/Resources/Locale/en-US/stunnable/stun-system.ftl @@ -1,2 +1,2 @@ -stunned-component-disarm-success = You push {$target} down! -stunned-component-disarm-success-others = {$source} pushes {$target} down! \ No newline at end of file +stunned-component-disarm-success = You push {THE($target)} down! +stunned-component-disarm-success-others = {CAPITALIZE(THE($source))} pushes {THE($target)} down! diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 7e7da58c56..2d5537b857 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -42,6 +42,7 @@ - HidesHair - type: DiseaseProtection protection: 0.05 + - type: IdentityBlocker - type: entity abstract: true @@ -76,6 +77,7 @@ - HidesHair - type: DiseaseProtection protection: 0.05 + - type: IdentityBlocker - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 818868f1b9..140a2686cf 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -17,6 +17,7 @@ Blunt: 0.5 Slash: 0.8 Piercing: 0.8 + - type: IdentityBlocker - type: entity parent: ClothingHeadEVAHelmetBase @@ -143,6 +144,7 @@ - type: Tag tags: - HidesHair + - type: IdentityBlocker - type: entity parent: ClothingHeadEVAHelmetBase @@ -167,6 +169,7 @@ - type: Clothing sprite: Clothing/Head/Helmets/templar.rsi - type: IngestionBlocker + - type: IdentityBlocker - type: entity parent: ClothingHeadBase @@ -190,6 +193,7 @@ - type: Clothing sprite: Clothing/Head/Helmets/wizardhelm.rsi - type: IngestionBlocker + - type: IdentityBlocker - type: entity parent: ClothingHeadHardsuitWithLightBase @@ -221,7 +225,7 @@ - type: PressureProtection highPressureMultiplier: 0.65 lowPressureMultiplier: 1000 - + - type: IdentityBlocker - type: entity parent: ClothingHeadHardsuitWithLightBase @@ -250,6 +254,7 @@ - type: PressureProtection highPressureMultiplier: 0.45 lowPressureMultiplier: 1000 + - type: IdentityBlocker - type: entity parent: ClothingHeadBase @@ -268,6 +273,7 @@ Slash: 0.5 Piercing: 0.5 Heat: 0.9 + - type: IdentityBlocker - type: entity parent: ClothingHeadEVAHelmetBase diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml index b92e129092..8cc3d7c40f 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml @@ -6,6 +6,7 @@ components: - type: IngestionBlocker - type: FlashImmunity + - type: IdentityBlocker - type: entity parent: WeldingMaskBase diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index d260371acd..4b3c714e9a 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -12,9 +12,10 @@ - type: IngestionBlocker - type: DiseaseProtection protection: 0.05 + - type: IdentityBlocker - type: entity - parent: ClothingMaskPullableBase + parent: ClothingMaskGas id: ClothingMaskGasSecurity name: security gas mask description: A standard issue Security gas mask. @@ -23,13 +24,9 @@ sprite: Clothing/Mask/gassecurity.rsi - type: Clothing sprite: Clothing/Mask/gassecurity.rsi - - type: BreathMask - - type: IngestionBlocker - - type: DiseaseProtection - protection: 0.05 - type: entity - parent: ClothingMaskPullableBase + parent: ClothingMaskGas id: ClothingMaskGasSyndicate name: syndicate gas mask description: A close-fitting tactical mask that can be connected to an air supply. @@ -38,14 +35,10 @@ sprite: Clothing/Mask/gassyndicate.rsi - type: Clothing sprite: Clothing/Mask/gassyndicate.rsi - - type: BreathMask - - type: IngestionBlocker - - type: DiseaseProtection - protection: 0.05 - type: FlashImmunity - type: entity - parent: ClothingMaskPullableBase + parent: ClothingMaskGas id: ClothingMaskGasAtmos name: atmospheric gas mask description: Improved gas mask utilized by atmospheric technicians. It's flameproof! @@ -54,10 +47,6 @@ sprite: Clothing/Mask/gasatmos.rsi - type: Clothing sprite: Clothing/Mask/gasatmos.rsi - - type: BreathMask - - type: IngestionBlocker - - type: DiseaseProtection - protection: 0.05 - type: Armor modifiers: coefficients: @@ -94,7 +83,7 @@ protection: 0.05 - type: entity - parent: ClothingMaskPullableBase + parent: ClothingMaskGas id: ClothingMaskGasExplorer name: explorer gas mask description: A military-grade gas mask that can be connected to an air supply. @@ -103,10 +92,6 @@ sprite: Clothing/Mask/gasexplorer.rsi - type: Clothing sprite: Clothing/Mask/gasexplorer.rsi - - type: BreathMask - - type: IngestionBlocker - - type: DiseaseProtection - protection: 0.05 - type: Armor modifiers: coefficients: diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e8359ff04f..dc6ca2b16c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -32,6 +32,7 @@ - !type:WashCreamPieReaction - type: Flashable - type: Polymorphable + - type: Identity - type: Hands - type: MovementSpeedModifier - type: MovedByPressure