Add limited-reagent dispensers (#23907)

* Add limited-reagent dispensers

* Add empty versions for all dispensers

* Fix lint

* Set initial window size so all buttons are visible

* Simplify logic, add parenthesis

* Use localized name for initial labels

* Adjust button style

* Avoid touching items before MapInit

* Remove pre-labeling

* Reduce diff

* Clean up YAML

* Fix test

* Really fix test

* Document

* Adjust based on review

* Add labels for obnoxiously long bottles

---------

Co-authored-by: AWF <you@example.com>
This commit is contained in:
Kevin Zheng
2024-01-17 13:43:48 -08:00
committed by GitHub
parent 47159d1c23
commit 9394a26245
19 changed files with 609 additions and 168 deletions

View File

@@ -57,13 +57,23 @@ namespace Content.Client.Chemistry.UI
_window.OnDispenseReagentButtonMouseEntered += (args, button) =>
{
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState, button.ReagentId);
_window.UpdateContainerInfo(_lastState);
};
_window.OnDispenseReagentButtonMouseExited += (args, button) =>
{
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
_window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId));
_window.OnEjectJugButtonMouseEntered += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
_window.OnEjectJugButtonMouseExited += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
}
/// <summary>

View File

@@ -1,8 +1,7 @@
<DefaultWindow xmlns="https://spacestation14.io"
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'reagent-dispenser-bound-user-interface-title'}"
SetSize="620 450"
MinSize="620 450">
MinSize="680 450">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'reagent-dispenser-window-amount-to-dispense-label'}"/>
@@ -18,10 +17,8 @@
<Button Name="DispenseButton100" Access="Public" Text="100" StyleClasses="OpenLeft"/>
</BoxContainer>
<Control MinSize="0 10"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" MinSize="0 170">
<GridContainer Name="ChemicalList" Access="Public" Columns="4">
<GridContainer Name="ChemicalList" HorizontalExpand="True" VerticalExpand="True" Access="Public" Columns="6">
</GridContainer>
</ScrollContainer>
<Control MinSize="0 10"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'reagent-dispenser-window-container-label'}"/>

View File

@@ -23,6 +23,10 @@ namespace Content.Client.Chemistry.UI
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseExited;
public event Action<BaseButton.ButtonEventArgs, EjectJugButton>? OnEjectJugButtonPressed;
public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseExited;
/// <summary>
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
/// actual data isn't filled in until the server sends data about the dispenser.
@@ -48,25 +52,25 @@ namespace Content.Client.Chemistry.UI
/// Update the button grid of reagents which can be dispensed.
/// </summary>
/// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
public void UpdateReagentsList(List<ReagentId> inventory)
public void UpdateReagentsList(List<KeyValuePair<string, KeyValuePair<string,string>>> inventory)
{
if (ChemicalList == null)
return;
ChemicalList.Children.Clear();
foreach (var entry in inventory
.OrderBy(r => {_prototypeManager.TryIndex(r.Prototype, out ReagentPrototype? p); return p?.LocalizedName;}))
foreach (KeyValuePair<string, KeyValuePair<string, string>> entry in inventory)
{
var localizedName = _prototypeManager.TryIndex(entry.Prototype, out ReagentPrototype? p)
? p.LocalizedName
: Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
var button = new DispenseReagentButton(entry, localizedName);
var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value);
button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
ChemicalList.AddChild(button);
var ejectButton = new EjectJugButton(entry.Key);
ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton);
ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton);
ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton);
ChemicalList.AddChild(ejectButton);
}
}
@@ -121,9 +125,8 @@ namespace Content.Client.Chemistry.UI
/// <para>Also highlights a reagent if it's dispense button is being mouse hovered.</para>
/// </summary>
/// <param name="state">State data for the dispenser.</param>
/// <param name="highlightedReagentId">Prototype ID of the reagent whose dispense button is currently being mouse hovered,
/// or null if no button is being hovered.</param>
public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, ReagentId? highlightedReagentId = null)
public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
{
ContainerInfo.Children.Clear();
@@ -161,12 +164,6 @@ namespace Content.Client.Chemistry.UI
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
};
// Check if the reagent is being moused over. If so, color it green.
if (reagent == highlightedReagentId) {
nameLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
quantityLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
}
ContainerInfo.Children.Add(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
@@ -180,13 +177,27 @@ namespace Content.Client.Chemistry.UI
}
}
public sealed class DispenseReagentButton : Button {
public ReagentId ReagentId { get; }
public DispenseReagentButton(ReagentId reagentId, string text)
public sealed class DispenseReagentButton : Button
{
public string ReagentId { get; }
public DispenseReagentButton(string reagentId, string text, string amount)
{
AddStyleClass("OpenRight");
ReagentId = reagentId;
Text = text;
Text = text + " " + amount;
}
}
public sealed class EjectJugButton : Button
{
public string ReagentId { get; }
public EjectJugButton(string reagentId)
{
AddStyleClass("OpenLeft");
ReagentId = reagentId;
Text = "⏏";
}
}
}

View File

@@ -1,6 +1,7 @@
using Content.Client.Chemistry.UI;
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Chemistry;
using Content.Server.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
namespace Content.IntegrationTests.Tests.Chemistry;
@@ -24,7 +25,7 @@ public sealed class DispenserTest : InteractionTest
await Interact();
// Eject beaker via BUI.
var ev = new ItemSlotButtonPressedEvent(SharedChemMaster.InputSlotName);
var ev = new ItemSlotButtonPressedEvent(ReagentDispenserComponent.BeakerSlotId);
await SendBui(ReagentDispenserUiKey.Key, ev);
// Beaker is back in the player's hands

View File

@@ -1,3 +1,5 @@
using Content.Shared.Whitelist;
using Content.Shared.Containers.ItemSlots;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Dispenser;
@@ -7,20 +9,57 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.Components
{
/// <summary>
/// A machine that dispenses reagents into a solution container.
/// A machine that dispenses reagents into a solution container from containers in its storage slots.
/// </summary>
[RegisterComponent]
[Access(typeof(ReagentDispenserSystem))]
public sealed partial class ReagentDispenserComponent : Component
{
/// <summary>
/// String with the pack name that stores the initial fill of the dispenser. The initial
/// fill is added to the dispenser on MapInit. Note that we don't use ContainerFill because
/// we have to generate the storage slots at MapInit first, then fill them.
/// </summary>
[DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? PackPrototypeId = default!;
[DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? EmagPackPrototypeId = default!;
/// <summary>
/// Maximum number of internal storage slots. Dispenser can't store (or dispense) more than
/// this many chemicals (without unloading and reloading).
/// </summary>
[DataField("numStorageSlots")]
public int NumSlots = 25;
/// <summary>
/// For each created storage slot for the reagent containers being dispensed, apply this
/// entity whitelist. Makes sure weird containers don't fit in the dispenser and that beakers
/// don't accidentally get slotted into the source slots.
/// </summary>
[DataField]
public EntityWhitelist? StorageWhitelist;
/// <summary>
/// Slot for container to dispense into.
/// </summary>
public static string BeakerSlotId = "ReagentDispenser-beakerSlot";
[DataField]
public ItemSlot BeakerSlot = new();
/// <summary>
/// Prefix for automatically-generated slot name for storage, up to NumSlots.
/// </summary>
public static string BaseStorageSlotId = "ReagentDispenser-storageSlot";
/// <summary>
/// List of storage slots that were created at MapInit.
/// </summary>
[DataField]
public List<string> StorageSlotIds = new List<string>();
[DataField]
public List<ItemSlot> StorageSlots = new List<ItemSlot>();
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");

View File

@@ -1,14 +1,18 @@
using Content.Server.Administration.Logs;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Labels.Components;
using Content.Server.Chemistry;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
@@ -28,10 +32,13 @@ namespace Content.Server.Chemistry.EntitySystems
{
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly SolutionTransferSystem _solutionTransferSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
public override void Initialize()
{
base.Initialize();
@@ -41,11 +48,12 @@ namespace Content.Server.Chemistry.EntitySystems
SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
}
private void SubscribeUpdateUiState<T>(Entity<ReagentDispenserComponent> ent, ref T ev)
@@ -80,35 +88,38 @@ namespace Content.Server.Chemistry.EntitySystems
return null;
}
private List<ReagentId> GetInventory(Entity<ReagentDispenserComponent> ent)
private List<KeyValuePair<string, KeyValuePair<string, string>>> GetInventory(ReagentDispenserComponent reagentDispenser)
{
var reagentDispenser = ent.Comp;
var inventory = new List<ReagentId>();
var inventory = new List<KeyValuePair<string, KeyValuePair<string, string>>>();
if (reagentDispenser.PackPrototypeId is not null
&& _prototypeManager.TryIndex(reagentDispenser.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
for (var i = 0; i < reagentDispenser.NumSlots; i++)
{
inventory.AddRange(packPrototype.Inventory.Select(x => new ReagentId(x, null)));
var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, storageSlotId);
// Set label from manually-applied label, or metadata if unavailable
string reagentLabel;
if (TryComp<LabelComponent>(storedContainer, out var label) && !string.IsNullOrEmpty(label.CurrentLabel))
reagentLabel = label.CurrentLabel;
else if (storedContainer != null)
reagentLabel = Name(storedContainer.Value);
else
continue;
// Add volume remaining label
FixedPoint2 quantity = 0f;
if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol))
{
quantity = sol.Volume;
}
var storedAmount = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity));
if (HasComp<EmaggedComponent>(ent)
&& reagentDispenser.EmagPackPrototypeId is not null
&& _prototypeManager.TryIndex(reagentDispenser.EmagPackPrototypeId, out ReagentDispenserInventoryPrototype? emagPackPrototype))
{
inventory.AddRange(emagPackPrototype.Inventory.Select(x => new ReagentId(x, null)));
inventory.Add(new KeyValuePair<string, KeyValuePair<string, string>>(storageSlotId, new KeyValuePair<string, string>(reagentLabel, storedAmount)));
}
return inventory;
}
private void OnEmagged(Entity<ReagentDispenserComponent> reagentDispenser, ref GotEmaggedEvent args)
{
// adding component manually to have correct state
EntityManager.AddComponent<EmaggedComponent>(reagentDispenser);
UpdateUiState(reagentDispenser);
args.Handled = true;
}
private void OnSetDispenseAmountMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserSetDispenseAmountMessage message)
{
reagentDispenser.Comp.DispenseAmount = message.ReagentDispenserDispenseAmount;
@@ -119,18 +130,23 @@ namespace Content.Server.Chemistry.EntitySystems
private void OnDispenseReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserDispenseReagentMessage message)
{
// Ensure that the reagent is something this reagent dispenser can dispense.
if (!GetInventory(reagentDispenser).Contains(message.ReagentId))
var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, message.SlotId);
if (storedContainer == null)
return;
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
return;
if (_solutionContainerSystem.TryAddReagent(solution.Value, message.ReagentId, (int) reagentDispenser.Comp.DispenseAmount, out var dispensedAmount)
&& message.Session.AttachedEntity is not null)
if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out var src, out _) &&
_solutionContainerSystem.TryGetRefillableSolution(outputContainer.Value, out var dst, out _))
{
_adminLogger.Add(LogType.ChemicalReaction, LogImpact.Medium,
$"{ToPrettyString(message.Session.AttachedEntity.Value):player} dispensed {dispensedAmount}u of {message.ReagentId} into {ToPrettyString(outputContainer.Value):entity}");
// force open container, if applicable, to avoid confusing people on why it doesn't dispense
_openable.SetOpen(storedContainer.Value, true);
_solutionTransferSystem.Transfer(reagentDispenser,
storedContainer.Value, src.Value,
outputContainer.Value, dst.Value,
(int)reagentDispenser.Comp.DispenseAmount);
}
UpdateUiState(reagentDispenser);
@@ -152,5 +168,41 @@ namespace Content.Server.Chemistry.EntitySystems
{
_audioSystem.PlayPvs(reagentDispenser.Comp.ClickSound, reagentDispenser, AudioParams.Default.WithVolume(-2f));
}
/// <summary>
/// Automatically generate storage slots for all NumSlots, and fill them with their initial chemicals.
/// The actual spawning of entities happens in ItemSlotsSystem's MapInit.
/// </summary>
private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
{
// Get list of pre-loaded containers
List<string> preLoad = new List<string>();
if (component.PackPrototypeId is not null
&& _prototypeManager.TryIndex(component.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
{
preLoad.AddRange(packPrototype.Inventory);
}
// Populate storage slots with base storage slot whitelist
for (var i = 0; i < component.NumSlots; i++)
{
var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
ItemSlot storageComponent = new();
storageComponent.Whitelist = component.StorageWhitelist;
storageComponent.Swap = false;
storageComponent.EjectOnBreak = true;
// Check corresponding index in pre-loaded container (if exists) and set starting item
if (i < preLoad.Count)
storageComponent.StartingItem = preLoad[i];
component.StorageSlotIds.Add(storageSlotId);
component.StorageSlots.Add(storageComponent);
component.StorageSlots[i].Name = "Storage Slot " + (i+1);
_itemSlotsSystem.AddItemSlot(uid, component.StorageSlotIds[i], component.StorageSlots[i]);
}
_itemSlotsSystem.AddItemSlot(uid, ReagentDispenserComponent.BeakerSlotId, component.BeakerSlot);
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
@@ -14,8 +14,7 @@ namespace Content.Shared.Chemistry.Dispenser
[Serializable, NetSerializable, Prototype("reagentDispenserInventory")]
public sealed partial class ReagentDispenserInventoryPrototype : IPrototype
{
// TODO use ReagentId
[DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
[DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> Inventory = new();
[ViewVariables, IdDataField]

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Chemistry
/// </summary>
public sealed class SharedReagentDispenser
{
public const string OutputSlotName = "beakerSlot";
public const string OutputSlotName = "ReagentDispenser-beakerSlot";
}
[Serializable, NetSerializable]
@@ -25,11 +25,11 @@ namespace Content.Shared.Chemistry
[Serializable, NetSerializable]
public sealed class ReagentDispenserDispenseReagentMessage : BoundUserInterfaceMessage
{
public readonly ReagentId ReagentId;
public readonly string SlotId;
public ReagentDispenserDispenseReagentMessage(ReagentId reagentId)
public ReagentDispenserDispenseReagentMessage(string slotId)
{
ReagentId = reagentId;
SlotId = slotId;
}
}
@@ -59,11 +59,11 @@ namespace Content.Shared.Chemistry
/// <summary>
/// A list of the reagents which this dispenser can dispense.
/// </summary>
public readonly List<ReagentId> Inventory;
public readonly List<KeyValuePair<string, KeyValuePair<string, string>>> Inventory;
public readonly ReagentDispenserDispenseAmount SelectedDispenseAmount;
public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List<ReagentId> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List<KeyValuePair<string, KeyValuePair<string, string>>> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
{
OutputContainer = outputContainer;
Inventory = inventory;

View File

@@ -67,8 +67,8 @@ namespace Content.Shared.Containers.ItemSlots
CopyFrom(other);
}
[DataField("whitelist")]
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
public EntityWhitelist? Whitelist;
[DataField("blacklist")]
@@ -179,6 +179,7 @@ namespace Content.Shared.Containers.ItemSlots
/// The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem.
/// </remarks>
[DataField("ejectOnDeconstruct")]
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
[NonSerialized]
public bool EjectOnDeconstruct = true;
@@ -187,6 +188,7 @@ namespace Content.Shared.Containers.ItemSlots
/// ejected when it is broken or destroyed?
/// </summary>
[DataField("ejectOnBreak")]
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
[NonSerialized]
public bool EjectOnBreak = false;
@@ -205,6 +207,7 @@ namespace Content.Shared.Containers.ItemSlots
/// want to insert more than one item that matches the same whitelist.
/// </remarks>
[DataField("swap")]
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
public bool Swap = true;
public string? ID => ContainerSlot?.ID;

View File

@@ -1,61 +1,43 @@
- type: reagentDispenserInventory
id: SodaDispenserInventory
inventory:
- Ice
- Coffee
- Cream
- Tea
- GreenTea
- IcedTea
- IcedGreenTea
- Cola
- SpaceMountainWind
- DrGibb
- RootBeer
- SpaceUp
- TonicWater
- SodaWater
- LemonLime
- Sugar
- JuiceOrange
- JuiceLime
- JuiceWatermelon
###Hacked
#- Fourteen Loko
#- GrapeSoda
- DrinkIceJug
- DrinkCoffeeJug
- DrinkCreamCartonXL
- DrinkTeaJug
- DrinkGreenTeaJug
- DrinkIcedTeaJug
- DrinkColaBottleFull
- DrinkSpaceMountainWindBottleFull
- DrinkDrGibbJug
- DrinkRootBeerJug
- DrinkSpaceUpBottleFull
- DrinkTonicWaterBottleFull
- DrinkSodaWaterBottleFull
- DrinkLemonLimeJug
- DrinkSugarJug
- DrinkJuiceOrangeCartonXL
- DrinkJuiceLimeCartonXL
- DrinkWaterMelonJuiceJug
- type: reagentDispenserInventory
id: BoozeDispenserInventory
inventory:
- Beer
- CoffeeLiqueur
- Whiskey
- Wine
- Vodka
- Gin
- Rum
- Tequila
- Vermouth
- Cognac
- Ale
- Mead
###Hacked
#- Goldschlager
#- Patron
#- JuiceWatermelon
#- JuiceBerry
- type: reagentDispenserInventory
id: SodaDispenserEmagInventory
inventory:
- FourteenLoko
- Ephedrine
- Histamine
- type: reagentDispenserInventory
id: BoozeDispenserEmagInventory
inventory:
- AtomicBomb
- Ethanol
- Iron
- DrinkLemonLimeJug
- DrinkSugarJug
- DrinkJuiceOrangeCartonXL
- DrinkJuiceLimeCartonXL
- DrinkTonicWaterBottleFull
- DrinkSodaWaterBottleFull
- DrinkBeerGrowler
- DrinkCoffeeLiqueurBottleFull
- DrinkWhiskeyBottleFull
- DrinkWineBottleFull
- DrinkVodkaBottleFull
- DrinkGinBottleFull
- DrinkRumBottleFull
- DrinkTequilaBottleFull
- DrinkVermouthBottleFull
- DrinkCognacBottleFull
- DrinkAleBottleFullGrowler
- DrinkMeadJug

View File

@@ -1,31 +1,26 @@
- type: reagentDispenserInventory
id: ChemDispenserStandardInventory
inventory:
- Aluminium
- Carbon
- Chlorine
- Copper
- Ethanol
- Fluorine
- Sugar
- Hydrogen
- Iodine
- Iron
- Lithium
- Mercury
- Nitrogen
- Oxygen
- Phosphorus
- Potassium
- Radium
- Silicon
- Sodium
- Sulfur
- JugAluminium
- JugCarbon
- JugChlorine
- JugCopper
- JugEthanol
- JugFluorine
- JugSugar
- JugHydrogen
- JugIodine
- JugIron
- JugLithium
- JugMercury
- JugNitrogen
- JugOxygen
- JugPhosphorus
- JugPotassium
- JugRadium
- JugSilicon
- JugSodium
- JugSulfur
- type: reagentDispenserInventory
id: ChemDispenserEmaggedInventory
inventory: ##Feel free to change this to something more interesting when more chems are added
- Napalm
- Toxin
- Epinephrine
- Ultravasculine
id: EmptyInventory

View File

@@ -4,6 +4,9 @@
id: DrinkBottlePlasticBaseFull
abstract: true
components:
- type: Tag
tags:
- DrinkBottle
- type: Openable
sound:
collection: bottleOpenSounds
@@ -13,6 +16,7 @@
maxVol: 100
- type: Sprite
state: icon
sprite: Objects/Consumable/Drinks/water.rsi # fallback to boring water jug
- type: Item
size: Normal
- type: Damageable
@@ -285,6 +289,8 @@
reagents:
- ReagentId: Rum
Quantity: 100
- type: Label
currentLabel: rum
- type: Sprite
sprite: Objects/Consumable/Drinks/rumbottle.rsi
@@ -332,6 +338,8 @@
reagents:
- ReagentId: Tequila
Quantity: 100
- type: Label
currentLabel: tequila
- type: Sprite
sprite: Objects/Consumable/Drinks/tequillabottle.rsi
@@ -347,6 +355,8 @@
reagents:
- ReagentId: Vermouth
Quantity: 100
- type: Label
currentLabel: vermouth
- type: Sprite
sprite: Objects/Consumable/Drinks/vermouthbottle.rsi
@@ -377,6 +387,8 @@
reagents:
- ReagentId: Whiskey
Quantity: 100
- type: Label
currentLabel: whiskey
- type: Sprite
sprite: Objects/Consumable/Drinks/whiskeybottle.rsi
@@ -392,6 +404,8 @@
reagents:
- ReagentId: Wine
Quantity: 100
- type: Label
currentLabel: wine
- type: Sprite
sprite: Objects/Consumable/Drinks/winebottle.rsi
@@ -417,6 +431,22 @@
- type: entity
parent: DrinkBottleGlassBaseFull
id: DrinkBeerGrowler # Needs to be renamed DrinkBeerBottleFull
name: Beer Growler # beer it is. coffee. beer? coff-ee? be-er? c-o... b-e
description: An alcoholic beverage made from malted grains, hops, yeast, and water. XL growler bottle.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: Beer
Quantity: 150
- type: Sprite
sprite: Objects/Consumable/Drinks/beer.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkAleBottleFull
name: Magm-Ale
description: A true dorf's drink of choice.
@@ -433,6 +463,22 @@
- type: Sprite
sprite: Objects/Consumable/Drinks/alebottle.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkAleBottleFullGrowler
name: Magm-Ale Growler
description: A true dorf's drink of choice. XL growler bottle.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: Ale
Quantity: 150
- type: Sprite
sprite: Objects/Consumable/Drinks/alebottle.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkWaterBottleFull
@@ -451,3 +497,258 @@
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/waterbottle.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkSodaWaterBottleFull
name: soda water bottle
description: Like water, but angry!
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: SodaWater
Quantity: 150
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/waterbottle.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkTonicWaterBottleFull
name: tonic water bottle
description: Like soda water, but angrier maybe? Often sweeter.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: TonicWater
Quantity: 150
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/waterbottle.rsi
# Cartons, TODO: this needs to be moved elsewhere eventually, since cartons shouldnt smash into glass shards
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkJuiceLimeCartonXL
name: lime juice XL
description: Sweet-sour goodness.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: JuiceLime
Quantity: 150
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/limejuice.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkJuiceOrangeCartonXL
name: orange juice XL
description: Full of vitamins and deliciousness!
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: JuiceOrange
Quantity: 150
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/orangejuice.rsi
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkCreamCartonXL
name: Milk Cream XL
description: It's cream. Made from milk. What else did you think you'd find in there?
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: Cream
Quantity: 150
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/cream.rsi
#boring jugs some more sprites are made
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkSugarJug
name: sugar
suffix: for drinks
description: some people put this in their coffee...
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: Sugar
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkLemonLimeJug
name: lemon lime
description: a dual citrus sensation.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: LemonLime
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkMeadJug
name: mead jug
description: storing mead in a plastic jug should be a crime.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 150
reagents:
- ReagentId: Mead
Quantity: 150
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkIceJug
name: ice jug
description: stubborn water. pretty cool.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: Ice
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkCoffeeJug
name: coffee jug
description: wake up juice, of the heated kind.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: Coffee
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkTeaJug
name: tea jug
description: the drink of choice for the Bri'ish and hipsters.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: Tea
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkGreenTeaJug
name: green tea jug
description: its like tea... but green! great for settling the stomach.
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: GreenTea
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkIcedTeaJug
name: iced tea jug
description: for when the regular tea is too hot for you boohoo
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: IcedTea
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkDrGibbJug
name: dr gibb jug
description: yeah I don't know either...
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: DrGibb
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkRootBeerJug
name: root beer jug
description: this drink makes Australians giggle
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: RootBeer
Quantity: 300
- type: Drink
- type: entity
parent: DrinkBottlePlasticBaseFull
id: DrinkWaterMelonJuiceJug
name: watermelon juice jug
description: May include leftover seeds
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 300
reagents:
- ReagentId: JuiceWatermelon
Quantity: 300
- type: Drink

View File

@@ -553,7 +553,7 @@
- type: Sprite
state: medical
- type: MachineBoard
prototype: ChemDispenser
prototype: ChemDispenserEmpty
requirements:
Capacitor: 1
materialRequirements:
@@ -1145,7 +1145,7 @@
- type: Sprite
state: service
- type: MachineBoard
prototype: BoozeDispenser
prototype: BoozeDispenserEmpty
materialRequirements:
Steel: 5
tagRequirements:
@@ -1178,7 +1178,7 @@
- type: Sprite
state: service
- type: MachineBoard
prototype: soda_dispenser
prototype: SodaDispenserEmpty
materialRequirements:
Steel: 5
tagRequirements:

View File

@@ -47,6 +47,9 @@
price: 60
- type: Label
originalName: jug
- type: Tag
tags:
- ChemDispensable
- type: entity
parent: Jug

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
abstract: true
id: ReagentDispenserBase
parent: ConstructibleMachine
@@ -55,18 +55,20 @@
sound:
path: /Audio/Effects/metalbreak.ogg
- type: ReagentDispenser
- type: ItemSlots
slots:
storageWhitelist:
tags:
- Bottle
beakerSlot:
whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message
whitelist:
components:
- FitsInDispenser
- type: ItemSlots
- type: ContainerContainer
containers:
machine_board: !type:Container
machine_parts: !type:Container
beakerSlot: !type:ContainerSlot
ReagentDispenser-beakerSlot: !type:ContainerSlot
- type: StaticPrice
price: 1000
- type: Wires

View File

@@ -1,6 +1,7 @@
- type: entity
id: BoozeDispenser
name: booze dispenser
suffix: Filled
description: A booze dispenser with a single slot for a container to be filled.
parent: ReagentDispenserBase
components:
@@ -10,8 +11,10 @@
drawdepth: SmallObjects
state: booze
- type: ReagentDispenser
storageWhitelist:
tags:
- DrinkBottle
pack: BoozeDispenserInventory
emagPack: BoozeDispenserEmagInventory
- type: Transform
noRot: false
- type: Machine
@@ -24,3 +27,14 @@
- Bartender
- type: StealTarget
stealGroup: BoozeDispenser
- type: entity
id: BoozeDispenserEmpty
suffix: Empty
parent: BoozeDispenser
components:
- type: ReagentDispenser
storageWhitelist:
tags:
- DrinkBottle
pack: EmptyInventory

View File

@@ -1,16 +1,19 @@
- type: entity
- type: entity
id: ChemDispenser
name: chemical dispenser
suffix: Filled
parent: ReagentDispenserBase
description: An industrial grade chemical dispenser with a sizeable chemical supply.
description: An industrial grade chemical dispenser.
components:
- type: Sprite
sprite: Structures/dispensers.rsi
state: industrial-working
snapCardinals: true
- type: ReagentDispenser
storageWhitelist:
tags:
- ChemDispensable
pack: ChemDispenserStandardInventory
emagPack: ChemDispenserEmaggedInventory
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: Destructible
@@ -37,3 +40,12 @@
- Chemist
- type: StealTarget
stealGroup: ChemDispenser
- type: entity
id: ChemDispenserEmpty
name: chemical dispenser
suffix: Empty
parent: ChemDispenser
components:
- type: ReagentDispenser
pack: EmptyInventory

View File

@@ -1,6 +1,7 @@
- type: entity
id: soda_dispenser
name: soda dispenser
suffix: Filled
parent: ReagentDispenserBase
description: A beverage dispenser with a selection of soda and several other common beverages. Has a single fill slot for containers.
components:
@@ -10,8 +11,10 @@
drawdepth: SmallObjects
state: soda
- type: ReagentDispenser
storageWhitelist:
tags:
- DrinkBottle
pack: SodaDispenserInventory
emagPack: SodaDispenserEmagInventory
- type: Transform
noRot: false
- type: Machine
@@ -22,3 +25,14 @@
- type: GuideHelp
guides:
- Bartender
- type: entity
id: SodaDispenserEmpty
suffix: Empty
parent: soda_dispenser
components:
- type: ReagentDispenser
storageWhitelist:
tags:
- DrinkBottle
pack: EmptyInventory

View File

@@ -305,6 +305,9 @@
- type: Tag
id: Chicken
- type: Tag
id: ChemDispensable # container that can go into the chem dispenser
- type: Tag
id: Cigarette
@@ -458,6 +461,9 @@
- type: Tag
id: DrinkSpaceGlue
- type: Tag
id: DrinkBottle
- type: Tag
id: DroneUsable