Fix PseudoItems (#821)

* Fix PseudoItem

* Add pain

* Cancel attack events
This commit is contained in:
Debug 2024-02-12 21:52:49 +01:00 committed by GitHub
parent f1e6147313
commit d8df4327ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 393 additions and 169 deletions

View File

@ -0,0 +1,7 @@
using Content.Shared.Nyanotrasen.Item.PseudoItem;
namespace Content.Client.Nyanotrasen.Item.PseudoItem;
public sealed class PseudoItemSystem : SharedPseudoItemSystem
{
}

View File

@ -1,174 +1,56 @@
using Content.Server.DoAfter;
using Content.Server.DoAfter;
using Content.Server.Item;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Item;
using Content.Shared.Item.PseudoItem;
using Content.Shared.Nyanotrasen.Item.PseudoItem;
using Content.Shared.Storage;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
namespace Content.Server.Item.PseudoItem;
namespace Content.Server.Nyanotrasen.Item.PseudoItem;
public sealed class PseudoItemSystem : EntitySystem
public sealed class PseudoItemSystem : SharedPseudoItemSystem
{
// [Dependency] private readonly StorageSystem _storageSystem = default!;
// [Dependency] private readonly ItemSystem _itemSystem = default!;
// [Dependency] private readonly DoAfterSystem _doAfter = default!;
// [Dependency] private readonly TagSystem _tagSystem = default!;
//
// [ValidatePrototypeId<TagPrototype>]
// private const string PreventTag = "PreventLabel";
//
// public override void Initialize()
// {
// base.Initialize();
// SubscribeLocalEvent<PseudoItemComponent, GetVerbsEvent<InnateVerb>>(AddInsertVerb);
// SubscribeLocalEvent<PseudoItemComponent, GetVerbsEvent<AlternativeVerb>>(AddInsertAltVerb);
// SubscribeLocalEvent<PseudoItemComponent, EntGotRemovedFromContainerMessage>(OnEntRemoved);
// SubscribeLocalEvent<PseudoItemComponent, GettingPickedUpAttemptEvent>(OnGettingPickedUpAttempt);
// SubscribeLocalEvent<PseudoItemComponent, DropAttemptEvent>(OnDropAttempt);
// SubscribeLocalEvent<PseudoItemComponent, PseudoItemInsertDoAfterEvent>(OnDoAfter);
// SubscribeLocalEvent<PseudoItemComponent, ContainerGettingInsertedAttemptEvent>(OnInsertAttempt);
// }
//
// private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<InnateVerb> args)
// {
// if (!args.CanInteract || !args.CanAccess)
// return;
//
// if (component.Active)
// return;
//
// if (!TryComp<StorageComponent>(args.Target, out var targetStorage))
// return;
//
// if (component.Size > targetStorage.StorageCapacityMax - targetStorage.StorageUsed)
// return;
//
// if (Transform(args.Target).ParentUid == uid)
// return;
//
// InnateVerb verb = new()
// {
// Act = () =>
// {
// TryInsert(args.Target, uid, component, targetStorage);
// },
// Text = Loc.GetString("action-name-insert-self"),
// Priority = 2
// };
// args.Verbs.Add(verb);
// }
//
// private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<AlternativeVerb> args)
// {
// if (!args.CanInteract || !args.CanAccess)
// return;
//
// if (args.User == args.Target)
// return;
//
// if (args.Hands == null)
// return;
//
// if (!TryComp<StorageComponent>(args.Hands.ActiveHandEntity, out var targetStorage))
// return;
//
// AlternativeVerb verb = new()
// {
// Act = () =>
// {
// StartInsertDoAfter(args.User, uid, args.Hands.ActiveHandEntity.Value, component);
// },
// Text = Loc.GetString("action-name-insert-other", ("target", Identity.Entity(args.Target, EntityManager))),
// Priority = 2
// };
// args.Verbs.Add(verb);
// }
//
// private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRemovedFromContainerMessage args)
// {
// if (!component.Active)
// return;
//
// RemComp<ItemComponent>(uid);
// component.Active = false;
// }
//
// private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
// GettingPickedUpAttemptEvent args)
// {
// if (args.User == args.Item)
// return;
//
// Transform(uid).AttachToGridOrMap();
// args.Cancel();
// }
//
// private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
// {
// if (component.Active)
// args.Cancel();
// }
//
// private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
// {
// if (args.Handled || args.Cancelled || args.Args.Used == null)
// return;
//
// args.Handled = TryInsert(args.Args.Used.Value, uid, component);
// }
//
// public bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component,
// StorageComponent? storage = null)
// {
// if (!Resolve(storageUid, ref storage))
// return false;
//
// if (component.Size > storage.StorageCapacityMax - storage.StorageUsed)
// return false;
//
// var item = EnsureComp<ItemComponent>(toInsert);
// _tagSystem.TryAddTag(toInsert, PreventTag);
// _itemSystem.SetSize(toInsert, component.Size, item);
//
// if (!_storageSystem.Insert(storageUid, toInsert, out _, null, storage))
// {
// component.Active = false;
// RemComp<ItemComponent>(toInsert);
// return false;
// }
//
// component.Active = true;
// return true;
// }
//
// private void StartInsertDoAfter(EntityUid inserter, EntityUid toInsert, EntityUid storageEntity,
// PseudoItemComponent? pseudoItem = null)
// {
// if (!Resolve(toInsert, ref pseudoItem))
// return;
//
// var ev = new PseudoItemInsertDoAfterEvent();
// var args = new DoAfterArgs(EntityManager, inserter, 5f, ev, toInsert, toInsert, storageEntity)
// {
// BreakOnTargetMove = true,
// BreakOnUserMove = true,
// NeedHand = true
// };
//
// _doAfter.TryStartDoAfter(args);
// }
//
// private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
// ContainerGettingInsertedAttemptEvent args)
// {
// if (!component.Active)
// return;
// // This hopefully shouldn't trigger, but this is a failsafe just in case so we dont bluespace them cats
// args.Cancel();
// }
[Dependency] private readonly StorageSystem _storage = default!;
[Dependency] private readonly ItemSystem _item = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PseudoItemComponent, GetVerbsEvent<AlternativeVerb>>(AddInsertAltVerb);
}
private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
if (component.Active)
return;
if (!TryComp<StorageComponent>(args.Using, out var targetStorage))
return;
if (!CheckItemFits((uid, component), (args.Using.Value, targetStorage)))
return;
if (args.Hands?.ActiveHandEntity == null)
return;
AlternativeVerb verb = new()
{
Act = () =>
{
StartInsertDoAfter(args.User, uid, args.Hands.ActiveHandEntity.Value, component);
},
Text = Loc.GetString("action-name-insert-other", ("target", Identity.Entity(args.Target, EntityManager))),
Priority = 2
};
args.Verbs.Add(verb);
}
}

View File

@ -1,4 +1,5 @@
using Content.Shared.Hands.Components;
using Content.Shared.Nyanotrasen.Item.PseudoItem;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@ -12,11 +13,11 @@ namespace Content.Shared.Item;
/// </summary>
[RegisterComponent]
[NetworkedComponent]
[Access(typeof(SharedItemSystem)), AutoGenerateComponentState(true)]
[Access(typeof(SharedItemSystem), typeof(SharedPseudoItemSystem)), AutoGenerateComponentState(true)] // DeltaV - Gave PseudoItem access
public sealed partial class ItemComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[Access(typeof(SharedItemSystem))]
[Access(typeof(SharedItemSystem), typeof(SharedPseudoItemSystem))] // DeltaV - Gave PseudoItem access
public ProtoId<ItemSizePrototype> Size = "Small";
[Access(typeof(SharedItemSystem))]

View File

@ -1,8 +1,7 @@
using Content.Shared.Item;
using Robust.Shared.Prototypes;
namespace Content.Shared.Nyanotrasen.Item.Components;
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
/// <summary>
/// For entities that behave like an item under certain conditions,
@ -21,5 +20,8 @@ public sealed partial class PseudoItemComponent : Component
[DataField, AutoNetworkedField]
public List<Box2i>? Shape;
[DataField, AutoNetworkedField]
public Vector2i StoredOffset;
public bool Active = false;
}

View File

@ -0,0 +1,165 @@
using Content.Shared.Item;
using Content.Shared.Storage;
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
/// <summary>
/// Almost all of this is code taken from other systems, but adapted to use PseudoItem.
/// I couldn't use the original functions because the resolve would fuck shit up, even if I passed a constructed itemcomp
///
/// This is horrible, and I hate it. But such is life
/// </summary>
public partial class SharedPseudoItemSystem
{
protected bool CheckItemFits(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt)
{
if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp))
return false;
if (Transform(itemEnt).Anchored)
return false;
if (storageEnt.Comp.Whitelist?.IsValid(itemEnt, EntityManager) == false)
return false;
if (storageEnt.Comp.Blacklist?.IsValid(itemEnt, EntityManager) == true)
return false;
var maxSize = _storage.GetMaxItemSize(storageEnt);
if (_item.GetSizePrototype(itemEnt.Comp.Size) > maxSize)
return false;
// The following is shitfucked together straight from TryGetAvailableGridSpace, but eh, it works
var itemComp = new ItemComponent
{ Size = itemEnt.Comp.Size, Shape = itemEnt.Comp.Shape, StoredOffset = itemEnt.Comp.StoredOffset };
var storageBounding = storageEnt.Comp.Grid.GetBoundingBox();
Angle startAngle;
if (storageEnt.Comp.DefaultStorageOrientation == null)
startAngle = Angle.FromDegrees(-itemComp.StoredRotation); // PseudoItem doesn't support this
else
{
if (storageBounding.Width < storageBounding.Height)
{
startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Horizontal
? Angle.Zero
: Angle.FromDegrees(90);
}
else
{
startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Vertical
? Angle.Zero
: Angle.FromDegrees(90);
}
}
for (var y = storageBounding.Bottom; y <= storageBounding.Top; y++)
{
for (var x = storageBounding.Left; x <= storageBounding.Right; x++)
{
for (var angle = startAngle; angle <= Angle.FromDegrees(360 - startAngle); angle += Math.PI / 2f)
{
var location = new ItemStorageLocation(angle, (x, y));
if (ItemFitsInGridLocation(itemEnt, storageEnt, location.Position, location.Rotation))
return true;
}
}
}
return false;
}
private bool ItemFitsInGridLocation(
Entity<PseudoItemComponent?> itemEnt,
Entity<StorageComponent?> storageEnt,
Vector2i position,
Angle rotation)
{
if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp))
return false;
var gridBounds = storageEnt.Comp.Grid.GetBoundingBox();
if (!gridBounds.Contains(position))
return false;
var itemShape = GetAdjustedItemShape(itemEnt, rotation, position);
foreach (var box in itemShape)
{
for (var offsetY = box.Bottom; offsetY <= box.Top; offsetY++)
{
for (var offsetX = box.Left; offsetX <= box.Right; offsetX++)
{
var pos = (offsetX, offsetY);
if (!IsGridSpaceEmpty(itemEnt, storageEnt, pos, itemShape))
return false;
}
}
}
return true;
}
private IReadOnlyList<Box2i> GetAdjustedItemShape(Entity<PseudoItemComponent?> entity, Angle rotation,
Vector2i position)
{
if (!Resolve(entity, ref entity.Comp))
return new Box2i[] { };
var shapes = entity.Comp.Shape ?? _item.GetSizePrototype(entity.Comp.Size).DefaultShape;
var boundingShape = shapes.GetBoundingBox();
var boundingCenter = ((Box2) boundingShape).Center;
var matty = Matrix3.CreateTransform(boundingCenter, rotation);
var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft;
var adjustedShapes = new List<Box2i>();
foreach (var shape in shapes)
{
var transformed = matty.TransformBox(shape).Translated(drift);
var floored = new Box2i(transformed.BottomLeft.Floored(), transformed.TopRight.Floored());
var translated = floored.Translated(position);
adjustedShapes.Add(translated);
}
return adjustedShapes;
}
private bool IsGridSpaceEmpty(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt,
Vector2i location, IReadOnlyList<Box2i> shape)
{
if (!Resolve(storageEnt, ref storageEnt.Comp))
return false;
var validGrid = false;
foreach (var grid in storageEnt.Comp.Grid)
{
if (grid.Contains(location))
{
validGrid = true;
break;
}
}
if (!validGrid)
return false;
foreach (var (ent, storedItem) in storageEnt.Comp.StoredItems)
{
if (ent == itemEnt.Owner)
continue;
var adjustedShape = shape;
foreach (var box in adjustedShape)
{
if (box.Contains(location))
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,165 @@
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Item.PseudoItem;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
public abstract partial class SharedPseudoItemSystem : EntitySystem
{
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly TagSystem _tag = default!;
[ValidatePrototypeId<TagPrototype>]
private const string PreventTag = "PreventLabel";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PseudoItemComponent, GetVerbsEvent<InnateVerb>>(AddInsertVerb);
SubscribeLocalEvent<PseudoItemComponent, EntGotRemovedFromContainerMessage>(OnEntRemoved);
SubscribeLocalEvent<PseudoItemComponent, GettingPickedUpAttemptEvent>(OnGettingPickedUpAttempt);
SubscribeLocalEvent<PseudoItemComponent, DropAttemptEvent>(OnDropAttempt);
SubscribeLocalEvent<PseudoItemComponent, ContainerGettingInsertedAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<PseudoItemComponent, InteractionAttemptEvent>(OnInteractAttempt);
SubscribeLocalEvent<PseudoItemComponent, PseudoItemInsertDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<PseudoItemComponent, AttackAttemptEvent>(OnAttackAttempt);
}
private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<InnateVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
if (component.Active)
return;
if (!TryComp<StorageComponent>(args.Target, out var targetStorage))
return;
if (!CheckItemFits((uid, component), (args.Target, targetStorage)))
return;
if (Transform(args.Target).ParentUid == uid)
return;
InnateVerb verb = new()
{
Act = () =>
{
TryInsert(args.Target, uid, component, targetStorage);
},
Text = Loc.GetString("action-name-insert-self"),
Priority = 2
};
args.Verbs.Add(verb);
}
private bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component,
StorageComponent? storage = null)
{
if (!Resolve(storageUid, ref storage))
return false;
if (!CheckItemFits((toInsert, component), (storageUid, storage)))
return false;
var itemComp = new ItemComponent
{ Size = component.Size, Shape = component.Shape, StoredOffset = component.StoredOffset };
AddComp(toInsert, itemComp);
_item.VisualsChanged(toInsert);
_tag.TryAddTag(toInsert, PreventTag);
if (!_storage.Insert(storageUid, toInsert, out _, null, storage))
{
component.Active = false;
RemComp<ItemComponent>(toInsert);
return false;
}
component.Active = true;
return true;
}
private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRemovedFromContainerMessage args)
{
if (!component.Active)
return;
RemComp<ItemComponent>(uid);
component.Active = false;
}
private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
GettingPickedUpAttemptEvent args)
{
if (args.User == args.Item)
return;
Transform(uid).AttachToGridOrMap();
args.Cancel();
}
private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
{
if (component.Active)
args.Cancel();
}
private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
ContainerGettingInsertedAttemptEvent args)
{
if (!component.Active)
return;
// This hopefully shouldn't trigger, but this is a failsafe just in case so we dont bluespace them cats
args.Cancel();
}
// Prevents moving within the bag :)
private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args)
{
if (args.Uid == args.Target && component.Active)
args.Cancel();
}
private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Used == null)
return;
args.Handled = TryInsert(args.Args.Used.Value, uid, component);
}
protected void StartInsertDoAfter(EntityUid inserter, EntityUid toInsert, EntityUid storageEntity,
PseudoItemComponent? pseudoItem = null)
{
if (!Resolve(toInsert, ref pseudoItem))
return;
var ev = new PseudoItemInsertDoAfterEvent();
var args = new DoAfterArgs(EntityManager, inserter, 5f, ev, toInsert, toInsert, storageEntity)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
};
_doAfter.TryStartDoAfter(args);
}
private void OnAttackAttempt(EntityUid uid, PseudoItemComponent component, AttackAttemptEvent args)
{
if (component.Active)
args.Cancel();
}
}

View File

@ -10,9 +10,8 @@ using Content.Shared.Hands.EntitySystems;
using Content.Shared.Implants.Components;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Item.PseudoItem;
using Content.Shared.Lock;
using Content.Shared.Nyanotrasen.Item.Components;
using Content.Shared.Nyanotrasen.Item.PseudoItem;
using Content.Shared.Placeable;
using Content.Shared.Popups;
using Content.Shared.Stacks;

View File

@ -50,8 +50,11 @@
- type: TypingIndicator
proto: felinid
- type: PseudoItem
storedOffset: 0,17
shape:
- 0,0,5,1
- 0,0,1,4
- 0,2,3,4
- 4,0,5,4
- type: Vocal
wilhelm: "/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg"
sounds: