diff --git a/Content.Client/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs b/Content.Client/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs new file mode 100644 index 00000000000..f9427362a66 --- /dev/null +++ b/Content.Client/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Nyanotrasen.Item.PseudoItem; + +namespace Content.Client.Nyanotrasen.Item.PseudoItem; + +public sealed class PseudoItemSystem : SharedPseudoItemSystem +{ +} diff --git a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs index 03c494332cf..76cfe7d904b 100644 --- a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs +++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs @@ -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] - // private const string PreventTag = "PreventLabel"; - // - // public override void Initialize() - // { - // base.Initialize(); - // SubscribeLocalEvent>(AddInsertVerb); - // SubscribeLocalEvent>(AddInsertAltVerb); - // SubscribeLocalEvent(OnEntRemoved); - // SubscribeLocalEvent(OnGettingPickedUpAttempt); - // SubscribeLocalEvent(OnDropAttempt); - // SubscribeLocalEvent(OnDoAfter); - // SubscribeLocalEvent(OnInsertAttempt); - // } - // - // private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args) - // { - // if (!args.CanInteract || !args.CanAccess) - // return; - // - // if (component.Active) - // return; - // - // if (!TryComp(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 args) - // { - // if (!args.CanInteract || !args.CanAccess) - // return; - // - // if (args.User == args.Target) - // return; - // - // if (args.Hands == null) - // return; - // - // if (!TryComp(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(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(toInsert); - // _tagSystem.TryAddTag(toInsert, PreventTag); - // _itemSystem.SetSize(toInsert, component.Size, item); - // - // if (!_storageSystem.Insert(storageUid, toInsert, out _, null, storage)) - // { - // component.Active = false; - // RemComp(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>(AddInsertAltVerb); + } + + private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (component.Active) + return; + + if (!TryComp(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); + } } diff --git a/Content.Shared/Item/ItemComponent.cs b/Content.Shared/Item/ItemComponent.cs index 9fadfa0783c..0f599ebdbed 100644 --- a/Content.Shared/Item/ItemComponent.cs +++ b/Content.Shared/Item/ItemComponent.cs @@ -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; /// [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 Size = "Small"; [Access(typeof(SharedItemSystem))] diff --git a/Content.Shared/Nyanotrasen/Item/Components/PseudoItemComponent.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs similarity index 84% rename from Content.Shared/Nyanotrasen/Item/Components/PseudoItemComponent.cs rename to Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs index cad826aa6b9..d3774439d36 100644 --- a/Content.Shared/Nyanotrasen/Item/Components/PseudoItemComponent.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs @@ -1,8 +1,7 @@ - using Content.Shared.Item; using Robust.Shared.Prototypes; -namespace Content.Shared.Nyanotrasen.Item.Components; +namespace Content.Shared.Nyanotrasen.Item.PseudoItem; /// /// For entities that behave like an item under certain conditions, @@ -21,5 +20,8 @@ public sealed partial class PseudoItemComponent : Component [DataField, AutoNetworkedField] public List? Shape; + [DataField, AutoNetworkedField] + public Vector2i StoredOffset; + public bool Active = false; } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs new file mode 100644 index 00000000000..7000c654048 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs @@ -0,0 +1,165 @@ +using Content.Shared.Item; +using Content.Shared.Storage; + +namespace Content.Shared.Nyanotrasen.Item.PseudoItem; + +/// +/// 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 +/// +public partial class SharedPseudoItemSystem +{ + protected bool CheckItemFits(Entity itemEnt, Entity 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 itemEnt, + Entity 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 GetAdjustedItemShape(Entity 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(); + 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 itemEnt, Entity storageEnt, + Vector2i location, IReadOnlyList 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; + } +} diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs new file mode 100644 index 00000000000..4b7910746f1 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs @@ -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] + private const string PreventTag = "PreventLabel"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddInsertVerb); + SubscribeLocalEvent(OnEntRemoved); + SubscribeLocalEvent(OnGettingPickedUpAttempt); + SubscribeLocalEvent(OnDropAttempt); + SubscribeLocalEvent(OnInsertAttempt); + SubscribeLocalEvent(OnInteractAttempt); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnAttackAttempt); + } + + private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (component.Active) + return; + + if (!TryComp(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(toInsert); + return false; + } + + component.Active = true; + return true; + } + + private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRemovedFromContainerMessage args) + { + if (!component.Active) + return; + + RemComp(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(); + } +} diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index eb81a78e3ef..c00df042653 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -10,9 +10,8 @@ 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; diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml index e2e15dcfa95..5110470a5d6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml @@ -48,8 +48,11 @@ - type: Stamina critThreshold: 85 - 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: