Rerun of "Add Reagent Grinder/Juicer" (#2570)

* commit skeleton reagent grinder component

* add reagentgrinder bounduserinterface, add suffix to grinder yml, add reagentgrinder sprites

* implement much more of the grinder ui functionality

* get more use out of hasbeaker bool

* complete wiring up most of the chamber/beaker UI controls

* remove whitelist prototype id. add grindable tag component

* add juiceable component

* rename boolparam to be clearer

* more juice

* add some juice reagents and apply them to their drink prototypes

* re add glassyellow

* implement juicing and results

* add time delay to grindjuice

* add reagent grinder visualizer, add reagent grinder sounds, fix some yaml errors too

* interface has clear indication of currennt operation, and busy status

* add ReagentGrinder to Client ignnored components

* Update Content.Client/GameObjects/Components/Kitchen/ReagentGrinderBoundUserInterface.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update Content.Client/GameObjects/Components/Kitchen/ReagentGrinderBoundUserInterface.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update Content.Shared/Kitchen/SharedReagentGrinderComponent.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* implemennt  sloth changes

* fix isbusy

* Disable grind/juice buttons if there's nothing to grind or juice, also some misc fixes

* unsubscribe from messages on remove + fix xmlcomment

* fix bounding box

* Add these to dictionary

* Add these to client ignore

* Whitespace and unneeded import fixes

* tommy's toes

* Where'd these newlines come from

* power

* improve bounding box

* Check power better & show contents when beaker is ejected

* check power here

* Disable eject buttons when running

* improve comments

* readwrite for viewvars on work time and capacity

* Address most of Sloth's reviews

* Make this cleaner

* add grindablecomponent to motherfucking everything

* Fix reviews

* some more null suppressions

* remove unused random field

Co-authored-by: scuffedjays <yetanotherscuffed@gmail.com>
Co-authored-by: namespace-Memory <66768086+namespace-Memory@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Peter Wedder
2020-11-26 14:53:42 +02:00
committed by GitHub
parent 4b77f27e77
commit 32d8cc0c1e
20 changed files with 1347 additions and 42 deletions

View File

@@ -0,0 +1,292 @@
using System.Collections.Generic;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Content.Shared.Kitchen;
using Robust.Shared.GameObjects;
using Content.Shared.Chemistry;
using Robust.Shared.IoC;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Client.GameObjects;
namespace Content.Client.GameObjects.Components.Kitchen
{
public class ReagentGrinderBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private GrinderMenu _menu;
private Dictionary<int, EntityUid> _chamberVisualContents = new Dictionary<int, EntityUid>();
private Dictionary<int, Solution.ReagentQuantity> _beakerVisualContents = new Dictionary<int, Solution.ReagentQuantity>();
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
protected override void Open()
{
base.Open();
_menu = new GrinderMenu(this);
_menu.OpenCentered();
_menu.OnClose += Close;
_menu.GrindButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
_menu.JuiceButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
_menu.ChamberContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
_menu.BeakerContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
_menu.ChamberContentBox.BoxContents.OnItemSelected += args =>
{
SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(_chamberVisualContents[args.ItemIndex]));
};
_menu.BeakerContentBox.BoxContents.OnItemSelected += args =>
{
SendMessage(new SharedReagentGrinderComponent.ReagentGrinderVaporizeReagentIndexedMessage(_beakerVisualContents[args.ItemIndex]));
};
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
{
return;
}
_chamberVisualContents?.Clear();
_beakerVisualContents?.Clear();
_menu?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is ReagentGrinderInterfaceState cState))
{
return;
}
_menu.BeakerContentBox.EjectButton.Disabled = !cState.HasBeakerIn;
_menu.ChamberContentBox.EjectButton.Disabled = cState.ChamberContents.Length <= 0;
_menu.GrindButton.Disabled = !cState.CanGrind || !cState.Powered;
_menu.JuiceButton.Disabled = !cState.CanJuice || !cState.Powered;
RefreshContentsDisplay(cState.ReagentQuantities, cState.ChamberContents, cState.HasBeakerIn);
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
switch (message)
{
case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
_menu.GrindButton.Disabled = true;
_menu.GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
_menu.JuiceButton.Disabled = true;
_menu.JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
_menu.BeakerContentBox.EjectButton.Disabled = true;
_menu.ChamberContentBox.EjectButton.Disabled = true;
break;
case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage doneMessage:
_menu.GrindButton.Disabled = false;
_menu.JuiceButton.Disabled = false;
_menu.GrindButton.Modulate = Color.White;
_menu.JuiceButton.Modulate = Color.White;
_menu.BeakerContentBox.EjectButton.Disabled = false;
_menu.ChamberContentBox.EjectButton.Disabled = false;
break;
}
}
private void RefreshContentsDisplay(IList<Solution.ReagentQuantity> reagents, IReadOnlyList<EntityUid> containedSolids, bool isBeakerAttached)
{
//Refresh chamber contents
_chamberVisualContents.Clear();
_menu.ChamberContentBox.BoxContents.Clear();
foreach (var uid in containedSolids)
{
if (!_entityManager.TryGetEntity(uid, out var entity))
{
return;
}
var texture = entity.GetComponent<SpriteComponent>().Icon?.Default;
var solidItem = _menu.ChamberContentBox.BoxContents.AddItem(entity.Name, texture);
var solidIndex = _menu.ChamberContentBox.BoxContents.IndexOf(solidItem);
_chamberVisualContents.Add(solidIndex, uid);
}
//Refresh beaker contents
_beakerVisualContents.Clear();
_menu.BeakerContentBox.BoxContents.Clear();
//if no beaker is attached use this guard to prevent hitting a null reference.
if (!isBeakerAttached || reagents == null)
{
return;
}
//Looks like we have a beaker attached.
if (reagents.Count <= 0)
{
_menu.BeakerContentBox.BoxContents.AddItem(Loc.GetString("Empty"));
}
else
{
for (var i = 0; i < reagents.Count; i++)
{
var goodIndex = _prototypeManager.TryIndex(reagents[i].ReagentId, out ReagentPrototype proto);
var reagentName = goodIndex ? Loc.GetString($"{reagents[i].Quantity} {proto.Name}") : Loc.GetString("???");
var reagentAdded = _menu.BeakerContentBox.BoxContents.AddItem(reagentName);
var reagentIndex = _menu.BeakerContentBox.BoxContents.IndexOf(reagentAdded);
_beakerVisualContents.Add(reagentIndex, reagents[i]);
}
}
}
public class GrinderMenu : SS14Window
{
/*The contents of the chamber and beaker will both be vertical scroll rectangles.
* Will have a vsplit to split the g/j buttons from the contents menu.
* |--------------------------------\
* | | Chamber [E] Beaker [E] |
* | [G] | | | | | |
* | | | | | | |
* | | | | | | |
* | [J] | |-----| |-----| |
* | | |
* \---------------------------------/
*
*/
private ReagentGrinderBoundUserInterface Owner { get; set; }
protected override Vector2? CustomSize => (512, 256);
//We'll need 4 buttons, grind, juice, eject beaker, eject the chamber contents.
//The other 2 are referenced in the Open function.
public Button GrindButton { get; }
public Button JuiceButton { get; }
public LabelledContentBox ChamberContentBox { get; }
public LabelledContentBox BeakerContentBox { get; }
public sealed class LabelledContentBox : VBoxContainer
{
public string LabelText { get; set; }
public ItemList BoxContents { get; set; }
public Button EjectButton { get; set; }
private Label _label;
public LabelledContentBox(string labelText, string buttonText)
{
_label = new Label
{
Text = labelText,
Align = Label.AlignMode.Center,
};
EjectButton = new Button
{
Text = buttonText,
TextAlign = Label.AlignMode.Center,
};
var vSplit = new HSplitContainer
{
Children =
{
_label,
EjectButton
}
};
AddChild(vSplit);
BoxContents = new ItemList
{
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SelectMode = ItemList.ItemListSelectMode.Button,
SizeFlagsStretchRatio = 2,
CustomMinimumSize = (100, 128)
};
AddChild(BoxContents);
}
}
public GrinderMenu(ReagentGrinderBoundUserInterface owner = null)
{
Owner = owner;
Title = Loc.GetString("All-In-One Grinder 3000");
var hSplit = new HBoxContainer
{
SizeFlagsHorizontal = SizeFlags.Fill,
SizeFlagsVertical = SizeFlags.Fill
};
var vBoxGrindJuiceButtonPanel = new VBoxContainer
{
SizeFlagsVertical = SizeFlags.ShrinkCenter
};
GrindButton = new Button
{
Text = Loc.GetString("Grind"),
TextAlign = Label.AlignMode.Center,
CustomMinimumSize = (64, 64)
};
JuiceButton = new Button
{
Text = Loc.GetString("Juice"),
TextAlign = Label.AlignMode.Center,
CustomMinimumSize = (64, 64)
};
vBoxGrindJuiceButtonPanel.AddChild(GrindButton);
//inner button padding
vBoxGrindJuiceButtonPanel.AddChild(new Control
{
CustomMinimumSize = (0, 16),
});
vBoxGrindJuiceButtonPanel.AddChild(JuiceButton);
ChamberContentBox = new LabelledContentBox(Loc.GetString("Chamber"), Loc.GetString("Eject Contents"))
{
//Modulate = Color.Red,
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 2,
};
BeakerContentBox = new LabelledContentBox(Loc.GetString("Beaker"), Loc.GetString("Eject Beaker"))
{
//Modulate = Color.Blue,
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 2,
};
hSplit.AddChild(vBoxGrindJuiceButtonPanel);
//Padding between the g/j buttons panel and the itemlist boxes panel.
hSplit.AddChild(new Control
{
CustomMinimumSize = (16, 0),
});
hSplit.AddChild(ChamberContentBox);
//Padding between the two itemlists.
hSplit.AddChild(new Control
{
CustomMinimumSize = (8, 0),
});
hSplit.AddChild(BeakerContentBox);
Contents.AddChild(hSplit);
}
}
}
}

View File

@@ -0,0 +1,17 @@
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using static Content.Shared.Kitchen.SharedReagentGrinderComponent;
namespace Content.Client.GameObjects.Components.Kitchen
{
public class ReagentGrinderVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(ReagentGrinderVisualState.BeakerAttached, out bool hasBeaker);
sprite.LayerSetState(0, $"juicer{(hasBeaker ? "1" : "0")}");
}
}
}

View File

@@ -209,6 +209,9 @@
"CrematoriumEntityStorage", "CrematoriumEntityStorage",
"RandomArcade", "RandomArcade",
"RandomSpriteState", "RandomSpriteState",
"ReagentGrinder",
"Grindable",
"Juiceable",
"WelderRefinable", "WelderRefinable",
"ConveyorAssembly", "ConveyorAssembly",
"TwoWayLever", "TwoWayLever",

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Kitchen
{
/// <summary>
/// Tag component that denotes an entity as Grindable by the reagentgrinder.
/// </summary>
[RegisterComponent]
public class GrindableComponent : Component
{
public override string Name => "Grindable";
}
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Kitchen
{
/// <summary>
/// Tag component that denotes an entity as Juiceable
/// </summary>
[RegisterComponent]
public class JuiceableComponent : Component
{
public override string Name => "Juiceable";
[ViewVariables] public Solution JuiceResultSolution;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.JuiceResultSolution, "result", new Solution());
}
}
}

View File

@@ -0,0 +1,375 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Kitchen;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Kitchen
{
/// <summary>
/// The combo reagent grinder/juicer. The reason why grinding and juicing are seperate is simple,
/// think of grinding as a utility to break an object down into its reagents. Think of juicing as
/// converting something into its single juice form. E.g, grind an apple and get the nutriment and sugar
/// it contained, juice an apple and get "apple juice".
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing
{
private AudioSystem _audioSystem = default!;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
/// <summary>
/// Can be null since we won't always have a beaker in the grinder.
/// </summary>
[ViewVariables] private SolutionContainerComponent? _heldBeaker = default!;
/// <summary>
/// Contains the things that are going to be ground or juiced.
/// </summary>
[ViewVariables] private Container _chamber = default!;
[ViewVariables] private bool ChamberEmpty => _chamber.ContainedEntities.Count <= 0;
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentGrinderUiKey.Key);
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
/// <summary>
/// Should the BoundUI be told to update?
/// </summary>
private bool _uiDirty = true;
/// <summary>
/// Is the machine actively doing something and can't be used right now?
/// </summary>
private bool _busy = false;
//YAML serialization vars
[ViewVariables(VVAccess.ReadWrite)] private int _storageCap = 16;
[ViewVariables(VVAccess.ReadWrite)] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now.
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _storageCap, "chamberCapacity", 16);
serializer.DataField(ref _workTime, "workTime", 3500);
}
public override void Initialize()
{
base.Initialize();
//A slot for the beaker where the grounds/juices will go.
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
//A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
_chamber =
ContainerManagerComponent.Ensure<Container>($"{Name}-entityContainerContainer", Owner);
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerStateChanged;
}
_audioSystem = EntitySystem.Get<AudioSystem>();
}
public override void OnRemove()
{
base.OnRemove();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage -= UserInterfaceOnReceiveMessage;
}
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= OnPowerStateChanged;
}
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if(_busy)
{
return;
}
switch(message.Message)
{
case ReagentGrinderGrindStartMessage msg:
if (!Powered) break;
ClickSound();
DoWork(message.Session.AttachedEntity!, GrinderProgram.Grind);
break;
case ReagentGrinderJuiceStartMessage msg:
if (!Powered) break;
ClickSound();
DoWork(message.Session.AttachedEntity!, GrinderProgram.Juice);
break;
case ReagentGrinderEjectChamberAllMessage msg:
if(!ChamberEmpty)
{
ClickSound();
for (var i = _chamber.ContainedEntities.Count - 1; i >= 0; i--)
{
EjectSolid(_chamber.ContainedEntities.ElementAt(i).Uid);
}
}
break;
case ReagentGrinderEjectChamberContentMessage msg:
if (!ChamberEmpty)
{
EjectSolid(msg.EntityID);
ClickSound();
_uiDirty = true;
}
break;
case ReagentGrinderEjectBeakerMessage msg:
ClickSound();
EjectBeaker(message.Session.AttachedEntity);
//EjectBeaker will dirty the UI for us, we don't have to do it explicitly here.
break;
}
}
private void OnPowerStateChanged(object? sender, PowerStateEventArgs e)
{
_uiDirty = true;
}
private void ClickSound()
{
_audioSystem.PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
private void SetAppearance()
{
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(ReagentGrinderVisualState.BeakerAttached, HasBeaker);
}
}
public void OnUpdate()
{
if(_uiDirty)
{
UpdateInterface();
_uiDirty = false;
}
}
// This doesn't check for UI dirtiness so handle that when calling this.
private void UpdateInterface()
{
bool canJuice = false;
bool canGrind = false;
if (HasBeaker)
{
foreach (var entity in _chamber.ContainedEntities)
{
if (!canJuice && entity.HasComponent<JuiceableComponent>()) canJuice = true;
if (!canGrind && entity.HasComponent<GrindableComponent>()) canGrind = true;
if (canJuice && canGrind) break;
}
}
UserInterface?.SetState(new ReagentGrinderInterfaceState
(
_busy,
HasBeaker,
Powered,
canJuice,
canGrind,
_chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
//Remember the beaker can be null!
_heldBeaker?.Solution.Contents.ToArray()
));
_uiDirty = false;
}
private void EjectSolid(EntityUid entityID)
{
if (_busy)
return;
if (Owner.EntityManager.TryGetEntity(entityID, out var entity))
{
_chamber.Remove(entity);
//Give the ejected entity a tiny bit of offset so each one is apparent in case of a big stack,
//but (hopefully) not enough to clip it through a solid (wall).
entity.RandomOffset(0.4f);
}
_uiDirty = true;
}
/// <summary>
/// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top
/// of the grinder.
/// </summary>
private void EjectBeaker(IEntity? user)
{
if (!HasBeaker || _heldBeaker == null || _busy)
return;
_beakerContainer.Remove(_beakerContainer.ContainedEntity);
if (user == null || !user.TryGetComponent<HandsComponent>(out var hands) || !_heldBeaker.Owner.TryGetComponent<ItemComponent>(out var item))
return;
hands.PutInHandOrDrop(item);
_heldBeaker = null;
_uiDirty = true;
SetAppearance();
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
_uiDirty = true;
UserInterface?.Toggle(actor.playerSession);
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands))
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("You have no hands."));
return true;
}
IEntity heldEnt = eventArgs.Using;
//First, check if user is trying to insert a beaker.
//No promise it will be a beaker right now, but whatever.
//Maybe this should whitelist "beaker" in the prototype id of heldEnt?
if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
{
_beakerContainer.Insert(heldEnt);
_heldBeaker = beaker;
_uiDirty = true;
//We are done, return. Insert the beaker and exit!
SetAppearance();
ClickSound();
return true;
}
//Next, see if the user is trying to insert something they want to be ground/juiced.
if(!heldEnt.TryGetComponent(out GrindableComponent? grind) && !heldEnt.TryGetComponent(out JuiceableComponent? juice))
{
//Entity did NOT pass the whitelist for grind/juice.
//Wouldn't want the clown grinding up the Captain's ID card now would you?
//Why am I asking you? You're biased.
return false;
}
//Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
//Maybe I should have done that for the microwave too?
if (_chamber.ContainedEntities.Count >= _storageCap)
{
return false;
}
if (!_chamber.Insert(heldEnt))
return false;
_uiDirty = true;
return true;
}
/// <summary>
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
/// </summary>
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
private async void DoWork(IEntity user, GrinderProgram program)
{
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
if(!Powered || _busy || ChamberEmpty || !HasBeaker || _heldBeaker == null)
{
return;
}
_busy = true;
UserInterface?.SendMessage(new ReagentGrinderWorkStartedMessage(program));
switch (program)
{
case GrinderProgram.Grind:
_audioSystem.PlayFromEntity("/Audio/Machines/blender.ogg", Owner, AudioParams.Default);
//Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in.
Owner.SpawnTimer(_workTime, (Action) (() =>
{
foreach (var item in _chamber.ContainedEntities.ToList())
{
if (!item.HasComponent<GrindableComponent>()) continue;
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue;
if (_heldBeaker.CurrentVolume + solution.CurrentVolume > _heldBeaker.MaxVolume) continue;
_heldBeaker.TryAddSolution(solution.Solution);
solution.RemoveAllSolution();
item.Delete();
}
_busy = false;
_uiDirty = true;
UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
}));
break;
case GrinderProgram.Juice:
_audioSystem.PlayFromEntity("/Audio/Machines/juicer.ogg", Owner, AudioParams.Default);
Owner.SpawnTimer(_workTime, (Action) (() =>
{
foreach (var item in _chamber.ContainedEntities.ToList())
{
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue;
if (_heldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > _heldBeaker.MaxVolume) continue;
_heldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
item.Delete();
}
UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
_busy = false;
_uiDirty = true;
}));
break;
}
}
}
}

View File

@@ -0,0 +1,19 @@
using Content.Server.GameObjects.Components.Kitchen;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
internal sealed class ReagentGrinderSystem : EntitySystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var comp in ComponentManager.EntityQuery<ReagentGrinderComponent>())
{
comp.OnUpdate();
}
}
}
}

View File

@@ -85,6 +85,7 @@
public const uint GAS_TANK = 1079; public const uint GAS_TANK = 1079;
public const uint SINGULARITY = 1080; public const uint SINGULARITY = 1080;
public const uint CHARACTERINFO = 1081; public const uint CHARACTERINFO = 1081;
public const uint REAGENT_GRINDER = 1082;
// Net IDs for integration tests. // Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001; public const uint PREDICTION_TEST = 10001;

View File

@@ -0,0 +1,126 @@
using System;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.Kitchen
{
public abstract class SharedReagentGrinderComponent : Component
{
public override string Name => "ReagentGrinder";
public override uint? NetID => ContentNetIDs.REAGENT_GRINDER;
[Serializable, NetSerializable]
public class ReagentGrinderGrindStartMessage : BoundUserInterfaceMessage
{
public ReagentGrinderGrindStartMessage()
{
}
}
[Serializable, NetSerializable]
public class ReagentGrinderJuiceStartMessage : BoundUserInterfaceMessage
{
public ReagentGrinderJuiceStartMessage()
{
}
}
[Serializable, NetSerializable]
public class ReagentGrinderEjectChamberAllMessage : BoundUserInterfaceMessage
{
public ReagentGrinderEjectChamberAllMessage()
{
}
}
[Serializable, NetSerializable]
public class ReagentGrinderEjectBeakerMessage : BoundUserInterfaceMessage
{
public ReagentGrinderEjectBeakerMessage()
{
}
}
[Serializable, NetSerializable]
public class ReagentGrinderEjectChamberContentMessage : BoundUserInterfaceMessage
{
public EntityUid EntityID;
public ReagentGrinderEjectChamberContentMessage(EntityUid entityID)
{
EntityID = entityID;
}
}
[Serializable, NetSerializable]
public class ReagentGrinderVaporizeReagentIndexedMessage : BoundUserInterfaceMessage
{
public Solution.ReagentQuantity ReagentQuantity;
public ReagentGrinderVaporizeReagentIndexedMessage(Solution.ReagentQuantity reagentQuantity)
{
ReagentQuantity = reagentQuantity;
}
}
[Serializable, NetSerializable]
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
{
public GrinderProgram GrinderProgram;
public ReagentGrinderWorkStartedMessage(GrinderProgram grinderProgram)
{
GrinderProgram = grinderProgram;
}
}
[Serializable, NetSerializable]
public class ReagentGrinderWorkCompleteMessage : BoundUserInterfaceMessage
{
public ReagentGrinderWorkCompleteMessage()
{
}
}
[Serializable, NetSerializable]
public enum ReagentGrinderVisualState : byte
{
BeakerAttached
}
[NetSerializable, Serializable]
public enum ReagentGrinderUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum GrinderProgram : byte
{
Grind,
Juice
}
}
[NetSerializable, Serializable]
public sealed class ReagentGrinderInterfaceState : BoundUserInterfaceState
{
public bool IsBusy;
public bool HasBeakerIn;
public bool Powered;
public bool CanJuice;
public bool CanGrind;
public EntityUid[] ChamberContents;
public Solution.ReagentQuantity[] ReagentQuantities;
public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, EntityUid[] chamberContents, Solution.ReagentQuantity[] heldBeakerContents)
{
IsBusy = isBusy;
HasBeakerIn = hasBeaker;
Powered = powered;
CanJuice = canJuice;
CanGrind = canGrind;
ChamberContents = chamberContents;
ReagentQuantities = heldBeakerContents;
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,35 @@
- type: entity
id: KitchenReagentGrinder
name: reagent grinder
suffix: grinder/juicer
placement:
mode: SnapgridCenter
components:
- type: SnapGrid
offset: Center
- type: ReagentGrinder
- type: UserInterface
interfaces:
- key: enum.ReagentGrinderUiKey.Key
type: ReagentGrinderBoundUserInterface
- type: Appearance
visuals:
- type: ReagentGrinderVisualizer
- type: Clickable
- type: InteractionOutline
- type: PowerReceiver
- type: LoopingSound
- type: Physics
shapes:
- !type:PhysShapeAabb
bounds: "-0.35,-0.08,0.25,0.15"
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- type: Sprite
netsync: false
sprite: Constructible/Power/juicer.rsi
state: juicer0
drawdepth: Items

View File

@@ -27,6 +27,7 @@
Quantity: 5 Quantity: 5
- type: Produce - type: Produce
seed: wheat seed: wheat
- type: Grindable
- type: entity - type: entity
name: sugarcane name: sugarcane
@@ -46,6 +47,7 @@
Quantity: 5 Quantity: 5
- type: Produce - type: Produce
seed: sugarcane seed: sugarcane
- type: Grindable
- type: entity - type: entity
name: tower-cap log name: tower-cap log
@@ -79,6 +81,12 @@
sprite: Objects/Specific/Hydroponics/banana.rsi sprite: Objects/Specific/Hydroponics/banana.rsi
- type: Produce - type: Produce
seed: banana seed: banana
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.BananaJuice
Quantity: 10
- type: entity - type: entity
name: carrot name: carrot
@@ -96,6 +104,12 @@
sprite: Objects/Specific/Hydroponics/carrot.rsi sprite: Objects/Specific/Hydroponics/carrot.rsi
- type: Produce - type: Produce
seed: carrots seed: carrots
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.CarrotJuice
Quantity: 10
- type: entity - type: entity
name: lemon name: lemon
@@ -113,6 +127,12 @@
sprite: Objects/Specific/Hydroponics/lemon.rsi sprite: Objects/Specific/Hydroponics/lemon.rsi
- type: Produce - type: Produce
seed: lemon seed: lemon
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.LimeJuice
Quantity: 10
- type: entity - type: entity
name: potato name: potato
@@ -130,6 +150,12 @@
sprite: Objects/Specific/Hydroponics/potato.rsi sprite: Objects/Specific/Hydroponics/potato.rsi
- type: Produce - type: Produce
seed: potato seed: potato
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.PotatoJuice
Quantity: 10
- type: entity - type: entity
name: tomato name: tomato
@@ -147,6 +173,12 @@
sprite: Objects/Specific/Hydroponics/tomato.rsi sprite: Objects/Specific/Hydroponics/tomato.rsi
- type: Produce - type: Produce
seed: tomato seed: tomato
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.TomatoJuice
Quantity: 10
- type: entity - type: entity
name: eggplant name: eggplant
@@ -164,6 +196,7 @@
sprite: Objects/Specific/Hydroponics/eggplant.rsi sprite: Objects/Specific/Hydroponics/eggplant.rsi
- type: Produce - type: Produce
seed: eggplant seed: eggplant
- type: Grindable
- type: entity - type: entity
name: apple name: apple
@@ -181,6 +214,12 @@
sprite: Objects/Specific/Hydroponics/apple.rsi sprite: Objects/Specific/Hydroponics/apple.rsi
- type: Produce - type: Produce
seed: apple seed: apple
- type: Grindable
- type: Juiceable
result:
reagents:
- ReagentId: chem.AppleJuice
Quantity: 10
- type: entity - type: entity
name: ear of corn name: ear of corn
@@ -199,6 +238,7 @@
sprite: Objects/Specific/Hydroponics/corn.rsi sprite: Objects/Specific/Hydroponics/corn.rsi
- type: Produce - type: Produce
seed: corn seed: corn
- type: Grindable
- type: entity - type: entity
name: chanterelle cluster name: chanterelle cluster
@@ -216,3 +256,4 @@
sprite: Objects/Specific/Hydroponics/chanterelle.rsi sprite: Objects/Specific/Hydroponics/chanterelle.rsi
- type: Produce - type: Produce
seed: chanterelle seed: chanterelle
- type: Grindable

View File

@@ -165,11 +165,17 @@
- type: entity - type: entity
parent: DrinkGlassBase parent: DrinkGlassBase
id: DrinkBerryjJice id: DrinkBerryJuice
name: berry juice name: berry juice
description: A delicious blend of several different kinds of berries. description: A delicious blend of several different kinds of berries.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.BerryJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/berryjuice.rsi sprite: Objects/Consumable/Drinks/berryjuice.rsi
@@ -252,6 +258,12 @@
description: Has a uniquely sweet flavour of concentrated carrots. description: Has a uniquely sweet flavour of concentrated carrots.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.CarrotJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/carrotjuice.rsi sprite: Objects/Consumable/Drinks/carrotjuice.rsi
@@ -564,6 +576,12 @@
description: The sweet-sour juice of limes. description: The sweet-sour juice of limes.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.LimeJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/glass_green.rsi sprite: Objects/Consumable/Drinks/glass_green.rsi
@@ -575,9 +593,31 @@
description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges. description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.OrangeJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/glass_orange.rsi sprite: Objects/Consumable/Drinks/glass_orange.rsi
- type: entity
parent: DrinkGlassBase
id: DrinkGlassYellow
name: lemon juice
description: Used to make lemonade, soft drinks, and cocktails.
components:
- type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.LemonJuice
Quantity: 20
- type: Sprite
sprite: Objects/Consumable/Drinks/glass_yellow.rsi
- type: entity - type: entity
parent: DrinkGlassBase parent: DrinkGlassBase
@@ -586,6 +626,12 @@
description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.TomatoJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/glass_red.rsi sprite: Objects/Consumable/Drinks/glass_red.rsi
@@ -605,18 +651,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/glass_white.rsi sprite: Objects/Consumable/Drinks/glass_white.rsi
- type: entity
parent: DrinkGlassBase
id: DrinkGlassYellow
name: lemon juice
description: ''
components:
- type: Drink
- type: Sprite
sprite: Objects/Consumable/Drinks/glass_yellow.rsi
- type: entity - type: entity
parent: DrinkGlassBase parent: DrinkGlassBase
id: DrinkGoldschlagerGlass id: DrinkGoldschlagerGlass
@@ -635,6 +669,12 @@
description: The juice is often sold in stores or fermented and made into wine, brandy, or vinegar. description: The juice is often sold in stores or fermented and made into wine, brandy, or vinegar.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.GrapeJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/grapejuice.rsi sprite: Objects/Consumable/Drinks/grapejuice.rsi
@@ -903,6 +943,12 @@
description: Used to make lemonade, soft drinks, and cocktails. description: Used to make lemonade, soft drinks, and cocktails.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.LemonJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/lemonjuice.rsi sprite: Objects/Consumable/Drinks/lemonjuice.rsi
@@ -914,6 +960,14 @@
description: A tangy substance made of lime and lemon. description: A tangy substance made of lime and lemon.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.LemonJuice
Quantity: 10
- ReagentId: chem.LimeJuice
Quantity: 10
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/lemonlime.rsi sprite: Objects/Consumable/Drinks/lemonlime.rsi
@@ -925,6 +979,12 @@
description: The sweet-sour juice of limes. description: The sweet-sour juice of limes.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.LimeJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/limejuice.rsi sprite: Objects/Consumable/Drinks/limejuice.rsi
@@ -1100,6 +1160,12 @@
description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges. description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.OrangeJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/orangejuice.rsi sprite: Objects/Consumable/Drinks/orangejuice.rsi
@@ -1122,6 +1188,12 @@
description: A tasty juice blended from various kinds of very deadly and toxic berries. description: A tasty juice blended from various kinds of very deadly and toxic berries.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.PoisonBerryJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/poisonberryjuice.rsi sprite: Objects/Consumable/Drinks/poisonberryjuice.rsi
@@ -1521,9 +1593,15 @@
parent: DrinkGlassBase parent: DrinkGlassBase
id: DrinkTomatoJuice id: DrinkTomatoJuice
name: tomato juice name: tomato juice
description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.TomatoJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/tomatojuice.rsi sprite: Objects/Consumable/Drinks/tomatojuice.rsi
@@ -1612,6 +1690,12 @@
description: Delicious juice made from watermelon. description: Delicious juice made from watermelon.
components: components:
- type: Drink - type: Drink
- type: SolutionContainer
maxVol: 20
contents:
reagents:
- ReagentId: chem.WatermelonJuice
Quantity: 20
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Drinks/watermelon.rsi sprite: Objects/Consumable/Drinks/watermelon.rsi

File diff suppressed because it is too large Load Diff

View File

@@ -182,7 +182,7 @@
- type: reagent - type: reagent
id: chem.Tea id: chem.Tea
name: tea name: tea
desc: A made by boiling leaves of the tea tree, Camellia sinensis. desc: A drink made by boiling leaves of the tea tree, Camellia sinensis.
physicalDesc: aromatic physicalDesc: aromatic
color: "#8a5a3a" color: "#8a5a3a"
metabolism: metabolism:
@@ -253,3 +253,152 @@
metabolism: metabolism:
- !type:DefaultDrink - !type:DefaultDrink
rate: 1 rate: 1
#Juices
- type: reagent
id: chem.AppleJuice
name: apple juice
desc: It's a little piece of Eden.
physicalDesc: crisp
color: "#FDAD01"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.BerryJuice
name: berry juice
desc: A delicious blend of several different kinds of berries.
physicalDesc: sweet
color: "#660099"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.BananaJuice
name: banana juice
desc: The raw essence of a banana. HONK.
physicalDesc: crisp
color: "#FFE777"
metabolism:
- !type:DefaultDrink
rate: 1
#TODO: port on_mob_life: restore eyesight
#if(..())
#return 1
# M.eye_blurry = max(M.eye_blurry - 1 , 0)
# M.eye_blind = max(M.eye_blind - 1 , 0)
# switch(data)
# if(21 to INFINITY)
# if(prob(data - 10))
# M.disabilities &= ~NEARSIGHTED
# data++
- type: reagent
id: chem.CarrotJuice
name: carrot juice
desc: It's like a carrot, but less crunchy.
physicalDesc: crisp
color: "#FF8820"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.LimeJuice
name: lime juice
desc: The sweet-sour juice of limes.
physicalDesc: citric
color: "#99bb43"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.LemonJuice
name: lemon juice
desc: This juice is VERY sour.
physicalDesc: citric
color: "#fff690"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.GrapeJuice
name: grape juice
desc: Freshly squeezed juice from red grapes. Quite sweet.
physicalDesc: crisp
color: "#512284"
metabolism:
- !type:DefaultDrink
rate: 1
# /datum/reagent/drink/orangejuice/on_mob_life(var/mob/living/M)
# if(..())
# return 1
# if(M.getToxLoss() && prob(20))
# M.adjustToxLoss(-REM)
- type: reagent
id: chem.OrangeJuice
name: orange juice
desc: Both delicious AND rich in Vitamin C. What more do you need?
physicalDesc: citric
color: "#E78108"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.TomatoJuice
name: tomato juice
desc: Tomatoes made into juice. What a waste of good tomatoes, huh?
physicalDesc: saucey
color: "#731008"
metabolism:
- !type:DefaultDrink
rate: 1
# /datum/reagent/drink/poisonberryjuice/on_mob_life(var/mob/living/M)
# if(..())
# return 1
# M.adjustToxLoss(1)
- type: reagent
id: chem.PoisonBerryJuice
name: poison berry juice
desc: A surprisingly tasty juice blended from various kinds of very deadly and toxic berries.
physicalDesc: aromatic #maybe should be 'sickly'?
color: "#6600CC"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.WatermelonJuice
name: water melon juice
desc: The delicious juice of a watermelon.
physicalDesc: sweet
color: "#EF3520"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.PotatoJuice
name: potato juice
desc: Juice of the potato. Bleh.
physicalDesc: starchy
color: "#302000"
metabolism:
- !type:DefaultDrink
rate: 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

View File

@@ -0,0 +1 @@
{"license": "CC-BY-SA 3.0", "copyright": "https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d","version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "juicer0", "directions": 1, "delays": [[1.0]]}, {"name": "juicer1", "directions": 1, "delays": [[1.0]]}]}

View File

@@ -104,9 +104,11 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=freepats/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=freepats/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gamemode/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=gamemode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=godmode/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=godmode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grindable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=hardcode/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=hardcode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=hbox/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=hbox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=inhand/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=inhand/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Juiceable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=keybind/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=keybind/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=keybinds/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=keybinds/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kibibyte/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Kibibyte/@EntryIndexedValue">True</s:Boolean>