PolymorphSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. using Content.Server.Actions;
  2. using Content.Server.Humanoid;
  3. using Content.Server.Inventory;
  4. using Content.Server.Mind.Commands;
  5. using Content.Server.Polymorph.Components;
  6. using Content.Shared.Actions;
  7. using Content.Shared.Buckle;
  8. using Content.Shared.Damage;
  9. using Content.Shared.Destructible;
  10. using Content.Shared.Hands.EntitySystems;
  11. using Content.Shared.IdentityManagement;
  12. using Content.Shared.Mind;
  13. using Content.Shared.Mobs.Components;
  14. using Content.Shared.Mobs.Systems;
  15. using Content.Shared.Nutrition;
  16. using Content.Shared.Polymorph;
  17. using Content.Shared.Popups;
  18. using Robust.Server.Audio;
  19. using Robust.Server.Containers;
  20. using Robust.Server.GameObjects;
  21. using Robust.Shared.Map;
  22. using Robust.Shared.Prototypes;
  23. using Robust.Shared.Timing;
  24. using Robust.Shared.Utility;
  25. namespace Content.Server.Polymorph.Systems;
  26. public sealed partial class PolymorphSystem : EntitySystem
  27. {
  28. [Dependency] private readonly IComponentFactory _compFact = default!;
  29. [Dependency] private readonly IMapManager _mapManager = default!;
  30. [Dependency] private readonly IPrototypeManager _proto = default!;
  31. [Dependency] private readonly IGameTiming _gameTiming = default!;
  32. [Dependency] private readonly ActionsSystem _actions = default!;
  33. [Dependency] private readonly AudioSystem _audio = default!;
  34. [Dependency] private readonly SharedBuckleSystem _buckle = default!;
  35. [Dependency] private readonly ContainerSystem _container = default!;
  36. [Dependency] private readonly DamageableSystem _damageable = default!;
  37. [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
  38. [Dependency] private readonly MobStateSystem _mobState = default!;
  39. [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
  40. [Dependency] private readonly ServerInventorySystem _inventory = default!;
  41. [Dependency] private readonly SharedHandsSystem _hands = default!;
  42. [Dependency] private readonly SharedPopupSystem _popup = default!;
  43. [Dependency] private readonly TransformSystem _transform = default!;
  44. [Dependency] private readonly SharedMindSystem _mindSystem = default!;
  45. [Dependency] private readonly MetaDataSystem _metaData = default!;
  46. private const string RevertPolymorphId = "ActionRevertPolymorph";
  47. public override void Initialize()
  48. {
  49. SubscribeLocalEvent<PolymorphableComponent, ComponentStartup>(OnComponentStartup);
  50. SubscribeLocalEvent<PolymorphedEntityComponent, MapInitEvent>(OnMapInit);
  51. SubscribeLocalEvent<PolymorphableComponent, PolymorphActionEvent>(OnPolymorphActionEvent);
  52. SubscribeLocalEvent<PolymorphedEntityComponent, RevertPolymorphActionEvent>(OnRevertPolymorphActionEvent);
  53. SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
  54. SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
  55. SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
  56. InitializeMap();
  57. InitializeTrigger();
  58. }
  59. public override void Update(float frameTime)
  60. {
  61. base.Update(frameTime);
  62. var query = EntityQueryEnumerator<PolymorphedEntityComponent>();
  63. while (query.MoveNext(out var uid, out var comp))
  64. {
  65. comp.Time += frameTime;
  66. if (comp.Configuration.Duration != null && comp.Time >= comp.Configuration.Duration)
  67. {
  68. Revert((uid, comp));
  69. continue;
  70. }
  71. if (!TryComp<MobStateComponent>(uid, out var mob))
  72. continue;
  73. if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) ||
  74. comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob))
  75. {
  76. Revert((uid, comp));
  77. }
  78. }
  79. UpdateTrigger();
  80. }
  81. private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
  82. {
  83. if (ent.Comp.InnatePolymorphs != null)
  84. {
  85. foreach (var morph in ent.Comp.InnatePolymorphs)
  86. {
  87. CreatePolymorphAction(morph, ent);
  88. }
  89. }
  90. }
  91. private void OnMapInit(Entity<PolymorphedEntityComponent> ent, ref MapInitEvent args)
  92. {
  93. var (uid, component) = ent;
  94. if (component.Configuration.Forced)
  95. return;
  96. if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
  97. {
  98. action.EntityIcon = component.Parent;
  99. action.UseDelay = TimeSpan.FromSeconds(component.Configuration.Delay);
  100. }
  101. }
  102. private void OnPolymorphActionEvent(Entity<PolymorphableComponent> ent, ref PolymorphActionEvent args)
  103. {
  104. if (!_proto.TryIndex(args.ProtoId, out var prototype) || args.Handled)
  105. return;
  106. PolymorphEntity(ent, prototype.Configuration);
  107. args.Handled = true;
  108. }
  109. private void OnRevertPolymorphActionEvent(Entity<PolymorphedEntityComponent> ent,
  110. ref RevertPolymorphActionEvent args)
  111. {
  112. Revert((ent, ent));
  113. }
  114. private void OnBeforeFullyEaten(Entity<PolymorphedEntityComponent> ent, ref BeforeFullyEatenEvent args)
  115. {
  116. var (_, comp) = ent;
  117. if (comp.Configuration.RevertOnEat)
  118. {
  119. args.Cancel();
  120. Revert((ent, ent));
  121. }
  122. }
  123. private void OnBeforeFullySliced(Entity<PolymorphedEntityComponent> ent, ref BeforeFullySlicedEvent args)
  124. {
  125. var (_, comp) = ent;
  126. if (comp.Configuration.RevertOnEat)
  127. {
  128. args.Cancel();
  129. Revert((ent, ent));
  130. }
  131. }
  132. /// <summary>
  133. /// It is possible to be polymorphed into an entity that can't "die", but is instead
  134. /// destroyed. This handler ensures that destruction is treated like death.
  135. /// </summary>
  136. private void OnDestruction(Entity<PolymorphedEntityComponent> ent, ref DestructionEventArgs args)
  137. {
  138. if (ent.Comp.Configuration.RevertOnDeath)
  139. {
  140. Revert((ent, ent));
  141. }
  142. }
  143. /// <summary>
  144. /// Polymorphs the target entity into the specific polymorph prototype
  145. /// </summary>
  146. /// <param name="uid">The entity that will be transformed</param>
  147. /// <param name="protoId">The id of the polymorph prototype</param>
  148. public EntityUid? PolymorphEntity(EntityUid uid, ProtoId<PolymorphPrototype> protoId)
  149. {
  150. var config = _proto.Index(protoId).Configuration;
  151. return PolymorphEntity(uid, config);
  152. }
  153. /// <summary>
  154. /// Polymorphs the target entity into another
  155. /// </summary>
  156. /// <param name="uid">The entity that will be transformed</param>
  157. /// <param name="configuration">Polymorph data</param>
  158. /// <returns></returns>
  159. public EntityUid? PolymorphEntity(EntityUid uid, PolymorphConfiguration configuration)
  160. {
  161. // if it's already morphed, don't allow it again with this condition active.
  162. if (!configuration.AllowRepeatedMorphs && HasComp<PolymorphedEntityComponent>(uid))
  163. return null;
  164. // If this polymorph has a cooldown, check if that amount of time has passed since the
  165. // last polymorph ended.
  166. if (TryComp<PolymorphableComponent>(uid, out var polymorphableComponent) &&
  167. polymorphableComponent.LastPolymorphEnd != null &&
  168. _gameTiming.CurTime < polymorphableComponent.LastPolymorphEnd + configuration.Cooldown)
  169. return null;
  170. // mostly just for vehicles
  171. _buckle.TryUnbuckle(uid, uid, true);
  172. var targetTransformComp = Transform(uid);
  173. if (configuration.PolymorphSound != null)
  174. _audio.PlayPvs(configuration.PolymorphSound, targetTransformComp.Coordinates);
  175. var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid));
  176. if (configuration.PolymorphPopup != null)
  177. _popup.PopupEntity(Loc.GetString(configuration.PolymorphPopup,
  178. ("parent", Identity.Entity(uid, EntityManager)),
  179. ("child", Identity.Entity(child, EntityManager))),
  180. child);
  181. MakeSentientCommand.MakeSentient(child, EntityManager);
  182. var polymorphedComp = _compFact.GetComponent<PolymorphedEntityComponent>();
  183. polymorphedComp.Parent = uid;
  184. polymorphedComp.Configuration = configuration;
  185. AddComp(child, polymorphedComp);
  186. var childXform = Transform(child);
  187. _transform.SetLocalRotation(child, targetTransformComp.LocalRotation, childXform);
  188. if (_container.TryGetContainingContainer((uid, targetTransformComp, null), out var cont))
  189. _container.Insert(child, cont);
  190. //Transfers all damage from the original to the new one
  191. if (configuration.TransferDamage &&
  192. TryComp<DamageableComponent>(child, out var damageParent) &&
  193. _mobThreshold.GetScaledDamage(uid, child, out var damage) &&
  194. damage != null)
  195. {
  196. _damageable.SetDamage(child, damageParent, damage);
  197. }
  198. if (configuration.Inventory == PolymorphInventoryChange.Transfer)
  199. {
  200. _inventory.TransferEntityInventories(uid, child);
  201. foreach (var hand in _hands.EnumerateHeld(uid))
  202. {
  203. _hands.TryDrop(uid, hand, checkActionBlocker: false);
  204. _hands.TryPickupAnyHand(child, hand);
  205. }
  206. }
  207. else if (configuration.Inventory == PolymorphInventoryChange.Drop)
  208. {
  209. if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator))
  210. {
  211. while (enumerator.MoveNext(out var slot))
  212. {
  213. _inventory.TryUnequip(uid, slot.ID, true, true);
  214. }
  215. }
  216. foreach (var held in _hands.EnumerateHeld(uid))
  217. {
  218. _hands.TryDrop(uid, held);
  219. }
  220. }
  221. if (configuration.TransferName && TryComp(uid, out MetaDataComponent? targetMeta))
  222. _metaData.SetEntityName(child, targetMeta.EntityName);
  223. if (configuration.TransferHumanoidAppearance)
  224. {
  225. _humanoid.CloneAppearance(uid, child);
  226. }
  227. if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
  228. _mindSystem.TransferTo(mindId, child, mind: mind);
  229. //Ensures a map to banish the entity to
  230. EnsurePausedMap();
  231. if (PausedMap != null)
  232. _transform.SetParent(uid, targetTransformComp, PausedMap.Value);
  233. // Raise an event to inform anything that wants to know about the entity swap
  234. var ev = new PolymorphedEvent(uid, child, false);
  235. RaiseLocalEvent(uid, ref ev);
  236. return child;
  237. }
  238. /// <summary>
  239. /// Reverts a polymorphed entity back into its original form
  240. /// </summary>
  241. /// <param name="uid">The entityuid of the entity being reverted</param>
  242. /// <param name="component"></param>
  243. public EntityUid? Revert(Entity<PolymorphedEntityComponent?> ent)
  244. {
  245. var (uid, component) = ent;
  246. if (!Resolve(ent, ref component))
  247. return null;
  248. if (Deleted(uid))
  249. return null;
  250. var parent = component.Parent;
  251. if (Deleted(parent))
  252. return null;
  253. var uidXform = Transform(uid);
  254. var parentXform = Transform(parent);
  255. if (component.Configuration.ExitPolymorphSound != null)
  256. _audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
  257. _transform.SetParent(parent, parentXform, uidXform.ParentUid);
  258. _transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);
  259. if (component.Configuration.TransferDamage &&
  260. TryComp<DamageableComponent>(parent, out var damageParent) &&
  261. _mobThreshold.GetScaledDamage(uid, parent, out var damage) &&
  262. damage != null)
  263. {
  264. _damageable.SetDamage(parent, damageParent, damage);
  265. }
  266. if (component.Configuration.Inventory == PolymorphInventoryChange.Transfer)
  267. {
  268. _inventory.TransferEntityInventories(uid, parent);
  269. foreach (var held in _hands.EnumerateHeld(uid))
  270. {
  271. _hands.TryDrop(uid, held);
  272. _hands.TryPickupAnyHand(parent, held, checkActionBlocker: false);
  273. }
  274. }
  275. else if (component.Configuration.Inventory == PolymorphInventoryChange.Drop)
  276. {
  277. if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator))
  278. {
  279. while (enumerator.MoveNext(out var slot))
  280. {
  281. _inventory.TryUnequip(uid, slot.ID);
  282. }
  283. }
  284. foreach (var held in _hands.EnumerateHeld(uid))
  285. {
  286. _hands.TryDrop(uid, held);
  287. }
  288. }
  289. if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
  290. _mindSystem.TransferTo(mindId, parent, mind: mind);
  291. if (TryComp<PolymorphableComponent>(parent, out var polymorphableComponent))
  292. polymorphableComponent.LastPolymorphEnd = _gameTiming.CurTime;
  293. // if an item polymorph was picked up, put it back down after reverting
  294. _transform.AttachToGridOrMap(parent, parentXform);
  295. // Raise an event to inform anything that wants to know about the entity swap
  296. var ev = new PolymorphedEvent(uid, parent, true);
  297. RaiseLocalEvent(uid, ref ev);
  298. if (component.Configuration.ExitPolymorphPopup != null)
  299. _popup.PopupEntity(Loc.GetString(component.Configuration.ExitPolymorphPopup,
  300. ("parent", Identity.Entity(uid, EntityManager)),
  301. ("child", Identity.Entity(parent, EntityManager))),
  302. parent);
  303. QueueDel(uid);
  304. return parent;
  305. }
  306. /// <summary>
  307. /// Creates a sidebar action for an entity to be able to polymorph at will
  308. /// </summary>
  309. /// <param name="id">The string of the id of the polymorph action</param>
  310. /// <param name="target">The entity that will be gaining the action</param>
  311. public void CreatePolymorphAction(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
  312. {
  313. target.Comp.PolymorphActions ??= new();
  314. if (target.Comp.PolymorphActions.ContainsKey(id))
  315. return;
  316. if (!_proto.TryIndex(id, out var polyProto))
  317. return;
  318. var entProto = _proto.Index(polyProto.Configuration.Entity);
  319. EntityUid? actionId = default!;
  320. if (!_actions.AddAction(target, ref actionId, RevertPolymorphId, target))
  321. return;
  322. target.Comp.PolymorphActions.Add(id, actionId.Value);
  323. var metaDataCache = MetaData(actionId.Value);
  324. _metaData.SetEntityName(actionId.Value, Loc.GetString("polymorph-self-action-name", ("target", entProto.Name)), metaDataCache);
  325. _metaData.SetEntityDescription(actionId.Value, Loc.GetString("polymorph-self-action-description", ("target", entProto.Name)), metaDataCache);
  326. if (!_actions.TryGetActionData(actionId, out var baseAction))
  327. return;
  328. baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyProto.Configuration.Entity);
  329. if (baseAction is InstantActionComponent action)
  330. action.Event = new PolymorphActionEvent(id);
  331. }
  332. public void RemovePolymorphAction(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
  333. {
  334. if (target.Comp.PolymorphActions == null)
  335. return;
  336. if (target.Comp.PolymorphActions.TryGetValue(id, out var val))
  337. _actions.RemoveAction(target, val);
  338. }
  339. }