Implanters and Subdermal Implants (#11840)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
33
Content.Client/Implants/ImplanterSystem.cs
Normal file
33
Content.Client/Implants/ImplanterSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Content.Client.Implants.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Implants;
|
||||
|
||||
public sealed class ImplanterSystem : SharedImplanterSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ImplanterComponent, ComponentHandleState>(OnHandleImplanterState);
|
||||
SubscribeLocalEvent<ImplanterComponent, ItemStatusCollectMessage>(OnItemImplanterStatus);
|
||||
}
|
||||
|
||||
private void OnHandleImplanterState(EntityUid uid, ImplanterComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ImplanterComponentState state)
|
||||
return;
|
||||
|
||||
component.CurrentMode = state.CurrentMode;
|
||||
component.ImplantOnly = state.ImplantOnly;
|
||||
component.UiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
private void OnItemImplanterStatus(EntityUid uid, ImplanterComponent component, ItemStatusCollectMessage args)
|
||||
{
|
||||
args.Controls.Add(new ImplanterStatusControl(component));
|
||||
}
|
||||
}
|
||||
53
Content.Client/Implants/UI/ImplanterStatusControl.cs
Normal file
53
Content.Client/Implants/UI/ImplanterStatusControl.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Implants.UI;
|
||||
|
||||
public sealed class ImplanterStatusControl : Control
|
||||
{
|
||||
private readonly ImplanterComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public ImplanterStatusControl(ImplanterComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
if (!_parent.UiUpdateNeeded)
|
||||
return;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_parent.UiUpdateNeeded = false;
|
||||
|
||||
var modeStringLocalized = _parent.CurrentMode switch
|
||||
{
|
||||
ImplanterToggleMode.Draw => Loc.GetString("implanter-draw-text"),
|
||||
ImplanterToggleMode.Inject => Loc.GetString("implanter-inject-text"),
|
||||
_ => Loc.GetString("injector-invalid-injector-toggle-mode")
|
||||
};
|
||||
|
||||
var entitiesStringLocalized = _parent.ImplanterSlot.HasItem switch
|
||||
{
|
||||
false => Loc.GetString("implanter-empty-text"),
|
||||
true => Loc.GetString("implanter-implant-text", ("implantName", _parent.ImplantData.Item1), ("implantDescription", _parent.ImplantData.Item2), ("lineBreak", "\n")),
|
||||
};
|
||||
|
||||
|
||||
_label.SetMarkup(Loc.GetString("implanter-label", ("currentEntities", entitiesStringLocalized), ("modeString", modeStringLocalized), ("lineBreak", "\n")));
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public sealed class BodySystem : SharedBodySystem
|
||||
InitPart(partComponent, prototype, prototype.Root);
|
||||
}
|
||||
|
||||
public override HashSet<EntityUid> GibBody(EntityUid? bodyId, bool gibOrgans = false, BodyComponent? body = null)
|
||||
public override HashSet<EntityUid> GibBody(EntityUid? bodyId, bool gibOrgans = false, BodyComponent? body = null, bool deleteItems = false)
|
||||
{
|
||||
if (bodyId == null || !Resolve(bodyId.Value, ref body, false))
|
||||
return new HashSet<EntityUid>();
|
||||
@@ -150,9 +150,16 @@ public sealed class BodySystem : SharedBodySystem
|
||||
{
|
||||
foreach (var ent in cont.ContainedEntities)
|
||||
{
|
||||
cont.ForceRemove(ent);
|
||||
Transform(ent).Coordinates = coordinates;
|
||||
ent.RandomOffset(0.25f);
|
||||
if (deleteItems)
|
||||
{
|
||||
QueueDel(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
cont.ForceRemove(ent);
|
||||
Transform(ent).Coordinates = coordinates;
|
||||
ent.RandomOffset(0.25f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Content.Server.Cuffs.Components
|
||||
[ViewVariables]
|
||||
public int CuffedHandCount => Container.ContainedEntities.Count * 2;
|
||||
|
||||
private EntityUid LastAddedCuffs => Container.ContainedEntities[^1];
|
||||
public EntityUid LastAddedCuffs => Container.ContainedEntities[^1];
|
||||
|
||||
public IReadOnlyList<EntityUid> StoredEntities => Container.ContainedEntities;
|
||||
|
||||
@@ -254,70 +254,7 @@ namespace Content.Server.Cuffs.Components
|
||||
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
SoundSystem.Play(cuff.EndUncuffSound.GetSound(), Filter.Pvs(Owner), Owner);
|
||||
|
||||
_entMan.EntitySysManager.GetEntitySystem<HandVirtualItemSystem>().DeleteInHandsMatching(user, cuffsToRemove.Value);
|
||||
_entMan.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(user, cuffsToRemove.Value);
|
||||
|
||||
if (cuff.BreakOnRemove)
|
||||
{
|
||||
cuff.Broken = true;
|
||||
|
||||
var meta = _entMan.GetComponent<MetaDataComponent>(cuffsToRemove.Value);
|
||||
meta.EntityName = cuff.BrokenName;
|
||||
meta.EntityDescription = cuff.BrokenDesc;
|
||||
|
||||
if (_entMan.TryGetComponent<SpriteComponent?>(cuffsToRemove, out var sprite) && cuff.BrokenState != null)
|
||||
{
|
||||
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
|
||||
}
|
||||
|
||||
_entMan.AddComponent<RecyclableComponent>(cuffsToRemove.Value);
|
||||
}
|
||||
|
||||
CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? handsComponent) && handsComponent.SortedHands.Count() > CuffedHandCount;
|
||||
_entMan.EntitySysManager.GetEntitySystem<ActionBlockerSystem>().UpdateCanMove(Owner);
|
||||
|
||||
var ev = new CuffedStateChangeEvent();
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref ev, true);
|
||||
UpdateAlert();
|
||||
Dirty(_entMan);
|
||||
|
||||
if (CuffedHandCount == 0)
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-success-message"));
|
||||
|
||||
if (!isOwner)
|
||||
{
|
||||
user.PopupMessage(Owner, Loc.GetString("cuffable-component-remove-cuffs-by-other-success-message", ("otherName", user)));
|
||||
}
|
||||
|
||||
if (user == Owner)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{_entMan.ToPrettyString(user):player} has successfully uncuffed themselves");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{_entMan.ToPrettyString(user):player} has successfully uncuffed {_entMan.ToPrettyString(Owner):player}");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message",
|
||||
("cuffedHandCount", CuffedHandCount),
|
||||
("otherName", user)));
|
||||
user.PopupMessage(Owner, Loc.GetString("cuffable-component-remove-cuffs-by-other-partial-success-message",
|
||||
("otherName", user),
|
||||
("cuffedHandCount", CuffedHandCount)));
|
||||
}
|
||||
else
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", ("cuffedHandCount", CuffedHandCount)));
|
||||
}
|
||||
}
|
||||
Uncuff(user, cuffsToRemove.Value, cuff, isOwner);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -326,5 +263,71 @@ namespace Content.Server.Cuffs.Components
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Lord forgive me for putting this here
|
||||
//Cuff ECS when
|
||||
public void Uncuff(EntityUid user, EntityUid cuffsToRemove, HandcuffComponent cuff, bool isOwner)
|
||||
{
|
||||
SoundSystem.Play(cuff.EndUncuffSound.GetSound(), Filter.Pvs(Owner), Owner);
|
||||
|
||||
_entMan.EntitySysManager.GetEntitySystem<HandVirtualItemSystem>().DeleteInHandsMatching(user, cuffsToRemove);
|
||||
_entMan.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(user, cuffsToRemove);
|
||||
|
||||
if (cuff.BreakOnRemove)
|
||||
{
|
||||
cuff.Broken = true;
|
||||
|
||||
var meta = _entMan.GetComponent<MetaDataComponent>(cuffsToRemove);
|
||||
meta.EntityName = cuff.BrokenName;
|
||||
meta.EntityDescription = cuff.BrokenDesc;
|
||||
|
||||
if (_entMan.TryGetComponent<SpriteComponent>(cuffsToRemove, out var sprite) && cuff.BrokenState != null)
|
||||
{
|
||||
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
|
||||
}
|
||||
|
||||
_entMan.AddComponent<RecyclableComponent>(cuffsToRemove);
|
||||
}
|
||||
|
||||
CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? handsComponent) && handsComponent.SortedHands.Count() > CuffedHandCount;
|
||||
_entMan.EntitySysManager.GetEntitySystem<ActionBlockerSystem>().UpdateCanMove(Owner);
|
||||
|
||||
var ev = new CuffedStateChangeEvent();
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref ev, true);
|
||||
UpdateAlert();
|
||||
Dirty(_entMan);
|
||||
|
||||
if (CuffedHandCount == 0)
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-success-message"));
|
||||
|
||||
if (!isOwner)
|
||||
{
|
||||
user.PopupMessage(Owner, Loc.GetString("cuffable-component-remove-cuffs-by-other-success-message", ("otherName", user)));
|
||||
}
|
||||
|
||||
if (user == Owner)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{_entMan.ToPrettyString(user):player} has successfully uncuffed themselves");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{_entMan.ToPrettyString(user):player} has successfully uncuffed {_entMan.ToPrettyString(Owner):player}");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", ("cuffedHandCount", CuffedHandCount), ("otherName", user)));
|
||||
user.PopupMessage(Owner, Loc.GetString("cuffable-component-remove-cuffs-by-other-partial-success-message", ("otherName", user), ("cuffedHandCount", CuffedHandCount)));
|
||||
}
|
||||
else
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", ("cuffedHandCount", CuffedHandCount)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
Content.Server/Explosion/Components/GibOnTriggerComponent.cs
Normal file
16
Content.Server/Explosion/Components/GibOnTriggerComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Gibs on trigger, self explanatory.
|
||||
/// Also in case of an implant using this, gibs the implant user instead.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class GibOnTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Should gibbing also delete the owners items?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("deleteItems")]
|
||||
public bool DeleteItems = false;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.MobState;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Explosion.Components
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Shared.MobState;
|
||||
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Use where you want something to trigger on mobstate change
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class TriggerOnMobstateChangeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// What state should trigger this?
|
||||
/// </summary>
|
||||
[DataField("mobState", required: true)]
|
||||
public DamageState MobState = DamageState.Alive;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Sticky.Events;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Server.Flash;
|
||||
using Content.Server.Flash.Components;
|
||||
using Content.Server.Sticky.Events;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Body.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -14,7 +16,9 @@ using Robust.Shared.Player;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Payload.Components;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using Robust.Server.Containers;
|
||||
@@ -48,6 +52,7 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -61,11 +66,14 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
|
||||
SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnTriggerCollide);
|
||||
SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<TriggerImplantActionComponent, ActivateImplantEvent>(OnImplantTrigger);
|
||||
SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredEvent>(OnStepTriggered);
|
||||
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
|
||||
SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(HandleExplodeTrigger);
|
||||
SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(HandleFlashTrigger);
|
||||
SubscribeLocalEvent<GibOnTriggerComponent, TriggerEvent>(HandleGibTrigger);
|
||||
}
|
||||
|
||||
private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args)
|
||||
@@ -89,6 +97,17 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void HandleGibTrigger(EntityUid uid, GibOnTriggerComponent component, TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(uid, out var xform))
|
||||
return;
|
||||
|
||||
_body.GibBody(xform.ParentUid, deleteItems: component.DeleteItems);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if(args.OurFixture.ID == component.FixtureID)
|
||||
@@ -101,11 +120,39 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnImplantTrigger(EntityUid uid, TriggerImplantActionComponent component, ActivateImplantEvent args)
|
||||
{
|
||||
Trigger(uid);
|
||||
}
|
||||
|
||||
private void OnStepTriggered(EntityUid uid, TriggerOnStepTriggerComponent component, ref StepTriggeredEvent args)
|
||||
{
|
||||
Trigger(uid, args.Tripper);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, TriggerOnMobstateChangeComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (component.MobState < args.CurrentMobState)
|
||||
return;
|
||||
|
||||
//This chains Mobstate Changed triggers with OnUseTimerTrigger if they have it
|
||||
//Very useful for things that require a mobstate change and a timer
|
||||
if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
|
||||
{
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.Origin,
|
||||
timerTrigger.Delay,
|
||||
timerTrigger.BeepInterval,
|
||||
timerTrigger.InitialBeepDelay,
|
||||
timerTrigger.BeepSound,
|
||||
timerTrigger.BeepParams);
|
||||
}
|
||||
|
||||
else
|
||||
Trigger(uid);
|
||||
}
|
||||
|
||||
public bool Trigger(EntityUid trigger, EntityUid? user = null)
|
||||
{
|
||||
var triggerEvent = new TriggerEvent(trigger, user);
|
||||
|
||||
25
Content.Server/Implants/ImplantedSystem.cs
Normal file
25
Content.Server/Implants/ImplantedSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed partial class ImplanterSystem
|
||||
{
|
||||
public void InitializeImplanted()
|
||||
{
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentInit>(OnImplantedInit);
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnImplantedInit(EntityUid uid, ImplantedComponent component, ComponentInit args)
|
||||
{
|
||||
component.ImplantContainer = _container.EnsureContainer<Container>(uid, ImplanterComponent.ImplantSlotId);
|
||||
component.ImplantContainer.OccludesLight = false;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, ImplantedComponent component, ComponentShutdown args)
|
||||
{
|
||||
//If the entity is deleted, get rid of the implants
|
||||
_container.CleanContainer(component.ImplantContainer);
|
||||
}
|
||||
}
|
||||
188
Content.Server/Implants/ImplanterSystem.cs
Normal file
188
Content.Server/Implants/ImplanterSystem.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System.Threading;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Guardian;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed partial class ImplanterSystem : SharedImplanterSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeImplanted();
|
||||
|
||||
SubscribeLocalEvent<ImplanterComponent, HandDeselectedEvent>(OnHandDeselect);
|
||||
SubscribeLocalEvent<ImplanterComponent, AfterInteractEvent>(OnImplanterAfterInteract);
|
||||
SubscribeLocalEvent<ImplanterComponent, ComponentGetState>(OnImplanterGetState);
|
||||
|
||||
SubscribeLocalEvent<ImplanterComponent, ImplanterImplantCompleteEvent>(OnImplantAttemptSuccess);
|
||||
SubscribeLocalEvent<ImplanterComponent, ImplanterDrawCompleteEvent>(OnDrawAttemptSuccess);
|
||||
SubscribeLocalEvent<ImplanterComponent, ImplanterCancelledEvent>(OnImplantAttemptFail);
|
||||
}
|
||||
|
||||
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target == null || !args.CanReach || args.Handled)
|
||||
return;
|
||||
|
||||
if (component.CancelToken != null)
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
//Simplemobs and regular mobs should be injectable, but only regular mobs have mind.
|
||||
//So just don't implant/draw anything that isn't living or is a guardian
|
||||
//TODO: Rework a bit when surgery is in to work with implant cases
|
||||
if (!HasComp<MobStateComponent>(args.Target.Value) || HasComp<GuardianComponent>(args.Target.Value))
|
||||
return;
|
||||
|
||||
//TODO: Rework when surgery is in for implant cases
|
||||
if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly)
|
||||
{
|
||||
TryDraw(component, args.User, args.Target.Value, uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Implant self instantly, otherwise try to inject the target.
|
||||
if (args.User == args.Target)
|
||||
Implant(uid, args.Target.Value, component);
|
||||
|
||||
else
|
||||
TryImplant(component, args.User, args.Target.Value, uid);
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnHandDeselect(EntityUid uid, ImplanterComponent component, HandDeselectedEvent args)
|
||||
{
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to implant someone else.
|
||||
/// </summary>
|
||||
/// <param name="component">Implanter component</param>
|
||||
/// <param name="user">The entity using the implanter</param>
|
||||
/// <param name="target">The entity being implanted</param>
|
||||
/// <param name="implanter">The implanter being used</param>
|
||||
public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
|
||||
|
||||
var userName = Identity.Entity(user, EntityManager);
|
||||
_popup.PopupEntity(Loc.GetString("implanter-component-implanting-target", ("user", userName)), user, Filter.Entities(target), PopupType.LargeCaution);
|
||||
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = new CancellationTokenSource();
|
||||
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token, target, implanter)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
UsedFinishedEvent = new ImplanterImplantCompleteEvent(implanter, target),
|
||||
UserCancelledEvent = new ImplanterCancelledEvent()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to remove an implant and store it in an implanter
|
||||
/// </summary>
|
||||
/// <param name="component">Implanter component</param>
|
||||
/// <param name="user">The entity using the implanter</param>
|
||||
/// <param name="target">The entity getting their implant removed</param>
|
||||
/// <param name="implanter">The implanter being used</param>
|
||||
//TODO: Remove when surgery is in
|
||||
public void TryDraw(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
|
||||
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = new CancellationTokenSource();
|
||||
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, component.CancelToken.Token, target ,implanter)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
UsedFinishedEvent = new ImplanterDrawCompleteEvent(implanter, user, target),
|
||||
UsedCancelledEvent = new ImplanterCancelledEvent()
|
||||
});
|
||||
}
|
||||
|
||||
private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly);
|
||||
}
|
||||
|
||||
private void OnImplantAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterImplantCompleteEvent args)
|
||||
{
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = null;
|
||||
Implant(args.Implanter, args.Target, component);
|
||||
}
|
||||
|
||||
private void OnDrawAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterDrawCompleteEvent args)
|
||||
{
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = null;
|
||||
Draw(args.Implanter, args.User, args.Target, component);
|
||||
}
|
||||
|
||||
private void OnImplantAttemptFail(EntityUid uid, ImplanterComponent component, ImplanterCancelledEvent args)
|
||||
{
|
||||
component.CancelToken?.Cancel();
|
||||
component.CancelToken = null;
|
||||
}
|
||||
|
||||
private sealed class ImplanterImplantCompleteEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Implanter;
|
||||
public EntityUid Target;
|
||||
|
||||
public ImplanterImplantCompleteEvent(EntityUid implanter, EntityUid target)
|
||||
{
|
||||
Implanter = implanter;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ImplanterCancelledEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private sealed class ImplanterDrawCompleteEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Implanter;
|
||||
public EntityUid User;
|
||||
public EntityUid Target;
|
||||
|
||||
public ImplanterDrawCompleteEvent(EntityUid implanter, EntityUid user, EntityUid target)
|
||||
{
|
||||
Implanter = implanter;
|
||||
User = user;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
58
Content.Server/Implants/SubdermalImplantSystem.cs
Normal file
58
Content.Server/Implants/SubdermalImplantSystem.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Content.Server.Cuffs.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.MobState;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, UseFreedomImplantEvent>(OnFreedomImplant);
|
||||
|
||||
SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
|
||||
}
|
||||
|
||||
private void OnFreedomImplant(EntityUid uid, SubdermalImplantComponent component, UseFreedomImplantEvent args)
|
||||
{
|
||||
if (!TryComp<CuffableComponent>(component.ImplantedEntity, out var cuffs) || cuffs.Container.ContainedEntities.Count < 1)
|
||||
return;
|
||||
|
||||
if (TryComp<HandcuffComponent>(cuffs.LastAddedCuffs, out var cuff))
|
||||
{
|
||||
cuffs.Uncuff(component.ImplantedEntity.Value, cuffs.LastAddedCuffs, cuff, true);
|
||||
}
|
||||
}
|
||||
|
||||
#region Relays
|
||||
|
||||
|
||||
//Relays from the implanted to the implant
|
||||
private void RelayToImplantEvent<T>(EntityUid uid, ImplantedComponent component, T args) where T : EntityEventArgs
|
||||
{
|
||||
if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
return;
|
||||
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
RaiseLocalEvent(implant, args);
|
||||
}
|
||||
}
|
||||
|
||||
//Relays from the implant to the implanted
|
||||
private void RelayToImplantedEvent<T>(EntityUid uid, SubdermalImplantComponent component, T args) where T : EntityEventArgs
|
||||
{
|
||||
if (component.ImplantedEntity != null)
|
||||
{
|
||||
RaiseLocalEvent(component.ImplantedEntity.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
39
Content.Server/Jobs/AddImplantSpecial.cs
Normal file
39
Content.Server/Jobs/AddImplantSpecial.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// Adds implants on spawn to the entity
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class AddImplantSpecial : JobSpecial
|
||||
{
|
||||
|
||||
[DataField("implants", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<EntityPrototype>))]
|
||||
public HashSet<String> Implants { get; } = new();
|
||||
|
||||
public override void AfterEquip(EntityUid mob)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var implantSystem = entMan.System<SharedSubdermalImplantSystem>();
|
||||
var xformQuery = entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!xformQuery.TryGetComponent(mob, out var xform))
|
||||
return;
|
||||
|
||||
foreach (var implantId in Implants)
|
||||
{
|
||||
var implant = entMan.SpawnEntity(implantId, xform.Coordinates);
|
||||
|
||||
if (!entMan.TryGetComponent<SubdermalImplantComponent>(implant, out var implantComp))
|
||||
return;
|
||||
|
||||
implantSystem.ForceImplant(mob, implant, implantComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,12 @@ namespace Content.Server.Medical.SuitSensors
|
||||
[DataField("activationSlot")]
|
||||
public string ActivationSlot = "jumpsuit";
|
||||
|
||||
/// <summary>
|
||||
/// Activate sensor if user has this in a sensor-compatible container.
|
||||
/// </summary>
|
||||
[DataField("activationContainer")]
|
||||
public string? ActivationContainer;
|
||||
|
||||
/// <summary>
|
||||
/// How often does sensor update its owners status (in seconds). Limited by the system update rate.
|
||||
/// </summary>
|
||||
@@ -43,6 +49,7 @@ namespace Content.Server.Medical.SuitSensors
|
||||
/// <summary>
|
||||
/// Current user that wears suit sensor. Null if nobody wearing it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? User = null;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
@@ -35,6 +36,8 @@ namespace Content.Server.Medical.SuitSensors
|
||||
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -150,6 +153,22 @@ namespace Content.Server.Medical.SuitSensors
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ActivationContainer)
|
||||
return;
|
||||
|
||||
component.User = args.Container.Owner;
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ActivationContainer)
|
||||
return;
|
||||
|
||||
component.User = null;
|
||||
}
|
||||
|
||||
private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode)
|
||||
{
|
||||
return new Verb()
|
||||
|
||||
@@ -29,6 +29,7 @@ using Content.Shared.Destructible;
|
||||
using static Content.Shared.Storage.SharedStorageComponent;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
|
||||
namespace Content.Server.Storage.EntitySystems
|
||||
@@ -62,6 +63,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
SubscribeLocalEvent<ServerStorageComponent, GetVerbsEvent<UtilityVerb>>(AddTransferVerbs);
|
||||
SubscribeLocalEvent<ServerStorageComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<ServerStorageComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<ServerStorageComponent, OpenStorageImplantEvent>(OnImplantActivate);
|
||||
SubscribeLocalEvent<ServerStorageComponent, AfterInteractEvent>(AfterInteract);
|
||||
SubscribeLocalEvent<ServerStorageComponent, DestructionEventArgs>(OnDestroy);
|
||||
SubscribeLocalEvent<ServerStorageComponent, StorageInteractWithItemEvent>(OnInteractWithItem);
|
||||
@@ -277,6 +279,17 @@ namespace Content.Server.Storage.EntitySystems
|
||||
OpenStorageUI(uid, args.User, storageComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifically for storage implants.
|
||||
/// </summary>
|
||||
private void OnImplantActivate(EntityUid uid, ServerStorageComponent storageComp, OpenStorageImplantEvent args)
|
||||
{
|
||||
if (args.Handled || !TryComp<TransformComponent>(uid, out var xform))
|
||||
return;
|
||||
|
||||
OpenStorageUI(uid, xform.ParentUid, storageComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a user to pick up entities by clicking them, or pick up all entities in a certain radius
|
||||
/// around a click.
|
||||
|
||||
@@ -162,7 +162,7 @@ public partial class SharedBodySystem
|
||||
}
|
||||
|
||||
public virtual HashSet<EntityUid> GibBody(EntityUid? partId, bool gibOrgans = false,
|
||||
BodyComponent? body = null)
|
||||
BodyComponent? body = null, bool deleteItems = false)
|
||||
{
|
||||
if (partId == null || !Resolve(partId.Value, ref body, false))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
13
Content.Shared/Implants/Components/ImplantedComponent.cs
Normal file
13
Content.Shared/Implants/Components/ImplantedComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Implants.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an entity via the <see cref="SharedImplanterSystem"/> on implant
|
||||
/// Used in instances where mob info needs to be passed to the implant such as MobState triggers
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ImplantedComponent : Component
|
||||
{
|
||||
public Container ImplantContainer = default!;
|
||||
}
|
||||
107
Content.Shared/Implants/Components/ImplanterComponent.cs
Normal file
107
Content.Shared/Implants/Components/ImplanterComponent.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Implants.Components;
|
||||
/// <summary>
|
||||
/// Implanters are used to implant or extract implants from an entity
|
||||
/// Some can be single use (implant only) or some can draw out an implant
|
||||
/// </summary>
|
||||
//TODO: Rework drawing to work with implant cases when surgery is in
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class ImplanterComponent : Component
|
||||
{
|
||||
public const string ImplanterSlotId = "implanter_slot";
|
||||
public const string ImplantSlotId = "implant";
|
||||
|
||||
/// <summary>
|
||||
/// Used for implanters that start with specific implants
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("implant", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Implant;
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes to implant someone else
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("implantTime")]
|
||||
public float ImplantTime = 5f;
|
||||
|
||||
//TODO: Remove when surgery is a thing
|
||||
/// <summary>
|
||||
/// The time it takes to extract an implant from someone
|
||||
/// It's excessively long to deter from implant checking any antag
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("drawTime")]
|
||||
public float DrawTime = 300f;
|
||||
|
||||
/// <summary>
|
||||
/// Good for single-use injectors
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("implantOnly")]
|
||||
public bool ImplantOnly = false;
|
||||
|
||||
/// <summary>
|
||||
/// The current mode of the implanter
|
||||
/// Mode is changed automatically depending if it implants or draws
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("currentMode")]
|
||||
public ImplanterToggleMode CurrentMode;
|
||||
|
||||
/// <summary>
|
||||
/// The name and description of the implant to show on the implanter
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("implantData")]
|
||||
public (string, string) ImplantData;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ItemSlot"/> for this implanter
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("implanterSlot")]
|
||||
public ItemSlot ImplanterSlot = new();
|
||||
|
||||
public bool UiUpdateNeeded;
|
||||
|
||||
public CancellationTokenSource? CancelToken;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ImplanterComponentState : ComponentState
|
||||
{
|
||||
public ImplanterToggleMode CurrentMode;
|
||||
public bool ImplantOnly;
|
||||
|
||||
public ImplanterComponentState(ImplanterToggleMode currentMode, bool implantOnly)
|
||||
{
|
||||
CurrentMode = currentMode;
|
||||
ImplantOnly = implantOnly;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ImplanterToggleMode : byte
|
||||
{
|
||||
Inject,
|
||||
Draw
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ImplanterVisuals : byte
|
||||
{
|
||||
Full
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ImplanterImplantOnlyVisuals : byte
|
||||
{
|
||||
ImplantOnly
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared.Implants.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Subdermal implants get stored in a container on an entity and grant the entity special actions
|
||||
/// The actions can be activated via an action, a passive ability (ie tracking), or a reactive ability (ie on death) or some sort of combination
|
||||
/// They're added and removed with implanters
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SubdermalImplantComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Used where you want the implant to grant the owner an instant action.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("implantAction")]
|
||||
public string? ImplantAction;
|
||||
|
||||
/// <summary>
|
||||
/// The entity this implant is inside
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? ImplantedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Should this implant be removeable?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("permanent")]
|
||||
public bool Permanent = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for opening the storage implant via action.
|
||||
/// </summary>
|
||||
public sealed class OpenStorageImplantEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed class UseFreedomImplantEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for triggering trigger events on the implant via action
|
||||
/// </summary>
|
||||
public sealed class ActivateImplantEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Shared.Implants.Components;
|
||||
/// <summary>
|
||||
/// Triggers implants when the action is pressed
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class TriggerImplantActionComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
156
Content.Shared/Implants/SharedImplanterSystem.cs
Normal file
156
Content.Shared/Implants/SharedImplanterSystem.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.Implants;
|
||||
|
||||
public abstract class SharedImplanterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ImplanterComponent, ComponentInit>(OnImplanterInit);
|
||||
SubscribeLocalEvent<ImplanterComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||
|
||||
}
|
||||
|
||||
private void OnImplanterInit(EntityUid uid, ImplanterComponent component, ComponentInit args)
|
||||
{
|
||||
if (component.Implant != null)
|
||||
component.ImplanterSlot.StartingItem = component.Implant;
|
||||
|
||||
_itemSlots.AddItemSlot(uid, ImplanterComponent.ImplanterSlotId, component.ImplanterSlot);
|
||||
}
|
||||
|
||||
private void OnEntInserted(EntityUid uid, ImplanterComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
var implantData = EntityManager.GetComponent<MetaDataComponent>(args.Entity);
|
||||
component.ImplantData = (implantData.EntityName, implantData.EntityDescription);
|
||||
}
|
||||
|
||||
//Instantly implant something and add all necessary components and containers.
|
||||
//Set to draw mode if not implant only
|
||||
public void Implant(EntityUid implanter, EntityUid target, ImplanterComponent component)
|
||||
{
|
||||
var implanterContainer = component.ImplanterSlot.ContainerSlot;
|
||||
|
||||
if (implanterContainer is null)
|
||||
return;
|
||||
|
||||
var implant = implanterContainer.ContainedEntities.FirstOrDefault();
|
||||
|
||||
if (!TryComp<SubdermalImplantComponent>(implant, out var implantComp))
|
||||
return;
|
||||
|
||||
//If the target doesn't have the implanted component, add it.
|
||||
var implantedComp = EnsureComp<ImplantedComponent>(target);
|
||||
var implantContainer = implantedComp.ImplantContainer;
|
||||
|
||||
implanterContainer.Remove(implant);
|
||||
implantComp.ImplantedEntity = target;
|
||||
implantContainer.OccludesLight = false;
|
||||
implantContainer.Insert(implant);
|
||||
|
||||
if (component.CurrentMode == ImplanterToggleMode.Inject && !component.ImplantOnly)
|
||||
DrawMode(component);
|
||||
|
||||
else
|
||||
ImplantMode(component);
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
//Draw the implant out of the target
|
||||
//TODO: Rework when surgery is in so implant cases can be a thing
|
||||
public void Draw(EntityUid implanter, EntityUid user, EntityUid target, ImplanterComponent component)
|
||||
{
|
||||
var implanterContainer = component.ImplanterSlot.ContainerSlot;
|
||||
|
||||
if (implanterContainer is null)
|
||||
return;
|
||||
|
||||
var permanentFound = false;
|
||||
|
||||
if (_container.TryGetContainer(target, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
{
|
||||
var implantCompQuery = GetEntityQuery<SubdermalImplantComponent>();
|
||||
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
if (!implantCompQuery.TryGetComponent(implant, out var implantComp))
|
||||
return;
|
||||
|
||||
//Don't remove a permanent implant and look for the next that can be drawn
|
||||
if (!implantContainer.CanRemove(implant))
|
||||
{
|
||||
var implantName = Identity.Entity(implant, EntityManager);
|
||||
var targetName = Identity.Entity(target, EntityManager);
|
||||
var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent", ("implant", implantName), ("target", targetName));
|
||||
_popup.PopupEntity(failedPermanentMessage, target, Filter.Entities(user));
|
||||
permanentFound = implantComp.Permanent;
|
||||
continue;
|
||||
}
|
||||
|
||||
implantContainer.Remove(implant);
|
||||
implantComp.ImplantedEntity = null;
|
||||
implanterContainer.Insert(implant);
|
||||
permanentFound = implantComp.Permanent;
|
||||
//Break so only one implant is drawn
|
||||
break;
|
||||
}
|
||||
|
||||
if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly && !permanentFound)
|
||||
ImplantMode(component);
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
}
|
||||
|
||||
private void ImplantMode(ImplanterComponent component)
|
||||
{
|
||||
component.CurrentMode = ImplanterToggleMode.Inject;
|
||||
ChangeOnImplantVisualizer(component);
|
||||
}
|
||||
|
||||
private void DrawMode(ImplanterComponent component)
|
||||
{
|
||||
component.CurrentMode = ImplanterToggleMode.Draw;
|
||||
ChangeOnImplantVisualizer(component);
|
||||
}
|
||||
|
||||
private void ChangeOnImplantVisualizer(ImplanterComponent component)
|
||||
{
|
||||
if (!TryComp<AppearanceComponent>(component.Owner, out var appearance))
|
||||
return;
|
||||
|
||||
bool implantFound;
|
||||
|
||||
if (component.ImplanterSlot.HasItem)
|
||||
implantFound = true;
|
||||
|
||||
else
|
||||
implantFound = false;
|
||||
|
||||
if (component.CurrentMode == ImplanterToggleMode.Inject && !component.ImplantOnly)
|
||||
_appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
|
||||
|
||||
else if (component.CurrentMode == ImplanterToggleMode.Inject && component.ImplantOnly)
|
||||
{
|
||||
_appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
|
||||
_appearance.SetData(component.Owner, ImplanterImplantOnlyVisuals.ImplantOnly, component.ImplantOnly, appearance);
|
||||
}
|
||||
|
||||
else
|
||||
_appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
|
||||
}
|
||||
}
|
||||
120
Content.Shared/Implants/SharedSubdermalImplantSystem.cs
Normal file
120
Content.Shared/Implants/SharedSubdermalImplantSystem.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Implants;
|
||||
|
||||
public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
|
||||
public const string BaseStorageId = "storagebase";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
private void OnInsert(EntityUid uid, SubdermalImplantComponent component, EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (component.ImplantedEntity == null)
|
||||
return;
|
||||
|
||||
if (component.ImplantAction != null)
|
||||
{
|
||||
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(component.ImplantAction));
|
||||
_actionsSystem.AddAction(component.ImplantedEntity.Value, action, uid);
|
||||
}
|
||||
|
||||
//replace micro bomb with macro bomb
|
||||
if (_container.TryGetContainer(component.ImplantedEntity.Value, ImplanterComponent.ImplantSlotId, out var implantContainer) && _tag.HasTag(uid, "MacroBomb"))
|
||||
{
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
if (_tag.HasTag(implant, "MicroBomb"))
|
||||
{
|
||||
implantContainer.Remove(implant);
|
||||
QueueDel(implant);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemoveAttempt(EntityUid uid, SubdermalImplantComponent component, ContainerGettingRemovedAttemptEvent args)
|
||||
{
|
||||
if (component.Permanent && component.ImplantedEntity != null)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, SubdermalImplantComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (component.ImplantedEntity == null)
|
||||
return;
|
||||
|
||||
var entCoords = Transform(component.ImplantedEntity.Value).Coordinates;
|
||||
|
||||
if (component.ImplantAction != null)
|
||||
_actionsSystem.RemoveProvidedActions(component.ImplantedEntity.Value, uid);
|
||||
|
||||
if (!_container.TryGetContainer(uid, BaseStorageId, out var storageImplant))
|
||||
return;
|
||||
|
||||
_container.EmptyContainer(storageImplant, moveTo: entCoords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces an implant into a person
|
||||
/// Good for on spawn related code or admin additions
|
||||
/// </summary>
|
||||
/// <param name="target">The entity to be implanted</param>
|
||||
/// <param name="implant"> The implant</param>
|
||||
/// <param name="component">The implant component</param>
|
||||
public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component)
|
||||
{
|
||||
//If the target doesn't have the implanted component, add it.
|
||||
var implantedComp = EnsureComp<ImplantedComponent>(target);
|
||||
var implantContainer = implantedComp.ImplantContainer;
|
||||
|
||||
component.ImplantedEntity = target;
|
||||
implantContainer.Insert(implant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force remove a singular implant
|
||||
/// </summary>
|
||||
/// <param name="target">the implanted entity</param>
|
||||
/// <param name="implant">the implant</param>
|
||||
/// <param name="component">the implant component</param>
|
||||
public void ForceRemove(EntityUid target, EntityUid implant)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(target, out var implanted))
|
||||
return;
|
||||
|
||||
var implantContainer = implanted.ImplantContainer;
|
||||
|
||||
implantContainer.Remove(implant);
|
||||
QueueDel(implant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes and deletes implants by force
|
||||
/// </summary>
|
||||
/// <param name="target">The entity to have implants removed</param>
|
||||
public void WipeImplants(EntityUid target)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(target, out var implanted))
|
||||
return;
|
||||
|
||||
var implantContainer = implanted.ImplantContainer;
|
||||
|
||||
_container.CleanContainer(implantContainer);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,11 @@ bite.ogg take from https://github.com/tgstation/tgstation/commit/d4f678a1772007f
|
||||
|
||||
bone_rattle.ogg licensed under CC0 1.0 and taken from spookymodem at https://freesound.org/people/spookymodem/sounds/202102/
|
||||
|
||||
- files: ["sadtrombone.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "sadtrombone.ogg taken from Citadel Station."
|
||||
source: "https://github.com/Citadel-Station-13/Citadel-Station-13/commit/35a1723e98a60f375df590ca572cc90f1bb80bd5"
|
||||
|
||||
The following sounds are taken from TGstation github (licensed under CC by 3.0):
|
||||
|
||||
demon_attack1.ogg: taken at https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0
|
||||
|
||||
BIN
Resources/Audio/Effects/sadtrombone.ogg
Normal file
BIN
Resources/Audio/Effects/sadtrombone.ogg
Normal file
Binary file not shown.
25
Resources/Locale/en-US/implant/implant.ftl
Normal file
25
Resources/Locale/en-US/implant/implant.ftl
Normal file
@@ -0,0 +1,25 @@
|
||||
## Implanter Attempt Messages
|
||||
|
||||
implanter-component-implanting-target = {$user} is trying to implant you with something!
|
||||
implanter-draw-failed-permanent = The {$implant} in {$target} is fused with them and cannot be removed!
|
||||
implanter-draw-failed = You tried to remove an implant but found nothing.
|
||||
|
||||
## UI
|
||||
implanter-draw-text = Draw
|
||||
implanter-inject-text = Inject
|
||||
|
||||
implanter-empty-text = None
|
||||
implanter-implant-text = {$implantName}{$lineBreak}{$implantDescription}
|
||||
|
||||
implanter-label = [color=white]Implant: {$currentEntities}{$lineBreak}Mode: {$modeString}[/color]
|
||||
|
||||
## Implanter Actions
|
||||
|
||||
open-storage-implant-action-name = open storage implant
|
||||
open-storage-implant-action-description = opens the storage implant embedded under your skin
|
||||
|
||||
activate-micro-bomb-action-name = activate micro bomb
|
||||
activate-micro-bomb-action-description = activates your internal microbomb, completely destroying you and your equipment
|
||||
|
||||
use-freedom-implant-action-name = use freedom implant
|
||||
use-freedom-implant-action-description = activating the implant will free you from any hand restraints
|
||||
@@ -4,5 +4,8 @@ ent-ArmorySmg = { ent-CrateArmorySMG }
|
||||
ent-ArmoryShotgun = { ent-CrateArmoryShotgun }
|
||||
.desc = { ent-CrateArmoryShotgun.desc }
|
||||
|
||||
ent-TrackingImplants = { ent-CrateTrackingImplants }
|
||||
.desc = { ent-CrateTrackingImplants.desc }
|
||||
|
||||
ent-ArmoryLaser = { ent-CrateArmoryLaser }
|
||||
.desc = { ent-CrateArmoryLaser.desc }
|
||||
.desc = { ent-CrateArmoryLaser.desc }
|
||||
|
||||
@@ -4,5 +4,8 @@ ent-CrateArmorySMG = SMG crate
|
||||
ent-CrateArmoryShotgun = Shotgun crate
|
||||
.desc = For when the enemy absolutely needs to be replaced with lead. Contains two Enforcer Combat Shotguns, and some standard shotgun shells. Requires Armory access to open.
|
||||
|
||||
ent-CrateTrackingImplants = Tracking implants
|
||||
.desc = Contains a handful of tracking implanters. Good for prisoners you'd like to release but still keep track of.
|
||||
|
||||
ent-CrateArmoryLaser = lasers crate
|
||||
.desc = Contains three lethal, high-energy laser guns. Requires Armory access to open.
|
||||
.desc = Contains three lethal, high-energy laser guns. Requires Armory access to open.
|
||||
|
||||
@@ -16,5 +16,11 @@ ent-CrateFunBoardGames = Board game crate
|
||||
ent-CrateFunATV = ATV crate
|
||||
.desc = An Absolutely Taxable Vehicle to help cargo with hauling.
|
||||
|
||||
ent-CrateFunSadTromboneImplants = Sad Trombone Implants
|
||||
.desc = Death's never been so fun before! Implant these to make dying a bit more happy.
|
||||
|
||||
ent-CrateFunLightImplants = Light Implants
|
||||
.desc = Light up your skin with these implants!
|
||||
|
||||
ent-CrateFunSyndicateSegway = Syndicate segway crate
|
||||
.desc = A crate containing a two-wheeler that will help you escape from the security officers. Or not.
|
||||
|
||||
@@ -22,6 +22,39 @@
|
||||
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
|
||||
event: !type:ToggleActionEvent
|
||||
|
||||
- type: instantAction
|
||||
id: OpenStorageImplant
|
||||
name: open-storage-implant-action-name
|
||||
description: open-storage-implant-action-description
|
||||
itemIconStyle: BigAction
|
||||
icon:
|
||||
sprite: Clothing/Back/Backpacks/backpack.rsi
|
||||
state: icon
|
||||
event: !type:OpenStorageImplantEvent
|
||||
|
||||
- type: instantAction
|
||||
id: ActivateMicroBomb
|
||||
name: activate-micro-bomb-action-name
|
||||
description: activate-micro-bomb-action-description
|
||||
checkCanInteract: false
|
||||
itemIconStyle: BigAction
|
||||
icon:
|
||||
sprite: Actions/Implants/implants.rsi
|
||||
state: explosive
|
||||
event: !type:ActivateImplantEvent
|
||||
|
||||
- type: instantAction
|
||||
id: ActivateFreedomImplant
|
||||
name: use-freedom-implant-action-name
|
||||
description: use-freedom-implant-action-description
|
||||
charges: 3
|
||||
checkCanInteract: false
|
||||
itemIconStyle: BigAction
|
||||
icon:
|
||||
sprite: Actions/Implants/implants.rsi
|
||||
state: freedom
|
||||
event: !type:UseFreedomImplantEvent
|
||||
|
||||
- type: instantAction
|
||||
id: ToggleSuitHelmet
|
||||
name: action-name-hardsuit
|
||||
|
||||
@@ -18,6 +18,16 @@
|
||||
category: Armory
|
||||
group: market
|
||||
|
||||
- type: cargoProduct
|
||||
id: TrackingImplant
|
||||
icon:
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
state: syringe_base0
|
||||
product: CrateTrackingImplants
|
||||
cost: 1000
|
||||
category: Armory
|
||||
group: market
|
||||
|
||||
- type: cargoProduct
|
||||
id: ArmoryLaser
|
||||
icon:
|
||||
@@ -26,4 +36,4 @@
|
||||
product: CrateArmoryLaser
|
||||
cost: 1600
|
||||
category: Armory
|
||||
group: market
|
||||
group: market
|
||||
|
||||
@@ -57,3 +57,23 @@
|
||||
cost: 1500
|
||||
category: Fun
|
||||
group: market
|
||||
|
||||
- type: cargoProduct
|
||||
id: FunSadTromboneImplants
|
||||
icon:
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
state: syringe_base0
|
||||
product: CrateFunSadTromboneImplants
|
||||
cost: 1000
|
||||
category: Fun
|
||||
group: market
|
||||
|
||||
- type: cargoProduct
|
||||
id: FunLightImplants
|
||||
icon:
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
state: syringe_base0
|
||||
product: CrateFunLightImplants
|
||||
cost: 1000
|
||||
category: Fun
|
||||
group: market
|
||||
|
||||
@@ -20,6 +20,15 @@
|
||||
- id: BoxLethalshot
|
||||
amount: 3
|
||||
|
||||
- type: entity
|
||||
id: CrateTrackingImplants
|
||||
parent: CrateWeaponSecure
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: TrackingImplanter
|
||||
amount: 5
|
||||
|
||||
- type: entity
|
||||
id: CrateArmoryLaser
|
||||
parent: CrateWeaponSecure
|
||||
@@ -27,4 +36,4 @@
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: WeaponLaserGun
|
||||
amount: 3
|
||||
amount: 3
|
||||
|
||||
@@ -118,6 +118,24 @@
|
||||
- id: VehicleKeyATV
|
||||
amount: 1
|
||||
|
||||
- type: entity
|
||||
id: CrateFunSadTromboneImplants
|
||||
parent: CrateGenericSteel
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: SadTromboneImplanter
|
||||
amount: 3
|
||||
|
||||
- type: entity
|
||||
id: CrateFunLightImplants
|
||||
parent: CrateGenericSteel
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: LightImplanter
|
||||
amount: 3
|
||||
|
||||
- type: entity
|
||||
id: CrateFunSyndicateSegway
|
||||
parent: CrateLivestock
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
- ClothingHandsGlovesNitrile
|
||||
- ClothingMaskSterile
|
||||
- DiseaseSwab
|
||||
- Implanter
|
||||
|
||||
- type: technology
|
||||
name: technologies-advanced-botany
|
||||
|
||||
@@ -280,6 +280,56 @@
|
||||
categories:
|
||||
- UplinkUtility
|
||||
|
||||
# Utility-Implants
|
||||
|
||||
- type: listing
|
||||
id: UplinkStorageImplanter
|
||||
icon: /Textures/Clothing/Back/Backpacks/backpack.rsi/icon.png
|
||||
name: Storage Implanter
|
||||
description: Hide goodies inside of yourself with new bluespace technology!
|
||||
productEntity: StorageImplanter
|
||||
cost:
|
||||
Telecrystal: 12
|
||||
categories:
|
||||
- UplinkUtility
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
blacklist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkFreedomImplanter
|
||||
icon: /Textures/Actions/Implants/implants.rsi/freedom.png
|
||||
name: Freedom Implanter
|
||||
description: Get away from those nasty sec officers with this three use implant!
|
||||
productEntity: FreedomImplanter
|
||||
cost:
|
||||
Telecrystal: 10
|
||||
categories:
|
||||
- UplinkUtility
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
blacklist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkMacroBombImplanter
|
||||
icon: /Textures/Actions/Implants/implants.rsi/explosive.png
|
||||
name: Macro Bomb Implanter
|
||||
description: Inject this and on death you'll create a large explosion. Huge team casualty cost, use at own risk. Replaces internal micro bomb.
|
||||
productEntity: MacroBombImplanter
|
||||
cost:
|
||||
Telecrystal: 20
|
||||
categories:
|
||||
- UplinkUtility
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
blacklist:
|
||||
tags:
|
||||
- TraitorUplink
|
||||
|
||||
# Bundles
|
||||
|
||||
- type: listing
|
||||
|
||||
146
Resources/Prototypes/Entities/Objects/Misc/implanters.yml
Normal file
146
Resources/Prototypes/Entities/Objects/Misc/implanters.yml
Normal file
@@ -0,0 +1,146 @@
|
||||
# Implanters
|
||||
|
||||
- type: entity
|
||||
name: implanter
|
||||
description: a syringe fitted to be used exclusively with implants
|
||||
id: BaseImplanter
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: ItemSlots
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
implanter_slot: !type:ContainerSlot { }
|
||||
- type: Implanter
|
||||
currentMode: Draw
|
||||
implanterSlot:
|
||||
name: Implant
|
||||
priority: 0
|
||||
whitelist:
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
netsync: false
|
||||
state: syringe_base0
|
||||
layers:
|
||||
- state: syringe4
|
||||
map: [ "implantFull" ]
|
||||
color: '#1cd94e'
|
||||
visible: false
|
||||
- state: syringe_base0
|
||||
map: [ "implantOnly" ]
|
||||
- type: Item
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
heldPrefix: 0
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.ImplanterVisuals.Full:
|
||||
implantFull:
|
||||
True: {visible: true}
|
||||
False: {visible: false}
|
||||
enum.ImplanterImplantOnlyVisuals.ImplantOnly:
|
||||
implantOnly:
|
||||
True: {state: broken}
|
||||
False: {state: syringe_base0}
|
||||
|
||||
- type: entity
|
||||
id: Implanter
|
||||
parent: BaseImplanter
|
||||
components:
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
|
||||
- type: entity
|
||||
id: BaseImplantOnlyImplanter
|
||||
parent: Implanter
|
||||
description: a single use implanter
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Chemistry/syringe.rsi
|
||||
netsync: false
|
||||
state: syringe_base0
|
||||
layers:
|
||||
- state: syringe4
|
||||
map: [ "implantFull" ]
|
||||
color: '#1cd94e'
|
||||
visible: true
|
||||
- state: syringe_base0
|
||||
map: [ "implantOnly" ]
|
||||
- type: Implanter
|
||||
currentMode: Inject
|
||||
implantOnly: true
|
||||
|
||||
#Fun implanters
|
||||
|
||||
- type: entity
|
||||
id: SadTromboneImplanter
|
||||
name: sad trombone implanter
|
||||
description: a single use implanter, the implant plays a sad tune on death
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: SadTromboneImplant
|
||||
|
||||
- type: entity
|
||||
id: LightImplanter
|
||||
name: light implanter
|
||||
description: a single use implanter, the implant emits light on activation
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: LightImplant
|
||||
|
||||
#Security implanters
|
||||
|
||||
- type: entity
|
||||
id: TrackingImplanter
|
||||
name: tracking implanter
|
||||
description: a single use implanter, the implant tracks
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: TrackingImplant
|
||||
|
||||
#Traitor implanters
|
||||
|
||||
- type: entity
|
||||
id: StorageImplanter
|
||||
name: storage implanter
|
||||
description: a single use implanter, the implant grants hidden storage
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: StorageImplant
|
||||
|
||||
- type: entity
|
||||
id: FreedomImplanter
|
||||
name: freedom implanter
|
||||
description: a single use implanter, the implant lets the user break out of hand restraints three times
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: FreedomImplant
|
||||
|
||||
#Nuclear Operative/Special implanters
|
||||
|
||||
- type: entity
|
||||
id: MicroBombImplanter
|
||||
name: micro bomb implanter
|
||||
description: a single use implanter, the implant is permanent and blows the user up on death
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: MicroBombImplant
|
||||
|
||||
- type: entity
|
||||
id: MacroBombImplanter
|
||||
name: macro bomb implanter
|
||||
description: a single use implanter, the implant creates a large explosion on death after the alloted time
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: MacroBombImplant
|
||||
@@ -0,0 +1,174 @@
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: BaseSubdermalImplant
|
||||
name: implant
|
||||
description: a microscopic chip that's injected under the skin
|
||||
abstract: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
|
||||
#Fun implants
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: SadTromboneImplant
|
||||
name: sad trombone implant
|
||||
description: plays a sad tune when the user dies
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
- type: TriggerOnMobstateChange
|
||||
mobState: Dead
|
||||
- type: EmitSoundOnTrigger
|
||||
sound:
|
||||
collection: SadTrombone
|
||||
params:
|
||||
variation: 0.125
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: LightImplant
|
||||
name: light implant
|
||||
description: makes your skin emit a faint light
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ToggleLight
|
||||
- type: PointLight
|
||||
enabled: false
|
||||
radius: 2.5
|
||||
softness: 5
|
||||
mask: /Textures/Effects/LightMasks/cone.png
|
||||
autoRot: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- Flashlight
|
||||
- type: UnpoweredFlashlight
|
||||
toggleAction:
|
||||
name: action-name-toggle-light
|
||||
description: action-description-toggle-light
|
||||
icon: Objects/Tools/flashlight.rsi/flashlight.png
|
||||
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
|
||||
event: !type:ToggleActionEvent
|
||||
|
||||
#Security implants
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: TrackingImplant
|
||||
name: tracking implant
|
||||
description: tracks whoever was implanted via the suit sensor network
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
- type: SuitSensor
|
||||
randomMode: false
|
||||
controlsLocked: true
|
||||
mode: SensorCords
|
||||
activationContainer: "ImplantContainer"
|
||||
- type: DeviceNetwork
|
||||
deviceNetId: Wireless
|
||||
transmitFrequencyId: SuitSensor
|
||||
- type: WirelessNetworkConnection
|
||||
range: 500
|
||||
|
||||
#Traitor implants
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: StorageImplant
|
||||
name: storage implant
|
||||
description: made with bluespace technology, allows the user to fit a few items in hidden storage
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: OpenStorageImplant
|
||||
- type: Item
|
||||
size: 9999
|
||||
- type: Storage
|
||||
capacity: 20 #10-20 should be more than enough for this
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
storagebase: !type:Container
|
||||
ents: [ ]
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.StorageUiKey.Key
|
||||
type: StorageBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: FreedomImplant
|
||||
name: freedom implant
|
||||
description: you can break these cuffs
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActivateFreedomImplant
|
||||
|
||||
#Nuclear Operative/Special Exclusive implants
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: MicroBombImplant
|
||||
name: micro bomb implant
|
||||
description: mission failed, user blows up on death to prevent company equipment from being stolen
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
implantAction: ActivateMicroBomb
|
||||
- type: TriggerOnMobstateChange
|
||||
mobState: Dead
|
||||
- type: TriggerImplantAction
|
||||
- type: ExplodeOnTrigger
|
||||
- type: GibOnTrigger
|
||||
deleteItems: true
|
||||
- type: Explosive
|
||||
explosionType: MicroBomb
|
||||
totalIntensity: 120
|
||||
intensitySlope: 5
|
||||
maxIntensity: 30
|
||||
canCreateVacuum: false
|
||||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- MicroBomb
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: MacroBombImplant
|
||||
name: macro bomb implant
|
||||
description: a large explosion packed into a small implant, be warned as this could be dangerous for your teammates
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
- type: TriggerOnMobstateChange #Chains with OnUseTimerTrigger
|
||||
mobState: Dead
|
||||
- type: OnUseTimerTrigger
|
||||
delay: 5
|
||||
initialBeepDelay: 0
|
||||
beepSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
- type: ExplodeOnTrigger
|
||||
- type: GibOnTrigger
|
||||
deleteItems: true
|
||||
- type: Explosive
|
||||
explosionType: Default
|
||||
totalIntensity: 4000
|
||||
intensitySlope: 5
|
||||
maxIntensity: 50
|
||||
canCreateVacuum: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- MacroBomb
|
||||
@@ -70,9 +70,9 @@
|
||||
key: enum.StoreUiKey.Key
|
||||
- type: Store
|
||||
preset: StorePresetUplink
|
||||
balance:
|
||||
balance:
|
||||
Telecrystal: 0
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: BaseUplinkRadio
|
||||
id: BaseUplinkRadio20TC
|
||||
@@ -82,6 +82,9 @@
|
||||
preset: StorePresetUplink
|
||||
balance:
|
||||
Telecrystal: 20
|
||||
- type: Tag
|
||||
tags:
|
||||
- TraitorUplink
|
||||
|
||||
- type: entity
|
||||
parent: BaseUplinkRadio
|
||||
@@ -92,6 +95,9 @@
|
||||
preset: StorePresetUplink
|
||||
balance:
|
||||
Telecrystal: 25
|
||||
- type: Tag
|
||||
tags:
|
||||
- TraitorUplink
|
||||
|
||||
#this uplink MUST be used for nukeops, as it has the tag for filtering the listing.
|
||||
- type: entity
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
- CryostasisBeaker
|
||||
- Dropper
|
||||
- Syringe
|
||||
- Implanter
|
||||
- PillCanister
|
||||
- ChemistryEmptyBottle01
|
||||
- Drone
|
||||
@@ -406,6 +407,7 @@
|
||||
- CryostasisBeaker
|
||||
- Dropper
|
||||
- Syringe
|
||||
- Implanter
|
||||
- PillCanister
|
||||
- BodyBag
|
||||
- ChemistryEmptyBottle01
|
||||
|
||||
@@ -107,3 +107,12 @@
|
||||
materials:
|
||||
Cloth: 20
|
||||
Plastic: 20
|
||||
|
||||
- type: latheRecipe
|
||||
id: Implanter
|
||||
icon: Objects/Specific/Chemistry/syringe.rsi/syringe_base0.png
|
||||
result: Implanter
|
||||
completetime: 1
|
||||
materials:
|
||||
Glass: 500
|
||||
Steel: 500
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
Piercing: 4
|
||||
groups:
|
||||
Burn: 3
|
||||
- !type:AddImplantSpecial
|
||||
implants: [ SadTromboneImplant ]
|
||||
|
||||
- type: startingGear
|
||||
id: ClownGear
|
||||
|
||||
4
Resources/Prototypes/SoundCollections/sadtrombone.yml
Normal file
4
Resources/Prototypes/SoundCollections/sadtrombone.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- type: soundCollection
|
||||
id: SadTrombone
|
||||
files:
|
||||
- /Audio/Effects/sadtrombone.ogg
|
||||
@@ -29,6 +29,22 @@
|
||||
texturePath: /Textures/Effects/fire.rsi
|
||||
fireStates: 3
|
||||
|
||||
- type: explosion
|
||||
id: MicroBomb
|
||||
damagePerIntensity:
|
||||
types:
|
||||
Heat: 6
|
||||
Blunt: 6
|
||||
Piercing: 6
|
||||
Structural: 20
|
||||
tileBreakChance: [ 0, 0.5, 1 ]
|
||||
tileBreakIntensity: [ 1, 10, 15 ]
|
||||
tileBreakRerollReduction: 30
|
||||
intensityPerState: 20
|
||||
lightColor: Orange
|
||||
texturePath: /Textures/Effects/fire.rsi
|
||||
fireStates: 6
|
||||
|
||||
- type: explosion
|
||||
id: Radioactive
|
||||
damagePerIntensity:
|
||||
@@ -39,7 +55,7 @@
|
||||
Piercing: 3
|
||||
lightColor: Green
|
||||
fireColor: Green
|
||||
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||
fireStates: 3
|
||||
|
||||
- type: explosion
|
||||
@@ -47,10 +63,10 @@
|
||||
damagePerIntensity:
|
||||
types:
|
||||
Cold: 5
|
||||
Blunt: 2
|
||||
Blunt: 2
|
||||
tileBreakChance: [0]
|
||||
tileBreakIntensity: [0]
|
||||
lightColor: Blue
|
||||
fireColor: Blue
|
||||
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||
fireStates: 3
|
||||
|
||||
@@ -380,6 +380,9 @@
|
||||
- type: Tag
|
||||
id: NoSpinOnThrow
|
||||
|
||||
- type: Tag
|
||||
id: TraitorUplink
|
||||
|
||||
- type: Tag
|
||||
id: NoBlockAnchoring
|
||||
|
||||
@@ -395,6 +398,12 @@
|
||||
- type: Tag
|
||||
id: Payload # for grenade/bomb crafting
|
||||
|
||||
- type: Tag
|
||||
id: MacroBomb
|
||||
|
||||
- type: Tag
|
||||
id: MicroBomb
|
||||
|
||||
- type: Tag
|
||||
id: PaintableAirlock
|
||||
|
||||
@@ -582,3 +591,5 @@
|
||||
- type: Tag
|
||||
id: Write
|
||||
|
||||
- type: Tag
|
||||
id: SubdermalImplant
|
||||
|
||||
BIN
Resources/Textures/Actions/Implants/implants.rsi/explosive.png
Normal file
BIN
Resources/Textures/Actions/Implants/implants.rsi/explosive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Resources/Textures/Actions/Implants/implants.rsi/freedom.png
Normal file
BIN
Resources/Textures/Actions/Implants/implants.rsi/freedom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 595 B |
17
Resources/Textures/Actions/Implants/implants.rsi/meta.json
Normal file
17
Resources/Textures/Actions/Implants/implants.rsi/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Implant icons taken from Citadel Station at commit https://github.com/Citadel-Station-13/Citadel-Station-13/commit/a2f6a7c20763da3d2f3cfb982e9ccd7922df6162",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "freedom"
|
||||
},
|
||||
{
|
||||
"name": "explosive"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user