Add verbs to Open/Close Openable containers, and add optional seals (#24780)

* Implement closing; add open/close verbs

* Add breakable seals

* Allow custom verb names; make condiment bottles closeable

* Remove pointless VV annotations and false defaults

* Split Sealable off into a new component

* Should have a Closed event too

* Oh hey, there are icons I could use

* Ternary operator

* Add support for seal visualizers

* Moved Sealable to Shared, added networking

* Replaced bottle_close1.ogg
This commit is contained in:
Tayrtahn
2024-02-13 17:08:07 -05:00
committed by GitHub
parent 52f74fa39d
commit 75e47fff9e
14 changed files with 255 additions and 9 deletions

View File

@@ -14,20 +14,20 @@ public sealed partial class OpenableComponent : Component
/// Whether this drink or food is opened or not. /// Whether this drink or food is opened or not.
/// Drinks can only be drunk or poured from/into when open, and food can only be eaten when open. /// Drinks can only be drunk or poured from/into when open, and food can only be eaten when open.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public bool Opened; public bool Opened;
/// <summary> /// <summary>
/// If this is false you cant press Z to open it. /// If this is false you cant press Z to open it.
/// Requires an OpenBehavior damage threshold or other logic to open. /// Requires an OpenBehavior damage threshold or other logic to open.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public bool OpenableByHand = true; public bool OpenableByHand = true;
/// <summary> /// <summary>
/// Text shown when examining and its open. /// Text shown when examining and its open.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public LocId ExamineText = "drink-component-on-examine-is-opened"; public LocId ExamineText = "drink-component-on-examine-is-opened";
/// <summary> /// <summary>
@@ -35,12 +35,38 @@ public sealed partial class OpenableComponent : Component
/// Defaults to the popup drink uses since its "correct". /// Defaults to the popup drink uses since its "correct".
/// It's still generic enough that you should change it if you make openable non-drinks, i.e. unwrap it first, peel it first. /// It's still generic enough that you should change it if you make openable non-drinks, i.e. unwrap it first, peel it first.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public LocId ClosedPopup = "drink-component-try-use-drink-not-open"; public LocId ClosedPopup = "drink-component-try-use-drink-not-open";
/// <summary>
/// Text to show in the verb menu for the "Open" action.
/// You may want to change this for non-drinks, i.e. "Peel", "Unwrap"
/// </summary>
[DataField]
public LocId OpenVerbText = "openable-component-verb-open";
/// <summary>
/// Text to show in the verb menu for the "Close" action.
/// You may want to change this for non-drinks, i.e. "Wrap"
/// </summary>
[DataField]
public LocId CloseVerbText = "openable-component-verb-close";
/// <summary> /// <summary>
/// Sound played when opening. /// Sound played when opening.
/// </summary> /// </summary>
[DataField] [DataField]
public SoundSpecifier Sound = new SoundCollectionSpecifier("canOpenSounds"); public SoundSpecifier Sound = new SoundCollectionSpecifier("canOpenSounds");
/// <summary>
/// Can this item be closed again after opening?
/// </summary>
[DataField]
public bool Closeable;
/// <summary>
/// Sound played when closing.
/// </summary>
[DataField]
public SoundSpecifier? CloseSound;
} }

View File

@@ -1,22 +1,23 @@
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Shared.Nutrition.EntitySystems;
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.GameObjects; using Robust.Shared.Utility;
namespace Content.Server.Nutrition.EntitySystems; namespace Content.Server.Nutrition.EntitySystems;
/// <summary> /// <summary>
/// Provides API for openable food and drinks, handles opening on use and preventing transfer when closed. /// Provides API for openable food and drinks, handles opening on use and preventing transfer when closed.
/// </summary> /// </summary>
public sealed class OpenableSystem : EntitySystem public sealed class OpenableSystem : SharedOpenableSystem
{ {
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -32,6 +33,7 @@ public sealed class OpenableSystem : EntitySystem
SubscribeLocalEvent<OpenableComponent, SolutionTransferAttemptEvent>(OnTransferAttempt); SubscribeLocalEvent<OpenableComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
SubscribeLocalEvent<OpenableComponent, MeleeHitEvent>(HandleIfClosed); SubscribeLocalEvent<OpenableComponent, MeleeHitEvent>(HandleIfClosed);
SubscribeLocalEvent<OpenableComponent, AfterInteractEvent>(HandleIfClosed); SubscribeLocalEvent<OpenableComponent, AfterInteractEvent>(HandleIfClosed);
SubscribeLocalEvent<OpenableComponent, GetVerbsEvent<Verb>>(AddOpenCloseVerbs);
} }
private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args) private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args)
@@ -71,6 +73,36 @@ public sealed class OpenableSystem : EntitySystem
args.Handled = !comp.Opened; args.Handled = !comp.Opened;
} }
private void AddOpenCloseVerbs(EntityUid uid, OpenableComponent comp, GetVerbsEvent<Verb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
Verb verb;
if (comp.Opened)
{
if (!comp.Closeable)
return;
verb = new()
{
Text = Loc.GetString(comp.CloseVerbText),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")),
Act = () => TryClose(args.Target, comp)
};
}
else
{
verb = new()
{
Text = Loc.GetString(comp.OpenVerbText),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")),
Act = () => TryOpen(args.Target, comp)
};
}
args.Verbs.Add(verb);
}
/// <summary> /// <summary>
/// Returns true if the entity either does not have OpenableComponent or it is opened. /// Returns true if the entity either does not have OpenableComponent or it is opened.
/// Drinks that don't have OpenableComponent are automatically open, so it returns true. /// Drinks that don't have OpenableComponent are automatically open, so it returns true.
@@ -123,6 +155,17 @@ public sealed class OpenableSystem : EntitySystem
comp.Opened = opened; comp.Opened = opened;
if (opened)
{
var ev = new OpenableOpenedEvent();
RaiseLocalEvent(uid, ref ev);
}
else
{
var ev = new OpenableClosedEvent();
RaiseLocalEvent(uid, ref ev);
}
UpdateAppearance(uid, comp); UpdateAppearance(uid, comp);
} }
@@ -139,4 +182,19 @@ public sealed class OpenableSystem : EntitySystem
_audio.PlayPvs(comp.Sound, uid); _audio.PlayPvs(comp.Sound, uid);
return true; return true;
} }
/// <summary>
/// If opened, closes it and plays the close sound, if one is defined.
/// </summary>
/// <returns>Whether it got closed</returns>
public bool TryClose(EntityUid uid, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false) || !comp.Opened || !comp.Closeable)
return false;
SetOpen(uid, false, comp);
if (comp.CloseSound != null)
_audio.PlayPvs(comp.CloseSound, uid);
return true;
}
} }

View File

@@ -0,0 +1,32 @@
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Nutrition.Components;
/// <summary>
/// Represents a tamper-evident seal on an Openable.
/// Only affects the Examine text.
/// Once the seal has been broken, it cannot be resealed.
/// </summary>
[NetworkedComponent, AutoGenerateComponentState]
[RegisterComponent, Access(typeof(SealableSystem))]
public sealed partial class SealableComponent : Component
{
/// <summary>
/// Whether the item's seal is intact (i.e. it has never been opened)
/// </summary>
[DataField, AutoNetworkedField]
public bool Sealed = true;
/// <summary>
/// Text shown when examining and the item's seal has not been broken.
/// </summary>
[DataField]
public LocId ExamineTextSealed = "drink-component-on-examine-is-sealed";
/// <summary>
/// Text shown when examining and the item's seal has been broken.
/// </summary>
[DataField]
public LocId ExamineTextUnsealed = "drink-component-on-examine-is-unsealed";
}

View File

@@ -16,4 +16,11 @@ namespace Content.Shared.Nutrition.Components
Opened, Opened,
Layer Layer
} }
[Serializable, NetSerializable]
public enum SealableVisuals : byte
{
Sealed,
Layer,
}
} }

View File

@@ -0,0 +1,59 @@
using Content.Shared.Examine;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Nutrition.Components;
namespace Content.Shared.Nutrition.EntitySystems;
public sealed partial class SealableSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SealableComponent, ExaminedEvent>(OnExamined, after: new[] { typeof(SharedOpenableSystem) });
SubscribeLocalEvent<SealableComponent, OpenableOpenedEvent>(OnOpened);
}
private void OnExamined(EntityUid uid, SealableComponent comp, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
var sealedText = comp.Sealed ? Loc.GetString(comp.ExamineTextSealed) : Loc.GetString(comp.ExamineTextUnsealed);
args.PushMarkup(sealedText);
}
private void OnOpened(EntityUid uid, SealableComponent comp, OpenableOpenedEvent args)
{
comp.Sealed = false;
Dirty(uid, comp);
UpdateAppearance(uid, comp);
}
/// <summary>
/// Update seal visuals to the current value.
/// </summary>
public void UpdateAppearance(EntityUid uid, SealableComponent? comp = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref comp))
return;
_appearance.SetData(uid, SealableVisuals.Sealed, comp.Sealed, appearance);
}
/// <summary>
/// Returns true if the entity's seal is intact.
/// Items without SealableComponent are considered unsealed.
/// </summary>
public bool IsSealed(EntityUid uid, SealableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false))
return false;
return comp.Sealed;
}
}

View File

@@ -0,0 +1,17 @@
namespace Content.Shared.Nutrition.EntitySystems;
public abstract partial class SharedOpenableSystem : EntitySystem
{
}
/// <summary>
/// Raised after an Openable is opened.
/// </summary>
[ByRefEvent]
public record struct OpenableOpenedEvent;
/// <summary>
/// Raised after an Openable is closed.
/// </summary>
[ByRefEvent]
public record struct OpenableClosedEvent;

View File

@@ -63,6 +63,11 @@
copyright: "User volivieri on freesound.org. Modified by Velcroboy on github." copyright: "User volivieri on freesound.org. Modified by Velcroboy on github."
source: "https://freesound.org/people/volivieri/sounds/37190/" source: "https://freesound.org/people/volivieri/sounds/37190/"
- files: ["bottle_close1.ogg"]
license: "CC0-1.0"
copyright: "User MellowAudio on freesound.org. Modified by Tayrtahn on github."
source: "https://freesound.org/people/MellowAudio/sounds/591485/"
- files: ["bow_pull.ogg"] - files: ["bow_pull.ogg"]
license: "CC-BY-3.0" license: "CC-BY-3.0"
copyright: "User jzdnvdoosj on freesound.org. Converted to ogg by mirrorcult" copyright: "User jzdnvdoosj on freesound.org. Converted to ogg by mirrorcult"

Binary file not shown.

View File

@@ -1,6 +1,8 @@
drink-component-on-use-is-empty = {$owner} is empty! drink-component-on-use-is-empty = {$owner} is empty!
drink-component-on-examine-is-empty = [color=gray]Empty[/color] drink-component-on-examine-is-empty = [color=gray]Empty[/color]
drink-component-on-examine-is-opened = [color=yellow]Opened[/color] drink-component-on-examine-is-opened = [color=yellow]Opened[/color]
drink-component-on-examine-is-sealed = The seal is intact.
drink-component-on-examine-is-unsealed = The seal is broken.
drink-component-on-examine-is-full = Full drink-component-on-examine-is-full = Full
drink-component-on-examine-is-mostly-full = Mostly Full drink-component-on-examine-is-mostly-full = Mostly Full
drink-component-on-examine-is-half-full = Halfway Full drink-component-on-examine-is-half-full = Halfway Full

View File

@@ -0,0 +1,2 @@
openable-component-verb-open = Open
openable-component-verb-close = Close

View File

@@ -6,6 +6,10 @@
- type: Openable - type: Openable
sound: sound:
collection: bottleOpenSounds #Could use a new sound someday ¯\_(ツ)_/¯ collection: bottleOpenSounds #Could use a new sound someday ¯\_(ツ)_/¯
closeable: true
closeSound:
collection: bottleCloseSounds
- type: Sealable
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
drink: drink:

View File

@@ -10,6 +10,9 @@
- type: Openable - type: Openable
sound: sound:
collection: bottleOpenSounds collection: bottleOpenSounds
closeable: true
closeSound:
collection: bottleCloseSounds
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
drink: drink:
@@ -131,6 +134,7 @@
Quantity: 100 Quantity: 100
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/absinthebottle.rsi sprite: Objects/Consumable/Drinks/absinthebottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -170,6 +174,7 @@
Quantity: 100 Quantity: 100
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/bottleofnothing.rsi sprite: Objects/Consumable/Drinks/bottleofnothing.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull]
@@ -185,6 +190,8 @@
Quantity: 100 Quantity: 100
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/champagnebottle.rsi sprite: Objects/Consumable/Drinks/champagnebottle.rsi
- type: Openable
closeable: false # Champagne corks are fat. Not worth the effort.
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -202,6 +209,7 @@
currentLabel: cognac currentLabel: cognac
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/cognacbottle.rsi sprite: Objects/Consumable/Drinks/cognacbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull]
@@ -219,6 +227,7 @@
currentLabel: cola currentLabel: cola
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/colabottle.rsi sprite: Objects/Consumable/Drinks/colabottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -256,6 +265,7 @@
currentLabel: gin currentLabel: gin
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/ginbottle.rsi sprite: Objects/Consumable/Drinks/ginbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -271,6 +281,7 @@
Quantity: 100 Quantity: 100
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/gildlagerbottle.rsi sprite: Objects/Consumable/Drinks/gildlagerbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull]
@@ -288,6 +299,7 @@
currentLabel: coffee liqueur currentLabel: coffee liqueur
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/coffeeliqueurbottle.rsi sprite: Objects/Consumable/Drinks/coffeeliqueurbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -342,6 +354,7 @@
Quantity: 100 Quantity: 100
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/pwinebottle.rsi sprite: Objects/Consumable/Drinks/pwinebottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -359,6 +372,7 @@
currentLabel: rum currentLabel: rum
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/rumbottle.rsi sprite: Objects/Consumable/Drinks/rumbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull]
@@ -377,6 +391,7 @@
currentLabel: space mountain wind currentLabel: space mountain wind
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/space_mountain_wind_bottle.rsi sprite: Objects/Consumable/Drinks/space_mountain_wind_bottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull]
@@ -395,6 +410,7 @@
currentLabel: space-up currentLabel: space-up
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/space-up_bottle.rsi sprite: Objects/Consumable/Drinks/space-up_bottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -412,6 +428,7 @@
currentLabel: tequila currentLabel: tequila
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/tequillabottle.rsi sprite: Objects/Consumable/Drinks/tequillabottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -429,6 +446,7 @@
currentLabel: vermouth currentLabel: vermouth
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/vermouthbottle.rsi sprite: Objects/Consumable/Drinks/vermouthbottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -446,6 +464,7 @@
currentLabel: vodka currentLabel: vodka
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/vodkabottle.rsi sprite: Objects/Consumable/Drinks/vodkabottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -463,6 +482,7 @@
currentLabel: whiskey currentLabel: whiskey
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/whiskeybottle.rsi sprite: Objects/Consumable/Drinks/whiskeybottle.rsi
- type: Sealable
- type: entity - type: entity
parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull]
@@ -480,6 +500,7 @@
currentLabel: wine currentLabel: wine
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/winebottle.rsi sprite: Objects/Consumable/Drinks/winebottle.rsi
- type: Sealable
# Small Bottles # Small Bottles
@@ -500,6 +521,8 @@
Quantity: 50 Quantity: 50
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/beer.rsi sprite: Objects/Consumable/Drinks/beer.rsi
- type: Openable
closeable: false
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull]
@@ -518,6 +541,8 @@
currentLabel: beer currentLabel: beer
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/beer.rsi sprite: Objects/Consumable/Drinks/beer.rsi
- type: Openable
closeable: false
- type: entity - type: entity
@@ -535,9 +560,10 @@
reagents: reagents:
- ReagentId: Ale - ReagentId: Ale
Quantity: 50 Quantity: 50
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/alebottle.rsi sprite: Objects/Consumable/Drinks/alebottle.rsi
- type: Openable
closeable: false
- type: entity - type: entity
parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull] parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull]
@@ -556,6 +582,8 @@
currentLabel: ale currentLabel: ale
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/alebottle.rsi sprite: Objects/Consumable/Drinks/alebottle.rsi
- type: Openable
closeable: false
- type: entity - type: entity
parent: [DrinkBottleVisualsOpenable, DrinkBottlePlasticBaseFull] parent: [DrinkBottleVisualsOpenable, DrinkBottlePlasticBaseFull]
@@ -587,6 +615,7 @@
fillBaseName: icon- fillBaseName: icon-
inHandsMaxFillLevels: 2 inHandsMaxFillLevels: 2
inHandsFillBaseName: -fill- inHandsFillBaseName: -fill-
- type: Sealable
- type: entity - type: entity
parent: DrinkWaterBottleFull parent: DrinkWaterBottleFull

View File

@@ -337,6 +337,7 @@
- type: Openable - type: Openable
sound: sound:
collection: pop collection: pop
closeable: true
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
food: food:

View File

@@ -0,0 +1,4 @@
- type: soundCollection
id: bottleCloseSounds
files:
- /Audio/Items/bottle_close1.ogg