diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 5741ee1b8f..452f5be09f 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -299,6 +299,7 @@ namespace Content.Client.Entry "IncreaseDamageOnWield", "TabletopGame", "LitOnPowered", + "Foldable", "TriggerOnSignalReceived", "ToggleDoorOnTrigger", "DeviceNetworkComponent", diff --git a/Content.Client/Morgue/Visualizers/BodyBagVisualizer.cs b/Content.Client/Morgue/Visualizers/BodyBagVisualizer.cs index 1aeff3e8c8..b64ce7d8d8 100644 --- a/Content.Client/Morgue/Visualizers/BodyBagVisualizer.cs +++ b/Content.Client/Morgue/Visualizers/BodyBagVisualizer.cs @@ -23,6 +23,10 @@ namespace Content.Client.Morgue.Visualizers { sprite.LayerSetVisible(BodyBagVisualLayers.Label, labelVal); } + else + { + sprite.LayerSetVisible(BodyBagVisualLayers.Label, false); + } } } diff --git a/Content.Client/Storage/Visualizers/StorageVisualizer.cs b/Content.Client/Storage/Visualizers/StorageVisualizer.cs index cd1a73927e..a48e0535db 100644 --- a/Content.Client/Storage/Visualizers/StorageVisualizer.cs +++ b/Content.Client/Storage/Visualizers/StorageVisualizer.cs @@ -10,6 +10,9 @@ namespace Content.Client.Storage.Visualizers [UsedImplicitly] public sealed class StorageVisualizer : AppearanceVisualizer { + /// + /// Sets the base sprite to this layer. Exists to make the inheritance tree less boilerplate-y. + /// [DataField("state")] private string? _stateBase; [DataField("state_open")] @@ -41,9 +44,24 @@ namespace Content.Client.Storage.Visualizers } component.TryGetData(StorageVisuals.Open, out bool open); - var state = open ? _stateOpen ?? $"{_stateBase}_open" : _stateClosed ?? $"{_stateBase}_door"; - sprite.LayerSetState(StorageVisualLayers.Door, state); + if (sprite.LayerMapTryGet(StorageVisualLayers.Door, out _)) + { + sprite.LayerSetVisible(StorageVisualLayers.Door, true); + + if (open && _stateOpen != null) + { + sprite.LayerSetState(StorageVisualLayers.Door, _stateOpen); + } + else if (!open && _stateClosed != null) + { + sprite.LayerSetState(StorageVisualLayers.Door, _stateClosed); + } + else + { + sprite.LayerSetVisible(StorageVisualLayers.Door, false); + } + } if (component.TryGetData(StorageVisuals.CanLock, out bool canLock) && canLock) { diff --git a/Content.Client/Visualizer/FoldableVisualizer.cs b/Content.Client/Visualizer/FoldableVisualizer.cs new file mode 100644 index 0000000000..6b2861dc7b --- /dev/null +++ b/Content.Client/Visualizer/FoldableVisualizer.cs @@ -0,0 +1,36 @@ +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Client.Visualizer; + + +public sealed class FoldableVisualizer : AppearanceVisualizer +{ + [DataField("key")] + private string _key = default!; + + public override void OnChangeData(AppearanceComponent appearance) + { + base.OnChangeData(appearance); + + var entManager = IoCManager.Resolve(); + + if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return; + + if (appearance.TryGetData("FoldedState", out bool folded) && folded) + { + sprite.LayerSetState(FoldableVisualLayers.Base, $"{_key}_folded"); + } + else + { + sprite.LayerSetState(FoldableVisualLayers.Base, $"{_key}"); + } + } + + public enum FoldableVisualLayers : byte + { + Base, + } +} diff --git a/Content.Client/Visualizer/RollerbedVisualizer.cs b/Content.Client/Visualizer/RollerbedVisualizer.cs new file mode 100644 index 0000000000..053d521d57 --- /dev/null +++ b/Content.Client/Visualizer/RollerbedVisualizer.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Client.Visualizer +{ + [UsedImplicitly] + public sealed class RollerbedVisualizer : AppearanceVisualizer + { + [DataField("key")] + private string _key = default!; + + public override void OnChangeData(AppearanceComponent appearance) + { + base.OnChangeData(appearance); + + var entManager = IoCManager.Resolve(); + + if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return; + + if (appearance.TryGetData("StrapState", out bool strapped) && strapped) + { + sprite.LayerSetState(0, $"{_key}_buckled"); + } + } + } +} diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index 0c40c6c768..d140e264fb 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -133,7 +133,7 @@ namespace Content.Server.Buckle.Components break; } - ownTransform.LocalPosition = Vector2.Zero + BuckleOffset; + ownTransform.LocalPosition = Vector2.Zero + strap.BuckleOffset; } public bool CanBuckle(EntityUid user, EntityUid to, [NotNullWhen(true)] out StrapComponent? strap) @@ -317,10 +317,17 @@ namespace Content.Server.Buckle.Components BuckledTo = null; - if (_entMan.GetComponent(Owner).Parent == _entMan.GetComponent(oldBuckledTo.Owner)) + var entManager = IoCManager.Resolve(); + var xform = entManager.GetComponent(Owner); + var oldBuckledXform = entManager.GetComponent(oldBuckledTo.Owner); + + if (xform.ParentUid == oldBuckledXform.Owner) { - _entMan.GetComponent(Owner).AttachParentToContainerOrGrid(); - _entMan.GetComponent(Owner).WorldRotation = _entMan.GetComponent(oldBuckledTo.Owner).WorldRotation; + xform.AttachParentToContainerOrGrid(); + xform.WorldRotation = oldBuckledXform.WorldRotation; + + if (oldBuckledTo.UnbuckleOffset != Vector2.Zero) + xform.Coordinates = oldBuckledXform.Coordinates.Offset(oldBuckledTo.UnbuckleOffset); } Appearance?.SetData(BuckleVisuals.Buckled, false); diff --git a/Content.Server/Buckle/Components/StrapComponent.cs b/Content.Server/Buckle/Components/StrapComponent.cs index 3d06c638e0..21d1588225 100644 --- a/Content.Server/Buckle/Components/StrapComponent.cs +++ b/Content.Server/Buckle/Components/StrapComponent.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; +using Content.Shared.ActionBlocker; using Content.Shared.Acts; using Content.Shared.Alert; +using Content.Shared.Buckle; using Content.Shared.Buckle.Components; using Content.Shared.DragDrop; using Content.Shared.Interaction; @@ -9,6 +11,8 @@ using Content.Shared.Sound; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -21,8 +25,6 @@ namespace Content.Server.Buckle.Components { [ComponentDependency] public readonly SpriteComponent? SpriteComponent = null; - [Dependency] private readonly IEntityManager _entMan = default!; - private readonly HashSet _buckledEntities = new(); /// @@ -37,6 +39,50 @@ namespace Content.Server.Buckle.Components [ViewVariables] [DataField("size")] private int _size = 100; private int _occupiedSize; + /// + /// The buckled entity will be offset by this amount from the center of the strap object. + /// If this offset it too big, it will be clamped to + /// + [DataField("buckleOffset", required: false)] + private Vector2 _buckleOffset = Vector2.Zero; + + private bool _enabled = true; + + /// + /// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled + /// + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + if (_enabled == value) return; + RemoveAll(); + } + } + + /// + /// The distance above which a buckled entity will be automatically unbuckled. + /// Don't change it unless you really have to + /// + [DataField("maxBuckleDistance", required: false)] + public float MaxBuckleDistance = 1f; + + /// + /// You can specify the offset the entity will have after unbuckling. + /// + [DataField("unbuckleOffset", required: false)] + public Vector2 UnbuckleOffset = Vector2.Zero; + + /// + /// Gets and clamps the buckle offset to MaxBuckleDistance + /// + public Vector2 BuckleOffset => Vector2.Clamp( + _buckleOffset, + Vector2.One * -MaxBuckleDistance, + Vector2.One * MaxBuckleDistance); + /// /// The entity that is currently buckled here, synced from /// @@ -96,6 +142,8 @@ namespace Content.Server.Buckle.Components /// True if added, false otherwise public bool TryAdd(BuckleComponent buckle, bool force = false) { + if (!Enabled) return false; + if (!force && !HasSpace(buckle)) { return false; @@ -110,6 +158,12 @@ namespace Content.Server.Buckle.Components buckle.Appearance?.SetData(StrapVisuals.RotationAngle, _rotation); + // Update the visuals of the strap object + if (IoCManager.Resolve().TryGetComponent(Owner, out var appearance)) + { + appearance.SetData("StrapState", true); + } + #pragma warning disable 618 SendMessage(new StrapMessage(buckle.Owner, Owner)); #pragma warning restore 618 @@ -126,6 +180,11 @@ namespace Content.Server.Buckle.Components { if (_buckledEntities.Remove(buckle.Owner)) { + if (IoCManager.Resolve().TryGetComponent(Owner, out var appearance)) + { + appearance.SetData("StrapState", false); + } + _occupiedSize -= buckle.Size; #pragma warning disable 618 SendMessage(new UnStrapMessage(buckle.Owner, Owner)); @@ -147,9 +206,11 @@ namespace Content.Server.Buckle.Components private void RemoveAll() { + var entManager = IoCManager.Resolve(); + foreach (var entity in _buckledEntities.ToArray()) { - if (_entMan.TryGetComponent(entity, out var buckle)) + if (entManager.TryGetComponent(entity, out var buckle)) { buckle.TryUnbuckle(entity, true); } @@ -166,7 +227,9 @@ namespace Content.Server.Buckle.Components bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { - if (!_entMan.TryGetComponent(eventArgs.User, out var buckle)) + var entManager = IoCManager.Resolve(); + + if (!entManager.TryGetComponent(eventArgs.User, out var buckle)) { return false; } @@ -176,7 +239,9 @@ namespace Content.Server.Buckle.Components public override bool DragDropOn(DragDropEvent eventArgs) { - if (!_entMan.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false; + var entManager = IoCManager.Resolve(); + + if (!entManager.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false; return buckleComponent.TryBuckle(eventArgs.User, Owner); } } diff --git a/Content.Server/Buckle/Systems/BuckleSystem.cs b/Content.Server/Buckle/Systems/BuckleSystem.cs index 0135e3aea0..8ec67713a6 100644 --- a/Content.Server/Buckle/Systems/BuckleSystem.cs +++ b/Content.Server/Buckle/Systems/BuckleSystem.cs @@ -42,10 +42,12 @@ namespace Content.Server.Buckle.Systems if (!args.CanAccess || !args.CanInteract || !component.Buckled) return; - Verb verb = new(); - verb.Act = () => component.TryUnbuckle(args.User); - verb.Text = Loc.GetString("verb-categories-unbuckle"); - verb.IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"; + Verb verb = new() + { + Act = () => component.TryUnbuckle(args.User), + Text = Loc.GetString("verb-categories-unbuckle"), + IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png" + }; if (args.Target == args.User && args.Using == null) { @@ -81,7 +83,7 @@ namespace Content.Server.Buckle.Systems var strapPosition = EntityManager.GetComponent(strap.Owner).Coordinates.Offset(buckle.BuckleOffset); - if (ev.NewPosition.InRange(EntityManager, strapPosition, 0.2f)) + if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance)) { return; } diff --git a/Content.Server/Buckle/Systems/StrapSystem.cs b/Content.Server/Buckle/Systems/StrapSystem.cs index 9acd58d2db..c1d8ebe784 100644 --- a/Content.Server/Buckle/Systems/StrapSystem.cs +++ b/Content.Server/Buckle/Systems/StrapSystem.cs @@ -27,7 +27,7 @@ namespace Content.Server.Buckle.Systems private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetInteractionVerbsEvent args) { - if (args.Hands == null || !args.CanAccess || !args.CanInteract) + if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled) return; // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this @@ -41,9 +41,12 @@ namespace Content.Server.Buckle.Systems if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range)) continue; - Verb verb = new(); - verb.Act = () => buckledComp.TryUnbuckle(args.User); - verb.Category = VerbCategory.Unbuckle; + Verb verb = new() + { + Act = () => buckledComp.TryUnbuckle(args.User), + Category = VerbCategory.Unbuckle + }; + if (entity == args.User) verb.Text = Loc.GetString("verb-self-target-pronoun"); else @@ -64,10 +67,12 @@ namespace Content.Server.Buckle.Systems component.HasSpace(buckle) && _interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range)) { - Verb verb = new(); - verb.Act = () => buckle.TryBuckle(args.User, args.Target); - verb.Category = VerbCategory.Buckle; - verb.Text = Loc.GetString("verb-self-target-pronoun"); + Verb verb = new() + { + Act = () => buckle.TryBuckle(args.User, args.Target), + Category = VerbCategory.Buckle, + Text = Loc.GetString("verb-self-target-pronoun") + }; args.Verbs.Add(verb); } @@ -82,14 +87,15 @@ namespace Content.Server.Buckle.Systems if (!_interactionSystem.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored)) return; - Verb verb = new(); - verb.Act = () => usingBuckle.TryBuckle(args.User, args.Target); - verb.Category = VerbCategory.Buckle; - verb.Text = EntityManager.GetComponent(@using).EntityName; - - // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is - // just a held object, the user is probably just trying to sit down. - verb.Priority = EntityManager.HasComponent(@using) ? 1 : -1; + Verb verb = new() + { + Act = () => usingBuckle.TryBuckle(args.User, args.Target), + Category = VerbCategory.Buckle, + Text = EntityManager.GetComponent(@using).EntityName, + // just a held object, the user is probably just trying to sit down. + // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is + Priority = EntityManager.HasComponent(@using) ? 1 : -1 + }; args.Verbs.Add(verb); } diff --git a/Content.Server/Foldable/FoldableComponent.cs b/Content.Server/Foldable/FoldableComponent.cs new file mode 100644 index 0000000000..514d4eddb5 --- /dev/null +++ b/Content.Server/Foldable/FoldableComponent.cs @@ -0,0 +1,25 @@ +#nullable enable + +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Foldable +{ + + /// + /// Used to create "foldable structures" that you can pickup like an item when folded. Used for rollerbeds and wheelchairs + /// + [RegisterComponent] + public class FoldableComponent : Component + { + public override string Name => "Foldable"; + + [DataField("folded")] + [ViewVariables] + public bool IsFolded = false; + + public bool CanBeFolded = true; + } +} diff --git a/Content.Server/Foldable/FoldableSystem.cs b/Content.Server/Foldable/FoldableSystem.cs new file mode 100644 index 0000000000..380b9f29b5 --- /dev/null +++ b/Content.Server/Foldable/FoldableSystem.cs @@ -0,0 +1,131 @@ +using System.Linq; +using Content.Server.Buckle.Components; +using Content.Server.Storage.Components; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Foldable +{ + [UsedImplicitly] + public sealed class FoldableSystem : EntitySystem + { + [Dependency] private SharedContainerSystem _container = default!; + + private const string FoldKey = "FoldedState"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnFoldableInit); + SubscribeLocalEvent(OnFoldableOpenAttempt); + SubscribeLocalEvent(OnPickedUpAttempt); + SubscribeLocalEvent(AddFoldVerb); + } + + private void OnFoldableOpenAttempt(EntityUid uid, FoldableComponent component, StorageOpenAttemptEvent args) + { + if (component.IsFolded) + args.Cancel(); + } + + private void OnFoldableInit(EntityUid uid, FoldableComponent component, ComponentInit args) + { + SetFolded(component, component.IsFolded); + } + + private bool TryToggleFold(FoldableComponent comp) + { + return TrySetFolded(comp, !comp.IsFolded); + } + + /// + /// Try to fold/unfold + /// + /// + /// Folded state we want + /// True if successful + private bool TrySetFolded(FoldableComponent comp, bool state) + { + if (state == comp.IsFolded) + return false; + + if (_container.IsEntityInContainer(comp.Owner)) + return false; + + // First we check if the foldable object has a strap component + if (EntityManager.TryGetComponent(comp.Owner, out StrapComponent? strap)) + { + // If an entity is buckled to the object we can't pick it up or fold it + if (strap.BuckledEntities.Any()) + return false; + } + + SetFolded(comp, state); + return true; + } + + /// + /// Set the folded state of the given + /// + /// + /// If true, the component will become folded, else unfolded + private void SetFolded(FoldableComponent component, bool folded) + { + component.IsFolded = folded; + component.CanBeFolded = !_container.IsEntityInContainer(component.Owner); + + // You can't buckle an entity to a folded object + if (EntityManager.TryGetComponent(component.Owner, out StrapComponent? strap)) + strap.Enabled = !component.IsFolded; + + // Update visuals only if the value has changed + if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearance)) + appearance.SetData(FoldKey, folded); + } + + #region Event handlers + + /// + /// Prevents foldable objects to be picked up when unfolded + /// + /// + /// + /// + private void OnPickedUpAttempt(EntityUid uid, FoldableComponent component, AttemptItemPickupEvent args) + { + if (!component.IsFolded) + args.Cancel(); + } + + #endregion + + #region Verb + + private void AddFoldVerb(EntityUid uid, FoldableComponent component, GetAlternativeVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + Verb verb = new() + { + Act = () => TryToggleFold(component), + Text = component.IsFolded ? Loc.GetString("unfold-verb") : Loc.GetString("fold-verb"), + IconTexture = "/Textures/Interface/VerbIcons/fold.svg.192dpi.png", + + // If the object is unfolded and they click it, they want to fold it, if it's folded, they want to pick it up + Priority = component.IsFolded ? 0 : 2, + }; + + args.Verbs.Add(verb); + } + + #endregion + } +} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index 7ff590084a..bada7c58ca 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -193,12 +193,18 @@ namespace Content.Server.Storage.Components return false; } - return true; + var @event = new StorageOpenAttemptEvent(); + IoCManager.Resolve().EventBus.RaiseLocalEvent(Owner, @event); + + return !@event.Cancelled; } public virtual bool CanClose(EntityUid user, bool silent = false) { - return true; + var @event = new StorageCloseAttemptEvent(); + IoCManager.Resolve().EventBus.RaiseLocalEvent(Owner, @event); + + return !@event.Cancelled; } public void ToggleOpen(EntityUid user) @@ -479,4 +485,14 @@ namespace Content.Server.Storage.Components } } } + + public sealed class StorageOpenAttemptEvent : CancellableEntityEventArgs + { + + } + + public sealed class StorageCloseAttemptEvent : CancellableEntityEventArgs + { + + } } diff --git a/Content.Shared/Buckle/Components/SharedStrapComponent.cs b/Content.Shared/Buckle/Components/SharedStrapComponent.cs index d396eacf32..38aeb1f341 100644 --- a/Content.Shared/Buckle/Components/SharedStrapComponent.cs +++ b/Content.Shared/Buckle/Components/SharedStrapComponent.cs @@ -57,11 +57,13 @@ namespace Content.Shared.Buckle.Components } [Serializable, NetSerializable] - public enum StrapVisuals + public enum StrapVisuals : byte { - RotationAngle + RotationAngle, + BuckledState } + // TODO : Convert this to an Entity Message. Careful, it will Break ShuttleControllerComponent (only place where it's used) [Serializable, NetSerializable] #pragma warning disable 618 public abstract class StrapChangeMessage : ComponentMessage diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 67028d1353..398aec3203 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -615,11 +615,14 @@ namespace Content.Shared.Hands.Components protected bool CanInsertEntityIntoHand(Hand hand, EntityUid entity) { var handContainer = hand.Container; - if (handContainer == null) - return false; + if (handContainer == null) return false; - if (!handContainer.CanInsert(entity)) - return false; + if (!handContainer.CanInsert(entity)) return false; + + var @event = new AttemptItemPickupEvent(); + _entMan.EventBus.RaiseLocalEvent(entity, @event); + + if (@event.Cancelled) return false; return true; } diff --git a/Content.Shared/Item/PickupAttemptEvent.cs b/Content.Shared/Item/PickupAttemptEvent.cs index 1d922a6d04..e52c0722a9 100644 --- a/Content.Shared/Item/PickupAttemptEvent.cs +++ b/Content.Shared/Item/PickupAttemptEvent.cs @@ -2,6 +2,9 @@ namespace Content.Shared.Item { + /// + /// Raised on a *mob* when it tries to pickup something + /// public class PickupAttemptEvent : CancellableEntityEventArgs { public PickupAttemptEvent(EntityUid uid) @@ -11,4 +14,14 @@ namespace Content.Shared.Item public EntityUid Uid { get; } } + + /// + /// Raised on the *item* when tried to be picked up + /// + /// + /// Doesn't just handle "items" but calling it "PickedUpAttempt" is too close to "Pickup" for the sleep deprived brain. + /// + public sealed class AttemptItemPickupEvent : CancellableEntityEventArgs + { + } } diff --git a/Content.Shared/Item/SharedItemComponent.cs b/Content.Shared/Item/SharedItemComponent.cs index bd86f8e983..5c83bb95c7 100644 --- a/Content.Shared/Item/SharedItemComponent.cs +++ b/Content.Shared/Item/SharedItemComponent.cs @@ -9,7 +9,6 @@ using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Physics; -using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; diff --git a/Resources/Locale/en-US/foldable/components/foldable-component.ftl b/Resources/Locale/en-US/foldable/components/foldable-component.ftl new file mode 100644 index 0000000000..d3e4ecefb5 --- /dev/null +++ b/Resources/Locale/en-US/foldable/components/foldable-component.ftl @@ -0,0 +1,5 @@ +# Foldable + +foldable-deploy-fail = You can't deploy the {$object} here. +fold-verb = Fold +unfold-verb = Unfold diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml index f9d3b4f2b3..201928f6d2 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml @@ -9,6 +9,7 @@ sprite: Objects/Specific/Medical/Morgue/bodybags.rsi layers: - state: bag + map: ["enum.FoldableVisualLayers.Base"] - state: open_overlay map: ["enum.StorageVisualLayers.Door"] - state: label_overlay @@ -27,13 +28,18 @@ !type:PhysShapeAabb bounds: "-0.5,-0.45,0.5,0.1" mass: 5 + mask: + - Impassable + - SmallImpassable - type: BodyBagEntityStorage CanWeldShut: false Capacity: 1 + IsCollidableWhenOpen: true closeSound: path: /Audio/Misc/zip.ogg openSound: path: /Audio/Misc/zip.ogg + - type: Foldable - type: PaperLabel labelSlot: insertVerbText: Attach Label @@ -45,20 +51,20 @@ visuals: - type: StorageVisualizer state_open: open_overlay - state_closed: bag + - type: FoldableVisualizer + key: bag - type: BodyBagVisualizer - type: Pullable - type: entity - id: BodyBag_Item + id: BodyBag_Folded name: body bag description: A plastic bag designed for the storage and transportation of cadavers. - parent: BaseItem + parent: BodyBag_Container + suffix: Folded components: - - type: Sprite - netsync: false - sprite: Objects/Specific/Medical/Morgue/bodybags.rsi - state: item + - type: Foldable + folded: true # - type: BodyBagItem #TODO: we need some kind of generic placable, like thus: # - type: Placeable # prototype: someId diff --git a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml index 1ea3b3c08e..10081edb36 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml @@ -24,6 +24,7 @@ noRot: true - type: Strap position: Stand + buckleOffset: "0,0.15" - type: Pullable - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Structures/Furniture/rollerbeds.yml b/Resources/Prototypes/Entities/Structures/Furniture/rollerbeds.yml new file mode 100644 index 0000000000..524c812043 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Furniture/rollerbeds.yml @@ -0,0 +1,94 @@ +- type: entity + id: RollerBed + parent: BaseItem + name: rollerbed + description: Used to carry patients around without damaging them. + components: + - type: Transform + noRot: true + - type: Item + size: 5 + - type: Sprite + sprite: Structures/Furniture/rollerbeds.rsi + netsync: false + noRot: true + layers: + - state: rollerbed + map: ["enum.FoldableVisualLayers.Base"] + - type: MovedByPressure + - type: DamageOnHighSpeedImpact + soundHit: /Audio/Effects/bang.ogg + - type: InteractionOutline + - type: Physics + - type: Fixtures + fixtures: + - shape: + !type:PhysShapeCircle + radius: 0.35 + mass: 30 + mask: + - Impassable + - SmallImpassable + layer: + - Opaque + - type: Damageable + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 50 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + - !type:SpawnEntitiesBehavior + spawn: + SheetSteel1: + min: 1 + max: 1 + - type: Pullable + - type: Strap + position: Down + rotation: -90 + buckleOffset: "0,0.15" + unbuckleOffset: "0,0.15" + - type: Foldable + - type: Appearance + visuals: + - type: FoldableVisualizer + key: rollerbed + - type: RollerbedVisualizer + key: rollerbed + +- type: entity + id: CheapRollerBed + name: rollerbed + parent: RollerBed + description: A run-down rollerbed. Used to carry patients around. + components: + - type: Sprite + layers: + - state: cheap_rollerbed + map: [ "enum.FoldableVisualLayers.Base" ] + - type: Appearance + visuals: + - type: FoldableVisualizer + key: cheap_rollerbed + - type: RollerbedVisualizer + key: cheap_rollerbed + +- type: entity + id: EmergencyRollerBed + name: rollerbed + parent: RollerBed + description: A robust looking rollerbed used for emergencies. + components: + - type: Sprite + layers: + - state: emergency_rollerbed + map: [ "enum.FoldableVisualLayers.Base" ] + - type: Appearance + visuals: + - type: FoldableVisualizer + key: emergency_rollerbed + - type: RollerbedVisualizer + key: emergency_rollerbed diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index d8073e3104..e2b05bd992 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -8,6 +8,9 @@ visuals: - type: StorageVisualizer state: cabinet + state_open: cabinet_open + state_closed: cabinet_door + - type: AccessReader access: [["Bar"]] - type: EntityStorage @@ -26,6 +29,8 @@ visuals: - type: StorageVisualizer state: qm + state_open: qm_open + state_closed: qm_door - type: AccessReader access: [["Cargo"]] # TODO access [["Quartermaster"]] @@ -39,6 +44,8 @@ visuals: - type: StorageVisualizer state: cap + state_open: cap_open + state_closed: cap_door - type: AccessReader access: [["Captain"]] @@ -51,6 +58,8 @@ visuals: - type: StorageVisualizer state: hop + state_open: hop_open + state_closed: hop_door - type: AccessReader access: [["HeadOfPersonnel"]] @@ -64,6 +73,8 @@ visuals: - type: StorageVisualizer state: ce + state_open: ce_open + state_closed: ce_door - type: AccessReader access: [ [ "Engineering", "Command" ] ] @@ -77,6 +88,7 @@ visuals: - type: StorageVisualizer state: eng + state_open: eng_open state_closed: eng_elec_door - type: AccessReader access: [ [ "Engineering" ] ] @@ -91,6 +103,7 @@ visuals: - type: StorageVisualizer state: eng + state_open: eng_open state_closed: eng_weld_door - type: AccessReader access: [ [ "Engineering" ] ] @@ -105,6 +118,8 @@ visuals: - type: StorageVisualizer state: atmos + state_open: atmos_open + state_closed: atmos_door - type: AccessReader access: [ [ "Engineering" ] ] @@ -118,6 +133,8 @@ visuals: - type: StorageVisualizer state: eng_secure + state_open: eng_secure_open + state_closed: eng_secure_door - type: AccessReader access: [ [ "Engineering" ] ] @@ -131,6 +148,8 @@ visuals: - type: StorageVisualizer state: freezer + state_open: freezer_open + state_closed: freezer_door - type: AccessReader access: [ [ "Service" ] ] @@ -144,6 +163,8 @@ visuals: - type: StorageVisualizer state: hydro + state_open: hydro_open + state_closed: hydro_door - type: AccessReader access: [ [ "Service" ] ] @@ -158,6 +179,8 @@ visuals: - type: StorageVisualizer state: med + state_open: med_open + state_closed: med_door - type: AccessReader access: [ [ "Medical" ] ] @@ -171,6 +194,8 @@ visuals: - type: StorageVisualizer state: med_secure + state_open: med_secure_open + state_closed: med_secure_door - type: AccessReader access: [ [ "Medical" ] ] @@ -184,6 +209,7 @@ visuals: - type: StorageVisualizer state: med + state_open: med_open state_closed: chemical_door - type: AccessReader access: [ [ "Chemistry" ] ] @@ -198,6 +224,8 @@ visuals: - type: StorageVisualizer state: cmo + state_open: cmo_open + state_closed: cmo_door - type: AccessReader access: [ [ "Medical", "Command" ] ] @@ -211,6 +239,8 @@ visuals: - type: StorageVisualizer state: rd + state_open: rd_open + state_closed: rd_door - type: AccessReader access: [ [ "Research", "Command" ] ] @@ -223,6 +253,8 @@ visuals: - type: StorageVisualizer state: science + state_open: science_open + state_closed: science_door - type: AccessReader access: [ [ "Research" ] ] @@ -236,6 +268,8 @@ visuals: - type: StorageVisualizer state: hos + state_open: hos_open + state_closed: hos_door - type: AccessReader access: [["Security", "Command"]] @@ -249,6 +283,8 @@ visuals: - type: StorageVisualizer state: warden + state_open: warden_open + state_closed: warden_door - type: AccessReader access: [["Security", "Armory"]] # TODO access [["Brig"]] @@ -262,6 +298,8 @@ visuals: - type: StorageVisualizer state: sec + state_open: sec_open + state_closed: sec_door - type: AccessReader access: [["Security"]] @@ -286,3 +324,5 @@ visuals: - type: StorageVisualizer state: syndicate + state_open: syndicate_open + state_closed: syndicate_door diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml index 4df4cb70cb..a68b891b68 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml @@ -65,5 +65,6 @@ - type: Appearance visuals: - type: StorageVisualizer + state: generic state_open: generic_open state_closed: generic_door diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml index 41c491a005..0deada2727 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -9,6 +9,7 @@ visuals: - type: StorageVisualizer state: eng + state_open: eng_open state_closed: eng_tool_door # Radiation suit closet @@ -22,6 +23,7 @@ visuals: - type: StorageVisualizer state: eng + state_open: eng_open state_closed: eng_rad_door # Emergency closet @@ -35,6 +37,8 @@ visuals: - type: StorageVisualizer state: emergency + state_open: emergency_open + state_closed: emergency_door # Fire safety closet - type: entity @@ -47,6 +51,8 @@ visuals: - type: StorageVisualizer state: fire + state_open: fire_open + state_closed: fire_door # EOD closet - type: entity @@ -59,6 +65,8 @@ visuals: - type: StorageVisualizer state: bomb + state_open: bomb_open + state_closed: bomb_door # Biohazard @@ -73,6 +81,8 @@ visuals: - type: StorageVisualizer state: bio + state_open: bio_sec_open + false: bio_sec_door # Virology variant - type: entity @@ -83,6 +93,8 @@ visuals: - type: StorageVisualizer state: bio_viro + state_open: bio_viro_open + state_closed: bio_viro_door # Security variant - type: entity @@ -93,6 +105,8 @@ visuals: - type: StorageVisualizer state: bio_sec + state_open: bio_sec_open + state_closed: bio_sec_door # Janitor variant - type: entity @@ -103,11 +117,13 @@ visuals: - type: StorageVisualizer state: bio_jan + state_open: bio_jan_open + state_closed: bio_jan_door # Maintenance closet - type: entity id: ClosetMaintenance - name: maintenance closet + name: maintenance closet parent: ClosetBase description: It's a storage unit. components: diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/wardrobe.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/wardrobe.yml index c288d5641a..4404185472 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/wardrobe.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/wardrobe.yml @@ -17,6 +17,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: blue_door # Pink wardrobe @@ -29,6 +30,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: pink_door # Black wardrobe @@ -41,6 +43,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: black_door # Green wardrobe @@ -53,6 +56,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: green_door # Prison wardrobe @@ -65,6 +69,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: orange_door # Yellow wardrobe @@ -77,6 +82,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: yellow_door # White wardrobe @@ -89,6 +95,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: white_door # Grey wardrobe @@ -101,6 +108,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: grey_door # Mixed wardrobe @@ -113,6 +121,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: mixed_door # Jobs @@ -126,6 +135,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: red_door - type: entity @@ -137,6 +147,7 @@ visuals: - type: StorageVisualizer state: generic + state_open: generic_open state_closed: atmos_wardrobe_door - type: entity diff --git a/Resources/Prototypes/Recipes/Lathes/medical.yml b/Resources/Prototypes/Recipes/Lathes/medical.yml index 05d6931950..24d6431f47 100644 --- a/Resources/Prototypes/Recipes/Lathes/medical.yml +++ b/Resources/Prototypes/Recipes/Lathes/medical.yml @@ -73,9 +73,9 @@ Steel: 200 - type: latheRecipe - id: BodyBag_Item - icon: Objects/Specific/Medical/Morgue/bodybags.rsi/item.png - result: BodyBag_Item + id: BodyBag_Folded + icon: Objects/Specific/Medical/Morgue/bodybags.rsi/bag_folded.png + result: BodyBag_Folded completetime: 300 materials: Plastic: 200 diff --git a/Resources/Textures/Interface/VerbIcons/fold.svg b/Resources/Textures/Interface/VerbIcons/fold.svg new file mode 100644 index 0000000000..b931cd45ae --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/fold.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png new file mode 100644 index 0000000000..c0afa2c525 Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png differ diff --git a/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png.yml b/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/fold.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/item.png b/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/bag_folded.png similarity index 100% rename from Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/item.png rename to Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/bag_folded.png diff --git a/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/meta.json index fab67ad463..b6feb49959 100644 --- a/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Medical/Morgue/bodybags.rsi/meta.json @@ -11,7 +11,7 @@ "name": "bag" }, { - "name": "item" + "name": "bag_folded" }, { "name": "label_overlay" diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed.png new file mode 100644 index 0000000000..c5dbd975b9 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_buckled.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_buckled.png new file mode 100644 index 0000000000..61330875a5 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_buckled.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_folded.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_folded.png new file mode 100644 index 0000000000..c0a7bec080 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/cheap_rollerbed_folded.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed.png new file mode 100644 index 0000000000..809a1b0499 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_buckled.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_buckled.png new file mode 100644 index 0000000000..afad4c3fe0 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_buckled.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_folded.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_folded.png new file mode 100644 index 0000000000..f1e3a2b820 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/emergency_rollerbed_folded.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/meta.json b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/meta.json new file mode 100644 index 0000000000..669e74f8e2 --- /dev/null +++ b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/59fe5dd2841f47a8abce60eecb9fafad34282bd0, Baystation 12 and AuroraStation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "rollerbed" + }, + { + "name": "rollerbed_buckled" + }, + { + "name": "rollerbed_folded" + }, + { + "name": "cheap_rollerbed" + }, + { + "name": "cheap_rollerbed_buckled" + }, + { + "name": "cheap_rollerbed_folded" + }, + { + "name": "emergency_rollerbed" + }, + { + "name": "emergency_rollerbed_buckled" + }, + { + "name": "emergency_rollerbed_folded" + } + ] +} diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed.png new file mode 100644 index 0000000000..846341ca49 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_buckled.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_buckled.png new file mode 100644 index 0000000000..eaf05429e0 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_buckled.png differ diff --git a/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_folded.png b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_folded.png new file mode 100644 index 0000000000..5783eaef98 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/rollerbeds.rsi/rollerbed_folded.png differ