using Content.Server.Actions; using Content.Server.Buckle.Systems; using Content.Server.Humanoid; using Content.Server.Inventory; using Content.Server.Mind.Commands; using Content.Server.Mind.Components; using Content.Server.Polymorph.Components; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Damage; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Polymorph; using Content.Shared.Popups; using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Polymorph.Systems { public sealed partial class PolymorphSystem : EntitySystem { [Dependency] private readonly IComponentFactory _compFact = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; [Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TransformSystem _transform = default!; private readonly ISawmill _saw = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnPolymorphActionEvent); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnRevertPolymorphActionEvent); InitializeCollide(); InitializeMap(); } private void OnStartup(EntityUid uid, PolymorphableComponent component, ComponentStartup args) { if (component.InnatePolymorphs != null) { foreach (var morph in component.InnatePolymorphs) { CreatePolymorphAction(morph, uid); } } } private void OnPolymorphActionEvent(EntityUid uid, PolymorphableComponent component, PolymorphActionEvent args) { PolymorphEntity(uid, args.Prototype); } private void OnRevertPolymorphActionEvent(EntityUid uid, PolymorphedEntityComponent component, RevertPolymorphActionEvent args) { Revert(uid, component); } public void OnStartup(EntityUid uid, PolymorphedEntityComponent component, ComponentStartup args) { if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto)) { // warning instead of error because of the all-comps one entity test. Logger.Warning($"{nameof(PolymorphSystem)} encountered an improperly set up polymorph component while initializing. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); RemCompDeferred(uid, component); return; } if (proto.Forced) return; var act = new InstantAction { Event = new RevertPolymorphActionEvent(), EntityIcon = component.Parent, DisplayName = Loc.GetString("polymorph-revert-action-name"), Description = Loc.GetString("polymorph-revert-action-description"), UseDelay = TimeSpan.FromSeconds(proto.Delay), }; _actions.AddAction(uid, act, null); } /// /// Polymorphs the target entity into the specific polymorph prototype /// /// The entity that will be transformed /// The id of the polymorph prototype public EntityUid? PolymorphEntity(EntityUid target, string id) { if (!_proto.TryIndex(id, out var proto)) { _saw.Error("Invalid polymorph prototype"); return null; } return PolymorphEntity(target, proto); } /// /// Polymorphs the target entity into the specific polymorph prototype /// /// The entity that will be transformed /// The polymorph prototype public EntityUid? PolymorphEntity(EntityUid uid, PolymorphPrototype proto) { // if it's already morphed, don't allow it again with this condition active. if (!proto.AllowRepeatedMorphs && HasComp(uid)) return null; // mostly just for vehicles _buckle.TryUnbuckle(uid, uid, true); var targetTransformComp = Transform(uid); var child = Spawn(proto.Entity, targetTransformComp.Coordinates); MakeSentientCommand.MakeSentient(child, EntityManager); var comp = _compFact.GetComponent(); comp.Owner = child; comp.Parent = uid; comp.Prototype = proto.ID; EntityManager.AddComponent(child, comp); var childXform = Transform(child); childXform.LocalRotation = targetTransformComp.LocalRotation; if (_container.TryGetContainingContainer(uid, out var cont)) cont.Insert(child); //Transfers all damage from the original to the new one if (proto.TransferDamage && TryComp(child, out var damageParent) && _mobThreshold.GetScaledDamage(uid, child, out var damage) && damage != null) { _damageable.SetDamage(damageParent, damage); } if (proto.Inventory == PolymorphInventoryChange.Transfer) { _inventory.TransferEntityInventories(uid, child); foreach (var hand in _hands.EnumerateHeld(uid)) { _hands.TryDrop(uid, hand, checkActionBlocker: false); _hands.TryPickupAnyHand(child, hand); } } else if (proto.Inventory == PolymorphInventoryChange.Drop) { if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator)) { while (enumerator.MoveNext(out var slot)) { _inventory.TryUnequip(uid, slot.ID, true, true); } } foreach (var held in _hands.EnumerateHeld(uid)) { _hands.TryDrop(uid, held); } } if (proto.TransferName && TryComp(uid, out var targetMeta) && TryComp(child, out var childMeta)) { childMeta.EntityName = targetMeta.EntityName; } if (proto.TransferHumanoidAppearance) { _humanoid.CloneAppearance(uid, child); } if (TryComp(uid, out var mind) && mind.Mind != null) mind.Mind.TransferTo(child); //Ensures a map to banish the entity to EnsurePausesdMap(); if (PausedMap != null) _transform.SetParent(uid, targetTransformComp, PausedMap.Value); return child; } /// /// Reverts a polymorphed entity back into its original form /// /// The entityuid of the entity being reverted /// public void Revert(EntityUid uid, PolymorphedEntityComponent? component = null) { if (Deleted(uid)) return; if (!Resolve(uid, ref component)) return; var parent = component.Parent; if (Deleted(parent)) return; if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto)) { Logger.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while reverting. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); return; } var uidXform = Transform(uid); var parentXform = Transform(parent); _transform.SetParent(parent, parentXform, uidXform.ParentUid); parentXform.Coordinates = uidXform.Coordinates; parentXform.LocalRotation = uidXform.LocalRotation; if (_container.TryGetContainingContainer(uid, out var cont)) cont.Insert(component.Parent); if (proto.TransferDamage && TryComp(parent, out var damageParent) && _mobThreshold.GetScaledDamage(uid, parent, out var damage) && damage != null) { _damageable.SetDamage(damageParent, damage); } if (proto.Inventory == PolymorphInventoryChange.Transfer) { _inventory.TransferEntityInventories(uid, parent); foreach (var held in _hands.EnumerateHeld(uid)) { _hands.TryDrop(uid, held); _hands.TryPickupAnyHand(parent, held, checkActionBlocker: false); } } else if (proto.Inventory == PolymorphInventoryChange.Drop) { if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator)) { while (enumerator.MoveNext(out var slot)) { _inventory.TryUnequip(uid, slot.ID); } } foreach (var held in _hands.EnumerateHeld(uid)) { _hands.TryDrop(uid, held); } } if (TryComp(uid, out var mind) && mind.Mind != null) { mind.Mind.TransferTo(parent); } _popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic", ("parent", Identity.Entity(uid, EntityManager)), ("child", Identity.Entity(parent, EntityManager))), parent); QueueDel(uid); } /// /// Creates a sidebar action for an entity to be able to polymorph at will /// /// The string of the id of the polymorph action /// The entity that will be gaining the action public void CreatePolymorphAction(string id, EntityUid target) { if (!_proto.TryIndex(id, out var polyproto)) { _saw.Error("Invalid polymorph prototype"); return; } if (!TryComp(target, out var polycomp)) return; var entproto = _proto.Index(polyproto.Entity); var act = new InstantAction { Event = new PolymorphActionEvent { Prototype = polyproto, }, DisplayName = Loc.GetString("polymorph-self-action-name", ("target", entproto.Name)), Description = Loc.GetString("polymorph-self-action-description", ("target", entproto.Name)), Icon = new SpriteSpecifier.EntityPrototype(polyproto.Entity), ItemIconStyle = ItemActionIconStyle.NoItem, }; polycomp.PolymorphActions ??= new(); polycomp.PolymorphActions.Add(id, act); _actions.AddAction(target, act, target); } [PublicAPI] public void RemovePolymorphAction(string id, EntityUid target, PolymorphableComponent? component = null) { if (!Resolve(target, ref component, false)) return; if (component.PolymorphActions == null) return; if (component.PolymorphActions.TryGetValue(id, out var val)) _actions.RemoveAction(target, val); } public override void Update(float frameTime) { base.Update(frameTime); foreach (var comp in EntityQuery()) { comp.Time += frameTime; var ent = comp.Owner; if (!_proto.TryIndex(comp.Prototype, out PolymorphPrototype? proto)) { Logger.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while updating. Entity {ToPrettyString(ent)}. Prototype: {comp.Prototype}"); RemCompDeferred(ent, comp); continue; } if(proto.Duration != null && comp.Time >= proto.Duration) Revert(ent, comp); if (!TryComp(ent, out var mob)) continue; if (proto.RevertOnDeath && _mobState.IsDead(ent, mob) || proto.RevertOnCrit && _mobState.IsIncapacitated(ent, mob)) Revert(ent, comp); } UpdateCollide(); } } public sealed class PolymorphActionEvent : InstantActionEvent { /// /// The polymorph prototype containing all the information about /// the specific polymorph. /// public PolymorphPrototype Prototype = default!; } public sealed class RevertPolymorphActionEvent : InstantActionEvent { } }