Id[entity] 2.0 (real) (#9612)
* starter API * network ID cards * Port more stuff from old identity * Re-implement identity representation + name updating * move * proper name returning for `IdentityName` * move everything important to server, give in to temptation * shared / server / client split sadly. move ensure to shared and spawn to server * identity update queueing + identityblocker * fixes * and just like that it's usable for admins * huge identity pass * pass dos * jesus christ * figs :D * fuck u * fix bad merge. Co-authored-by: Moony <moonheart08@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IdCardComponent, ComponentGetState>(OnComponentGetState);
|
||||
SubscribeLocalEvent<IdCardComponent, ComponentHandleState>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="PDAComponent"/>.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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<HandVirtualItemComponent>(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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.IdentityManagement.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class IdentityBlockerComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity and relayed to inventory to determine if its identity should be knowable.
|
||||
/// </summary>
|
||||
public sealed class SeeIdentityAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
// i.e. masks or helmets.
|
||||
public SlotFlags TargetSlots => SlotFlags.MASK | SlotFlags.HEAD;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Shared.IdentityManagement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a <see cref="ContainerSlot"/> and not just a datum entity because we do sort of care that it gets deleted and sent with the user.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class IdentityComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public ContainerSlot IdentityEntitySlot = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A data structure representing the 'identity' of an entity as presented to
|
||||
/// other players.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
60
Content.Shared/IdentityManagement/Identity.cs
Normal file
60
Content.Shared/IdentityManagement/Identity.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
|
||||
namespace Content.Shared.IdentityManagement;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the name that should be used for this entity for identity purposes.
|
||||
/// </summary>
|
||||
public static string Name(EntityUid uid, IEntityManager ent, EntityUid? viewer=null)
|
||||
{
|
||||
var uidName = ent.GetComponent<MetaDataComponent>(uid).EntityName;
|
||||
|
||||
if (!ent.TryGetComponent<IdentityComponent>(uid, out var identity))
|
||||
return uidName;
|
||||
|
||||
var ident = identity.IdentityEntitySlot.ContainedEntity;
|
||||
if (ident is null)
|
||||
return uidName;
|
||||
|
||||
var identName = ent.GetComponent<MetaDataComponent>(ident.Value).EntityName;
|
||||
if (viewer == null || !CanSeeThroughIdentity(uid, viewer.Value, ent))
|
||||
{
|
||||
return identName;
|
||||
}
|
||||
if (uidName == identName)
|
||||
{
|
||||
return uidName;
|
||||
}
|
||||
|
||||
return uidName + $" ({identName})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static EntityUid Entity(EntityUid uid, IEntityManager ent)
|
||||
{
|
||||
if (!ent.TryGetComponent<IdentityComponent>(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<SharedGhostComponent>(viewer);
|
||||
}
|
||||
|
||||
}
|
||||
28
Content.Shared/IdentityManagement/SharedIdentitySystem.cs
Normal file
28
Content.Shared/IdentityManagement/SharedIdentitySystem.cs
Normal file
@@ -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<IdentityComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(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<ContainerSlot>(uid, SlotName);
|
||||
}
|
||||
}
|
||||
@@ -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<InventoryComponent, RefreshMovementSpeedModifiersEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, BeforeStripEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, SeeIdentityAttemptEvent>(RelayInventoryEvent);
|
||||
}
|
||||
|
||||
protected void RelayInventoryEvent<T>(EntityUid uid, InventoryComponent component, T args) where T : EntityEventArgs, IInventoryRelayEvent
|
||||
|
||||
Reference in New Issue
Block a user