fish-station/Content.Server/Polymorph/Systems/PolymorphSystem.cs
Vigers Ray b378fdc706 Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	Content.Client/Entry/EntryPoint.cs
#	Content.Client/Stylesheets/StyleNano.cs
#	Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs
#	Content.Server/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Shared/Follower/FollowerSystem.cs
#	Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/PAI/SharedPAISystem.cs
#	Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
#	Resources/Locale/en-US/_strings/access/components/genpop.ftl
#	Resources/Locale/en-US/_strings/doors/components/turnstile.ftl
#	Resources/Locale/en-US/_strings/power/battery.ftl
#	Resources/Locale/en-US/_strings/store/pai-catalog.ftl
#	Resources/Locale/en-US/_strings/tools/simple-tool-usage.ftl
#	Resources/Locale/en-US/_strings/ui/controls.ftl
#	Resources/Locale/en-US/_strings/weapons/ranged/upgrades.ftl
#	Resources/Prototypes/Access/misc.yml
#	Resources/Prototypes/Catalog/Cargo/cargo_vending.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
#	Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml
#	Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
#	Resources/Prototypes/Entities/Mobs/Player/dragon.yml
#	Resources/Prototypes/Entities/Mobs/Player/humanoid.yml
#	Resources/Prototypes/Entities/Objects/Fun/toys.yml
#	Resources/Prototypes/Entities/Objects/Misc/paper.yml
#	Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
#	Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/shotgun.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
#	Resources/Prototypes/Entities/Stations/base.yml
#	Resources/Prototypes/Entities/Structures/Walls/asteroid.yml
#	Resources/Prototypes/Loadouts/role_loadouts.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/science.yml
#	Resources/Prototypes/Research/industrial.yml
#	Resources/Prototypes/Species/vox.yml
#	Resources/Prototypes/Store/currency.yml
#	Resources/Prototypes/Voice/speech_emotes.yml
#	Resources/Prototypes/ai_factions.yml
#	Resources/Prototypes/borg_types.yml
#	Resources/Textures/Clothing/Head/Hats/captain.rsi/meta.json
#	Resources/Textures/Clothing/Uniforms/Jumpskirt/capformaldress.rsi/meta.json
#	Resources/Textures/Clothing/Uniforms/Jumpskirt/captain.rsi/meta.json
#	Resources/Textures/Clothing/Uniforms/Jumpskirt/paramedic.rsi/equipped-INNERCLOTHING.png
#	Resources/Textures/Clothing/Uniforms/Jumpskirt/paramedic.rsi/icon.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/capformal.rsi/equipped-INNERCLOTHING-monkey.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/capformal.rsi/meta.json
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/captain.rsi/equipped-INNERCLOTHING-monkey.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/captain.rsi/meta.json
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/paramedic.rsi/equipped-INNERCLOTHING-monkey.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/paramedic.rsi/equipped-INNERCLOTHING.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/paramedic.rsi/icon.png
#	Resources/Textures/Clothing/Uniforms/Jumpsuit/paramedic.rsi/meta.json
#	Resources/Textures/Objects/Weapons/Melee/captain_sabre.rsi/meta.json
#	Resources/Textures/Objects/Weapons/Melee/energykatana.rsi/meta.json
#	Resources/Textures/Objects/Weapons/Melee/katana.rsi/meta.json
#	Resources/Textures/Objects/Weapons/Melee/machete.rsi/meta.json
#	Resources/Textures/Structures/Doors/Airlocks/Glass/firelock.rsi/closed.png
#	Resources/Textures/Structures/Doors/Airlocks/Glass/firelock.rsi/closing.png
#	Resources/Textures/Structures/Doors/Airlocks/Glass/firelock.rsi/deny.png
#	Resources/Textures/Structures/Doors/Airlocks/Glass/firelock.rsi/locked.png
#	Resources/Textures/Structures/Doors/Airlocks/Glass/firelock.rsi/opening.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/assembly.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/closed.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/closing.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/meta.json
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/open.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/opening.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/firelock.rsi/closed.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/firelock.rsi/closing.png
#	Resources/Textures/Structures/Doors/Airlocks/Standard/firelock.rsi/opening.png
#	Resources/Textures/Structures/Wallmounts/posters.rsi/poster4_contraband.png
#	Resources/migration.yml
2025-04-28 01:29:55 +03:00

430 lines
16 KiB
C#

using Content.Server.Actions;
using Content.Server.Humanoid;
using Content.Server.Inventory;
using Content.Server.Mind.Commands;
using Content.Shared.Nutrition;
using Content.Server.Polymorph.Components;
using Content.Shared.Actions;
using Content.Shared.Buckle;
using Content.Shared.Coordinates;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition;
using Content.Shared.Polymorph;
using Content.Shared.Popups;
using Robust.Server.Audio;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
// Sunrise-Start
using Content.Shared.EntityEffects;
using Content.Shared.Mind.Components;
// Sunrise-End
namespace Content.Server.Polymorph.Systems;
public sealed partial class PolymorphSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ActionsSystem _actions = 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()
{
SubscribeLocalEvent<PolymorphableComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<PolymorphedEntityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PolymorphableComponent, PolymorphActionEvent>(OnPolymorphActionEvent);
SubscribeLocalEvent<PolymorphedEntityComponent, RevertPolymorphActionEvent>(OnRevertPolymorphActionEvent);
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
InitializeMap();
InitializeTrigger();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<PolymorphedEntityComponent>();
while (query.MoveNext(out var uid, out var comp))
{
comp.Time += frameTime;
if (comp.Configuration.Duration != null && comp.Time >= comp.Configuration.Duration)
{
Revert((uid, comp));
continue;
}
if (!TryComp<MobStateComponent>(uid, out var mob))
continue;
if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) ||
comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob))
{
Revert((uid, comp));
}
}
UpdateTrigger();
}
private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
{
if (ent.Comp.InnatePolymorphs != null)
{
foreach (var morph in ent.Comp.InnatePolymorphs)
{
CreatePolymorphAction(morph, ent);
}
}
}
private void OnMapInit(Entity<PolymorphedEntityComponent> ent, ref MapInitEvent args)
{
var (uid, component) = ent;
if (component.Configuration.Forced)
return;
if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
{
action.EntityIcon = component.Parent;
action.UseDelay = TimeSpan.FromSeconds(component.Configuration.Delay);
}
}
private void OnPolymorphActionEvent(Entity<PolymorphableComponent> ent, ref PolymorphActionEvent args)
{
if (!_proto.TryIndex(args.ProtoId, out var prototype) || args.Handled)
return;
PolymorphEntity(ent, prototype.Configuration);
args.Handled = true;
}
private void OnRevertPolymorphActionEvent(Entity<PolymorphedEntityComponent> ent,
ref RevertPolymorphActionEvent args)
{
Revert((ent, ent));
}
private void OnBeforeFullyEaten(Entity<PolymorphedEntityComponent> ent, ref BeforeFullyEatenEvent args)
{
var (_, comp) = ent;
if (comp.Configuration.RevertOnEat)
{
args.Cancel();
Revert((ent, ent));
}
}
private void OnBeforeFullySliced(Entity<PolymorphedEntityComponent> ent, ref BeforeFullySlicedEvent args)
{
var (_, comp) = ent;
if (comp.Configuration.RevertOnEat)
{
args.Cancel();
Revert((ent, 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(Entity<PolymorphedEntityComponent> ent, ref DestructionEventArgs args)
{
if (ent.Comp.Configuration.RevertOnDeath)
{
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="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);
}
/// <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)
{
// Sunrise-Start
if (configuration.BlockIfHasMind
&& TryComp<MindContainerComponent>(uid, out var mindContainer)
&& mindContainer.HasMind)
{
return null;
}
// Sunrise-End
// if it's already morphed, don't allow it again with this condition active.
if (!configuration.AllowRepeatedMorphs && HasComp<PolymorphedEntityComponent>(uid))
return null;
// 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;
// mostly just for vehicles
_buckle.TryUnbuckle(uid, uid, true);
var targetTransformComp = Transform(uid);
if (configuration.PolymorphSound != null)
_audio.PlayPvs(configuration.PolymorphSound, targetTransformComp.Coordinates);
var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid));
if (configuration.PolymorphPopup != null)
_popup.PopupEntity(Loc.GetString(configuration.PolymorphPopup,
("parent", Identity.Entity(uid, EntityManager)),
("child", Identity.Entity(child, EntityManager))),
child);
MakeSentientCommand.MakeSentient(child, EntityManager);
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, targetTransformComp, null), 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 (configuration.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 (configuration.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 (configuration.TransferName && TryComp(uid, out MetaDataComponent? targetMeta))
_metaData.SetEntityName(child, targetMeta.EntityName);
if (configuration.TransferHumanoidAppearance)
{
_humanoid.CloneAppearance(uid, child);
}
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
_mindSystem.TransferTo(mindId, child, mind: mind);
//Ensures a map to banish the entity to
EnsurePausedMap();
if (PausedMap != null)
_transform.SetParent(uid, targetTransformComp, PausedMap.Value);
// Raise an event to inform anything that wants to know about the entity swap
var ev = new PolymorphedEvent(uid, child, false);
RaiseLocalEvent(uid, ref ev);
// visual effect spawn
if (configuration.EffectProto != null)
SpawnAttachedTo(configuration.EffectProto, child.ToCoordinates());
return 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(Entity<PolymorphedEntityComponent?> ent)
{
var (uid, component) = ent;
if (!Resolve(ent, ref component))
return null;
if (Deleted(uid))
return null;
var parent = component.Parent;
if (Deleted(parent))
return null;
var uidXform = Transform(uid);
var parentXform = Transform(parent);
if (component.Configuration.ExitPolymorphSound != null)
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
_transform.SetParent(parent, parentXform, uidXform.ParentUid);
_transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);
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))
{
_hands.TryDrop(uid, held);
_hands.TryPickupAnyHand(parent, held, checkActionBlocker: false);
}
}
else if (component.Configuration.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 (_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.AttachToGridOrMap(parent, parentXform);
// Raise an event to inform anything that wants to know about the entity swap
var ev = new PolymorphedEvent(uid, parent, true);
RaiseLocalEvent(uid, ref ev);
// visual effect spawn
if (component.Configuration.EffectProto != null)
SpawnAttachedTo(component.Configuration.EffectProto, parent.ToCoordinates());
if (component.Configuration.ExitPolymorphPopup != null)
_popup.PopupEntity(Loc.GetString(component.Configuration.ExitPolymorphPopup,
("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(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
{
target.Comp.PolymorphActions ??= new();
if (target.Comp.PolymorphActions.ContainsKey(id))
return;
if (!_proto.TryIndex(id, out var polyProto))
return;
var entProto = _proto.Index(polyProto.Configuration.Entity);
EntityUid? actionId = default!;
if (!_actions.AddAction(target, ref actionId, RevertPolymorphId, target))
return;
target.Comp.PolymorphActions.Add(id, actionId.Value);
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 (!_actions.TryGetActionData(actionId, out var baseAction))
return;
baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyProto.Configuration.Entity);
if (baseAction is InstantActionComponent action)
action.Event = new PolymorphActionEvent(id);
}
public void RemovePolymorphAction(ProtoId<PolymorphPrototype> id, Entity<PolymorphableComponent> target)
{
if (target.Comp.PolymorphActions == null)
return;
if (target.Comp.PolymorphActions.TryGetValue(id, out var val))
_actions.RemoveAction(target, val);
}
}