/// Event raised on the mask entity when it is toggled.
/// </summary>
[ByRefEvent]
-public readonly record struct ItemMaskToggledEvent(EntityUid Wearer, bool IsToggled, bool IsEquip);
+public readonly record struct ItemMaskToggledEvent(EntityUid Wearer, string? equippedPrefix, bool IsToggled, bool IsEquip);
/// <summary>
/// Event raised on the entity wearing the mask when it is toggled.
--- /dev/null
+using Content.Shared.Inventory;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clothing.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class FoldableClothingComponent : Component
+{
+ /// <summary>
+ /// Which slots does this fit into when folded?
+ /// </summary>
+ [DataField]
+ public SlotFlags? FoldedSlots;
+
+ /// <summary>
+ /// Which slots does this fit into when unfolded?
+ /// </summary>
+ [DataField]
+ public SlotFlags? UnfoldedSlots;
+}
-using Content.Shared.Clothing.EntitySystems;
+using Content.Shared.Clothing.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
[DataField, AutoNetworkedField]
public bool IsToggled;
+
+ [DataField, AutoNetworkedField]
+ public string EquippedPrefix = "toggled";
}
private void OnMaskToggled(Entity<ClothingComponent> ent, ref ItemMaskToggledEvent args)
{
//TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix
- SetEquippedPrefix(ent, args.IsToggled ? "toggled" : null, ent);
+ if(args.equippedPrefix != null)
+ SetEquippedPrefix(ent, args.IsToggled ? args.equippedPrefix : null, ent);
}
private void OnEquipDoAfter(Entity<ClothingComponent> ent, ref ClothingEquipDoAfterEvent args)
--- /dev/null
+using Content.Shared.Clothing.Components;
+using Content.Shared.Foldable;
+using Content.Shared.Inventory;
+
+namespace Content.Shared.Clothing.EntitySystems;
+
+public sealed class FoldableClothingSystem : EntitySystem
+{
+ [Dependency] private readonly ClothingSystem _clothingSystem = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<FoldableClothingComponent, FoldAttemptEvent>(OnFoldAttempt);
+ SubscribeLocalEvent<FoldableClothingComponent, FoldedEvent>(OnFolded);
+ }
+
+ private void OnFoldAttempt(Entity<FoldableClothingComponent> ent, ref FoldAttemptEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ // allow folding while equipped if allowed slots are the same:
+ // e.g. flip a hat backwards while on your head
+ if (_inventorySystem.TryGetContainingSlot(ent.Owner, out var slot) &&
+ !ent.Comp.FoldedSlots.Equals(ent.Comp.UnfoldedSlots))
+ args.Cancelled = true;
+ }
+
+ private void OnFolded(Entity<FoldableClothingComponent> ent, ref FoldedEvent args)
+ {
+ if (TryComp<ClothingComponent>(ent.Owner, out var clothingComp))
+ {
+ if (args.IsFolded && ent.Comp.FoldedSlots.HasValue)
+ _clothingSystem.SetSlots(ent.Owner, ent.Comp.FoldedSlots.Value, clothingComp);
+ else if (!args.IsFolded && ent.Comp.UnfoldedSlots.HasValue)
+ _clothingSystem.SetSlots(ent.Owner, ent.Comp.UnfoldedSlots.Value, clothingComp);
+ }
+ }
+}
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
+using Content.Shared.Foldable;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
+using Content.Shared.Item;
using Content.Shared.Popups;
using Robust.Shared.Timing;
SubscribeLocalEvent<MaskComponent, ToggleMaskEvent>(OnToggleMask);
SubscribeLocalEvent<MaskComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<MaskComponent, GotUnequippedEvent>(OnGotUnequipped);
+ SubscribeLocalEvent<MaskComponent, FoldedEvent>(OnFolded);
}
private void OnGetActions(EntityUid uid, MaskComponent component, GetItemActionsEvent args)
{
- if (!args.InHands)
+ if (_inventorySystem.InSlotWithFlags(uid, SlotFlags.MASK))
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
}
else
_popupSystem.PopupEntity(Loc.GetString("action-mask-pull-up-popup-message", ("mask", uid)), args.Performer, args.Performer);
- ToggleMaskComponents(uid, mask, args.Performer);
+ ToggleMaskComponents(uid, mask, args.Performer, mask.EquippedPrefix);
}
// set to untoggled when unequipped, so it isn't left in a 'pulled down' state
Dirty(uid, mask);
_actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled);
- ToggleMaskComponents(uid, mask, args.Equipee, true);
+ ToggleMaskComponents(uid, mask, args.Equipee, mask.EquippedPrefix, true);
}
- private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, bool isEquip = false)
+ private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, string? equippedPrefix = null, bool isEquip = false)
{
- var maskEv = new ItemMaskToggledEvent(wearer, mask.IsToggled, isEquip);
+ var maskEv = new ItemMaskToggledEvent(wearer, equippedPrefix, mask.IsToggled, isEquip);
RaiseLocalEvent(uid, ref maskEv);
var wearerEv = new WearerMaskToggledEvent(mask.IsToggled);
RaiseLocalEvent(wearer, ref wearerEv);
}
+
+ private void OnFolded(Entity<MaskComponent> ent, ref FoldedEvent args)
+ {
+ ent.Comp.IsToggled = args.IsFolded;
+
+ ToggleMaskComponents(ent.Owner, ent.Comp, ent.Owner);
+ }
}
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
namespace Content.Shared.Foldable;
/// <remarks>
/// Will prevent any insertions into containers while this item is unfolded.
/// </remarks>
-[RegisterComponent]
-[NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(FoldableSystem))]
public sealed partial class FoldableComponent : Component
{
- [DataField("folded")]
+ [DataField("folded"), AutoNetworkedField]
public bool IsFolded = false;
-}
-
-// ahhh, the ol' "state thats just a copy of the component".
-[Serializable, NetSerializable]
-public sealed class FoldableComponentState : ComponentState
-{
- public readonly bool IsFolded;
- public FoldableComponentState(bool isFolded)
- {
- IsFolded = isFolded;
- }
+ [DataField]
+ public bool CanFoldInsideContainer = false;
}
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
-using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
base.Initialize();
SubscribeLocalEvent<FoldableComponent, GetVerbsEvent<AlternativeVerb>>(AddFoldVerb);
- SubscribeLocalEvent<FoldableComponent, ComponentGetState>(OnGetState);
- SubscribeLocalEvent<FoldableComponent, ComponentHandleState>(OnHandleState);
+ SubscribeLocalEvent<FoldableComponent, AfterAutoHandleStateEvent>(OnHandleState);
SubscribeLocalEvent<FoldableComponent, ComponentInit>(OnFoldableInit);
SubscribeLocalEvent<FoldableComponent, ContainerGettingInsertedAttemptEvent>(OnInsertEvent);
SubscribeLocalEvent<FoldableComponent, BuckleAttemptEvent>(OnBuckleAttempt);
}
- private void OnGetState(EntityUid uid, FoldableComponent component, ref ComponentGetState args)
+ private void OnHandleState(EntityUid uid, FoldableComponent component, ref AfterAutoHandleStateEvent args)
{
- args.State = new FoldableComponentState(component.IsFolded);
- }
-
- private void OnHandleState(EntityUid uid, FoldableComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not FoldableComponentState state)
- return;
-
- if (state.IsFolded != component.IsFolded)
- SetFolded(uid, component, state.IsFolded);
+ SetFolded(uid, component, component.IsFolded);
}
private void OnFoldableInit(EntityUid uid, FoldableComponent component, ComponentInit args)
Dirty(uid, component);
_appearance.SetData(uid, FoldedVisuals.State, folded);
_buckle.StrapSetEnabled(uid, !component.IsFolded);
+
+ var ev = new FoldedEvent(folded);
+ RaiseLocalEvent(uid, ref ev);
}
private void OnInsertEvent(EntityUid uid, FoldableComponent component, ContainerGettingInsertedAttemptEvent args)
{
- if (!component.IsFolded)
+ if (!component.IsFolded && !component.CanFoldInsideContainer)
args.Cancel();
}
if (!Resolve(uid, ref fold))
return false;
- // Can't un-fold in any container (locker, hands, inventory, whatever).
- if (_container.IsEntityInContainer(uid))
+ // Can't un-fold in any container unless enabled (locker, hands, inventory, whatever).
+ if (_container.IsEntityInContainer(uid) && !fold.CanFoldInsideContainer)
return false;
var ev = new FoldAttemptEvent();
/// <param name="Cancelled"></param>
[ByRefEvent]
public record struct FoldAttemptEvent(bool Cancelled = false);
+
+/// <summary>
+/// Event raised on an entity after it has been folded.
+/// </summary>
+/// <param name="IsFolded"></param>
+[ByRefEvent]
+public readonly record struct FoldedEvent(bool IsFolded);
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBaseButcherable, BaseFoldable]
+ id: ClothingHeadBandBase
+ abstract: true
+ components:
+ - type: Foldable
+ folded: true
+ - type: Mask
+ isToggled: true
+ - type: IngestionBlocker
+ enabled: false
+ - type: IdentityBlocker
+ enabled: false
+ coverage: MOUTH
+ - type: Sprite # needed for vendor inventory icons
+ layers:
+ - state: icon
+ map: ["foldedLayer"]
+ visible: true
+ - state: icon_mask
+ map: [ "unfoldedLayer" ]
+ visible: false
+
+- type: entity
+ parent: [ClothingHeadBandBase, ClothingMaskBandBlack]
id: ClothingHeadBandBlack
name: black bandana
- description: A black bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/black.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/black.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandBlue]
id: ClothingHeadBandBlue
name: blue bandana
- description: A blue bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/blue.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/blue.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandBotany]
id: ClothingHeadBandBotany
name: botany bandana
- description: A botany bandana to make you look cool, made from natural fibers.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/botany.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/botany.rsi
- - type: Tag
- tags:
- - ClothMade
- - HamsterWearable
- - WhitelistChameleon
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandGold]
id: ClothingHeadBandGold
name: gold bandana
- description: A gold bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/gold.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/gold.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandGreen]
id: ClothingHeadBandGreen
name: green bandana
- description: A green bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/green.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/green.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandGrey]
id: ClothingHeadBandGrey
name: grey bandana
- description: A grey bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/grey.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/grey.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandRed]
id: ClothingHeadBandRed
name: red bandana
- description: A red bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/red.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/red.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandSkull]
id: ClothingHeadBandSkull
name: skull bandana
- description: A bandana with a skull to make you look even cooler.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/skull.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/skull.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandMerc]
id: ClothingHeadBandMerc
name: mercenary bandana
- description: To protect the head from the sun, insects and other dangers of the higher path.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/merc.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/merc.rsi
- type: entity
- parent: ClothingHeadBaseButcherable
+ parent: [ClothingHeadBandBase, ClothingMaskBandBrown]
id: ClothingHeadBandBrown
- name: brown bandana
- description: A brown bandana to make you look cool.
- components:
- - type: Sprite
- sprite: Clothing/Head/Bandanas/brown.rsi
- - type: Clothing
- sprite: Clothing/Head/Bandanas/brown.rsi
+ name: brown bandana
\ No newline at end of file
--- /dev/null
+- type: entity
+ parent: [ClothingMaskBaseButcherable, BaseFoldable]
+ id: ClothingMaskBandanaBase
+ abstract: true
+ components:
+ - type: Appearance
+ - type: Foldable
+ canFoldInsideContainer: true
+ - type: FoldableClothing
+ foldedSlots:
+ - HEAD
+ unfoldedSlots:
+ - MASK
+ - type: Mask
+ - type: IngestionBlocker
+ - type: IdentityBlocker
+ coverage: MOUTH
+ - type: Sprite
+ layers:
+ - state: icon_mask
+ map: [ "unfoldedLayer" ]
+ - state: icon
+ map: ["foldedLayer"]
+ visible: false
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandBlack
+ name: black bandana
+ description: A black bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/black.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/black.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandBlue
+ name: blue bandana
+ description: A blue bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/blue.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/blue.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandBotany
+ name: botany bandana
+ description: A botany bandana to make you look cool, made from natural fibers.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/botany.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/botany.rsi
+ - type: Tag
+ tags:
+ - ClothMade
+ - WhitelistChameleon
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandGold
+ name: gold bandana
+ description: A gold bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/gold.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/gold.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandGreen
+ name: green bandana
+ description: A green bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/green.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/green.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandGrey
+ name: grey bandana
+ description: A grey bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/grey.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/grey.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandRed
+ name: red bandana
+ description: A red bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/red.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/red.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandSkull
+ name: skull bandana
+ description: A bandana with a skull to make you look even cooler.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/skull.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/skull.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandMerc
+ name: mercenary bandana
+ description: To protect the head from the sun, insects and other dangers of the higher path.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/merc.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/merc.rsi
+
+- type: entity
+ parent: ClothingMaskBandanaBase
+ id: ClothingMaskBandBrown
+ name: brown bandana
+ description: A brown bandana to make you look cool.
+ components:
+ - type: Sprite
+ sprite: Clothing/Head/Bandanas/brown.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Bandanas/brown.rsi
icon: { sprite: Clothing/Mask/gas.rsi, state: icon }
iconOn: Interface/Default/blocked.png
event: !type:ToggleMaskEvent
+
+- type: entity
+ id: ClothingMaskBaseButcherable
+ parent: ClothingMaskBase
+ abstract: true
+ components:
+ - type: Butcherable
+ butcheringType: Knife
+ spawned:
+ - id: MaterialCloth1
+ amount: 1
+ - type: Food
+ requiresSpecialDigestion: true
+ - type: SolutionContainerManager
+ solutions:
+ food:
+ maxVol: 10
+ reagents:
+ - ReagentId: Fiber
+ Quantity: 10
+ - type: Tag
+ tags:
+ - ClothMade
\ No newline at end of file
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{
"directions": 4
},
{
- "name": "mask-equipped-HELMET",
+ "name": "equipped-MASK",
"directions": 4
},
{