SharedChameleonProjectorSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. using Content.Shared.Actions;
  2. using Content.Shared.Coordinates;
  3. using Content.Shared.Damage;
  4. using Content.Shared.Hands;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Item;
  7. using Content.Shared.Polymorph.Components;
  8. using Content.Shared.Popups;
  9. using Content.Shared.Storage.Components;
  10. using Content.Shared.Verbs;
  11. using Content.Shared.Whitelist;
  12. using Robust.Shared.Containers;
  13. using Robust.Shared.Network;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Serialization.Manager;
  16. using System.Diagnostics.CodeAnalysis;
  17. namespace Content.Shared.Polymorph.Systems;
  18. /// <summary>
  19. /// Handles disguise validation, disguising and revealing.
  20. /// Most appearance copying is done clientside.
  21. /// </summary>
  22. public abstract class SharedChameleonProjectorSystem : EntitySystem
  23. {
  24. [Dependency] private readonly DamageableSystem _damageable = default!;
  25. [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
  26. [Dependency] private readonly INetManager _net = default!;
  27. [Dependency] private readonly IPrototypeManager _proto = default!;
  28. [Dependency] private readonly ISerializationManager _serMan = default!;
  29. [Dependency] private readonly MetaDataSystem _meta = default!;
  30. [Dependency] private readonly SharedActionsSystem _actions = default!;
  31. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  32. [Dependency] private readonly SharedContainerSystem _container = default!;
  33. [Dependency] private readonly SharedPopupSystem _popup = default!;
  34. [Dependency] private readonly SharedTransformSystem _xform = default!;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<ChameleonDisguiseComponent, InteractHandEvent>(OnDisguiseInteractHand, before: [typeof(SharedItemSystem)]);
  39. SubscribeLocalEvent<ChameleonDisguiseComponent, DamageChangedEvent>(OnDisguiseDamaged);
  40. SubscribeLocalEvent<ChameleonDisguiseComponent, InsertIntoEntityStorageAttemptEvent>(OnDisguiseInsertAttempt);
  41. SubscribeLocalEvent<ChameleonDisguiseComponent, ComponentShutdown>(OnDisguiseShutdown);
  42. SubscribeLocalEvent<ChameleonDisguisedComponent, EntGotInsertedIntoContainerMessage>(OnDisguisedInserted);
  43. SubscribeLocalEvent<ChameleonProjectorComponent, AfterInteractEvent>(OnInteract);
  44. SubscribeLocalEvent<ChameleonProjectorComponent, GetVerbsEvent<UtilityVerb>>(OnGetVerbs);
  45. SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
  46. SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
  47. SubscribeLocalEvent<ChameleonProjectorComponent, HandDeselectedEvent>(OnDeselected);
  48. SubscribeLocalEvent<ChameleonProjectorComponent, GotUnequippedHandEvent>(OnUnequipped);
  49. SubscribeLocalEvent<ChameleonProjectorComponent, ComponentShutdown>(OnProjectorShutdown);
  50. }
  51. #region Disguise entity
  52. private void OnDisguiseInteractHand(Entity<ChameleonDisguiseComponent> ent, ref InteractHandEvent args)
  53. {
  54. TryReveal(ent.Comp.User);
  55. args.Handled = true;
  56. }
  57. private void OnDisguiseDamaged(Entity<ChameleonDisguiseComponent> ent, ref DamageChangedEvent args)
  58. {
  59. // this mirrors damage 1:1
  60. if (args.DamageDelta is {} damage)
  61. _damageable.TryChangeDamage(ent.Comp.User, damage);
  62. }
  63. private void OnDisguiseInsertAttempt(Entity<ChameleonDisguiseComponent> ent, ref InsertIntoEntityStorageAttemptEvent args)
  64. {
  65. // stay parented to the user, not the storage
  66. args.Cancelled = true;
  67. TryReveal(ent.Comp.User);
  68. }
  69. private void OnDisguiseShutdown(Entity<ChameleonDisguiseComponent> ent, ref ComponentShutdown args)
  70. {
  71. _actions.RemoveProvidedActions(ent.Comp.User, ent.Comp.Projector);
  72. }
  73. #endregion
  74. #region Disguised player
  75. private void OnDisguisedInserted(Entity<ChameleonDisguisedComponent> ent, ref EntGotInsertedIntoContainerMessage args)
  76. {
  77. // prevent player going into locker/mech/etc while disguised
  78. TryReveal((ent, ent));
  79. }
  80. #endregion
  81. #region Projector
  82. private void OnInteract(Entity<ChameleonProjectorComponent> ent, ref AfterInteractEvent args)
  83. {
  84. if (args.Handled || !args.CanReach || args.Target is not {} target)
  85. return;
  86. args.Handled = true;
  87. TryDisguise(ent, args.User, target);
  88. }
  89. private void OnGetVerbs(Entity<ChameleonProjectorComponent> ent, ref GetVerbsEvent<UtilityVerb> args)
  90. {
  91. if (!args.CanAccess)
  92. return;
  93. var user = args.User;
  94. var target = args.Target;
  95. args.Verbs.Add(new UtilityVerb()
  96. {
  97. Act = () =>
  98. {
  99. TryDisguise(ent, user, target);
  100. },
  101. Text = Loc.GetString("chameleon-projector-set-disguise")
  102. });
  103. }
  104. public bool TryDisguise(Entity<ChameleonProjectorComponent> ent, EntityUid user, EntityUid target)
  105. {
  106. if (_container.IsEntityInContainer(target) || _container.IsEntityInContainer(user))
  107. {
  108. _popup.PopupClient(Loc.GetString("chameleon-projector-inside-container"), target, user);
  109. return false;
  110. }
  111. if (IsInvalid(ent.Comp, target))
  112. {
  113. _popup.PopupClient(Loc.GetString("chameleon-projector-invalid"), target, user);
  114. return false;
  115. }
  116. _popup.PopupClient(Loc.GetString("chameleon-projector-success"), target, user);
  117. Disguise(ent, user, target);
  118. return true;
  119. }
  120. private void OnToggleNoRot(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleNoRotEvent args)
  121. {
  122. if (ent.Comp.Disguised is not {} uid)
  123. return;
  124. var xform = Transform(uid);
  125. _xform.SetLocalRotationNoLerp(uid, 0, xform);
  126. xform.NoLocalRotation = !xform.NoLocalRotation;
  127. args.Handled = true;
  128. }
  129. private void OnToggleAnchored(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleAnchoredEvent args)
  130. {
  131. if (ent.Comp.Disguised is not {} uid)
  132. return;
  133. var xform = Transform(uid);
  134. if (xform.Anchored)
  135. _xform.Unanchor(uid, xform);
  136. else
  137. _xform.AnchorEntity((uid, xform));
  138. args.Handled = true;
  139. }
  140. private void OnDeselected(Entity<ChameleonProjectorComponent> ent, ref HandDeselectedEvent args)
  141. {
  142. RevealProjector(ent);
  143. }
  144. private void OnUnequipped(Entity<ChameleonProjectorComponent> ent, ref GotUnequippedHandEvent args)
  145. {
  146. RevealProjector(ent);
  147. }
  148. private void OnProjectorShutdown(Entity<ChameleonProjectorComponent> ent, ref ComponentShutdown args)
  149. {
  150. RevealProjector(ent);
  151. }
  152. #endregion
  153. #region API
  154. /// <summary>
  155. /// Returns true if an entity cannot be used as a disguise.
  156. /// </summary>
  157. public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target)
  158. {
  159. return _whitelist.IsWhitelistFail(comp.Whitelist, target)
  160. || _whitelist.IsBlacklistPass(comp.Blacklist, target);
  161. }
  162. /// <summary>
  163. /// On server, polymorphs the user into an entity and sets up the disguise.
  164. /// </summary>
  165. public void Disguise(Entity<ChameleonProjectorComponent> ent, EntityUid user, EntityUid entity)
  166. {
  167. var proj = ent.Comp;
  168. // no spawning prediction sorry
  169. if (_net.IsClient)
  170. return;
  171. // reveal first to allow quick switching
  172. TryReveal(user);
  173. // add actions for controlling transform aspects
  174. _actions.AddAction(user, ref proj.NoRotActionEntity, proj.NoRotAction, container: ent);
  175. _actions.AddAction(user, ref proj.AnchorActionEntity, proj.AnchorAction, container: ent);
  176. proj.Disguised = user;
  177. var disguise = SpawnAttachedTo(proj.DisguiseProto, user.ToCoordinates());
  178. var disguised = AddComp<ChameleonDisguisedComponent>(user);
  179. disguised.Disguise = disguise;
  180. Dirty(user, disguised);
  181. // make disguise look real (for simple things at least)
  182. var meta = MetaData(entity);
  183. _meta.SetEntityName(disguise, meta.EntityName);
  184. _meta.SetEntityDescription(disguise, meta.EntityDescription);
  185. var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
  186. comp.User = user;
  187. comp.Projector = ent;
  188. comp.SourceEntity = entity;
  189. comp.SourceProto = Prototype(entity)?.ID;
  190. Dirty(disguise, comp);
  191. // item disguises can be picked up to be revealed, also makes sure their examine size is correct
  192. CopyComp<ItemComponent>((disguise, comp));
  193. _appearance.CopyData(entity, disguise);
  194. }
  195. /// <summary>
  196. /// Removes the disguise, if the user is disguised.
  197. /// </summary>
  198. public bool TryReveal(Entity<ChameleonDisguisedComponent?> ent)
  199. {
  200. if (!Resolve(ent, ref ent.Comp, false))
  201. return false;
  202. if (TryComp<ChameleonDisguiseComponent>(ent.Comp.Disguise, out var disguise)
  203. && TryComp<ChameleonProjectorComponent>(disguise.Projector, out var proj))
  204. {
  205. proj.Disguised = null;
  206. }
  207. var xform = Transform(ent);
  208. xform.NoLocalRotation = false;
  209. _xform.Unanchor(ent, xform);
  210. Del(ent.Comp.Disguise);
  211. RemComp<ChameleonDisguisedComponent>(ent);
  212. return true;
  213. }
  214. /// <summary>
  215. /// Reveal a projector's user, if any.
  216. /// </summary>
  217. public void RevealProjector(Entity<ChameleonProjectorComponent> ent)
  218. {
  219. if (ent.Comp.Disguised is {} user)
  220. TryReveal(user);
  221. }
  222. #endregion
  223. /// <summary>
  224. /// Copy a component from the source entity/prototype to the disguise entity.
  225. /// </summary>
  226. /// <remarks>
  227. /// This would probably be a good thing to add to engine in the future.
  228. /// </remarks>
  229. protected bool CopyComp<T>(Entity<ChameleonDisguiseComponent> ent) where T: Component, new()
  230. {
  231. if (!GetSrcComp<T>(ent.Comp, out var src))
  232. return true;
  233. // remove then re-add to prevent a funny
  234. RemComp<T>(ent);
  235. var dest = AddComp<T>(ent);
  236. _serMan.CopyTo(src, ref dest, notNullableOverride: true);
  237. Dirty(ent, dest);
  238. return false;
  239. }
  240. /// <summary>
  241. /// Try to get a single component from the source entity/prototype.
  242. /// </summary>
  243. private bool GetSrcComp<T>(ChameleonDisguiseComponent comp, [NotNullWhen(true)] out T? src) where T : Component, new()
  244. {
  245. if (TryComp(comp.SourceEntity, out src))
  246. return true;
  247. if (comp.SourceProto is not { } protoId)
  248. return false;
  249. if (!_proto.TryIndex<EntityPrototype>(protoId, out var proto))
  250. return false;
  251. return proto.TryGetComponent(out src, EntityManager.ComponentFactory);
  252. }
  253. }
  254. /// <summary>
  255. /// Action event for toggling transform NoRot on a disguise.
  256. /// </summary>
  257. public sealed partial class DisguiseToggleNoRotEvent : InstantActionEvent
  258. {
  259. }
  260. /// <summary>
  261. /// Action event for toggling transform Anchored on a disguise.
  262. /// </summary>
  263. public sealed partial class DisguiseToggleAnchoredEvent : InstantActionEvent
  264. {
  265. }