diff --git a/Content.Client/_DV/Forensics/DVItemSlotVisualsSystem.cs b/Content.Client/_DV/Forensics/DVItemSlotVisualsSystem.cs new file mode 100644 index 00000000000..2e76979f765 --- /dev/null +++ b/Content.Client/_DV/Forensics/DVItemSlotVisualsSystem.cs @@ -0,0 +1,73 @@ +using System.Linq; +using Content.Shared._DV.Forensics; +using Robust.Client.GameObjects; +using Robust.Client.ResourceManagement; +using Robust.Shared.Containers; +using Robust.Shared.Utility; + +namespace Content.Client._DV.Forensics; + +public sealed class DVItemSlotVisualsSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + private static readonly ResPath TextureRoot = new("/Textures"); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInsertedIntoContainer); + SubscribeLocalEvent(OnRemovedFromContainer); + } + + private void RefreshVisuals(Entity ent) + { + var slot = _container.EnsureContainer(ent, ent.Comp.ItemSlot); + if (slot.ContainedEntity is { } contained) + { + _sprite.CopySprite(contained, ent.Owner); + _sprite.AddLayer(ent.Owner, ent.Comp.FilledSprite); + } + else + { + var sprite = Comp(ent); + var count = sprite.AllLayers.Count(); + for (var i = count - 1; i >= 0; i--) + { + _sprite.RemoveLayer((ent, sprite), i); + } + + if (ent.Comp.UnfilledSprite is not SpriteSpecifier.Rsi rsi) + { + throw new InvalidOperationException($"{ToPrettyString(ent)} has an unfilled sprite that's not an RSI"); + } + + if (!_resourceCache.TryGetResource(TextureRoot / rsi.RsiPath, out var res)) + { + throw new InvalidOperationException($"{ToPrettyString(ent)} has invalid RSI: {TextureRoot / rsi.RsiPath}"); + } + + _sprite.SetBaseRsi(ent.Owner, res.RSI); + _sprite.AddLayer(ent.Owner, ent.Comp.UnfilledSprite); + } + } + + private void OnInsertedIntoContainer(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ItemSlot) + return; + + RefreshVisuals(ent); + } + + private void OnRemovedFromContainer(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ItemSlot) + return; + + RefreshVisuals(ent); + } +} diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 3764d015e4c..5dc0fc4644c 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -103,6 +103,22 @@ public abstract class SharedItemSystem : EntitySystem VisualsChanged(uid); } + /// + /// DeltaV - clear item visuals + /// + public void ClearVisuals(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + ent.Comp.RsiPath = null; + ent.Comp.InhandVisuals = new(); + ent.Comp.HeldPrefix = null; + + Dirty(ent, ent.Comp); + VisualsChanged(ent.Owner); + } + #endregion private void OnHandInteract(EntityUid uid, ItemComponent component, InteractHandEvent args) diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 22564e64bc3..723d3ddd3a2 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -188,12 +188,20 @@ public abstract class SharedStorageSystem : EntitySystem return; } + // Begin DeltaV - the grid check excludes the item being resized because it's already in the storage which defeats the purpose so yoink it out to remove that + storage.StoredItems.Remove(itemEnt.Owner); UpdateOccupied((container.Owner, storage)); - if (!ItemFitsInGridLocation((itemEnt.Owner, itemEnt.Comp), (container.Owner, storage), loc)) + if (ItemFitsInGridLocation((itemEnt.Owner, itemEnt.Comp), (container.Owner, storage), loc)) + { + storage.StoredItems.Add(itemEnt.Owner, loc); + UpdateOccupied((container.Owner, storage)); + } + else { ContainerSystem.Remove(itemEnt.Owner, container, force: true); } + // End DeltaV - the grid check excludes the item being resized because it's already in the storage which defeats the purpose so yoink it out to remove that } private void OnNestedStorageCvar(bool obj) diff --git a/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeComponent.cs b/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeComponent.cs new file mode 100644 index 00000000000..09cc36a782b --- /dev/null +++ b/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Item; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Forensics; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVExpandToInsertedItemSizeComponent : Component +{ + [DataField(required: true)] + public string ItemSlot; + + [DataField(required: true)] + public ProtoId EmptySize; +} diff --git a/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeSystem.cs b/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeSystem.cs new file mode 100644 index 00000000000..5010e842f54 --- /dev/null +++ b/Content.Shared/_DV/Forensics/DVExpandToInsertedItemSizeSystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Item; +using Robust.Shared.Containers; + +namespace Content.Shared._DV.Forensics; + +public sealed class DVExpandToInsertedItemSizeSystem : EntitySystem +{ + [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnItemInserted); + SubscribeLocalEvent(OnItemRemoved); + } + + private void Refresh(Entity ent) + { + var slot = _container.EnsureContainer(ent, ent.Comp.ItemSlot); + if (slot.ContainedEntity is { } contained) + { + var item = Comp(contained); + _item.SetSize(ent, item.Size); + _item.SetShape(ent, item.Shape); + } + else + { + _item.SetSize(ent, ent.Comp.EmptySize); + _item.SetShape(ent, null); + } + } + + private void OnItemInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (ent.Comp.ItemSlot != args.Container.ID) + return; + + Refresh(ent); + } + + private void OnItemRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (ent.Comp.ItemSlot != args.Container.ID) + return; + + Refresh(ent); + } +} diff --git a/Content.Shared/_DV/Forensics/DVItemSlotVisualsComponent.cs b/Content.Shared/_DV/Forensics/DVItemSlotVisualsComponent.cs new file mode 100644 index 00000000000..0995def0c6d --- /dev/null +++ b/Content.Shared/_DV/Forensics/DVItemSlotVisualsComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared._DV.Forensics; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVItemSlotVisualsComponent : Component +{ + [DataField(required: true)] + public string ItemSlot; + + [DataField(required: true)] + public SpriteSpecifier FilledSprite; + + [DataField(required: true)] + public SpriteSpecifier UnfilledSprite; +} diff --git a/Content.Shared/_DV/Forensics/DVSeenInsertedItemComponent.cs b/Content.Shared/_DV/Forensics/DVSeenInsertedItemComponent.cs new file mode 100644 index 00000000000..115d3fe3d39 --- /dev/null +++ b/Content.Shared/_DV/Forensics/DVSeenInsertedItemComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Forensics; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVSeenInsertedItemComponent : Component +{ + [DataField(required: true)] + public string ItemSlot; +} diff --git a/Content.Shared/_DV/Forensics/DVSeenInsertedItemSystem.cs b/Content.Shared/_DV/Forensics/DVSeenInsertedItemSystem.cs new file mode 100644 index 00000000000..70511e57025 --- /dev/null +++ b/Content.Shared/_DV/Forensics/DVSeenInsertedItemSystem.cs @@ -0,0 +1,80 @@ +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Item; +using Content.Shared.NameModifier.EntitySystems; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared._DV.Forensics; + +public sealed class DVSeenInsertedItemSystem : EntitySystem +{ + [Dependency] private readonly NameModifierSystem _nameModifier = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedItemSystem _item = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnRefreshModifiers); + SubscribeLocalEvent(OnItemInserted); + SubscribeLocalEvent(OnItemRemoved); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + var slot = _container.EnsureContainer(ent, ent.Comp.ItemSlot); + if (slot.ContainedEntity is { } contained) + { + args.PushMarkup(Loc.GetString("dv-seen-inserted-item-examined.full", ("container", Identity.Entity(ent, EntityManager, args.Examiner)), ("contained", contained))); + } + else + { + args.PushMarkup(Loc.GetString("dv-seen-inserted-item-examined.empty", ("container", Identity.Entity(ent, EntityManager, args.Examiner)))); + } + } + + private void OnRefreshModifiers(Entity ent, ref RefreshNameModifiersEvent args) + { + var slot = _container.EnsureContainer(ent, ent.Comp.ItemSlot); + if (slot.ContainedEntity is not { } contained) + return; + + args.AddModifier("dv-seen-inserted-item-name-modifier", extraArgs: ("contained", contained)); + } + + private void Refresh(Entity ent) + { + _nameModifier.RefreshNameModifiers(ent.Owner); + + var slot = _container.EnsureContainer(ent, ent.Comp.ItemSlot); + if (slot.ContainedEntity is { } contained) + { + var item = Comp(contained); + _item.CopyVisuals(ent.Owner, item); + } + else + { + _item.ClearVisuals(ent.Owner); + } + } + + private void OnItemInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ItemSlot || _timing.ApplyingState) + return; + + Refresh(ent); + } + + private void OnItemRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ItemSlot || _timing.ApplyingState) + return; + + Refresh(ent); + } +} diff --git a/Resources/Locale/en-US/_DV/forensics/evidence_bag.ftl b/Resources/Locale/en-US/_DV/forensics/evidence_bag.ftl new file mode 100644 index 00000000000..b7ee7621261 --- /dev/null +++ b/Resources/Locale/en-US/_DV/forensics/evidence_bag.ftl @@ -0,0 +1,4 @@ +dv-seen-inserted-item-name-modifier = {$baseName} with { INDEFINITE($contained) } { $contained } +dv-seen-inserted-item-examined = + .full = { CAPITALIZE(SUBJECT($container)) } has [color=paleturquoise]{ INDEFINITE($contained) } { $contained }[/color] in { SUBJECT($container) }. + .empty = { CAPITALIZE(SUBJECT($container)) } has nothing in { SUBJECT($container) }. diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml index 5e03e195978..be47499d620 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml @@ -31,6 +31,8 @@ ClothingHeadHelmetBasic: 2 # DeltaV - added helmets to the SecTech. Another line of defense between the tide and your grey matter. BreachingCharge: 8 # DeltaV - added breaching charges TicketPad: 5 # imp + DVEvidenceBag: 10 # DeltaV - evidence bags + DVBoxEvidenceBags: 2 # DeltaV - evidence bags # security officers need to follow a diet regimen! contrabandInventory: WeaponMeleeNeedle: 2 diff --git a/Resources/Prototypes/_DV/Catalog/Fills/Lockers/security.yml b/Resources/Prototypes/_DV/Catalog/Fills/Lockers/security.yml index 230981678e3..f0f529a0935 100644 --- a/Resources/Prototypes/_DV/Catalog/Fills/Lockers/security.yml +++ b/Resources/Prototypes/_DV/Catalog/Fills/Lockers/security.yml @@ -238,3 +238,5 @@ - id: ClothingHeadsetSecurity - id: LunchboxSecurityFilledRandom prob: 0.3 + - id: DVBoxEvidenceBags + amount: 2 diff --git a/Resources/Prototypes/_DV/Entities/Objects/Specific/Forensics/evidence_bag.yml b/Resources/Prototypes/_DV/Entities/Objects/Specific/Forensics/evidence_bag.yml new file mode 100644 index 00000000000..b0361c828ef --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Objects/Specific/Forensics/evidence_bag.yml @@ -0,0 +1,49 @@ +- type: entity + parent: BaseItem + id: DVEvidenceBag + name: evidence bag + description: An evidence bag for containing evidence. + components: + - type: Sprite + sprite: _DV/Objects/Specific/Forensics/evidencebag.rsi + state: evidenceobj + - type: NameModifier + baseName: evidence bag + - type: ItemSlots + slots: + contained_evidence: + swap: false + - type: ContainerContainer + containers: + contained_evidence: !type:ContainerSlot + - type: DVItemSlotVisuals + itemSlot: contained_evidence + filledSprite: + sprite: _DV/Objects/Specific/Forensics/evidencebag.rsi + state: evidence + unfilledSprite: + sprite: _DV/Objects/Specific/Forensics/evidencebag.rsi + state: evidenceobj + - type: DVSeenInsertedItem + itemSlot: contained_evidence + - type: DVExpandToInsertedItemSize + itemSlot: contained_evidence + emptySize: Tiny + - type: Item + size: Tiny + +- type: entity + name: evidence bag box + parent: BoxCardboard + id: DVBoxEvidenceBags + description: A box that allegedly claims to contain evidence bags. + components: + - type: EntityTableContainerFill + containers: + storagebase: + id: DVEvidenceBag + amount: 9 + - type: Sprite + layers: + - state: box_security + - state: writing diff --git a/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidence.png b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidence.png new file mode 100644 index 00000000000..ed7f51283dd Binary files /dev/null and b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidence.png differ diff --git a/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidenceobj.png b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidenceobj.png new file mode 100644 index 00000000000..123dd31cc26 Binary files /dev/null and b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/evidenceobj.png differ diff --git a/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/meta.json b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/meta.json new file mode 100644 index 00000000000..543beff48bc --- /dev/null +++ b/Resources/Textures/_DV/Objects/Specific/Forensics/evidencebag.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Baystation12 at https://github.com/Baystation12/Baystation12/blob/0404a67862b7a32a4ac1017ad27c38e243f0057f/icons/obj/bags.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "evidence" + }, + { + "name": "evidenceobj" + } + ] +}