]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
cham projector fixes/rewrite (#27111)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Fri, 8 Nov 2024 11:15:41 +0000 (11:15 +0000)
committerGitHub <noreply@github.com>
Fri, 8 Nov 2024 11:15:41 +0000 (12:15 +0100)
* cant disguise to thing in a container

* copy cigarette visualiser

* prevent aghost throwing an error

* make disguises die in space

* fuck it rewrite it to not use polymorph

* fix action troll

* oop

* add vebr

* add access to the components

* 2/3

* fix

* relay damage from disguise to user

* fix integrity

* :trollface:

* :trollface:

* m

* kill integrity

* fix a bug

* review

* remove them from component

* relay flash effect to the disguise

* fix icon being weird

* change method since multiple systems cant handle same network event

* :trollface:

* actually network Disguise real

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Content.Client/Effects/ColorFlashEffectSystem.cs
Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
Content.Server/Polymorph/Systems/ChameleonProjectorSystem.cs
Content.Shared/Polymorph/Components/ChameleonDisguiseComponent.cs
Content.Shared/Polymorph/Components/ChameleonDisguisedComponent.cs [new file with mode: 0644]
Content.Shared/Polymorph/Components/ChameleonProjectorComponent.cs
Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs
Resources/Locale/en-US/chameleon-projector/chameleon-projector.ftl
Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml

index 956c9465244200243ba5e3eab0dfa6a93ed778fe..b584aa9ad1bd35a689f638b3e935b878be9a612f 100644 (file)
@@ -124,6 +124,10 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
                 continue;
             }
 
+            var targetEv = new GetFlashEffectTargetEvent(ent);
+            RaiseLocalEvent(ent, ref targetEv);
+            ent = targetEv.Target;
+
             EnsureComp<ColorFlashEffectComponent>(ent, out comp);
             comp.NetSyncEnabled = false;
             comp.Color = sprite.Color;
@@ -132,3 +136,9 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
         }
     }
 }
+
+/// <summary>
+/// Raised on an entity to change the target for a color flash effect.
+/// </summary>
+[ByRefEvent]
+public record struct GetFlashEffectTargetEvent(EntityUid Target);
index 5ba4878c6d4534e0b88d940e42324c503056c6b5..8ba09c661701ba7ae8315fb27625ec2eb95bc90e 100644 (file)
@@ -1,7 +1,10 @@
+using Content.Client.Effects;
+using Content.Client.Smoking;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Polymorph.Components;
 using Content.Shared.Polymorph.Systems;
 using Robust.Client.GameObjects;
+using Robust.Shared.Player;
 
 namespace Content.Client.Polymorph.Systems;
 
@@ -10,14 +13,20 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
 
     private EntityQuery<AppearanceComponent> _appearanceQuery;
+    private EntityQuery<SpriteComponent> _spriteQuery;
 
     public override void Initialize()
     {
         base.Initialize();
 
         _appearanceQuery = GetEntityQuery<AppearanceComponent>();
+        _spriteQuery = GetEntityQuery<SpriteComponent>();
 
         SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState);
+
+        SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentStartup>(OnStartup);
+        SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentShutdown>(OnShutdown);
+        SubscribeLocalEvent<ChameleonDisguisedComponent, GetFlashEffectTargetEvent>(OnGetFlashEffectTargetEvent);
     }
 
     private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
@@ -25,9 +34,30 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
         CopyComp<SpriteComponent>(ent);
         CopyComp<GenericVisualizerComponent>(ent);
         CopyComp<SolutionContainerVisualsComponent>(ent);
+        CopyComp<BurnStateVisualsComponent>(ent);
 
         // reload appearance to hopefully prevent any invisible layers
         if (_appearanceQuery.TryComp(ent, out var appearance))
             _appearance.QueueUpdate(ent, appearance);
     }
+
+    private void OnStartup(Entity<ChameleonDisguisedComponent> ent, ref ComponentStartup args)
+    {
+        if (!_spriteQuery.TryComp(ent, out var sprite))
+            return;
+
+        ent.Comp.WasVisible = sprite.Visible;
+        sprite.Visible = false;
+    }
+
+    private void OnShutdown(Entity<ChameleonDisguisedComponent> ent, ref ComponentShutdown args)
+    {
+        if (_spriteQuery.TryComp(ent, out var sprite))
+            sprite.Visible = ent.Comp.WasVisible;
+    }
+
+    private void OnGetFlashEffectTargetEvent(Entity<ChameleonDisguisedComponent> ent, ref GetFlashEffectTargetEvent args)
+    {
+        args.Target = ent.Comp.Disguise;
+    }
 }
index 1586973a21e7e4a63d5007d3b40f853c239d1657..ab12f2764cfba65690735428b6836efe08cfc941 100644 (file)
@@ -1,99 +1,5 @@
-using Content.Server.Polymorph.Components;
-using Content.Shared.Actions;
-using Content.Shared.Construction.Components;
-using Content.Shared.Hands;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Polymorph;
-using Content.Shared.Polymorph.Components;
 using Content.Shared.Polymorph.Systems;
-using Content.Shared.StatusIcon.Components;
-using Robust.Shared.Physics.Components;
 
 namespace Content.Server.Polymorph.Systems;
 
-public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
-{
-    [Dependency] private readonly MetaDataSystem _meta = default!;
-    [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
-    [Dependency] private readonly PolymorphSystem _polymorph = default!;
-    [Dependency] private readonly SharedActionsSystem _actions = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-    [Dependency] private readonly SharedTransformSystem _xform = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ChameleonDisguiseComponent, GotEquippedHandEvent>(OnEquippedHand);
-        SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
-        SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
-    }
-
-    private void OnEquippedHand(Entity<ChameleonDisguiseComponent> ent, ref GotEquippedHandEvent args)
-    {
-        if (!TryComp<PolymorphedEntityComponent>(ent, out var poly))
-            return;
-
-        _polymorph.Revert((ent, poly));
-        args.Handled = true;
-    }
-
-    public override void Disguise(ChameleonProjectorComponent proj, EntityUid user, EntityUid entity)
-    {
-        if (_polymorph.PolymorphEntity(user, proj.Polymorph) is not {} disguise)
-            return;
-
-        // make disguise look real (for simple things at least)
-        var meta = MetaData(entity);
-        _meta.SetEntityName(disguise, meta.EntityName);
-        _meta.SetEntityDescription(disguise, meta.EntityDescription);
-
-        var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
-        comp.SourceEntity = entity;
-        comp.SourceProto = Prototype(entity)?.ID;
-        Dirty(disguise, comp);
-
-        // no sechud trolling
-        RemComp<StatusIconComponent>(disguise);
-
-        _appearance.CopyData(entity, disguise);
-
-        var mass = CompOrNull<PhysicsComponent>(entity)?.Mass ?? 0f;
-
-        // let the disguise die when its taken enough damage, which then transfers to the player
-        // health is proportional to mass, and capped to not be insane
-        if (TryComp<MobThresholdsComponent>(disguise, out var thresholds))
-        {
-            // if the player is of flesh and blood, cap max health to theirs
-            // so that when reverting damage scales 1:1 and not round removing
-            var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead).Float();
-            var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax);
-
-            var health = Math.Clamp(mass, proj.MinHealth, proj.MaxHealth);
-            _mobThreshold.SetMobStateThreshold(disguise, health, MobState.Critical, thresholds);
-            _mobThreshold.SetMobStateThreshold(disguise, max, MobState.Dead, thresholds);
-        }
-
-        // add actions for controlling transform aspects
-        _actions.AddAction(disguise, proj.NoRotAction);
-        _actions.AddAction(disguise, proj.AnchorAction);
-    }
-
-    private void OnToggleNoRot(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleNoRotEvent args)
-    {
-        var xform = Transform(ent);
-        xform.NoLocalRotation = !xform.NoLocalRotation;
-    }
-
-    private void OnToggleAnchored(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleAnchoredEvent args)
-    {
-        var uid = ent.Owner;
-        var xform = Transform(uid);
-        if (xform.Anchored)
-            _xform.Unanchor(uid, xform);
-        else
-            _xform.AnchorEntity((uid, xform));
-    }
-}
+public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem;
index 2b9fba7b3919ac156e0143362a3c3b91dfb3c729..282106b8f6fff1f4de84debb341a506d5065b259 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Polymorph.Systems;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 
@@ -7,9 +8,22 @@ namespace Content.Shared.Polymorph.Components;
 /// Component added to disguise entities.
 /// Used by client to copy over appearance from the disguise's source entity.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedChameleonProjectorSystem))]
+[AutoGenerateComponentState(true)]
 public sealed partial class ChameleonDisguiseComponent : Component
 {
+    /// <summary>
+    /// The user of this disguise.
+    /// </summary>
+    [DataField]
+    public EntityUid User;
+
+    /// <summary>
+    /// The projector that created this disguise.
+    /// </summary>
+    [DataField]
+    public EntityUid Projector;
+
     /// <summary>
     /// The disguise source entity for copying the sprite.
     /// </summary>
diff --git a/Content.Shared/Polymorph/Components/ChameleonDisguisedComponent.cs b/Content.Shared/Polymorph/Components/ChameleonDisguisedComponent.cs
new file mode 100644 (file)
index 0000000..cd2e26c
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Shared.Polymorph.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Polymorph.Components;
+
+/// <summary>
+/// Added to a player when they use a chameleon projector.
+/// Handles making them invisible and revealing when damaged enough or switching hands.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedChameleonProjectorSystem))]
+[AutoGenerateComponentState]
+public sealed partial class ChameleonDisguisedComponent : Component
+{
+    /// <summary>
+    /// The disguise entity parented to the player.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid Disguise;
+
+    /// <summary>
+    /// For client, whether the user's sprite was previously visible or not.
+    /// </summary>
+    [DataField]
+    public bool WasVisible;
+}
index 239b5236f2799b1e275d0809f6f965e2b4f13f33..1b289c54fc75e4bd1ea3b3e13d57c496115a70d2 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Shared.Polymorph;
 using Content.Shared.Polymorph.Systems;
 using Content.Shared.Whitelist;
 using Robust.Shared.Prototypes;
@@ -25,22 +24,26 @@ public sealed partial class ChameleonProjectorComponent : Component
     public EntityWhitelist? Blacklist;
 
     /// <summary>
-    /// Polymorph configuration for the disguise entity.
+    /// Disguise entity to spawn and use.
     /// </summary>
     [DataField(required: true)]
-    public PolymorphConfiguration Polymorph = new();
+    public EntProtoId DisguiseProto = string.Empty;
 
     /// <summary>
     /// Action for disabling your disguise's rotation.
     /// </summary>
     [DataField]
     public EntProtoId NoRotAction = "ActionDisguiseNoRot";
+    [DataField]
+    public EntityUid? NoRotActionEntity;
 
     /// <summary>
     /// Action for anchoring your disguise in place.
     /// </summary>
     [DataField]
     public EntProtoId AnchorAction = "ActionDisguiseAnchor";
+    [DataField]
+    public EntityUid? AnchorActionEntity;
 
     /// <summary>
     /// Minimum health to give the disguise.
@@ -55,14 +58,8 @@ public sealed partial class ChameleonProjectorComponent : Component
     public float MaxHealth = 100f;
 
     /// <summary>
-    /// Popup shown to the user when they try to disguise as an invalid entity.
-    /// </summary>
-    [DataField]
-    public LocId InvalidPopup = "chameleon-projector-invalid";
-
-    /// <summary>
-    /// Popup shown to the user when they disguise as a valid entity.
+    /// User currently disguised by this projector, if any
     /// </summary>
     [DataField]
-    public LocId SuccessPopup = "chameleon-projector-success";
+    public EntityUid? Disguised;
 }
index 00096b7d4094db945eab90397e4735524a939336..99737996b0873de7e8399016847f5a61ffb108d1 100644 (file)
 using Content.Shared.Actions;
+using Content.Shared.Coordinates;
+using Content.Shared.Damage;
+using Content.Shared.Hands;
 using Content.Shared.Interaction;
-using Content.Shared.Polymorph;
+using Content.Shared.Item;
 using Content.Shared.Polymorph.Components;
 using Content.Shared.Popups;
-using Robust.Shared.Serialization.Manager;
+using Content.Shared.Storage.Components;
+using Content.Shared.Verbs;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
 using System.Diagnostics.CodeAnalysis;
-using Content.Shared.Whitelist;
 
 namespace Content.Shared.Polymorph.Systems;
 
 /// <summary>
-/// Handles whitelist/blacklist checking.
-/// Actual polymorphing and deactivation is done serverside.
+/// Handles disguise validation, disguising and revealing.
+/// Most appearance copying is done clientside.
 /// </summary>
 public abstract class SharedChameleonProjectorSystem : EntitySystem
 {
+    [Dependency] private readonly DamageableSystem _damageable = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+    [Dependency] private readonly INetManager _net = default!;
     [Dependency] private readonly IPrototypeManager _proto = default!;
     [Dependency] private readonly ISerializationManager _serMan = default!;
+    [Dependency] private readonly MetaDataSystem _meta = default!;
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
+        SubscribeLocalEvent<ChameleonDisguiseComponent, InteractHandEvent>(OnDisguiseInteractHand, before: [typeof(SharedItemSystem)]);
+        SubscribeLocalEvent<ChameleonDisguiseComponent, DamageChangedEvent>(OnDisguiseDamaged);
+        SubscribeLocalEvent<ChameleonDisguiseComponent, InsertIntoEntityStorageAttemptEvent>(OnDisguiseInsertAttempt);
+        SubscribeLocalEvent<ChameleonDisguiseComponent, ComponentShutdown>(OnDisguiseShutdown);
+
         SubscribeLocalEvent<ChameleonProjectorComponent, AfterInteractEvent>(OnInteract);
+        SubscribeLocalEvent<ChameleonProjectorComponent, GetVerbsEvent<UtilityVerb>>(OnGetVerbs);
+        SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
+        SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
+        SubscribeLocalEvent<ChameleonProjectorComponent, HandDeselectedEvent>(OnDeselected);
+        SubscribeLocalEvent<ChameleonProjectorComponent, GotUnequippedHandEvent>(OnUnequipped);
+        SubscribeLocalEvent<ChameleonProjectorComponent, ComponentShutdown>(OnProjectorShutdown);
+    }
+
+    #region Disguise entity
+
+    private void OnDisguiseInteractHand(Entity<ChameleonDisguiseComponent> ent, ref InteractHandEvent args)
+    {
+        TryReveal(ent.Comp.User);
+        args.Handled = true;
     }
 
+    private void OnDisguiseDamaged(Entity<ChameleonDisguiseComponent> ent, ref DamageChangedEvent args)
+    {
+        // this mirrors damage 1:1
+        if (args.DamageDelta is {} damage)
+            _damageable.TryChangeDamage(ent.Comp.User, damage);
+    }
+
+    private void OnDisguiseInsertAttempt(Entity<ChameleonDisguiseComponent> ent, ref InsertIntoEntityStorageAttemptEvent args)
+    {
+        // stay parented to the user, not the storage
+        args.Cancelled = true;
+    }
+
+    private void OnDisguiseShutdown(Entity<ChameleonDisguiseComponent> ent, ref ComponentShutdown args)
+    {
+        _actions.RemoveProvidedActions(ent.Comp.User, ent.Comp.Projector);
+    }
+
+    #endregion
+
+    #region Projector
+
     private void OnInteract(Entity<ChameleonProjectorComponent> ent, ref AfterInteractEvent args)
     {
-        if (!args.CanReach || args.Target is not {} target)
+        if (args.Handled || !args.CanReach || args.Target is not {} target)
             return;
 
-        var user = args.User;
         args.Handled = true;
+        TryDisguise(ent, args.User, target);
+    }
+
+    private void OnGetVerbs(Entity<ChameleonProjectorComponent> ent, ref GetVerbsEvent<UtilityVerb> args)
+    {
+        if (!args.CanAccess)
+            return;
+
+        var user = args.User;
+        var target = args.Target;
+        args.Verbs.Add(new UtilityVerb()
+        {
+            Act = () =>
+            {
+                TryDisguise(ent, user, target);
+            },
+            Text = Loc.GetString("chameleon-projector-set-disguise")
+        });
+    }
+
+    public bool TryDisguise(Entity<ChameleonProjectorComponent> ent, EntityUid user, EntityUid target)
+    {
+        if (_container.IsEntityInContainer(target))
+        {
+            _popup.PopupClient(Loc.GetString("chameleon-projector-inside-container"), target, user);
+            return false;
+        }
 
         if (IsInvalid(ent.Comp, target))
         {
-            _popup.PopupClient(Loc.GetString(ent.Comp.InvalidPopup), target, user);
-            return;
+            _popup.PopupClient(Loc.GetString("chameleon-projector-invalid"), target, user);
+            return false;
         }
 
-        _popup.PopupClient(Loc.GetString(ent.Comp.SuccessPopup), target, user);
-        Disguise(ent.Comp, user, target);
+        _popup.PopupClient(Loc.GetString("chameleon-projector-success"), target, user);
+        Disguise(ent, user, target);
+        return true;
     }
 
+    private void OnToggleNoRot(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleNoRotEvent args)
+    {
+        if (ent.Comp.Disguised is not {} uid)
+            return;
+
+        var xform = Transform(uid);
+        _xform.SetLocalRotationNoLerp(uid, 0, xform);
+        xform.NoLocalRotation = !xform.NoLocalRotation;
+        args.Handled = true;
+    }
+
+    private void OnToggleAnchored(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleAnchoredEvent args)
+    {
+        if (ent.Comp.Disguised is not {} uid)
+            return;
+
+        var xform = Transform(uid);
+        if (xform.Anchored)
+            _xform.Unanchor(uid, xform);
+        else
+            _xform.AnchorEntity((uid, xform));
+
+        args.Handled = true;
+    }
+
+    private void OnDeselected(Entity<ChameleonProjectorComponent> ent, ref HandDeselectedEvent args)
+    {
+        RevealProjector(ent);
+    }
+
+    private void OnUnequipped(Entity<ChameleonProjectorComponent> ent, ref GotUnequippedHandEvent args)
+    {
+        RevealProjector(ent);
+    }
+
+    private void OnProjectorShutdown(Entity<ChameleonProjectorComponent> ent, ref ComponentShutdown args)
+    {
+        RevealProjector(ent);
+    }
+
+    #endregion
+
+    #region API
+
     /// <summary>
     /// Returns true if an entity cannot be used as a disguise.
     /// </summary>
     public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target)
     {
-        return _whitelistSystem.IsWhitelistFail(comp.Whitelist, target)
-            || _whitelistSystem.IsBlacklistPass(comp.Blacklist, target);
+        return _whitelist.IsWhitelistFail(comp.Whitelist, target)
+            || _whitelist.IsBlacklistPass(comp.Blacklist, target);
     }
 
     /// <summary>
     /// On server, polymorphs the user into an entity and sets up the disguise.
     /// </summary>
-    public virtual void Disguise(ChameleonProjectorComponent comp, EntityUid user, EntityUid entity)
+    public void Disguise(Entity<ChameleonProjectorComponent> ent, EntityUid user, EntityUid entity)
+    {
+        var proj = ent.Comp;
+
+        // no spawning prediction sorry
+        if (_net.IsClient)
+            return;
+
+        // reveal first to allow quick switching
+        TryReveal(user);
+
+        // add actions for controlling transform aspects
+        _actions.AddAction(user, ref proj.NoRotActionEntity, proj.NoRotAction, container: ent);
+        _actions.AddAction(user, ref proj.AnchorActionEntity, proj.AnchorAction, container: ent);
+
+        proj.Disguised = user;
+
+        var disguise = SpawnAttachedTo(proj.DisguiseProto, user.ToCoordinates());
+
+        var disguised = AddComp<ChameleonDisguisedComponent>(user);
+        disguised.Disguise = disguise;
+        Dirty(user, disguised);
+
+        // make disguise look real (for simple things at least)
+        var meta = MetaData(entity);
+        _meta.SetEntityName(disguise, meta.EntityName);
+        _meta.SetEntityDescription(disguise, meta.EntityDescription);
+
+        var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
+        comp.User = user;
+        comp.Projector = ent;
+        comp.SourceEntity = entity;
+        comp.SourceProto = Prototype(entity)?.ID;
+        Dirty(disguise, comp);
+
+        // item disguises can be picked up to be revealed, also makes sure their examine size is correct
+        CopyComp<ItemComponent>((disguise, comp));
+
+        _appearance.CopyData(entity, disguise);
+    }
+
+    /// <summary>
+    /// Removes the disguise, if the user is disguised.
+    /// </summary>
+    public bool TryReveal(Entity<ChameleonDisguisedComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp, false))
+            return false;
+
+        if (TryComp<ChameleonDisguiseComponent>(ent.Comp.Disguise, out var disguise)
+            && TryComp<ChameleonProjectorComponent>(disguise.Projector, out var proj))
+        {
+            proj.Disguised = null;
+        }
+
+        var xform = Transform(ent);
+        xform.NoLocalRotation = false;
+        _xform.Unanchor(ent, xform);
+
+        Del(ent.Comp.Disguise);
+        RemComp<ChameleonDisguisedComponent>(ent);
+        return true;
+    }
+
+    /// <summary>
+    /// Reveal a projector's user, if any.
+    /// </summary>
+    public void RevealProjector(Entity<ChameleonProjectorComponent> ent)
     {
+        if (ent.Comp.Disguised is {} user)
+            TryReveal(user);
     }
 
+    #endregion
+
     /// <summary>
     /// Copy a component from the source entity/prototype to the disguise entity.
     /// </summary>
index 8a79516077d02d8e121842077b2c19748e02b8e3..b525c9da1a33633c4dc7c79860c2a68226ee2472 100644 (file)
@@ -1,2 +1,4 @@
+chameleon-projector-inside-container = There's no room to scan that!
 chameleon-projector-invalid = You can't disguise as that!
 chameleon-projector-success = Projected new disguise.
+chameleon-projector-set-disguise = Set Disguise
index f07ae635696ee8b1da4e46181b38f803beaa5819..b6819a18b96462a8cf028c7cf2fb1da2743b6680 100644 (file)
     blacklist:
       components:
       - ChameleonDisguise # no becoming kleiner
-      - InsideEntityStorage # no clark kent going in phone booth and becoming superman
       - MindContainer # no
       - Pda # PDAs currently make you invisible /!\
-    polymorph:
-      entity: ChameleonDisguise
+    disguiseProto: ChameleonDisguise
 
 - type: entity
   categories: [ HideSpawnMenu ]
-  parent: BaseMob
   id: ChameleonDisguise
   name: Urist McKleiner
   components:
   - type: Sprite
     sprite: /Textures/Mobs/Species/Human/parts.rsi
     state: full
-  # so people can attempt to pick it up
-  - type: Item
-  # so it can take damage
-  # projector system sets health to be proportional to mass
+  - type: Transform
+    noRot: true # players rotation and anchor is used instead
+  - type: InteractionOutline
+  - type: Clickable
   - type: Damageable
-  - type: MobState
-  - type: MobThresholds
-    thresholds:
-      0: Alive
-      1: Critical
-      200: Dead
-  - type: MovementSpeedModifier
-    baseWalkSpeed: 1 # precise movement for the perfect spot
-    baseSprintSpeed: 5 # the jig is up
   - type: ChameleonDisguise
 
 # actions
@@ -57,6 +45,7 @@
   components:
   - type: InstantAction
     icon: Interface/VerbIcons/refresh.svg.192dpi.png
+    itemIconStyle: BigAction
     event: !type:DisguiseToggleNoRotEvent
 
 - type: entity
@@ -68,4 +57,5 @@
     icon:
       sprite: Objects/Tools/wrench.rsi
       state: icon
+    itemIconStyle: BigAction
     event: !type:DisguiseToggleAnchoredEvent