CatStation/Content.Shared/Item/ForcedItemStatusEffectSystem.cs
ScarKy0 9e065f7453
Fake Armblade Sting (#45724)
* init

* further work

TODO:
- Sprite
- Fix sound playing if item(s) were removed

* work work work

TODO:
- Sprite

* clean

* sprite

* yeah

* guh

* multi item support

* Update ForcedItemStatusEffectComponent.cs

* review + balance i dk
2026-09-03 19:58:20 +00:00

188 lines
6.9 KiB
C#

using Content.Shared.Cuffs;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Item;
public sealed partial class ForcedItemStatusEffectSystem : EntitySystem
{
[Dependency] private INetManager _net = default!;
[Dependency] private SharedHandsSystem _hands = default!;
[Dependency] private InventorySystem _inventory = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private StatusEffectsSystem _status = default!;
public override void Initialize()
{
Subs.SubscribeWithRelay<ForcedItemStatusEffectItemComponent, BeforeTargetHandcuffedEvent>(OnBeforeGotHandcuffed);
}
[SubscribeLocalEvent]
private void OnEffectApplied(Entity<ForcedItemStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
{
// "Why the IsServer check? Cannot this be predicted?"
// Yes and no. Status effects cannot really have a "user", as such we cannot play the sound in a predicted way.
// It only works for the local client who had this status effect applied to them.
// The audio could be the only unpredicted thing, but the audio and visual being not predicted
// makes it extremely obvious as to this being a fake item and not for example being a real armblade or some shit.
// Basically, if we want to sync it up we need self-predicting audio, like how popups do it.
// Also container prediction makes this a nightmare too but thats a lesser issue
if (!_net.IsServer)
return;
// No reapplying
if (entity.Comp.ItemEntities.Count != 0)
return;
foreach (var item in entity.Comp.HandItems)
{
var spawned = SpawnAtPosition(item, Transform(entity).Coordinates);
var pickedUp = entity.Comp.DropExisting
? _hands.TryForcePickupAnyHand(args.Target, spawned, false)
: _hands.TryPickupAnyHand(args.Target, spawned, false);
if (entity.Comp.Unremovable)
EnsureComp<UnremoveableComponent>(spawned);
if (!pickedUp)
{
QueueDel(spawned);
continue;
}
TrackItem(spawned, entity);
}
foreach (var slot in entity.Comp.InventoryItems)
{
var slots = _inventory.GetSlotEnumerator(args.Target, slot.Key);
while (slots.MoveNext(out var container))
{
if (container.ContainedEntity != null && entity.Comp.DropExisting)
{
if (_inventory.TryUnequip(args.Target, container.ID, true, entity.Comp.Force) &&
SpawnItemInInventory(entity, slot.Value, args.Target, container.ID))
{
continue;
}
}
if (container.ContainedEntities.Count == 0)
SpawnItemInInventory(entity, slot.Value, args.Target, container.ID);
}
}
if (entity.Comp.ItemEntities.Count == 0)
return;
_audio.PlayPvs(entity.Comp.SpawnSound, args.Target);
Dirty(entity);
}
[SubscribeLocalEvent]
private void OnEffectRemoved(Entity<ForcedItemStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
// Same reason as above.
if (!_net.IsServer)
return;
if (entity.Comp.ItemEntities.Count == 0)
return;
foreach (var item in entity.Comp.ItemEntities)
{
DisposeItem(item, entity, args.Target, false);
}
_audio.PlayPvs(entity.Comp.DespawnSound, args.Target);
}
[SubscribeLocalEvent]
private void OnItemShutdown(Entity<ForcedItemStatusEffectItemComponent> entity, ref ComponentShutdown args)
{
if (!TryComp<ForcedItemStatusEffectComponent>(entity.Comp.StatusEffect, out var effect))
return;
effect.ItemEntities.Remove(entity);
Dirty(entity.Comp.StatusEffect.Value, effect);
}
private void OnBeforeGotHandcuffed(Entity<ForcedItemStatusEffectItemComponent> entity, ref BeforeTargetHandcuffedEvent args)
{
if (!entity.Comp.RemoveWhenCuffed)
return;
if (!TryComp<ForcedItemStatusEffectComponent>(entity.Comp.StatusEffect, out var forcedItem))
return;
if (!TryComp<StatusEffectComponent>(entity.Comp.StatusEffect, out var status) || status.AppliedTo == null)
return;
DisposeItem(entity, (entity.Comp.StatusEffect.Value, forcedItem), status.AppliedTo.Value, true, args.User);
}
[SubscribeLocalEvent]
private void OnGotHandcuffed(Entity<ForcedItemStatusEffectComponent> entity, ref StatusEffectRelayedEvent<TargetHandcuffedEvent> args)
{
if (!entity.Comp.RemoveWhenCuffed)
return;
if (!TryComp<StatusEffectComponent>(entity, out var status) || status.AppliedTo == null)
return;
if (MetaData(entity).EntityPrototype?.ID is not { } entProto)
return;
_status.TryRemoveStatusEffect(status.AppliedTo.Value, entProto);
}
private void DisposeItem(EntityUid entity, Entity<ForcedItemStatusEffectComponent> status, EntityUid holder, bool playSound = true, EntityUid? user = null)
{
// Unremovable causes issues with predicted deletion so we get rid of it first.
if (HasComp<UnremoveableComponent>(entity))
RemComp<UnremoveableComponent>(entity);
// Cleanup is handled via shutdown of the item.
PredictedQueueDel(entity);
if (!playSound)
return;
// We want to predict the audio only if we have a user.
if (user == null && _net.IsServer)
_audio.PlayPvs(status.Comp.DespawnSound, holder);
else if (user != null)
_audio.PlayPredicted(status.Comp.DespawnSound, holder, user.Value);
}
private bool SpawnItemInInventory(Entity<ForcedItemStatusEffectComponent> ent, EntProtoId item, EntityUid target, string slot)
{
if (!_inventory.SpawnItemInSlot(target, slot, item, out var itemUid, true, force: ent.Comp.Force))
return false;
if (ent.Comp.Unremovable)
EnsureComp<UnremoveableComponent>(itemUid.Value);
TrackItem(itemUid.Value, ent);
return true;
}
private void TrackItem(EntityUid item, Entity<ForcedItemStatusEffectComponent> ent)
{
EnsureComp<ForcedItemStatusEffectItemComponent>(item, out var spawnedComp);
spawnedComp.StatusEffect = ent;
spawnedComp.RemoveWhenCuffed = ent.Comp.RemoveWhenCuffed;
ent.Comp.ItemEntities.Add(item);
Dirty(item, spawnedComp);
Dirty(ent);
}
}