Add DNA injector (#41271)
* add item * Update Content.Shared/Changeling/Systems/ChangelingClonerSystem.cs Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> --------- Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
|||||||
|
using Content.Shared.Charges.Components;
|
||||||
|
using Content.Shared.Cloning;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Changeling.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changeling transformation in item form!
|
||||||
|
/// An entity with this component works like an implanter:
|
||||||
|
/// First you use it on a humanoid to make a copy of their identity, along with all species relevant components,
|
||||||
|
/// then use it on someone else to tranform them into a clone of them.
|
||||||
|
/// Can be used in combination with <see cref="LimitedChargesComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class ChangelingClonerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A clone of the player you have copied the identity from.
|
||||||
|
/// This is a full humanoid backup, stored on a paused map.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Since this entity is stored on a separate map it will be outside PVS range.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? ClonedBackup;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current state of the item.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public ChangelingClonerState State = ChangelingClonerState.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cloning settings to use.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public ProtoId<CloningSettingsPrototype> Settings = "ChangelingCloningSettings";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Doafter time for drawing and injecting.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public TimeSpan DoAfter = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can this item be used more than once?
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool Reusable = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to add a reset verb to purge the stored identity,
|
||||||
|
/// allowing you to draw a new one.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool CanReset = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise events when renaming the target?
|
||||||
|
/// This will change their ID card, crew manifest entry, and so on.
|
||||||
|
/// For admeme purposes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool RaiseNameChangeEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound to play when taking someone's identity with the item.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public SoundSpecifier? DrawSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound to play when someone is transformed.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public SoundSpecifier? InjectSound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current state of the item.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ChangelingClonerState : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No sample taken yet.
|
||||||
|
/// </summary>
|
||||||
|
Empty,
|
||||||
|
/// <summary>
|
||||||
|
/// Filled with a DNA sample.
|
||||||
|
/// </summary>
|
||||||
|
Filled,
|
||||||
|
/// <summary>
|
||||||
|
/// Has been used (single use only).
|
||||||
|
/// </summary>
|
||||||
|
Spent,
|
||||||
|
}
|
||||||
308
Content.Shared/Changeling/Systems/ChangelingClonerSystem.cs
Normal file
308
Content.Shared/Changeling/Systems/ChangelingClonerSystem.cs
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Changeling.Components;
|
||||||
|
using Content.Shared.Cloning;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Forensics.Systems;
|
||||||
|
using Content.Shared.Humanoid;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Changeling.Systems;
|
||||||
|
|
||||||
|
public sealed class ChangelingClonerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidAppearance = default!;
|
||||||
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedCloningSystem _cloning = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly SharedChangelingIdentitySystem _changelingIdentity = default!;
|
||||||
|
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, ExaminedEvent>(OnExamine);
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, ClonerDrawDoAfterEvent>(OnDraw);
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, ClonerInjectDoAfterEvent>(OnInject);
|
||||||
|
SubscribeLocalEvent<ChangelingClonerComponent, ComponentShutdown>(OnShutDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutDown(Entity<ChangelingClonerComponent> ent, ref ComponentShutdown args)
|
||||||
|
{
|
||||||
|
// Delete the stored clone.
|
||||||
|
PredictedQueueDel(ent.Comp.ClonedBackup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(Entity<ChangelingClonerComponent> ent, ref ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.IsInDetailsRange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var msg = ent.Comp.State switch
|
||||||
|
{
|
||||||
|
ChangelingClonerState.Empty => "changeling-cloner-component-empty",
|
||||||
|
ChangelingClonerState.Filled => "changeling-cloner-component-filled",
|
||||||
|
ChangelingClonerState.Spent => "changeling-cloner-component-spent",
|
||||||
|
_ => "error"
|
||||||
|
};
|
||||||
|
|
||||||
|
args.PushMarkup(Loc.GetString(msg));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVerbs(Entity<ChangelingClonerComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanInteract || !args.CanAccess || args.Hands == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ent.Comp.CanReset || ent.Comp.State == ChangelingClonerState.Spent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = args.User;
|
||||||
|
args.Verbs.Add(new Verb
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("changeling-cloner-component-reset-verb"),
|
||||||
|
Disabled = ent.Comp.ClonedBackup == null,
|
||||||
|
Act = () => Reset(ent.AsNullable(), user),
|
||||||
|
DoContactInteraction = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAfterInteract(Entity<ChangelingClonerComponent> ent, ref AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || !args.CanReach || args.Target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (ent.Comp.State)
|
||||||
|
{
|
||||||
|
case ChangelingClonerState.Empty:
|
||||||
|
args.Handled |= TryDraw(ent.AsNullable(), args.Target.Value, args.User);
|
||||||
|
break;
|
||||||
|
case ChangelingClonerState.Filled:
|
||||||
|
args.Handled |= TryInject(ent.AsNullable(), args.Target.Value, args.User);
|
||||||
|
break;
|
||||||
|
case ChangelingClonerState.Spent:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDraw(Entity<ChangelingClonerComponent> ent, ref ClonerDrawDoAfterEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Cancelled || args.Target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Draw(ent.AsNullable(), args.Target.Value, args.User);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInject(Entity<ChangelingClonerComponent> ent, ref ClonerInjectDoAfterEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Cancelled || args.Target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Inject(ent.AsNullable(), args.Target.Value, args.User);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start a DoAfter to draw a DNA sample from the target.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryDraw(Entity<ChangelingClonerComponent?> ent, EntityUid target, EntityUid user)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ent.Comp.State != ChangelingClonerState.Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||||
|
return false; // cloning only works for humanoids at the moment
|
||||||
|
|
||||||
|
var args = new DoAfterArgs(EntityManager, user, ent.Comp.DoAfter, new ClonerDrawDoAfterEvent(), ent, target: target, used: ent)
|
||||||
|
{
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnMove = true,
|
||||||
|
NeedHand = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_doAfter.TryStartDoAfter(args))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var userIdentity = Identity.Entity(user, EntityManager);
|
||||||
|
var targetIdentity = Identity.Entity(target, EntityManager);
|
||||||
|
var userMsg = Loc.GetString("changeling-cloner-component-draw-user", ("user", userIdentity), ("target", targetIdentity));
|
||||||
|
var targetMsg = Loc.GetString("changeling-cloner-component-draw-target", ("user", userIdentity), ("target", targetIdentity));
|
||||||
|
_popup.PopupClient(userMsg, target, user);
|
||||||
|
|
||||||
|
if (user != target) // don't show the warning if using the item on yourself
|
||||||
|
_popup.PopupEntity(targetMsg, user, target, PopupType.LargeCaution);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start a DoAfter to inject a DNA sample into someone, turning them into a clone of the original.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryInject(Entity<ChangelingClonerComponent?> ent, EntityUid target, EntityUid user)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ent.Comp.State != ChangelingClonerState.Filled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||||
|
return false; // cloning only works for humanoids at the moment
|
||||||
|
|
||||||
|
var args = new DoAfterArgs(EntityManager, user, ent.Comp.DoAfter, new ClonerInjectDoAfterEvent(), ent, target: target, used: ent)
|
||||||
|
{
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnMove = true,
|
||||||
|
NeedHand = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_doAfter.TryStartDoAfter(args))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var userIdentity = Identity.Entity(user, EntityManager);
|
||||||
|
var targetIdentity = Identity.Entity(target, EntityManager);
|
||||||
|
var userMsg = Loc.GetString("changeling-cloner-component-inject-user", ("user", userIdentity), ("target", targetIdentity));
|
||||||
|
var targetMsg = Loc.GetString("changeling-cloner-component-inject-target", ("user", userIdentity), ("target", targetIdentity));
|
||||||
|
_popup.PopupClient(userMsg, target, user);
|
||||||
|
|
||||||
|
if (user != target) // don't show the warning if using the item on yourself
|
||||||
|
_popup.PopupEntity(targetMsg, user, target, PopupType.LargeCaution);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw a DNA sample from the target.
|
||||||
|
/// This will create a clone stored on a paused map for data storage.
|
||||||
|
/// </summary>
|
||||||
|
public void Draw(Entity<ChangelingClonerComponent?> ent, EntityUid target, EntityUid user)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent.Comp.State != ChangelingClonerState.Empty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||||
|
return; // cloning only works for humanoids at the moment
|
||||||
|
|
||||||
|
if (!_prototype.Resolve(ent.Comp.Settings, out var settings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_adminLogger.Add(LogType.Identity,
|
||||||
|
$"{user} is using {ent.Owner} to draw DNA from {target}.");
|
||||||
|
|
||||||
|
// Make a copy of the target on a paused map, so that we can apply their components later.
|
||||||
|
ent.Comp.ClonedBackup = _changelingIdentity.CloneToPausedMap(settings, target);
|
||||||
|
ent.Comp.State = ChangelingClonerState.Filled;
|
||||||
|
_appearance.SetData(ent.Owner, ChangelingClonerVisuals.State, ChangelingClonerState.Filled);
|
||||||
|
Dirty(ent);
|
||||||
|
|
||||||
|
_audio.PlayPredicted(ent.Comp.DrawSound, target, user);
|
||||||
|
_forensics.TransferDna(ent, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inject a DNA sample into someone, turning them into a clone of the original.
|
||||||
|
/// </summary>
|
||||||
|
public void Inject(Entity<ChangelingClonerComponent?> ent, EntityUid target, EntityUid user)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent.Comp.State != ChangelingClonerState.Filled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||||
|
return; // cloning only works for humanoids at the moment
|
||||||
|
|
||||||
|
if (!_prototype.Resolve(ent.Comp.Settings, out var settings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_audio.PlayPredicted(ent.Comp.InjectSound, target, user);
|
||||||
|
_forensics.TransferDna(ent, target); // transfer DNA before overwriting it
|
||||||
|
|
||||||
|
if (!ent.Comp.Reusable)
|
||||||
|
{
|
||||||
|
ent.Comp.State = ChangelingClonerState.Spent;
|
||||||
|
_appearance.SetData(ent.Owner, ChangelingClonerVisuals.State, ChangelingClonerState.Spent);
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Exists(ent.Comp.ClonedBackup))
|
||||||
|
return; // the entity is likely out of PVS range on the client
|
||||||
|
|
||||||
|
_adminLogger.Add(LogType.Identity,
|
||||||
|
$"{user} is using {ent.Owner} to inject DNA into {target} changing their identity to {ent.Comp.ClonedBackup.Value}.");
|
||||||
|
|
||||||
|
// Do the actual transformation.
|
||||||
|
_humanoidAppearance.CloneAppearance(ent.Comp.ClonedBackup.Value, target);
|
||||||
|
_cloning.CloneComponents(ent.Comp.ClonedBackup.Value, target, settings);
|
||||||
|
_metaData.SetEntityName(target, Name(ent.Comp.ClonedBackup.Value), raiseEvents: ent.Comp.RaiseNameChangeEvents);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Purge the stored DNA and allow to draw again.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset(Entity<ChangelingClonerComponent?> ent, EntityUid? user)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Delete the stored clone.
|
||||||
|
PredictedQueueDel(ent.Comp.ClonedBackup);
|
||||||
|
ent.Comp.ClonedBackup = null;
|
||||||
|
ent.Comp.State = ChangelingClonerState.Empty;
|
||||||
|
_appearance.SetData(ent.Owner, ChangelingClonerVisuals.State, ChangelingClonerState.Empty);
|
||||||
|
Dirty(ent);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_popup.PopupClient(Loc.GetString("changeling-cloner-component-reset-popup"), user.Value, user.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Doafter event for drawing a DNA sample.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class ClonerDrawDoAfterEvent : SimpleDoAfterEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DoAfterEvent for injecting a DNA sample, turning a player into someone else.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class ClonerInjectDoAfterEvent : SimpleDoAfterEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key for the generic visualizer.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ChangelingClonerVisuals : byte
|
||||||
|
{
|
||||||
|
State,
|
||||||
|
}
|
||||||
@@ -83,20 +83,19 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone a target humanoid into nullspace and add it to the Changelings list of identities.
|
/// Clone a target humanoid to a paused map.
|
||||||
/// It creates a perfect copy of the target and can be used to pull components down for future use
|
/// It creates a perfect copy of the target and can be used to pull components down for future use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ent">the Changeling</param>
|
/// <param name="settings">The settings to use for cloning.</param>
|
||||||
/// <param name="target">the targets uid</param>
|
/// <param name="target">The target to clone.</param>
|
||||||
public EntityUid? CloneToPausedMap(Entity<ChangelingIdentityComponent> ent, EntityUid target)
|
public EntityUid? CloneToPausedMap(CloningSettingsPrototype settings, EntityUid target)
|
||||||
{
|
{
|
||||||
// Don't create client side duplicate clones or a clientside map.
|
// Don't create client side duplicate clones or a clientside map.
|
||||||
if (_net.IsClient)
|
if (_net.IsClient)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (!TryComp<HumanoidAppearanceComponent>(target, out var humanoid)
|
if (!TryComp<HumanoidAppearanceComponent>(target, out var humanoid)
|
||||||
|| !_prototype.Resolve(humanoid.Species, out var speciesPrototype)
|
|| !_prototype.Resolve(humanoid.Species, out var speciesPrototype))
|
||||||
|| !_prototype.Resolve(ent.Comp.IdentityCloningSettings, out var settings))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
EnsurePausedMap();
|
EnsurePausedMap();
|
||||||
@@ -117,10 +116,30 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem
|
|||||||
|
|
||||||
var targetName = _nameMod.GetBaseName(target);
|
var targetName = _nameMod.GetBaseName(target);
|
||||||
_metaSystem.SetEntityName(clone, targetName);
|
_metaSystem.SetEntityName(clone, targetName);
|
||||||
ent.Comp.ConsumedIdentities.Add(clone);
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clone a target humanoid to a paused map and add it to the Changelings list of identities.
|
||||||
|
/// It creates a perfect copy of the target and can be used to pull components down for future use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">The Changeling.</param>
|
||||||
|
/// <param name="target">The target to clone.</param>
|
||||||
|
public EntityUid? CloneToPausedMap(Entity<ChangelingIdentityComponent> ent, EntityUid target)
|
||||||
|
{
|
||||||
|
if (!_prototype.Resolve(ent.Comp.IdentityCloningSettings, out var settings))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var clone = CloneToPausedMap(settings, target);
|
||||||
|
|
||||||
|
if (clone == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ent.Comp.ConsumedIdentities.Add(clone.Value);
|
||||||
|
|
||||||
Dirty(ent);
|
Dirty(ent);
|
||||||
HandlePvsOverride(ent, clone);
|
HandlePvsOverride(ent, clone.Value);
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
changeling-cloner-component-empty = It is empty.
|
||||||
|
changeling-cloner-component-filled = It has a DNA sample in it.
|
||||||
|
changeling-cloner-component-spent = It has been used.
|
||||||
|
|
||||||
|
changeling-cloner-component-reset-verb = Reset DNA
|
||||||
|
changeling-cloner-component-reset-popup = You purge the injector's DNA storage.
|
||||||
|
|
||||||
|
changeling-cloner-component-draw-user = You start drawing DNA from {THE($target)}.
|
||||||
|
changeling-cloner-component-draw-target = {CAPITALIZE(THE($user))} starts drawing DNA from you.
|
||||||
|
changeling-cloner-component-inject-user = You start injecting DNA into {THE($target)}.
|
||||||
|
changeling-cloner-component-inject-target = {CAPITALIZE(THE($user))} starts injecting DNA into you.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: DnaInjectorUnlimited
|
||||||
|
suffix: Admeme, unlimited
|
||||||
|
# Should not be a traitor item for several reasons:
|
||||||
|
# - Changeling code is still in development, and copying organs etc does not work yet.
|
||||||
|
# - Giving this to traitors makes them overlap with changelings or paradox clones too much.
|
||||||
|
# - It completely makes the voice mask redundant.
|
||||||
|
# - Unlike when disguising yourself as someone else, there is no way to get caught.
|
||||||
|
name: DNA injector
|
||||||
|
description: Can be used to take a DNA sample from someone and inject it into another person, turning them into a clone of the original.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Specific/Medical/implanter.rsi
|
||||||
|
state: implanter0
|
||||||
|
layers:
|
||||||
|
- state: implanter0
|
||||||
|
map: [ "injector" ]
|
||||||
|
visible: true
|
||||||
|
- state: implanter1
|
||||||
|
map: [ "fillState" ]
|
||||||
|
visible: false
|
||||||
|
- type: Item
|
||||||
|
sprite: Objects/Specific/Medical/implanter.rsi
|
||||||
|
heldPrefix: implanter
|
||||||
|
size: Small
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.ChangelingClonerVisuals.State:
|
||||||
|
injector:
|
||||||
|
Empty: {state: implanter0}
|
||||||
|
Filled: {state: implanter0}
|
||||||
|
Spent: {state: broken}
|
||||||
|
fillState:
|
||||||
|
Empty: {visible: false}
|
||||||
|
Filled: {visible: true}
|
||||||
|
Spent: {visible: false}
|
||||||
|
- type: ChangelingCloner
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: DnaInjectorUnlimited
|
||||||
|
id: DnaInjector
|
||||||
|
suffix: Admeme, single use
|
||||||
|
components:
|
||||||
|
- type: ChangelingCloner
|
||||||
|
reusable: false
|
||||||
Reference in New Issue
Block a user