using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Cloning;
using Content.Server.DoAfter;
using Content.Server.Mind.Components;
using Content.Server.Popups;
using Content.Server.Preferences.Managers;
using Content.Shared.Actions;
using Content.Shared.CharacterAppearance.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Preferences;
using Content.Shared.Species;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
///
/// Fair warning, this is all kinda shitcode, but it'll have to wait for a major
/// refactor until proper body systems get added. The current implementation is
/// definitely not ideal and probably will be prone to weird bugs.
///
namespace Content.Server.Body.Systems
{
public sealed class BodyReassembleSystem : EntitySystem
{
[Dependency] private readonly IServerPreferencesManager _prefsManager = null!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidAppearance = default!;
private const float SelfReassembleMultiplier = 2f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnPartGibbed);
SubscribeLocalEvent(StartReassemblyAction);
SubscribeLocalEvent>(AddReassembleVerbs);
SubscribeLocalEvent(ReassembleComplete);
SubscribeLocalEvent(ReassembleCancelled);
}
private void StartReassemblyAction(EntityUid uid, BodyReassembleComponent component, ReassembleActionEvent args)
{
args.Handled = true;
StartReassembly(uid, component, SelfReassembleMultiplier);
}
private void ReassembleCancelled(EntityUid uid, BodyReassembleComponent component, ReassembleCancelledEvent args)
{
component.CancelToken = null;
}
private void OnPartGibbed(EntityUid uid, BodyReassembleComponent component, PartGibbedEvent args)
{
if (!TryComp(args.EntityToGib, out var mindComp) || mindComp?.Mind == null)
return;
component.BodyParts = args.GibbedParts;
UpdateDNAEntry(uid, args.EntityToGib);
mindComp.Mind.TransferTo(uid);
if (component.ReassembleAction == null)
return;
_actions.AddAction(uid, component.ReassembleAction, null);
}
private void StartReassembly(EntityUid uid, BodyReassembleComponent component, float multiplier = 1f)
{
if (component.CancelToken != null)
return;
if (!GetNearbyParts(uid, component, out var partList))
return;
if (partList == null)
return;
var doAfterTime = component.DoAfterTime * multiplier;
var cancelToken = new CancellationTokenSource();
component.CancelToken = cancelToken;
var doAfterEventArgs = new DoAfterEventArgs(component.Owner, doAfterTime, cancelToken.Token, component.Owner)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = false,
TargetCancelledEvent = new ReassembleCancelledEvent(),
TargetFinishedEvent = new ReassembleCompleteEvent(uid, uid, partList),
};
_doAfterSystem.DoAfter(doAfterEventArgs);
}
///
/// Adds the custom verb for reassembling body parts
///
private void AddReassembleVerbs(EntityUid uid, BodyReassembleComponent component, GetVerbsEvent args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!TryComp(uid, out var mind) ||
!mind.HasMind ||
component.CancelToken != null)
return;
// doubles the time if you reconstruct yourself
var multiplier = args.User == uid ? SelfReassembleMultiplier : 1f;
// Custom verb
AlternativeVerb custom = new()
{
Text = Loc.GetString("reassemble-action"),
Act = () =>
{
StartReassembly(uid, component, multiplier);
},
IconEntity = uid,
Priority = 1
};
args.Verbs.Add(custom);
}
private bool GetNearbyParts(EntityUid uid, BodyReassembleComponent component, out HashSet? partList)
{
partList = new HashSet();
if (component.BodyParts == null)
return false;
// Ensures all of the old body part pieces are there
var xformQuery = GetEntityQuery();
var bodyXform = xformQuery.GetComponent(uid);
foreach (var part in component.BodyParts)
{
if (!xformQuery.TryGetComponent(part, out var xform) ||
!bodyXform.Coordinates.InRange(EntityManager, xform.Coordinates,2f))
{
_popupSystem.PopupEntity(Loc.GetString("reassemble-fail"), uid, Filter.Entities(uid));
return false;
}
partList.Add(part);
}
return true;
}
private void ReassembleComplete(EntityUid uid, BodyReassembleComponent component, ReassembleCompleteEvent args)
{
component.CancelToken = null;
if (component.DNA == null || component.BodyParts == null)
return;
// Creates the new entity and transfers the mind component
var speciesProto = _prototype.Index(component.DNA.Value.Profile.Species).Prototype;
var mob = EntityManager.SpawnEntity(speciesProto, EntityManager.GetComponent(component.Owner).MapPosition);
_humanoidAppearance.UpdateFromProfile(mob, component.DNA.Value.Profile);
MetaData(mob).EntityName = component.DNA.Value.Profile.Name;
if (TryComp(uid, out var mindcomp) && mindcomp.Mind != null)
mindcomp.Mind.TransferTo(mob);
// Cleans up all the body part pieces
foreach (var entity in component.BodyParts)
{
EntityManager.DeleteEntity(entity);
}
_popupSystem.PopupEntity(Loc.GetString("reassemble-success", ("user", Identity.Entity(mob, EntityManager))), mob, Filter.Entities(mob));
}
///
/// Called before the skeleton entity is gibbed in order to save
/// the dna for reassembly later
///
/// the entity that the player will transfer to
/// the entity whose DNA is being saved
private void UpdateDNAEntry(EntityUid uid, EntityUid body)
{
if (!TryComp(uid, out var skelBodyComp) || !TryComp(body, out var mindcomp))
return;
if (mindcomp.Mind == null)
return;
if (mindcomp.Mind.UserId == null)
return;
var profile = (HumanoidCharacterProfile) _prefsManager.GetPreferences(mindcomp.Mind.UserId.Value).SelectedCharacter;
skelBodyComp.DNA = new ClonerDNAEntry(mindcomp.Mind, profile);
}
private sealed class ReassembleCompleteEvent : EntityEventArgs
{
///
/// The entity being reassembled
///
public readonly EntityUid Uid;
///
/// The user performing the reassembly
///
public readonly EntityUid User;
public readonly HashSet PartList;
public ReassembleCompleteEvent(EntityUid uid, EntityUid user, HashSet partList)
{
Uid = uid;
User = user;
PartList = partList;
}
}
private sealed class ReassembleCancelledEvent : EntityEventArgs {}
}
}
public sealed class ReassembleActionEvent : InstantActionEvent { }