using Content.Shared.Actions; using Content.Shared.Interaction; using Content.Shared.Polymorph; using Content.Shared.Polymorph.Components; using Content.Shared.Popups; using Robust.Shared.Serialization.Manager; using Robust.Shared.Prototypes; using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Polymorph.Systems; /// /// Handles whitelist/blacklist checking. /// Actual polymorphing and deactivation is done serverside. /// public abstract class SharedChameleonProjectorSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ISerializationManager _serMan = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInteract); } private void OnInteract(Entity ent, ref AfterInteractEvent args) { if (!args.CanReach || args.Target is not {} target) return; var user = args.User; args.Handled = true; if (IsInvalid(ent.Comp, target)) { _popup.PopupClient(Loc.GetString(ent.Comp.InvalidPopup), target, user); return; } _popup.PopupClient(Loc.GetString(ent.Comp.SuccessPopup), target, user); Disguise(ent.Comp, user, target); } /// /// Returns true if an entity cannot be used as a disguise. /// public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target) { return (comp.Whitelist?.IsValid(target, EntityManager) == false) || (comp.Blacklist?.IsValid(target, EntityManager) == true); } /// /// On server, polymorphs the user into an entity and sets up the disguise. /// public virtual void Disguise(ChameleonProjectorComponent comp, EntityUid user, EntityUid entity) { } /// /// Copy a component from the source entity/prototype to the disguise entity. /// /// /// This would probably be a good thing to add to engine in the future. /// protected bool CopyComp(Entity ent) where T: Component, new() { if (!GetSrcComp(ent.Comp, out var src)) return true; // remove then re-add to prevent a funny RemComp(ent); var dest = AddComp(ent); _serMan.CopyTo(src, ref dest, notNullableOverride: true); Dirty(ent, dest); return false; } /// /// Try to get a single component from the source entity/prototype. /// private bool GetSrcComp(ChameleonDisguiseComponent comp, [NotNullWhen(true)] out T? src) where T: Component { src = null; if (TryComp(comp.SourceEntity, out src)) return true; if (comp.SourceProto is not {} protoId) return false; if (!_proto.TryIndex(protoId, out var proto)) return false; return proto.TryGetComponent(out src); } } /// /// Action event for toggling transform NoRot on a disguise. /// public sealed partial class DisguiseToggleNoRotEvent : InstantActionEvent { } /// /// Action event for toggling transform Anchored on a disguise. /// public sealed partial class DisguiseToggleAnchoredEvent : InstantActionEvent { }