Chemistry revamp and bartending features

-Ability to mix drinks to create cocktails with shiny icons
-New Chemistry System which can relay chemistry events to corresponding components
-moved some solution logic from Shared to Server
-fixed some weird stuff with DrinkComponent
This commit is contained in:
Injazz
2020-04-08 15:53:15 +05:00
parent 45e9be43ef
commit d400f77129
24 changed files with 822 additions and 1270 deletions

View File

@@ -134,6 +134,7 @@ namespace Content.Client
"Paper",
"Write",
"Bloodstream",
"TransformableContainer",
"Mind",
"MovementSpeedModifier",
"StorageFill"
@@ -148,7 +149,7 @@ namespace Content.Client
factory.Register<SharedLatheComponent>();
factory.Register<SharedSpawnPointComponent>();
factory.Register<SolutionComponent>();
factory.Register<SharedSolutionComponent>();
factory.Register<SharedVendingMachineComponent>();
factory.Register<SharedWiresComponent>();

View File

@@ -65,19 +65,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", 15);
serializer.DataField(ref _transferAmount, "transferAmount", 5);
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal storage
_internalContents = new SolutionComponent();
_internalContents.InitializeFromPrototype();
_internalContents.Init();
_internalContents.MaxVolume = _initialMaxVolume;
_internalContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
base.Startup();
_internalContents = Owner.GetComponent<SolutionComponent>();
_internalContents.Capabilities |= SolutionCaps.Injector;
//Set _toggleState based on prototype
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
}

View File

@@ -30,7 +30,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAttackBy))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
@@ -207,7 +207,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
var beaker = _beakerContainer.ContainedEntity;
Solution.SolutionChanged -= HandleSolutionChangedEvent;
_beakerContainer.Remove(_beakerContainer.ContainedEntity);
UpdateUserInterface();
@@ -304,7 +303,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_beakerContainer.Insert(activeHandEntity);
Solution.SolutionChanged += HandleSolutionChangedEvent;
UpdateUserInterface();
}
}
@@ -317,10 +315,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return true;
}
private void HandleSolutionChangedEvent()
{
UpdateUserInterface();
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
@@ -329,5 +324,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
sound.Play("/Audio/machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f));
}
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using Content.Server.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
@@ -12,16 +14,19 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Chemistry
{
/// <summary>
/// Shared ECS component that manages a liquid solution of reagents.
/// ECS component that manages a liquid solution of reagents.
/// </summary>
[RegisterComponent]
internal class SolutionComponent : Shared.GameObjects.Components.Chemistry.SolutionComponent, IExamine
internal class SolutionComponent : SharedSolutionComponent, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
@@ -31,17 +36,93 @@ namespace Content.Server.GameObjects.Components.Chemistry
private IEnumerable<ReactionPrototype> _reactions;
private AudioSystem _audioSystem;
private ChemistrySystem _chemistrySystem;
[ViewVariables]
protected Solution _containedSolution = new Solution();
protected int _maxVolume;
private SolutionCaps _capabilities;
/// <summary>
/// The maximum volume of the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxVolume
{
get => _maxVolume;
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
}
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables]
public int CurrentVolume => _containedSolution.TotalVolume;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public int EmptyVolume => MaxVolume - CurrentVolume;
/// <summary>
/// The current blended color of all the reagents in the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Color SubstanceColor { get; private set; }
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SolutionCaps Capabilities
{
get => _capabilities;
set => _capabilities = value;
}
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
/// <summary>
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
/// </summary>
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
/// <summary>
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
/// </summary>
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
/// <summary>
/// Shortcut for Capabilities Injectable flag
/// </summary>
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
/// <summary>
/// Shortcut for Capabilities Injector flag
/// </summary>
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", 0);
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
}
public override void Initialize()
{
base.Initialize();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
_chemistrySystem = _entitySystemManager.GetEntitySystem<ChemistrySystem>();
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
}
protected override void Startup()
{
base.Startup();
Init();
}
public void Init()
{
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
RecalculateColor();
}
/// <summary>
@@ -54,6 +135,85 @@ namespace Content.Server.GameObjects.Components.Chemistry
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
}
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
_containedSolution.RemoveAllSolution();
_containedSolution = new Solution();
}
public void RemoveAllSolution()
{
_containedSolution.RemoveAllSolution();
OnSolutionChanged();
}
public bool TryRemoveReagent(string reagentId, int quantity)
{
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
_containedSolution.RemoveReagent(reagentId, quantity);
OnSolutionChanged();
return true;
}
/// <summary>
/// Attempt to remove the specified quantity from this solution
/// </summary>
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(int quantity)
{
if (CurrentVolume == 0)
return false;
_containedSolution.RemoveSolution(quantity);
OnSolutionChanged();
return true;
}
public Solution SplitSolution(int quantity)
{
var solutionSplit = _containedSolution.SplitSolution(quantity);
OnSolutionChanged();
return solutionSplit;
}
protected void RecalculateColor()
{
if(_containedSolution.TotalVolume == 0)
SubstanceColor = Color.White;
Color mixColor = default;
float runningTotalQuantity = 0;
foreach (var reagent in _containedSolution)
{
runningTotalQuantity += reagent.Quantity;
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
if (mixColor == default)
mixColor = proto.SubstanceColor;
mixColor = BlendRGB(mixColor, proto.SubstanceColor, reagent.Quantity / runningTotalQuantity);
}
}
private Color BlendRGB(Color rgb1, Color rgb2, float amount)
{
var r = (float)Math.Round(rgb1.R + (rgb2.R - rgb1.R) * amount, 1);
var g = (float)Math.Round(rgb1.G + (rgb2.G - rgb1.G) * amount, 1);
var b = (float)Math.Round(rgb1.B + (rgb2.B - rgb1.B) * amount, 1);
var alpha = (float)Math.Round(rgb1.A + (rgb2.A - rgb1.A) * amount, 1);
return new Color(r, g, b, alpha);
}
/// <summary>
/// Transfers solution from the held container to the target container.
/// </summary>
@@ -121,6 +281,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
void IExamine.Examine(FormattedMessage message)
{
message.AddText(_loc.GetString("Contains:\n"));
if (ReagentList.Count == 0)
{
message.AddText("Nothing.\n");
}
foreach (var reagent in ReagentList)
{
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
@@ -256,7 +420,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
_chemistrySystem.HandleSolutionChange(Owner);
return true;
}
@@ -324,5 +488,37 @@ namespace Content.Server.GameObjects.Components.Chemistry
//Play reaction sound client-side
_audioSystem.Play("/Audio/effects/chemistry/bubbles.ogg", Owner.Transform.GridPosition);
}
/// <summary>
/// Check if the solution contains the specified reagent.
/// </summary>
/// <param name="reagentId">The reagent to check for.</param>
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
/// <returns>Return true if the solution contains the reagent.</returns>
public bool ContainsReagent(string reagentId, out int quantity)
{
foreach (var reagent in _containedSolution.Contents)
{
if (reagent.ReagentId == reagentId)
{
quantity = reagent.Quantity;
return true;
}
}
quantity = 0;
return false;
}
public string GetMajorReagentId()
{
if (_containedSolution.Contents.Count == 0)
{
return "";
}
var majorReagent = _containedSolution.Contents.OrderByDescending(reagent => reagent.Quantity).First();;
return majorReagent.ReagentId;
}
protected virtual void OnSolutionChanged() => _chemistrySystem.HandleSolutionChange(Owner);
}
}

View File

@@ -0,0 +1,80 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using ICSharpCode.SharpZipLib.Zip.Compression;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.Components.Chemistry
{
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
public override string Name => "TransformableContainer";
private SpriteSpecifier _initialSprite;
private string _initialName;
private string _initialDescription;
private SpriteComponent _sprite;
private ReagentPrototype _currentReagent;
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon");
_initialName = Owner.Name;
_initialDescription = Owner.Description;
}
public void CancelTransformation()
{
_currentReagent = null;
_sprite.LayerSetSprite(0, _initialSprite);
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
var solution = eventArgs.Owner.GetComponent<SolutionComponent>();
//Transform container into initial state when emptied
if (_currentReagent != null && solution.ReagentList.Count == 0)
{
CancelTransformation();
}
//the biggest reagent in the solution decides the appearance
var reagentId = solution.GetMajorReagentId();
//If biggest reagent didn't changed - don't change anything at all
if (_currentReagent != null && _currentReagent.ID == reagentId)
{
return;
}
//Only reagents with spritePath property can change appearance of transformable containers!
if (!string.IsNullOrWhiteSpace(reagentId) &&
_prototypeManager.TryIndex(reagentId, out ReagentPrototype proto) &&
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon");
_sprite.LayerSetSprite(0, spriteSpec);
Owner.Name = proto.Name;
Owner.Description = proto.Description;
_currentReagent = proto;
}
}
}
}

View File

@@ -47,16 +47,11 @@ namespace Content.Server.GameObjects.Components.Metabolism
serializer.DataField(ref _initialMaxVolume, "maxVolume", 250);
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal solution storage
_internalSolution = new SolutionComponent();
_internalSolution.InitializeFromPrototype();
_internalSolution.Init();
base.Startup();
_internalSolution = Owner.GetComponent<SolutionComponent>();
_internalSolution.MaxVolume = _initialMaxVolume;
_internalSolution.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
}
/// <summary>

View File

@@ -16,7 +16,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Nutrition
{
[RegisterComponent]
public class DrinkComponent : Component, IAfterAttack, IUse
public class DrinkComponent : Component, IAfterAttack, IUse, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
@@ -42,9 +42,6 @@ namespace Content.Server.GameObjects.Components.Nutrition
set => _contents.MaxVolume = value;
}
private Solution _initialContents; // This is just for loading from yaml
private int _maxVolume;
private bool _despawnOnFinish;
private bool _drinking;
@@ -63,53 +60,21 @@ namespace Content.Server.GameObjects.Components.Nutrition
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _maxVolume, "max_volume", 0);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/drink.ogg");
// E.g. cola can when done or clear bottle, whatever
// Currently this will enforce it has the same volume but this may change.
serializer.DataField(ref _despawnOnFinish, "despawn_empty", true);
// Currently this will enforce it has the same volume but this may change. - TODO: this should be implemented in a separate component
serializer.DataField(ref _despawnOnFinish, "despawn_empty", false);
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
public override void Initialize()
{
base.Initialize();
if (_contents == null)
{
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
}
else
{
_contents = Owner.AddComponent<SolutionComponent>();
//Ensure SolutionComponent supports click transferring if custom one not set
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
var pourable = Owner.AddComponent<PourableComponent>();
pourable.TransferAmount = 5;
}
}
_drinking = false;
if (_maxVolume != 0)
_contents.MaxVolume = _maxVolume;
else
_contents.MaxVolume = _initialContents.TotalVolume;
_contents.SolutionChanged += HandleSolutionChangedEvent;
}
protected override void Startup()
{
base.Startup();
if (_initialContents != null)
{
_contents.TryAddSolution(_initialContents, true, true);
}
_initialContents = null;
_contents = Owner.GetComponent<SolutionComponent>();
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
_drinking = false;
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.MaxUses, MaxVolume);
@@ -168,7 +133,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
_drinking = false;
}
Finish(user);
//Finish(user);
}
/// <summary>
@@ -177,14 +142,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
/// or convert it to another entity, like an empty variant.
/// </summary>
/// <param name="user">The entity that is using the drink</param>
/*
public void Finish(IEntity user)
{
// Drink containers are mostly transient.
// are you sure about that
if (_drinking || !_despawnOnFinish || UsesLeft() > 0)
return;
var gridPos = Owner.Transform.GridPosition;
_contents.SolutionChanged -= HandleSolutionChangedEvent;
Owner.Delete();
if (_finishPrototype == null || user == null)
@@ -205,16 +171,8 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
drinkComponent.MaxVolume = MaxVolume;
}
}
}*/
/// <summary>
/// Updates drink state when the solution is changed by something other
/// than this component. Without this some drinks won't properly delete
/// themselves without additional clicks/uses after them being emptied.
/// </summary>
private void HandleSolutionChangedEvent()
{
Finish(null);
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) { } //Finish(null);
}
}

View File

@@ -68,17 +68,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Doesn't use Owner.AddComponent<>() to avoid cross-contamination (e.g. with blood or whatever they holds other solutions)
_stomachContents = new SolutionComponent();
_stomachContents.InitializeFromPrototype();
_stomachContents = Owner.GetComponent<SolutionComponent>();
_stomachContents.MaxVolume = _initialMaxVolume;
_stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
//Ensure bloodstream in present
if (!Owner.TryGetComponent<BloodstreamComponent>(out _bloodstream))
{
Logger.Warning(_localizationManager.GetString(

View File

@@ -0,0 +1,42 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
{
/// <summary>
/// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed
/// </summary>
public interface ISolutionChange
{
/// <summary>
/// Called when solution is mixed with some other solution, or when some part of the solution is removed
/// </summary>
void SolutionChanged(SolutionChangeEventArgs eventArgs);
}
public class SolutionChangeEventArgs : EventArgs
{
public IEntity Owner { get; set; }
}
[UsedImplicitly]
public class ChemistrySystem : EntitySystem
{
public void HandleSolutionChange(IEntity owner)
{
var eventArgs = new SolutionChangeEventArgs
{
Owner = owner,
};
var solutionChangeArgs = owner.GetAllComponents<ISolutionChange>().ToList();
foreach (var solutionChangeArg in solutionChangeArgs)
{
solutionChangeArg.SolutionChanged(eventArgs);
}
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Content.Shared.Chemistry
private string _description;
private Color _substanceColor;
private List<IMetabolizable> _metabolism;
private string _spritePath;
public string ID => _id;
public string Name => _name;
@@ -29,6 +30,8 @@ namespace Content.Shared.Chemistry
//List of metabolism effects this reagent has, should really only be used server-side.
public List<IMetabolizable> Metabolism => _metabolism;
public string SpriteReplacementPath => _spritePath;
public ReagentPrototype()
{
IoCManager.InjectDependencies(this);
@@ -42,6 +45,7 @@ namespace Content.Shared.Chemistry
serializer.DataField(ref _name, "name", string.Empty);
serializer.DataField(ref _description, "desc", string.Empty);
serializer.DataField(ref _substanceColor, "color", Color.White);
serializer.DataField(ref _spritePath, "spritePath", string.Empty);
if (_moduleManager.IsServerModule)
serializer.DataField(ref _metabolism, "metabolism", new List<IMetabolizable> {new DefaultMetabolizable()});

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Chemistry
{
public class SharedSolutionComponent : Component
{
public override string Name => "Solution";
/// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
[Serializable, NetSerializable]
public class SolutionComponentState : ComponentState
{
public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { }
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{
return new SolutionComponentState();
}
/// <inheritdoc />
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if(curState == null)
return;
var compState = (SolutionComponentState)curState;
//TODO: Make me work!
}
}
}

View File

@@ -1,238 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Chemistry
{
public class SolutionComponent : Component
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[ViewVariables]
protected Solution _containedSolution = new Solution();
protected int _maxVolume;
private SolutionCaps _capabilities;
/// <summary>
/// Triggered when the solution contents change.
/// </summary>
public event Action SolutionChanged;
/// <summary>
/// The maximum volume of the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxVolume
{
get => _maxVolume;
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
}
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables]
public int CurrentVolume => _containedSolution.TotalVolume;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public int EmptyVolume => MaxVolume - CurrentVolume;
/// <summary>
/// The current blended color of all the reagents in the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Color SubstanceColor { get; private set; }
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SolutionCaps Capabilities
{
get => _capabilities;
set => _capabilities = value;
}
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
/// <summary>
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
/// </summary>
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
/// <summary>
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
/// </summary>
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
/// <summary>
/// Shortcut for Capabilities Injectable flag
/// </summary>
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
/// <summary>
/// Shortcut for Capabilities Injector flag
/// </summary>
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
/// <inheritdoc />
public override string Name => "Solution";
/// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", 0);
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
}
/// <inheritdoc />
protected override void Startup()
{
base.Startup();
RecalculateColor();
}
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
_containedSolution.RemoveAllSolution();
_containedSolution = new Solution();
}
public void RemoveAllSolution()
{
_containedSolution.RemoveAllSolution();
OnSolutionChanged();
}
public bool TryRemoveReagent(string reagentId, int quantity)
{
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
_containedSolution.RemoveReagent(reagentId, quantity);
OnSolutionChanged();
return true;
}
/// <summary>
/// Attempt to remove the specified quantity from this solution
/// </summary>
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(int quantity)
{
if (CurrentVolume == 0)
return false;
_containedSolution.RemoveSolution(quantity);
OnSolutionChanged();
return true;
}
public Solution SplitSolution(int quantity)
{
var solutionSplit = _containedSolution.SplitSolution(quantity);
OnSolutionChanged();
return solutionSplit;
}
protected void RecalculateColor()
{
if(_containedSolution.TotalVolume == 0)
SubstanceColor = Color.White;
Color mixColor = default;
float runningTotalQuantity = 0;
foreach (var reagent in _containedSolution)
{
runningTotalQuantity += reagent.Quantity;
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
if (mixColor == default)
mixColor = proto.SubstanceColor;
mixColor = BlendRGB(mixColor, proto.SubstanceColor, reagent.Quantity / runningTotalQuantity);
}
}
private Color BlendRGB(Color rgb1, Color rgb2, float amount)
{
var r = (float)Math.Round(rgb1.R + (rgb2.R - rgb1.R) * amount, 1);
var g = (float)Math.Round(rgb1.G + (rgb2.G - rgb1.G) * amount, 1);
var b = (float)Math.Round(rgb1.B + (rgb2.B - rgb1.B) * amount, 1);
var alpha = (float)Math.Round(rgb1.A + (rgb2.A - rgb1.A) * amount, 1);
return new Color(r, g, b, alpha);
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{
return new SolutionComponentState();
}
/// <inheritdoc />
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if(curState == null)
return;
var compState = (SolutionComponentState)curState;
//TODO: Make me work!
}
[Serializable, NetSerializable]
public class SolutionComponentState : ComponentState
{
public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { }
}
/// <summary>
/// Check if the solution contains the specified reagent.
/// </summary>
/// <param name="reagentId">The reagent to check for.</param>
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
/// <returns>Return true if the solution contains the reagent.</returns>
public bool ContainsReagent(string reagentId, out int quantity)
{
foreach (var reagent in _containedSolution.Contents)
{
if (reagent.ReagentId == reagentId)
{
quantity = reagent.Quantity;
return true;
}
}
quantity = 0;
return false;
}
protected virtual void OnSolutionChanged()
{
SolutionChanged?.Invoke();
}
}
}

View File

@@ -18,3 +18,7 @@
- chem.Ale
- chem.Wine
- chem.Ice
- chem.Beer
- chem.Vodka
- chem.Cognac
- chem.Kahlua

View File

@@ -37,3 +37,4 @@
- chem.K
- chem.Ra
- chem.Na
- chem.U

View File

@@ -19,3 +19,4 @@
- chem.Tea
- chem.Ice
- chem.H2O
- chem.Cream

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,6 @@
description: One sip of this and you just know you're gonna have a good time.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAbsinthe
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/absinthebottle.rsi
- type: Icon
@@ -23,12 +17,6 @@
description: A bottle of 46 proof Emeraldine Melon Liquor. Sweet and light.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAlcoClear
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/alco-green.rsi
- type: Icon
@@ -40,13 +28,12 @@
name: Magm-Ale
description: A true dorf's drink of choice.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAle
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Ale
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/alebottle.rsi
- type: Icon
@@ -59,12 +46,6 @@
description: A bottle filled with nothing
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAlcoClear
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/bottleofnothing.rsi
- type: Icon
@@ -76,13 +57,12 @@
name: Cognac bottle
description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleCognac
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Cognac
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/cognacbottle.rsi
- type: Icon
@@ -95,12 +75,6 @@
description: A bottle of high quality gin, produced in the New London Space Station.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleGin
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/ginbottle.rsi
- type: Icon
@@ -113,12 +87,6 @@
description: 100 proof cinnamon schnapps, made for alcoholic teen girls on spring break.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleGoldschlager
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/goldschlagerbottle.rsi
- type: Icon
@@ -130,13 +98,12 @@
name: Kahlua bottle
description: A widely known, Mexican coffee-flavoured liqueur. In production since 1936, HONK
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleKahlua
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/kahluabottle.rsi
- type: Icon
@@ -149,12 +116,6 @@
description: Silver laced tequilla, served in space night clubs across the galaxy.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottlePatron
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/patronbottle.rsi
- type: Icon
@@ -167,12 +128,6 @@
description: What a delightful packaging for a surely high quality wine! The vintage must be amazing!
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottlePoisonWine
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/pwinebottle.rsi
- type: Icon
@@ -185,12 +140,6 @@
description: This isn't just rum, oh no. It's practically GRIFF in a bottle.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleRum
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/rumbottle.rsi
- type: Icon
@@ -203,12 +152,6 @@
description: Made from premium petroleum distillates, pure thalidomide and other fine quality ingredients!
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleTequila
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/tequillabottle.rsi
- type: Icon
@@ -221,12 +164,6 @@
description: Sweet, sweet dryness~
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleVermouth
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/vermouthbottle.rsi
- type: Icon
@@ -238,13 +175,12 @@
name: Vodka bottle
description: Aah, vodka. Prime choice of drink AND fuel by Russians worldwide.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleVodka
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Vodka
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/vodkabottle.rsi
- type: Icon
@@ -256,13 +192,12 @@
name: Uncle Git's special reserve
description: A premium single-malt whiskey, gently matured inside the tunnels of a nuclear shelter. TUNNEL WHISKEY RULES.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleWhiskey
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Whiskey
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/whiskeybottle.rsi
- type: Icon
@@ -274,13 +209,12 @@
name: Doublebearded bearded special wine bottle
description: A faint aura of unease and asspainery surrounds the bottle.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleWine
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Wine
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/winebottle.rsi
- type: Icon

View File

@@ -30,11 +30,12 @@
name: Space cola
description: A refreshing beverage.
components:
- type: Drink
- type: Solution
maxVol: 20
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- ReagentId: chem.Cola
Quantity: 20
- type: Sprite
sprite: Objects/Drinks/cola.rsi
- type: Icon
@@ -62,10 +63,6 @@
description: ''
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/ice_tea_can.rsi
- type: Icon
@@ -93,10 +90,6 @@
description: You wanted ORANGE. It gave you Lemon Lime.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/lemon-lime.rsi
- type: Icon
@@ -124,10 +117,6 @@
description: ''
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/purple_can.rsi
- type: Icon
@@ -155,10 +144,6 @@
description: Blows right through you like a space wind.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/space_mountain_wind.rsi
- type: Icon
@@ -186,10 +171,6 @@
description: Tastes like a hull breach in your mouth.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/space-up.rsi
- type: Icon
@@ -217,10 +198,6 @@
description: The taste of a star in liquid form. And, a bit of tuna...?
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/starkist.rsi
- type: Icon
@@ -248,10 +225,6 @@
description: The MBO has advised crew members that consumption of Thirteen Loko may result in seizures, blindness, drunkeness, or even death. Please Drink Responsibly.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/thirteen_loko.rsi
- type: Icon

View File

@@ -5,8 +5,11 @@
name: Base cup
abstract: true
components:
- type: Solution
maxVol: 20
- type: Pourable
transferAmount: 5
- type: Drink
max_volume: 4
despawn_empty: false
- type: Sound
- type: Sprite
@@ -20,8 +23,8 @@
name: Golden cup
description: A golden cup
components:
- type: Drink
max_volume: 10
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/golden_cup.rsi
- type: Icon
@@ -33,8 +36,8 @@
name: Insulated pitcher
description: A stainless steel insulated pitcher. Everyone's best friend in the morning.
components:
- type: Drink
max_volume: 15
- type: Solution
maxVol: 15
- type: Sprite
sprite: Objects/Drinks/pitcher.rsi
state: icon-6
@@ -52,8 +55,8 @@
name: Mug
description: A plain white mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug.rsi
state: icon-3
@@ -71,8 +74,8 @@
name: Mug Black
description: A sleek black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_black.rsi
state: icon-3
@@ -90,8 +93,8 @@
name: Mug Blue
description: A blue and black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_blue.rsi
state: icon-3
@@ -109,8 +112,8 @@
name: Mug Green
description: A pale green and pink mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_green.rsi
state: icon-3
@@ -128,8 +131,8 @@
name: Mug Heart
description: A white mug, it prominently features a red heart.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_heart.rsi
state: icon-3
@@ -147,8 +150,8 @@
name: Mug Metal
description: A metal mug. You're not sure which metal.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_metal.rsi
state: icon-3
@@ -166,8 +169,8 @@
name: Mug Moebius
description: A mug with a Moebius Laboratories logo on it. Not even your morning coffee is safe from corporate advertising.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_moebius.rsi
state: icon-3
@@ -185,8 +188,8 @@
name: "#1 mug"
description: "A white mug, it prominently features a #1."
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_one.rsi
state: icon-3
@@ -204,8 +207,8 @@
name: Mug Rainbow
description: A rainbow mug. The colors are almost as blinding as a welder.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_rainbow.rsi
state: icon-3
@@ -223,8 +226,8 @@
name: Mug Red
description: A red and black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_red.rsi
state: icon-3

View File

@@ -10,13 +10,12 @@
state: icon
- type: Icon
state: icon
- type: Solution
maxVol: 10
- type: Pourable
transferAmount: 5
- type: Drink
despawn_empty: false
max_volume: 10
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 0
# Containers
- type: entity
@@ -89,19 +88,6 @@
- type: Icon
sprite: Objects/TrashDrinks/ginbottle_empty.rsi
# Couldn't think of a nice place to put this
- type: entity
name: Empty glass
parent: DrinkBottleBase
id: DrinkEmptyGlass
components:
- type: Sprite
sprite: Objects/TrashDrinks/alebottle_empty.rsi
- type: Icon
sprite: Objects/TrashDrinks/alebottle_empty.rsi
- type: Solution
max_volume: 4
- type: entity
name: Goldschlager bottle
parent: DrinkBottleBase

View File

@@ -14,11 +14,14 @@
- type: Hunger
- type: Thirst
# Organs
- type: Stomach
maxVolume: 100
digestionDelay: 20
- type: Solution
maxVol: 250
- type: Bloodstream
maxVolume: 250
max_volume: 100
- type: Stomach
max_volume: 250
digestionDelay: 20
- type: Inventory
- type: Constructor

View File

@@ -0,0 +1,103 @@
- type: reaction
id: react.ManlyDorf
reactants:
chem.Beer:
amount: 1
chem.Ale:
amount: 2
products:
chem.ManlyDorf: 3
- type: reaction
id: react.CubaLibre
reactants:
chem.Cola:
amount: 1
chem.Rum:
amount: 2
products:
chem.CubaLibre: 3
- type: reaction
id: react.IrishCream
reactants:
chem.Cream:
amount: 1
chem.Whiskey:
amount: 2
products:
chem.IrishCream: 3
- type: reaction
id: react.IrishCoffee
reactants:
chem.IrishCream:
amount: 2
chem.Coffee:
amount: 2
products:
chem.IrishCoffee: 4
- type: reaction
id: react.IrishCarBomb
reactants:
chem.IrishCream:
amount: 1
chem.Ale:
amount: 1
products:
chem.IrishCarBomb: 2
- type: reaction
id: react.B52
reactants:
chem.IrishCarBomb:
amount: 1
chem.Kahlua:
amount: 1
chem.Cognac:
amount: 1
products:
chem.B52: 3
- type: reaction
id: react.AtomicBomb
reactants:
chem.B52:
amount: 10
chem.U:
amount: 1
products:
chem.AtomicBomb: 11
- type: reaction
id: react.WhiskeyCola
reactants:
chem.Whiskey:
amount: 2
chem.Cola:
amount: 1
products:
chem.WhiskeyCola: 3
- type: reaction
id: react.SyndicateBomb
reactants:
chem.WhiskeyCola:
amount: 1
chem.Beer:
amount: 1
products:
chem.SyndicateBomb: 2
- type: reaction
id: react.Antifreeze
reactants:
chem.Vodka:
amount: 2
chem.Cream:
amount: 1
chem.Ice:
amount: 1
products:
chem.Antifreeze: 4

View File

@@ -2,16 +2,103 @@
id: chem.Whiskey
name: Whiskey
desc: An alcoholic beverage made from fermented grain mash
spritePath: whiskeyglass.rsi
- type: reagent
id: chem.Ale
name: Ale
desc: A type of beer brewed using a warm fermentation method, resulting in a sweet, full-bodied and fruity taste.
spritePath: aleglass.rsi
- type: reagent
id: chem.Wine
name: Wine
desc: An alcoholic drink made from fermented grapes
spritePath: wineglass.rsi
- type: reagent
id: chem.Beer
name: Beer
desc: A cold pint of pale lager.
spritePath: beerglass.rsi
- type: reagent
id: chem.Vodka
name: Vodka
desc: The glass contain wodka. Xynta.
- type: reagent
id: chem.Kahlua
name: Kahlua
desc: A widely known, Mexican coffee-flavoured liqueur. In production since 1936!
spritePath: kahluaglass.rsi
- type: reagent
id: chem.Cognac
name: Cognac
desc: A sweet and strongly alcoholic drink, twice distilled and left to mature for several years. Classy as fornication.
spritePath: cognacglass.rsi
- type: reagent
id: chem.ManlyDorf
name: Manly Dorf
desc: A dwarfy concoction made from ale and beer. Intended for stout dwarves only.
spritePath: manlydorfglass.rsi
- type: reagent
id: chem.CubaLibre
name: Cuba Libre
desc: A classic mix of rum and cola.
spritePath: cubalibreglass.rsi
- type: reagent
id: chem.IrishCarBomb
name: Irish Car Bomb
desc: A troubling mixture of irish cream and ale.
spritePath: irishcarbomb.rsi
- type: reagent
id: chem.IrishCoffee
name: Irish Coffee
desc: Coffee served with irish cream. Regular cream just isn't the same!
spritePath: irishcoffeeglass.rsi
- type: reagent
id: chem.IrishCream
name: Irish Cream
desc: Whiskey-imbued cream. What else could you expect from the Irish.
spritePath: irishcreamglass.rsi
- type: reagent
id: chem.B52
name: B-52
desc: Coffee, irish cream, and cognac. You will get bombed.
spritePath: b52glass.rsi
- type: reagent
id: chem.AtomicBomb
name: Atomic Bomb
desc: Nuclear proliferation never tasted so good.
spritePath: atomicbombglass.rsi
- type: reagent
id: chem.WhiskeyCola
name: Whiskey Cola
desc: An innocent-looking mixture of cola and whiskey. Delicious.
spritePath: whiskeycolaglass.rsi
- type: reagent
id: chem.SyndicateBomb
name: Syndicate Bomb
desc: Somebody set us up the bomb!
spritePath: syndicatebomb.rsi
- type: reagent
id: chem.Antifreeze
name: Antifreeze
desc: The ultimate refreshment.
spritePath: antifreeze.rsi
- type: reagent
id: chem.Cola
@@ -36,3 +123,19 @@
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.Cream
name: Cream
desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh?
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.Milk
name: Milk
desc: An opaque white liquid produced by the mammary glands of mammals.
metabolism:
- !type:DefaultDrink
rate: 1

View File

@@ -95,3 +95,9 @@
name: Sodium
desc: A silvery-white alkali metal. Highly reactive in it's pure form.
color: "#c6c8cc"
- type: reagent
id: chem.U
name: Uranium
desc: A silvery-white metallic chemical element in the actinide series, weakly radioactive.
color: "#00ff06"