using Content.Shared.Mobs.Systems;
using Content.Shared.Polymorph;
using Content.Shared.Popups;
-using JetBrains.Annotations;
using Robust.Server.Audio;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
-namespace Content.Server.Polymorph.Systems
+namespace Content.Server.Polymorph.Systems;
+
+public sealed partial class PolymorphSystem : EntitySystem
{
- 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 IGameTiming _gameTiming = default!;
+ [Dependency] private readonly ActionsSystem _actions = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+ [Dependency] private readonly AudioSystem _audio = default!;
+ [Dependency] private readonly SharedBuckleSystem _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!;
+ [Dependency] private readonly SharedMindSystem _mindSystem = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+
+ private const string RevertPolymorphId = "ActionRevertPolymorph";
+
+ public override void Initialize()
{
- [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 ActionContainerSystem _actionContainer = default!;
- [Dependency] private readonly AudioSystem _audio = default!;
- [Dependency] private readonly SharedBuckleSystem _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!;
- [Dependency] private readonly SharedMindSystem _mindSystem = default!;
- [Dependency] private readonly MetaDataSystem _metaData = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
-
- private ISawmill _sawmill = default!;
-
- private const string RevertPolymorphId = "ActionRevertPolymorph";
-
- public override void Initialize()
- {
- base.Initialize();
+ SubscribeLocalEvent<PolymorphableComponent, ComponentStartup>(OnComponentStartup);
+ SubscribeLocalEvent<PolymorphedEntityComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<PolymorphableComponent, ComponentStartup>(OnStartup);
- SubscribeLocalEvent<PolymorphableComponent, PolymorphActionEvent>(OnPolymorphActionEvent);
- SubscribeLocalEvent<PolymorphedEntityComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
- SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
- SubscribeLocalEvent<PolymorphedEntityComponent, RevertPolymorphActionEvent>(OnRevertPolymorphActionEvent);
- SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
+ SubscribeLocalEvent<PolymorphableComponent, PolymorphActionEvent>(OnPolymorphActionEvent);
+ SubscribeLocalEvent<PolymorphedEntityComponent, RevertPolymorphActionEvent>(OnRevertPolymorphActionEvent);
- InitializeCollide();
- InitializeMap();
-
- _sawmill = Logger.GetSawmill("polymorph");
- }
+ SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
+ SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
+ SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
- private void OnStartup(EntityUid uid, PolymorphableComponent component, ComponentStartup args)
- {
- if (component.InnatePolymorphs != null)
- {
- foreach (var morph in component.InnatePolymorphs)
- {
- CreatePolymorphAction(morph, uid);
- }
- }
- }
+ InitializeCollide();
+ InitializeMap();
+ }
- private void OnPolymorphActionEvent(EntityUid uid, PolymorphableComponent component, PolymorphActionEvent args)
- {
- PolymorphEntity(uid, args.Prototype);
- }
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
- private void OnRevertPolymorphActionEvent(EntityUid uid, PolymorphedEntityComponent component, RevertPolymorphActionEvent args)
+ var query = EntityQueryEnumerator<PolymorphedEntityComponent>();
+ while (query.MoveNext(out var uid, out var comp))
{
- Revert(uid, component);
- }
+ comp.Time += frameTime;
- private void OnMapInit(EntityUid uid, PolymorphedEntityComponent component, MapInitEvent args)
- {
- if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto))
+ if (comp.Configuration.Duration != null && comp.Time >= comp.Configuration.Duration)
{
- // warning instead of error because of the all-comps one entity test.
- _sawmill.Warning($"{nameof(PolymorphSystem)} encountered an improperly set up polymorph component while initializing. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}");
- RemCompDeferred(uid, component);
- return;
+ Revert((uid, comp));
+ continue;
}
- if (proto.Forced)
- return;
+ if (!TryComp<MobStateComponent>(uid, out var mob))
+ continue;
- if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
+ if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) ||
+ comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob))
{
- action.EntityIcon = component.Parent;
- action.UseDelay = TimeSpan.FromSeconds(proto.Delay);
+ Revert((uid, comp));
}
}
- private void OnBeforeFullyEaten(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullyEatenEvent args)
- {
- if (!_proto.TryIndex<PolymorphPrototype>(comp.Prototype, out var proto))
- {
- _sawmill.Error($"Invalid polymorph prototype {comp.Prototype}");
- return;
- }
+ UpdateCollide();
+ }
- if (proto.RevertOnEat)
+ private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
+ {
+ if (ent.Comp.InnatePolymorphs != null)
+ {
+ foreach (var morph in ent.Comp.InnatePolymorphs)
{
- args.Cancel();
- Revert(uid, comp);
+ CreatePolymorphAction(morph, ent);
}
}
+ }
- /// <summary>
- /// It is possible to be polymorphed into an entity that can't "die", but is instead
- /// destroyed. This handler ensures that destruction is treated like death.
- /// </summary>
- private void OnDestruction(EntityUid uid, PolymorphedEntityComponent comp, DestructionEventArgs args)
- {
- if (!_proto.TryIndex<PolymorphPrototype>(comp.Prototype, out var proto))
- {
- _sawmill.Error($"Invalid polymorph prototype {comp.Prototype}");
- return;
- }
+ private void OnMapInit(Entity<PolymorphedEntityComponent> ent, ref MapInitEvent args)
+ {
+ var (uid, component) = ent;
+ if (component.Configuration.Forced)
+ return;
- if (proto.RevertOnDeath)
- {
- Revert(uid, comp);
- }
+ if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
+ {
+ action.EntityIcon = component.Parent;
+ action.UseDelay = TimeSpan.FromSeconds(component.Configuration.Delay);
}
+ }
- private void OnBeforeFullySliced(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullySlicedEvent args)
- {
- if (!_proto.TryIndex<PolymorphPrototype>(comp.Prototype, out var proto))
- {
- _sawmill.Error("Invalid polymorph prototype {comp.Prototype}");
- return;
- }
+ private void OnPolymorphActionEvent(Entity<PolymorphableComponent> ent, ref PolymorphActionEvent args)
+ {
+ PolymorphEntity(ent, args.Prototype.Configuration);
+ }
- if (proto.RevertOnEat)
- {
- args.Cancel();
- Revert(uid, comp);
- }
- }
+ private void OnRevertPolymorphActionEvent(Entity<PolymorphedEntityComponent> ent,
+ ref RevertPolymorphActionEvent args)
+ {
+ Revert((ent, ent));
+ }
- /// <summary>
- /// Polymorphs the target entity into the specific polymorph prototype
- /// </summary>
- /// <param name="target">The entity that will be transformed</param>
- /// <param name="id">The id of the polymorph prototype</param>
- public EntityUid? PolymorphEntity(EntityUid target, string id)
+ private void OnBeforeFullyEaten(Entity<PolymorphedEntityComponent> ent, ref BeforeFullyEatenEvent args)
+ {
+ var (_, comp) = ent;
+ if (comp.Configuration.RevertOnEat)
{
- if (!_proto.TryIndex<PolymorphPrototype>(id, out var proto))
- {
- _sawmill.Error("Invalid polymorph prototype {id}");
- return null;
- }
+ args.Cancel();
+ Revert((ent, ent));
+ }
+ }
- return PolymorphEntity(target, proto);
+ private void OnBeforeFullySliced(Entity<PolymorphedEntityComponent> ent, ref BeforeFullySlicedEvent args)
+ {
+ var (_, comp) = ent;
+ if (comp.Configuration.RevertOnEat)
+ {
+ args.Cancel();
+ Revert((ent, ent));
}
+ }
- /// <summary>
- /// Polymorphs the target entity into the specific polymorph prototype
- /// </summary>
- /// <param name="uid">The entity that will be transformed</param>
- /// <param name="proto">The polymorph prototype</param>
- public EntityUid? PolymorphEntity(EntityUid uid, PolymorphPrototype proto)
+ /// <summary>
+ /// It is possible to be polymorphed into an entity that can't "die", but is instead
+ /// destroyed. This handler ensures that destruction is treated like death.
+ /// </summary>
+ private void OnDestruction(Entity<PolymorphedEntityComponent> ent, ref DestructionEventArgs args)
+ {
+ if (ent.Comp.Configuration.RevertOnDeath)
{
- // if it's already morphed, don't allow it again with this condition active.
- if (!proto.AllowRepeatedMorphs && HasComp<PolymorphedEntityComponent>(uid))
- return null;
+ Revert((ent, ent));
+ }
+ }
- // If this polymorph has a cooldown, check if that amount of time has passed since the
- // last polymorph ended.
- if (TryComp<PolymorphableComponent>(uid, out var polymorphableComponent) &&
- polymorphableComponent.LastPolymorphEnd != null &&
- _gameTiming.CurTime < polymorphableComponent.LastPolymorphEnd + proto.Cooldown)
- return null;
+ /// <summary>
+ /// Polymorphs the target entity into the specific polymorph prototype
+ /// </summary>
+ /// <param name="uid">The entity that will be transformed</param>
+ /// <param name="protoId">The id of the polymorph prototype</param>
+ public EntityUid? PolymorphEntity(EntityUid uid, ProtoId<PolymorphPrototype> protoId)
+ {
+ var config = _proto.Index(protoId).Configuration;
+ return PolymorphEntity(uid, config);
+ }
- // mostly just for vehicles
- _buckle.TryUnbuckle(uid, uid, true);
+ /// <summary>
+ /// Polymorphs the target entity into another
+ /// </summary>
+ /// <param name="uid">The entity that will be transformed</param>
+ /// <param name="configuration">Polymorph data</param>
+ /// <returns></returns>
+ public EntityUid? PolymorphEntity(EntityUid uid, PolymorphConfiguration configuration)
+ {
+ // if it's already morphed, don't allow it again with this condition active.
+ if (!configuration.AllowRepeatedMorphs && HasComp<PolymorphedEntityComponent>(uid))
+ return null;
- var targetTransformComp = Transform(uid);
+ // If this polymorph has a cooldown, check if that amount of time has passed since the
+ // last polymorph ended.
+ if (TryComp<PolymorphableComponent>(uid, out var polymorphableComponent) &&
+ polymorphableComponent.LastPolymorphEnd != null &&
+ _gameTiming.CurTime < polymorphableComponent.LastPolymorphEnd + configuration.Cooldown)
+ return null;
- var child = Spawn(proto.Entity, targetTransformComp.Coordinates);
- MakeSentientCommand.MakeSentient(child, EntityManager);
+ // mostly just for vehicles
+ _buckle.TryUnbuckle(uid, uid, true);
- var comp = _compFact.GetComponent<PolymorphedEntityComponent>();
- comp.Parent = uid;
- comp.Prototype = proto.ID;
- AddComp(child, comp);
+ var targetTransformComp = Transform(uid);
- var childXform = Transform(child);
- childXform.LocalRotation = targetTransformComp.LocalRotation;
+ var child = Spawn(configuration.Entity, targetTransformComp.Coordinates);
- if (_container.TryGetContainingContainer(uid, out var cont))
- _container.Insert(child, cont);
+ MakeSentientCommand.MakeSentient(child, EntityManager);
- //Transfers all damage from the original to the new one
- if (proto.TransferDamage &&
- TryComp<DamageableComponent>(child, out var damageParent) &&
- _mobThreshold.GetScaledDamage(uid, child, out var damage) &&
- damage != null)
- {
- _damageable.SetDamage(child, damageParent, damage);
- }
+ var polymorphedComp = _compFact.GetComponent<PolymorphedEntityComponent>();
+ polymorphedComp.Parent = uid;
+ polymorphedComp.Configuration = configuration;
+ AddComp(child, polymorphedComp);
+
+ var childXform = Transform(child);
+ _transform.SetLocalRotation(child, targetTransformComp.LocalRotation, childXform);
+
+ if (_container.TryGetContainingContainer(uid, out var cont))
+ _container.Insert(child, cont);
+
+ //Transfers all damage from the original to the new one
+ if (configuration.TransferDamage &&
+ TryComp<DamageableComponent>(child, out var damageParent) &&
+ _mobThreshold.GetScaledDamage(uid, child, out var damage) &&
+ damage != null)
+ {
+ _damageable.SetDamage(child, damageParent, damage);
+ }
- if (proto.Inventory == PolymorphInventoryChange.Transfer)
+ if (configuration.Inventory == PolymorphInventoryChange.Transfer)
+ {
+ _inventory.TransferEntityInventories(uid, child);
+ foreach (var hand in _hands.EnumerateHeld(uid))
{
- _inventory.TransferEntityInventories(uid, child);
- foreach (var hand in _hands.EnumerateHeld(uid))
- {
- _hands.TryDrop(uid, hand, checkActionBlocker: false);
- _hands.TryPickupAnyHand(child, hand);
- }
+ _hands.TryDrop(uid, hand, checkActionBlocker: false);
+ _hands.TryPickupAnyHand(child, hand);
}
- else if (proto.Inventory == PolymorphInventoryChange.Drop)
+ }
+ else if (configuration.Inventory == PolymorphInventoryChange.Drop)
+ {
+ if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator))
{
- 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))
+ while (enumerator.MoveNext(out var slot))
{
- _hands.TryDrop(uid, held);
+ _inventory.TryUnequip(uid, slot.ID, true, true);
}
}
- if (proto.TransferName && TryComp<MetaDataComponent>(uid, out var targetMeta))
- _metaData.SetEntityName(child, targetMeta.EntityName);
-
- if (proto.TransferHumanoidAppearance)
+ foreach (var held in _hands.EnumerateHeld(uid))
{
- _humanoid.CloneAppearance(uid, child);
+ _hands.TryDrop(uid, held);
}
+ }
- if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
- _mindSystem.TransferTo(mindId, child, mind: mind);
-
- //Ensures a map to banish the entity to
- EnsurePausesdMap();
- if (PausedMap != null)
- _transform.SetParent(uid, targetTransformComp, PausedMap.Value);
+ if (configuration.TransferName && TryComp<MetaDataComponent>(uid, out var targetMeta))
+ _metaData.SetEntityName(child, targetMeta.EntityName);
- return child;
+ if (configuration.TransferHumanoidAppearance)
+ {
+ _humanoid.CloneAppearance(uid, child);
}
- /// <summary>
- /// Reverts a polymorphed entity back into its original form
- /// </summary>
- /// <param name="uid">The entityuid of the entity being reverted</param>
- /// <param name="component"></param>
- public EntityUid? Revert(EntityUid uid, PolymorphedEntityComponent? component = null)
- {
- if (Deleted(uid))
- return null;
+ if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
+ _mindSystem.TransferTo(mindId, child, mind: mind);
- if (!Resolve(uid, ref component))
- return null;
+ //Ensures a map to banish the entity to
+ EnsurePausedMap();
+ if (PausedMap != null)
+ _transform.SetParent(uid, targetTransformComp, PausedMap.Value);
- var parent = component.Parent;
- if (Deleted(parent))
- return null;
+ return child;
+ }
- if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto))
- {
- _sawmill.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while reverting. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}");
- return null;
- }
+ /// <summary>
+ /// Reverts a polymorphed entity back into its original form
+ /// </summary>
+ /// <param name="uid">The entityuid of the entity being reverted</param>
+ /// <param name="component"></param>
+ public EntityUid? Revert(Entity<PolymorphedEntityComponent?> ent)
+ {
+ var (uid, component) = ent;
+ if (!Resolve(ent, ref component))
+ return null;
- var uidXform = Transform(uid);
- var parentXform = Transform(parent);
+ if (Deleted(uid))
+ return null;
- _transform.SetParent(parent, parentXform, uidXform.ParentUid);
- parentXform.Coordinates = uidXform.Coordinates;
- parentXform.LocalRotation = uidXform.LocalRotation;
+ var parent = component.Parent;
+ if (Deleted(parent))
+ return null;
- if (proto.TransferDamage &&
- TryComp<DamageableComponent>(parent, out var damageParent) &&
- _mobThreshold.GetScaledDamage(uid, parent, out var damage) &&
- damage != null)
- {
- _damageable.SetDamage(parent, damageParent, damage);
- }
+ var uidXform = Transform(uid);
+ var parentXform = Transform(parent);
+
+ _transform.SetParent(parent, parentXform, uidXform.ParentUid);
+ _transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);
- if (proto.Inventory == PolymorphInventoryChange.Transfer)
+ if (component.Configuration.TransferDamage &&
+ TryComp<DamageableComponent>(parent, out var damageParent) &&
+ _mobThreshold.GetScaledDamage(uid, parent, out var damage) &&
+ damage != null)
+ {
+ _damageable.SetDamage(parent, damageParent, damage);
+ }
+
+ if (component.Configuration.Inventory == PolymorphInventoryChange.Transfer)
+ {
+ _inventory.TransferEntityInventories(uid, parent);
+ foreach (var held in _hands.EnumerateHeld(uid))
{
- _inventory.TransferEntityInventories(uid, parent);
- foreach (var held in _hands.EnumerateHeld(uid))
- {
- _hands.TryDrop(uid, held);
- _hands.TryPickupAnyHand(parent, held, checkActionBlocker: false);
- }
+ _hands.TryDrop(uid, held);
+ _hands.TryPickupAnyHand(parent, held, checkActionBlocker: false);
}
- else if (proto.Inventory == PolymorphInventoryChange.Drop)
+ }
+ else if (component.Configuration.Inventory == PolymorphInventoryChange.Drop)
+ {
+ if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator))
{
- 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))
+ while (enumerator.MoveNext(out var slot))
{
- _hands.TryDrop(uid, held);
+ _inventory.TryUnequip(uid, slot.ID);
}
}
- if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
- _mindSystem.TransferTo(mindId, parent, mind: mind);
-
- if (TryComp<PolymorphableComponent>(parent, out var polymorphableComponent))
- polymorphableComponent.LastPolymorphEnd = _gameTiming.CurTime;
-
- // if an item polymorph was picked up, put it back down after reverting
- Transform(parent).AttachToGridOrMap();
-
- _popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic",
- ("parent", Identity.Entity(uid, EntityManager)),
- ("child", Identity.Entity(parent, EntityManager))),
- parent);
- QueueDel(uid);
-
- return parent;
- }
-
- /// <summary>
- /// Creates a sidebar action for an entity to be able to polymorph at will
- /// </summary>
- /// <param name="id">The string of the id of the polymorph action</param>
- /// <param name="target">The entity that will be gaining the action</param>
- public void CreatePolymorphAction(string id, EntityUid target)
- {
- if (!_proto.TryIndex<PolymorphPrototype>(id, out var polyproto))
+ foreach (var held in _hands.EnumerateHeld(uid))
{
- _sawmill.Error("Invalid polymorph prototype");
- return;
+ _hands.TryDrop(uid, held);
}
+ }
- if (!TryComp<PolymorphableComponent>(target, out var polycomp))
- return;
-
- polycomp.PolymorphActions ??= new Dictionary<string, EntityUid>();
- if (polycomp.PolymorphActions.ContainsKey(id))
- return;
+ if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
+ _mindSystem.TransferTo(mindId, parent, mind: mind);
- var entproto = _proto.Index<EntityPrototype>(polyproto.Entity);
+ if (TryComp<PolymorphableComponent>(parent, out var polymorphableComponent))
+ polymorphableComponent.LastPolymorphEnd = _gameTiming.CurTime;
- EntityUid? actionId = default!;
- if (!_actions.AddAction(target, ref actionId, RevertPolymorphId, target))
- return;
+ // if an item polymorph was picked up, put it back down after reverting
+ _transform.AttachToGridOrMap(parent, parentXform);
- polycomp.PolymorphActions.Add(id, actionId.Value);
- _metaData.SetEntityName(actionId.Value, Loc.GetString("polymorph-self-action-name", ("target", entproto.Name)));
- _metaData.SetEntityDescription(actionId.Value, Loc.GetString("polymorph-self-action-description", ("target", entproto.Name)));
+ _popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic",
+ ("parent", Identity.Entity(uid, EntityManager)),
+ ("child", Identity.Entity(parent, EntityManager))),
+ parent);
+ QueueDel(uid);
- if (!_actions.TryGetActionData(actionId, out var baseAction))
- return;
+ return parent;
+ }
- baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyproto.Entity);
- if (baseAction is InstantActionComponent action)
- action.Event = new PolymorphActionEvent { Prototype = polyproto };
- }
+ /// <summary>
+ /// Creates a sidebar action for an entity to be able to polymorph at will
+ /// </summary>
+ /// <param name="id">The string of the id of the polymorph action</param>
+ /// <param name="target">The entity that will be gaining the action</param>
+ public void CreatePolymorphAction(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
+ {
+ target.Comp.PolymorphActions ??= new();
+ if (target.Comp.PolymorphActions.ContainsKey(id))
+ return;
- [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);
- }
+ var polyProto = _proto.Index(id);
+ var entProto = _proto.Index(polyProto.Configuration.Entity);
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
+ EntityUid? actionId = default!;
+ if (!_actions.AddAction(target, ref actionId, RevertPolymorphId, target))
+ return;
- var query = EntityQueryEnumerator<PolymorphedEntityComponent>();
- while (query.MoveNext(out var uid, out var comp))
- {
- comp.Time += frameTime;
+ target.Comp.PolymorphActions.Add(id, actionId.Value);
- if (!_proto.TryIndex(comp.Prototype, out PolymorphPrototype? proto))
- {
- _sawmill.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while updating. Entity {ToPrettyString(uid)}. Prototype: {comp.Prototype}");
- RemCompDeferred(uid, comp);
- continue;
- }
+ var metaDataCache = MetaData(actionId.Value);
+ _metaData.SetEntityName(actionId.Value, Loc.GetString("polymorph-self-action-name", ("target", entProto.Name)), metaDataCache);
+ _metaData.SetEntityDescription(actionId.Value, Loc.GetString("polymorph-self-action-description", ("target", entProto.Name)), metaDataCache);
- if (proto.Duration != null && comp.Time >= proto.Duration)
- {
- Revert(uid, comp);
- continue;
- }
+ if (!_actions.TryGetActionData(actionId, out var baseAction))
+ return;
- if (!TryComp<MobStateComponent>(uid, out var mob))
- continue;
+ baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyProto.Configuration.Entity);
+ if (baseAction is InstantActionComponent action)
+ action.Event = new PolymorphActionEvent(prototype: polyProto);
+ }
- if (proto.RevertOnDeath && _mobState.IsDead(uid, mob) ||
- proto.RevertOnCrit && _mobState.IsIncapacitated(uid, mob))
- {
- Revert(uid, comp);
- }
- }
+ public void RemovePolymorphAction(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
+ {
+ if (target.Comp.PolymorphActions == null)
+ return;
- UpdateCollide();
- }
+ if (target.Comp.PolymorphActions.TryGetValue(id, out var val))
+ _actions.RemoveAction(target, val);
}
}
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
-namespace Content.Shared.Polymorph
+namespace Content.Shared.Polymorph;
+
+/// <summary>
+/// Polymorphs generally describe any type of transformation that can be applied to an entity.
+/// </summary>
+[Prototype]
+[DataDefinition]
+public sealed partial class PolymorphPrototype : IPrototype, IInheritingPrototype
+{
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ [ParentDataField(typeof(AbstractPrototypeIdArraySerializer<PolymorphPrototype>))]
+ public string[]? Parents { get; private set; }
+
+ [NeverPushInheritance]
+ [AbstractDataField]
+ public bool Abstract { get; private set; }
+
+ [DataField(required: true, serverOnly: true)]
+ public PolymorphConfiguration Configuration = new();
+
+}
+
+/// <summary>
+/// Defines information about the polymorph
+/// </summary>
+[DataDefinition]
+public sealed partial record PolymorphConfiguration
{
/// <summary>
- /// Polymorphs generally describe any type of transformation that can be applied to an entity.
+ /// What entity the polymorph will turn the target into
+ /// must be in here because it makes no sense if it isn't
+ /// </summary>
+ [DataField(required: true, serverOnly: true)]
+ public EntProtoId Entity;
+
+ /// <summary>
+ /// The delay between the polymorph's uses in seconds
+ /// Slightly weird as of right now.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public int Delay = 60;
+
+ /// <summary>
+ /// The duration of the transformation in seconds
+ /// can be null if there is not one
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public int? Duration;
+
+ /// <summary>
+ /// whether or not the target can transform as will
+ /// set to true for things like polymorph spells and curses
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool Forced;
+
+ /// <summary>
+ /// Whether or not the entity transfers its damage between forms.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool TransferDamage = true;
+
+ /// <summary>
+ /// Whether or not the entity transfers its name between forms.
/// </summary>
- [Prototype("polymorph")]
- [DataDefinition]
- public sealed partial class PolymorphPrototype : IPrototype, IInheritingPrototype
- {
- [ViewVariables]
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- [DataField("name")]
- public string Name { get; private set; } = string.Empty;
-
- [ParentDataField(typeof(AbstractPrototypeIdArraySerializer<PolymorphPrototype>))]
- public string[]? Parents { get; private set; }
-
- [NeverPushInheritance]
- [AbstractDataFieldAttribute]
- public bool Abstract { get; private set; }
-
- /// <summary>
- /// What entity the polymorph will turn the target into
- /// must be in here because it makes no sense if it isn't
- /// </summary>
- [DataField("entity", required: true, serverOnly: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string Entity = string.Empty;
-
- /// <summary>
- /// The delay between the polymorph's uses in seconds
- /// Slightly weird as of right now.
- /// </summary>
- [DataField("delay", serverOnly: true)]
- public int Delay = 60;
-
- /// <summary>
- /// The duration of the transformation in seconds
- /// can be null if there is not one
- /// </summary>
- [DataField("duration", serverOnly: true)]
- public int? Duration = null;
-
- /// <summary>
- /// whether or not the target can transform as will
- /// set to true for things like polymorph spells and curses
- /// </summary>
- [DataField("forced", serverOnly: true)]
- public bool Forced = false;
-
- /// <summary>
- /// Whether or not the entity transfers its damage between forms.
- /// </summary>
- [DataField("transferDamage", serverOnly: true)]
- public bool TransferDamage = true;
-
- /// <summary>
- /// Whether or not the entity transfers its name between forms.
- /// </summary>
- [DataField("transferName", serverOnly: true)]
- public bool TransferName = false;
-
- /// <summary>
- /// Whether or not the entity transfers its hair, skin color, hair color, etc.
- /// </summary>
- [DataField("transferHumanoidAppearance", serverOnly: true)]
- public bool TransferHumanoidAppearance = false;
-
- /// <summary>
- /// Whether or not the entity transfers its inventory and equipment between forms.
- /// </summary>
- [DataField("inventory", serverOnly: true)]
- public PolymorphInventoryChange Inventory = PolymorphInventoryChange.None;
-
- /// <summary>
- /// Whether or not the polymorph reverts when the entity goes into crit.
- /// </summary>
- [DataField("revertOnCrit", serverOnly: true)]
- public bool RevertOnCrit = true;
-
- /// <summary>
- /// Whether or not the polymorph reverts when the entity dies.
- /// </summary>
- [DataField("revertOnDeath", serverOnly: true)]
- public bool RevertOnDeath = true;
-
- /// <summary>
- /// Whether or not the polymorph reverts when the entity is eaten or fully sliced.
- /// </summary>
- [DataField("revertOnEat", serverOnly: true)]
- public bool RevertOnEat = false;
-
- [DataField("allowRepeatedMorphs", serverOnly: true)]
- public bool AllowRepeatedMorphs = false;
-
- /// <summary>
- /// The amount of time that should pass after this polymorph has ended, before a new one
- /// can occur.
- /// </summary>
- [DataField("cooldown", serverOnly: true)]
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan Cooldown = TimeSpan.Zero;
- }
-
- public enum PolymorphInventoryChange : byte
- {
- None,
- Drop,
- Transfer,
- }
+ [DataField(serverOnly: true)]
+ public bool TransferName;
+
+ /// <summary>
+ /// Whether or not the entity transfers its hair, skin color, hair color, etc.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool TransferHumanoidAppearance;
+
+ /// <summary>
+ /// Whether or not the entity transfers its inventory and equipment between forms.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public PolymorphInventoryChange Inventory = PolymorphInventoryChange.None;
+
+ /// <summary>
+ /// Whether or not the polymorph reverts when the entity goes into crit.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool RevertOnCrit = true;
+
+ /// <summary>
+ /// Whether or not the polymorph reverts when the entity dies.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool RevertOnDeath = true;
+
+ /// <summary>
+ /// Whether or not the polymorph reverts when the entity is eaten or fully sliced.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool RevertOnEat;
+
+ /// <summary>
+ /// Whether or not an already polymorphed entity is able to be polymorphed again
+ /// </summary>
+ [DataField(serverOnly: true)]
+ public bool AllowRepeatedMorphs;
+
+ /// <summary>
+ /// The amount of time that should pass after this polymorph has ended, before a new one
+ /// can occur.
+ /// </summary>
+ [DataField(serverOnly: true)]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan Cooldown = TimeSpan.Zero;
+}
+
+public enum PolymorphInventoryChange : byte
+{
+ None,
+ Drop,
+ Transfer,
}