Merge branch 'master' into 2020-08-19-firelocks
This commit is contained in:
@@ -90,11 +90,11 @@ namespace Content.Client.Commands
|
||||
{
|
||||
public string Command => "mapping";
|
||||
public string Description => "Creates and teleports you to a new uninitialized map for mapping.";
|
||||
public string Help => $"Usage: {Command} <id> <mapname>";
|
||||
public string Help => $"Usage: {Command} <mapname> / {Command} <id> <mapname>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
if (args.Length == 0)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Content.Client
|
||||
factory.Register<SharedLatheComponent>();
|
||||
factory.Register<SharedSpawnPointComponent>();
|
||||
|
||||
factory.Register<SharedSolutionComponent>();
|
||||
factory.Register<SharedSolutionContainerComponent>();
|
||||
|
||||
factory.Register<SharedVendingMachineComponent>();
|
||||
factory.Register<SharedWiresComponent>();
|
||||
|
||||
@@ -131,6 +131,7 @@ namespace Content.Client.State
|
||||
_userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error.");
|
||||
Logger.Warning(e.ToString());
|
||||
_netManager.ConnectFailed -= _onConnectFailed;
|
||||
_setConnectingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (!target.TryGetComponent(out SolutionComponent drink))
|
||||
if (!target.TryGetComponent(out SolutionContainerComponent drink))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Server.AI.Utility.Considerations.Nutrition.Food
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (!target.TryGetComponent(out SolutionComponent food))
|
||||
if (!target.TryGetComponent(out SolutionContainerComponent food))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.Atmos
|
||||
|
||||
public void AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent tank))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent tank))
|
||||
return;
|
||||
|
||||
if (tank.Solution.GetReagentQuantity(_fuelType) == 0)
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Content.Server.Body
|
||||
}
|
||||
else
|
||||
{
|
||||
body.DisconnectBodyPart(hand.Value, true);
|
||||
body.RemovePart(hand.Value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Preset;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body
|
||||
@@ -13,24 +14,25 @@ namespace Content.Server.Body
|
||||
/// </summary>
|
||||
public class BodyPreset
|
||||
{
|
||||
public BodyPreset(BodyPresetPrototype data)
|
||||
{
|
||||
LoadFromPrototype(data);
|
||||
}
|
||||
[ViewVariables] public bool Initialized { get; private set; }
|
||||
|
||||
[ViewVariables] public string Name { get; private set; }
|
||||
[ViewVariables] public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps a template slot to the ID of the <see cref="IBodyPart"/> that should
|
||||
/// fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
||||
/// Maps a template slot to the ID of the <see cref="IBodyPart"/>
|
||||
/// that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> PartIDs { get; private set; }
|
||||
public Dictionary<string, string> PartIDs { get; protected set; }
|
||||
|
||||
protected virtual void LoadFromPrototype(BodyPresetPrototype data)
|
||||
public virtual void Initialize(BodyPresetPrototype prototype)
|
||||
{
|
||||
Name = data.Name;
|
||||
PartIDs = data.PartIDs;
|
||||
DebugTools.Assert(!Initialized, $"{nameof(BodyPreset)} {Name} has already been initialized!");
|
||||
|
||||
Name = prototype.Name;
|
||||
PartIDs = prototype.PartIDs;
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Template;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body
|
||||
@@ -11,35 +12,22 @@ namespace Content.Server.Body
|
||||
/// <summary>
|
||||
/// This class is a data capsule representing the standard format of a
|
||||
/// <see cref="BodyManagerComponent"/>.
|
||||
/// For instance, the "humanoid" BodyTemplate defines two arms, each connected to
|
||||
/// a torso and so on.
|
||||
/// For instance, the "humanoid" BodyTemplate defines two arms, each
|
||||
/// connected to a torso and so on.
|
||||
/// Capable of loading data from a <see cref="BodyTemplatePrototype"/>.
|
||||
/// </summary>
|
||||
public class BodyTemplate
|
||||
{
|
||||
public BodyTemplate()
|
||||
{
|
||||
Name = "empty";
|
||||
CenterSlot = "";
|
||||
Slots = new Dictionary<string, BodyPartType>();
|
||||
Connections = new Dictionary<string, List<string>>();
|
||||
Layers = new Dictionary<string, string>();
|
||||
MechanismLayers = new Dictionary<string, string>();
|
||||
}
|
||||
[ViewVariables] public bool Initialized { get; private set; }
|
||||
|
||||
public BodyTemplate(BodyTemplatePrototype data)
|
||||
{
|
||||
LoadFromPrototype(data);
|
||||
}
|
||||
|
||||
[ViewVariables] public string Name { get; private set; }
|
||||
[ViewVariables] public string Name { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the center BodyPart. For humans, this is set to "torso".
|
||||
/// Used in many calculations.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string CenterSlot { get; set; }
|
||||
public string CenterSlot { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Maps all parts on this template to its BodyPartType.
|
||||
@@ -47,7 +35,7 @@ namespace Content.Server.Body
|
||||
/// template.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, BodyPartType> Slots { get; private set; }
|
||||
public Dictionary<string, BodyPartType> Slots { get; private set; } = new Dictionary<string, BodyPartType>();
|
||||
|
||||
/// <summary>
|
||||
/// Maps limb name to the list of their connections to other limbs.
|
||||
@@ -58,13 +46,13 @@ namespace Content.Server.Body
|
||||
/// map "left arm" to "torso".
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, List<string>> Connections { get; private set; }
|
||||
public Dictionary<string, List<string>> Connections { get; private set; } = new Dictionary<string, List<string>>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> Layers { get; private set; }
|
||||
public Dictionary<string, string> Layers { get; private set; } = new Dictionary<string, string>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> MechanismLayers { get; private set; }
|
||||
public Dictionary<string, string> MechanismLayers { get; private set; } = new Dictionary<string, string>();
|
||||
|
||||
public bool Equals(BodyTemplate other)
|
||||
{
|
||||
@@ -75,7 +63,7 @@ namespace Content.Server.Body
|
||||
/// Checks if the given slot exists in this <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it does, false otherwise.</returns>
|
||||
public bool SlotExists(string slotName)
|
||||
public bool HasSlot(string slotName)
|
||||
{
|
||||
return Slots.Keys.Any(slot => slot == slotName);
|
||||
}
|
||||
@@ -132,14 +120,18 @@ namespace Content.Server.Body
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected virtual void LoadFromPrototype(BodyTemplatePrototype data)
|
||||
public virtual void Initialize(BodyTemplatePrototype prototype)
|
||||
{
|
||||
Name = data.Name;
|
||||
CenterSlot = data.CenterSlot;
|
||||
Slots = data.Slots;
|
||||
Connections = data.Connections;
|
||||
Layers = data.Layers;
|
||||
MechanismLayers = data.MechanismLayers;
|
||||
DebugTools.Assert(!Initialized, $"{nameof(BodyTemplate)} {Name} has already been initialized!");
|
||||
|
||||
Name = prototype.Name;
|
||||
CenterSlot = prototype.CenterSlot;
|
||||
Slots = new Dictionary<string, BodyPartType>(prototype.Slots);
|
||||
Connections = new Dictionary<string, List<string>>(prototype.Connections);
|
||||
Layers = new Dictionary<string, string>(prototype.Layers);
|
||||
MechanismLayers = new Dictionary<string, string>(prototype.MechanismLayers);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,16 @@ namespace Content.Server.Body.Network
|
||||
public virtual void OnRemove() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update by <see cref="BodyManagerComponent.Update"/>.
|
||||
/// Called every update by
|
||||
/// <see cref="BodyManagerComponent.PreMetabolism"/>.
|
||||
/// </summary>
|
||||
public virtual void Update(float frameTime) { }
|
||||
public virtual void PreMetabolism(float frameTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update by
|
||||
/// <see cref="BodyManagerComponent.PostMetabolism"/>.
|
||||
/// </summary>
|
||||
public virtual void PostMetabolism(float frameTime) { }
|
||||
}
|
||||
|
||||
public static class BodyNetworkExtensions
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace Content.Server.Body.Surgery
|
||||
performer.PopupMessage(Loc.GetString("Saw off the limb!"));
|
||||
|
||||
// TODO do_after: Delay
|
||||
bmTarget.DisconnectBodyPart(Parent, true);
|
||||
bmTarget.RemovePart(Parent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.Chemistry.ReactionEffects
|
||||
float floatIntensity = (float)intensity;
|
||||
if (solutionEntity == null)
|
||||
return;
|
||||
if(!solutionEntity.TryGetComponent(out SolutionComponent solution))
|
||||
if(!solutionEntity.TryGetComponent(out SolutionContainerComponent solution))
|
||||
return;
|
||||
|
||||
//Handle scaling
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var rand = random.Next(100);
|
||||
// Let's not pad ourselves on the back too hard.
|
||||
// Let's not pat ourselves on the back too hard.
|
||||
// 1% chance of zumos
|
||||
if (rand == 0) Type = PlaqueType.Zumos;
|
||||
// 9% FEA
|
||||
|
||||
@@ -0,0 +1,543 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Body;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Shared.Body.Part.Properties.Movement;
|
||||
using Content.Shared.Body.Part.Properties.Other;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
public partial class BodyManagerComponent
|
||||
{
|
||||
private readonly Dictionary<string, IBodyPart> _parts = new Dictionary<string, IBodyPart>();
|
||||
|
||||
[ViewVariables] public BodyPreset Preset { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// All <see cref="IBodyPart"></see> with <see cref="LegProperty"></see>
|
||||
/// that are currently affecting move speed, mapped to how big that leg
|
||||
/// they're on is.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<IBodyPart, float> _activeLegs = new Dictionary<IBodyPart, float>();
|
||||
|
||||
/// <summary>
|
||||
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="IBodyPart"/>
|
||||
/// object filling it (if there is one).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, IBodyPart> Parts => _parts;
|
||||
|
||||
/// <summary>
|
||||
/// List of all occupied slots in this body, taken from the values of
|
||||
/// <see cref="Parts"/>.
|
||||
/// </summary>
|
||||
public IEnumerable<string> OccupiedSlots => Parts.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// List of all slots in this body, taken from the keys of
|
||||
/// <see cref="Template"/> slots.
|
||||
/// </summary>
|
||||
public IEnumerable<string> AllSlots => Template.Slots.Keys;
|
||||
|
||||
public bool TryAddPart(string slot, DroppedBodyPartComponent part, bool force = false)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!TryAddPart(slot, part.ContainedBodyPart, force))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
part.Owner.Delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryAddPart(string slot, IBodyPart part, bool force = false)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
DebugTools.AssertNotNull(slot);
|
||||
|
||||
// Make sure the given slot exists
|
||||
if (!force)
|
||||
{
|
||||
if (!HasSlot(slot))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// And that nothing is in it
|
||||
if (!_parts.TryAdd(slot, part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parts[slot] = part;
|
||||
}
|
||||
|
||||
part.Body = this;
|
||||
|
||||
var argsAdded = new BodyPartAddedEventArgs(part, slot);
|
||||
|
||||
foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray())
|
||||
{
|
||||
component.BodyPartAdded(argsAdded);
|
||||
}
|
||||
|
||||
// TODO: Sort this duplicate out
|
||||
OnBodyChanged();
|
||||
|
||||
if (!Template.Layers.TryGetValue(slot, out var partMap) ||
|
||||
!_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
part.RSIMap = partEnum;
|
||||
|
||||
var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum);
|
||||
|
||||
SendNetworkMessage(partMessage);
|
||||
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum);
|
||||
|
||||
SendNetworkMessage(mechanismMessage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasPart(string slot)
|
||||
{
|
||||
return _parts.ContainsKey(slot);
|
||||
}
|
||||
|
||||
public void RemovePart(IBodyPart part, bool drop)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
|
||||
|
||||
if (string.IsNullOrEmpty(slotName)) return;
|
||||
|
||||
RemovePart(slotName, drop);
|
||||
}
|
||||
|
||||
public bool RemovePart(string slot, bool drop)
|
||||
{
|
||||
DebugTools.AssertNotNull(slot);
|
||||
|
||||
if (!_parts.Remove(slot, out var part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IEntity? dropped = null;
|
||||
if (drop)
|
||||
{
|
||||
part.SpawnDropped(out dropped);
|
||||
}
|
||||
|
||||
part.Body = null;
|
||||
|
||||
var args = new BodyPartRemovedEventArgs(part, slot);
|
||||
|
||||
foreach (var component in Owner.GetAllComponents<IBodyPartRemoved>())
|
||||
{
|
||||
component.BodyPartRemoved(args);
|
||||
}
|
||||
|
||||
if (part.RSIMap != null)
|
||||
{
|
||||
var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid);
|
||||
SendNetworkMessage(message);
|
||||
}
|
||||
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum);
|
||||
|
||||
SendNetworkMessage(mechanismMessage);
|
||||
}
|
||||
|
||||
if (CurrentDamageState == DamageState.Dead) return true;
|
||||
|
||||
// creadth: fall down if no legs
|
||||
if (part.PartType == BodyPartType.Leg && Parts.Count(x => x.Value.PartType == BodyPartType.Leg) == 0)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
||||
}
|
||||
|
||||
// creadth: immediately kill entity if last vital part removed
|
||||
if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
|
||||
{
|
||||
CurrentDamageState = DamageState.Dead;
|
||||
ForceHealthChangedEvent();
|
||||
}
|
||||
|
||||
if (TryGetSlotConnections(slot, out var connections))
|
||||
{
|
||||
foreach (var connectionName in connections)
|
||||
{
|
||||
if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
|
||||
{
|
||||
RemovePart(connectionName, drop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnBodyChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemovePart(IBodyPart part, [NotNullWhen(true)] out string? slot)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
|
||||
|
||||
if (pair.Equals(default))
|
||||
{
|
||||
slot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
slot = pair.Key;
|
||||
|
||||
return RemovePart(slot, false);
|
||||
}
|
||||
|
||||
public IEntity? DropPart(IBodyPart part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!_parts.ContainsValue(part))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!RemovePart(part, out var slotName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call disconnect on all limbs that were hanging off this limb.
|
||||
if (TryGetSlotConnections(slotName, out var connections))
|
||||
{
|
||||
// This loop is an unoptimized travesty. TODO: optimize to be less shit
|
||||
foreach (var connectionName in connections)
|
||||
{
|
||||
if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
|
||||
{
|
||||
RemovePart(connectionName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
part.SpawnDropped(out var dropped);
|
||||
|
||||
OnBodyChanged();
|
||||
return dropped;
|
||||
}
|
||||
|
||||
public bool ConnectedToCenter(IBodyPart part)
|
||||
{
|
||||
var searchedSlots = new List<string>();
|
||||
|
||||
return TryGetSlot(part, out var result) &&
|
||||
ConnectedToCenterPartRecursion(searchedSlots, result);
|
||||
}
|
||||
|
||||
private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName)
|
||||
{
|
||||
if (!TryGetPart(slotName, out var part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (part == CenterPart())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
searchedSlots.Add(slotName);
|
||||
|
||||
if (!TryGetSlotConnections(slotName, out var connections))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedSlots.Contains(connection) &&
|
||||
ConnectedToCenterPartRecursion(searchedSlots, connection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IBodyPart? CenterPart()
|
||||
{
|
||||
Parts.TryGetValue(Template.CenterSlot, out var center);
|
||||
return center;
|
||||
}
|
||||
|
||||
public bool HasSlot(string slot)
|
||||
{
|
||||
return Template.HasSlot(slot);
|
||||
}
|
||||
|
||||
public bool TryGetPart(string slot, [NotNullWhen(true)] out IBodyPart? result)
|
||||
{
|
||||
return Parts.TryGetValue(slot, out result);
|
||||
}
|
||||
|
||||
public bool TryGetSlot(IBodyPart part, [NotNullWhen(true)] out string? slot)
|
||||
{
|
||||
// We enforce that there is only one of each value in the dictionary,
|
||||
// so we can iterate through the dictionary values to get the key from there.
|
||||
var pair = Parts.FirstOrDefault(x => x.Value == part);
|
||||
slot = pair.Key;
|
||||
|
||||
return !pair.Equals(default);
|
||||
}
|
||||
|
||||
public bool TryGetSlotType(string slot, out BodyPartType result)
|
||||
{
|
||||
return Template.Slots.TryGetValue(slot, out result);
|
||||
}
|
||||
|
||||
public bool TryGetSlotConnections(string slot, [NotNullWhen(true)] out List<string>? connections)
|
||||
{
|
||||
return Template.Connections.TryGetValue(slot, out connections);
|
||||
}
|
||||
|
||||
public bool TryGetPartConnections(string slot, [NotNullWhen(true)] out List<IBodyPart>? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!Template.Connections.TryGetValue(slot, out var connections))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var toReturn = new List<IBodyPart>();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (TryGetPart(connection, out var partResult))
|
||||
{
|
||||
toReturn.Add(partResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (toReturn.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = toReturn;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart>? connections)
|
||||
{
|
||||
connections = null;
|
||||
|
||||
return TryGetSlot(part, out var slotName) &&
|
||||
TryGetPartConnections(slotName, out connections);
|
||||
}
|
||||
|
||||
public List<IBodyPart> GetPartsOfType(BodyPartType type)
|
||||
{
|
||||
var toReturn = new List<IBodyPart>();
|
||||
|
||||
foreach (var part in Parts.Values)
|
||||
{
|
||||
if (part.PartType == type)
|
||||
{
|
||||
toReturn.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private void CalculateSpeed()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float speedSum = 0;
|
||||
foreach (var part in _activeLegs.Keys)
|
||||
{
|
||||
if (!part.HasProperty<LegProperty>())
|
||||
{
|
||||
_activeLegs.Remove(part);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _activeLegs)
|
||||
{
|
||||
if (key.TryGetProperty(out LegProperty? leg))
|
||||
{
|
||||
// Speed of a leg = base speed * (1+log1024(leg length))
|
||||
speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
if (speedSum <= 0.001f || _activeLegs.Count <= 0)
|
||||
{
|
||||
playerMover.BaseWalkSpeed = 0.8f;
|
||||
playerMover.BaseSprintSpeed = 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extra legs stack diminishingly.
|
||||
// Final speed = speed sum/(leg count-log4(leg count))
|
||||
playerMover.BaseWalkSpeed =
|
||||
speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0));
|
||||
|
||||
playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the layout of this body changes.
|
||||
/// </summary>
|
||||
private void OnBodyChanged()
|
||||
{
|
||||
// Calculate move speed based on this body.
|
||||
if (Owner.HasComponent<MovementSpeedModifierComponent>())
|
||||
{
|
||||
_activeLegs.Clear();
|
||||
var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty)));
|
||||
|
||||
foreach (var part in legParts)
|
||||
{
|
||||
var footDistance = DistanceToNearestFoot(part);
|
||||
|
||||
if (Math.Abs(footDistance - float.MinValue) > 0.001f)
|
||||
{
|
||||
_activeLegs.Add(part, footDistance);
|
||||
}
|
||||
}
|
||||
|
||||
CalculateSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the combined length of the distance to the nearest <see cref="BodyPart"/> with a
|
||||
/// <see cref="FootProperty"/>. Returns <see cref="float.MinValue"/>
|
||||
/// if there is no foot found. If you consider a <see cref="BodyManagerComponent"/> a node map, then it will look for
|
||||
/// a foot node from the given node. It can
|
||||
/// only search through BodyParts with <see cref="ExtensionProperty"/>.
|
||||
/// </summary>
|
||||
public float DistanceToNearestFoot(IBodyPart source)
|
||||
{
|
||||
if (source.HasProperty<FootProperty>() && source.TryGetProperty<ExtensionProperty>(out var property))
|
||||
{
|
||||
return property.ReachDistance;
|
||||
}
|
||||
|
||||
return LookForFootRecursion(source, new List<BodyPart>());
|
||||
}
|
||||
|
||||
private float LookForFootRecursion(IBodyPart current,
|
||||
ICollection<BodyPart> searchedParts)
|
||||
{
|
||||
if (!current.TryGetProperty<ExtensionProperty>(out var extProperty))
|
||||
{
|
||||
return float.MinValue;
|
||||
}
|
||||
|
||||
// Get all connected parts if the current part has an extension property
|
||||
if (!TryGetPartConnections(current, out var connections))
|
||||
{
|
||||
return float.MinValue;
|
||||
}
|
||||
|
||||
// If a connected BodyPart is a foot, return this BodyPart's length.
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedParts.Contains(connection) && connection.HasProperty<FootProperty>())
|
||||
{
|
||||
return extProperty.ReachDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, get the recursion values of all connected BodyParts and
|
||||
// store them in a list.
|
||||
var distances = new List<float>();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedParts.Contains(connection))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = LookForFootRecursion(connection, searchedParts);
|
||||
|
||||
if (Math.Abs(result - float.MinValue) > 0.001f)
|
||||
{
|
||||
distances.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
// If one or more of the searches found a foot, return the smallest one
|
||||
// and add this ones length.
|
||||
if (distances.Count > 0)
|
||||
{
|
||||
return distances.Min<float>() + extProperty.ReachDistance;
|
||||
}
|
||||
|
||||
return float.MinValue;
|
||||
|
||||
// No extension property, no go.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Body;
|
||||
using Content.Server.Body.Network;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Server.Observer;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Part.Properties.Movement;
|
||||
using Content.Shared.Body.Part.Properties.Other;
|
||||
using Content.Shared.Body.Preset;
|
||||
using Content.Shared.Body.Template;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
@@ -19,11 +15,8 @@ using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -39,8 +32,9 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
[ComponentReference(typeof(ISharedBodyManagerComponent))]
|
||||
[ComponentReference(typeof(IBodyPartManager))]
|
||||
[ComponentReference(typeof(IBodyManagerComponent))]
|
||||
public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
|
||||
public partial class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
|
||||
@@ -48,41 +42,10 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
[ViewVariables] private string _presetName = default!;
|
||||
|
||||
private readonly Dictionary<string, IBodyPart> _parts = new Dictionary<string, IBodyPart>();
|
||||
|
||||
[ViewVariables] private readonly Dictionary<Type, BodyNetwork> _networks = new Dictionary<Type, BodyNetwork>();
|
||||
|
||||
/// <summary>
|
||||
/// All <see cref="IBodyPart"></see> with <see cref="LegProperty"></see>
|
||||
/// that are currently affecting move speed, mapped to how big that leg
|
||||
/// they're on is.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<IBodyPart, float> _activeLegs = new Dictionary<IBodyPart, float>();
|
||||
|
||||
[ViewVariables] public BodyTemplate Template { get; private set; } = default!;
|
||||
|
||||
[ViewVariables] public BodyPreset Preset { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="IBodyPart"/>
|
||||
/// object filling it (if there is one).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, IBodyPart> Parts => _parts;
|
||||
|
||||
/// <summary>
|
||||
/// List of all slots in this body, taken from the keys of
|
||||
/// <see cref="Template"/> slots.
|
||||
/// </summary>
|
||||
public IEnumerable<string> AllSlots => Template.Slots.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// List of all occupied slots in this body, taken from the values of
|
||||
/// <see cref="Parts"/>.
|
||||
/// </summary>
|
||||
public IEnumerable<string> OccupiedSlots => Parts.Keys;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
@@ -92,14 +55,15 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
"bodyTemplate.Humanoid",
|
||||
template =>
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype templateData))
|
||||
if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype prototype))
|
||||
{
|
||||
// Invalid prototype
|
||||
throw new InvalidOperationException(
|
||||
$"No {nameof(BodyTemplatePrototype)} found with name {template}");
|
||||
}
|
||||
|
||||
Template = new BodyTemplate(templateData);
|
||||
Template = new BodyTemplate();
|
||||
Template.Initialize(prototype);
|
||||
},
|
||||
() => Template.Name);
|
||||
|
||||
@@ -108,14 +72,15 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
"bodyPreset.BasicHuman",
|
||||
preset =>
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype presetData))
|
||||
if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype prototype))
|
||||
{
|
||||
// Invalid prototype
|
||||
throw new InvalidOperationException(
|
||||
$"No {nameof(BodyPresetPrototype)} found with name {preset}");
|
||||
}
|
||||
|
||||
Preset = new BodyPreset(presetData);
|
||||
Preset = new BodyPreset();
|
||||
Preset.Initialize(prototype);
|
||||
},
|
||||
() => _presetName);
|
||||
}
|
||||
@@ -156,7 +121,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
// Try and remove an existing limb if that exists.
|
||||
RemoveBodyPart(slotName, false);
|
||||
RemovePart(slotName, false);
|
||||
|
||||
// Add a new BodyPart with the BodyPartPrototype as a baseline to our
|
||||
// BodyComponent.
|
||||
@@ -167,21 +132,21 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
OnBodyChanged(); // TODO: Duplicate code
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current <see cref="BodyTemplate"/> to the given
|
||||
/// <see cref="BodyTemplate"/>.
|
||||
/// Attempts to keep previous <see cref="IBodyPart"/> if there is a
|
||||
/// slot for them in both <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
|
||||
{
|
||||
foreach (var part in Parts)
|
||||
{
|
||||
// TODO: Make this work.
|
||||
}
|
||||
|
||||
OnBodyChanged();
|
||||
}
|
||||
// /// <summary>
|
||||
// /// Changes the current <see cref="BodyTemplate"/> to the given
|
||||
// /// <see cref="BodyTemplate"/>.
|
||||
// /// Attempts to keep previous <see cref="IBodyPart"/> if there is a
|
||||
// /// slot for them in both <see cref="BodyTemplate"/>.
|
||||
// /// </summary>
|
||||
// public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
|
||||
// {
|
||||
// foreach (var part in Parts)
|
||||
// {
|
||||
// // TODO: Make this work.
|
||||
// }
|
||||
//
|
||||
// OnBodyChanged();
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by <see cref="BodySystem.Update"/> before
|
||||
@@ -201,7 +166,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
foreach (var network in _networks.Values)
|
||||
{
|
||||
network.Update(frameTime);
|
||||
network.PreMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,73 +188,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
foreach (var network in _networks.Values)
|
||||
{
|
||||
network.Update(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the layout of this body changes.
|
||||
/// </summary>
|
||||
private void OnBodyChanged()
|
||||
{
|
||||
// Calculate move speed based on this body.
|
||||
if (Owner.HasComponent<MovementSpeedModifierComponent>())
|
||||
{
|
||||
_activeLegs.Clear();
|
||||
var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty)));
|
||||
|
||||
foreach (var part in legParts)
|
||||
{
|
||||
var footDistance = DistanceToNearestFoot(this, part);
|
||||
|
||||
if (Math.Abs(footDistance - float.MinValue) > 0.001f)
|
||||
{
|
||||
_activeLegs.Add(part, footDistance);
|
||||
}
|
||||
}
|
||||
|
||||
CalculateSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateSpeed()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float speedSum = 0;
|
||||
foreach (var part in _activeLegs.Keys)
|
||||
{
|
||||
if (!part.HasProperty<LegProperty>())
|
||||
{
|
||||
_activeLegs.Remove(part);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _activeLegs)
|
||||
{
|
||||
if (key.TryGetProperty(out LegProperty? leg))
|
||||
{
|
||||
// Speed of a leg = base speed * (1+log1024(leg length))
|
||||
speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
if (speedSum <= 0.001f || _activeLegs.Count <= 0)
|
||||
{
|
||||
playerMover.BaseWalkSpeed = 0.8f;
|
||||
playerMover.BaseSprintSpeed = 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extra legs stack diminishingly.
|
||||
// Final speed = speed sum/(leg count-log4(leg count))
|
||||
playerMover.BaseWalkSpeed =
|
||||
speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0));
|
||||
|
||||
playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f;
|
||||
network.PostMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,482 +200,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
}
|
||||
|
||||
#region BodyPart Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recursively searches for if <see cref="target"/> is connected to
|
||||
/// the center. Not efficient (O(n^2)), but most bodies don't have a ton
|
||||
/// of <see cref="IBodyPart"/>s.
|
||||
/// </summary>
|
||||
/// <param name="target">The body part to find the center for.</param>
|
||||
/// <returns>True if it is connected to the center, false otherwise.</returns>
|
||||
private bool ConnectedToCenterPart(IBodyPart target)
|
||||
{
|
||||
var searchedSlots = new List<string>();
|
||||
|
||||
return TryGetSlotName(target, out var result) &&
|
||||
ConnectedToCenterPartRecursion(searchedSlots, result);
|
||||
}
|
||||
|
||||
private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName)
|
||||
{
|
||||
if (!TryGetBodyPart(slotName, out var part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (part == GetCenterBodyPart())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
searchedSlots.Add(slotName);
|
||||
|
||||
if (!TryGetBodyPartConnections(slotName, out List<string> connections))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedSlots.Contains(connection) &&
|
||||
ConnectedToCenterPartRecursion(searchedSlots, connection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the central <see cref="IBodyPart"/>, if any, of this body based on
|
||||
/// the <see cref="BodyTemplate"/>. For humans, this is the torso.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="BodyPart"/> if one exists, null otherwise.</returns>
|
||||
private IBodyPart? GetCenterBodyPart()
|
||||
{
|
||||
Parts.TryGetValue(Template.CenterSlot, out var center);
|
||||
return center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given slot name exists within the current
|
||||
/// <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
private bool SlotExists(string slotName)
|
||||
{
|
||||
return Template.SlotExists(slotName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="IBodyPart"/> in the given <see cref="slotName"/> if
|
||||
/// one exists.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to search in.</param>
|
||||
/// <param name="result">The body part in that slot, if any.</param>
|
||||
/// <returns>True if found, false otherwise.</returns>
|
||||
private bool TryGetBodyPart(string slotName, [NotNullWhen(true)] out IBodyPart? result)
|
||||
{
|
||||
return Parts.TryGetValue(slotName, out result!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the slotName that the given <see cref="IBodyPart"/> resides in.
|
||||
/// </summary>
|
||||
/// <param name="part">The <see cref="IBodyPart"/> to find the slot for.</param>
|
||||
/// <param name="result">The slot found, if any.</param>
|
||||
/// <returns>True if a slot was found, false otherwise</returns>
|
||||
private bool TryGetSlotName(IBodyPart part, [NotNullWhen(true)] out string result)
|
||||
{
|
||||
// We enforce that there is only one of each value in the dictionary,
|
||||
// so we can iterate through the dictionary values to get the key from there.
|
||||
var pair = Parts.FirstOrDefault(x => x.Value == part);
|
||||
result = pair.Key;
|
||||
|
||||
return !pair.Equals(default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="BodyPartType"/> in the given
|
||||
/// <see cref="slotName"/> if one exists.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to search in.</param>
|
||||
/// <param name="result">
|
||||
/// The <see cref="BodyPartType"/> of that slot, if any.
|
||||
/// </param>
|
||||
/// <returns>True if found, false otherwise.</returns>
|
||||
public bool TryGetSlotType(string slotName, out BodyPartType result)
|
||||
{
|
||||
return Template.Slots.TryGetValue(slotName, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the names of all slots connected to the given
|
||||
/// <see cref="slotName"/> for the template.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to search in.</param>
|
||||
/// <param name="connections">The connections found, if any.</param>
|
||||
/// <returns>True if the connections are found, false otherwise.</returns>
|
||||
private bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List<string> connections)
|
||||
{
|
||||
return Template.Connections.TryGetValue(slotName, out connections!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all occupied slots connected to the given slot,
|
||||
/// regardless of whether the given <see cref="slotName"/> is occupied.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot name to find connections from.</param>
|
||||
/// <param name="result">The connected body parts, if any.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if there was an error or no connected
|
||||
/// <see cref="BodyPart"/>s were found.
|
||||
/// </returns>
|
||||
public bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List<IBodyPart> result)
|
||||
{
|
||||
result = null!;
|
||||
|
||||
if (!Template.Connections.TryGetValue(slotName, out var connections))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var toReturn = new List<IBodyPart>();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (TryGetBodyPart(connection, out var bodyPartResult))
|
||||
{
|
||||
toReturn.Add(bodyPartResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (toReturn.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = toReturn;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all parts connected to the given <see cref="part"/>, regardless
|
||||
/// of whether the given <see cref="part"/> is occupied.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if successful, false if there was an error or no connected
|
||||
/// <see cref="IBodyPart"/>s were found.
|
||||
/// </returns>
|
||||
private bool TryGetBodyPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart> result)
|
||||
{
|
||||
result = null!;
|
||||
|
||||
return TryGetSlotName(part, out var slotName) &&
|
||||
TryGetBodyPartConnections(slotName, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all <see cref="IBodyPart"/> of the given type in this body.
|
||||
/// </summary>
|
||||
public List<IBodyPart> GetBodyPartsOfType(BodyPartType type)
|
||||
{
|
||||
var toReturn = new List<IBodyPart>();
|
||||
|
||||
foreach (var part in Parts.Values)
|
||||
{
|
||||
if (part.PartType == type)
|
||||
{
|
||||
toReturn.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the
|
||||
/// given slot, deleting the <see cref="IEntity"/> afterwards.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!TryAddPart(slotName, part.ContainedBodyPart))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
part.Owner.Delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
|
||||
/// off of it.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="IEntity"/> representing the dropped
|
||||
/// <see cref="IBodyPart"/>, or null if none was dropped.
|
||||
/// </returns>
|
||||
public IEntity? DropPart(IBodyPart part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!_parts.ContainsValue(part))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!RemoveBodyPart(part, out var slotName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call disconnect on all limbs that were hanging off this limb.
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections))
|
||||
{
|
||||
// This loop is an unoptimized travesty. TODO: optimize to be less shit
|
||||
foreach (var connectionName in connections)
|
||||
{
|
||||
if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result))
|
||||
{
|
||||
DisconnectBodyPart(connectionName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
part.SpawnDropped(out var dropped);
|
||||
|
||||
OnBodyChanged();
|
||||
return dropped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
|
||||
/// off of it.
|
||||
/// </summary>
|
||||
public void DisconnectBodyPart(IBodyPart part, bool dropEntity)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
|
||||
if (string.IsNullOrEmpty(slotName)) return;
|
||||
DisconnectBodyPart(slotName, dropEntity);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects a body part in the given slot if one exists,
|
||||
/// optionally dropping it.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to remove the body part from</param>
|
||||
/// <param name="dropEntity">
|
||||
/// Whether or not to drop the body part as an entity if it exists.
|
||||
/// </param>
|
||||
private void DisconnectBodyPart(string slotName, bool dropEntity)
|
||||
{
|
||||
DebugTools.AssertNotNull(slotName);
|
||||
|
||||
if (!HasPart(slotName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveBodyPart(slotName, dropEntity);
|
||||
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections))
|
||||
{
|
||||
foreach (var connectionName in connections)
|
||||
{
|
||||
if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result))
|
||||
{
|
||||
DisconnectBodyPart(connectionName, dropEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnBodyChanged();
|
||||
}
|
||||
|
||||
public bool TryAddPart(string slot, IBodyPart part, bool force = false)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
DebugTools.AssertNotNull(slot);
|
||||
|
||||
// Make sure the given slot exists
|
||||
if (!force)
|
||||
{
|
||||
if (!SlotExists(slot))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// And that nothing is in it
|
||||
if (!_parts.TryAdd(slot, part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parts[slot] = part;
|
||||
}
|
||||
|
||||
part.Body = this;
|
||||
|
||||
var argsAdded = new BodyPartAddedEventArgs(part, slot);
|
||||
|
||||
foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray())
|
||||
{
|
||||
component.BodyPartAdded(argsAdded);
|
||||
}
|
||||
|
||||
// TODO: Sort this duplicate out
|
||||
OnBodyChanged();
|
||||
|
||||
if (!Template.Layers.TryGetValue(slot, out var partMap) ||
|
||||
!_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
part.RSIMap = partEnum;
|
||||
|
||||
var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum);
|
||||
|
||||
SendNetworkMessage(partMessage);
|
||||
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum);
|
||||
|
||||
SendNetworkMessage(mechanismMessage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasPart(string slot)
|
||||
{
|
||||
return _parts.ContainsKey(slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the body part in slot <see cref="slotName"/> from this body,
|
||||
/// if one exists.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to remove it from.</param>
|
||||
/// <param name="drop">
|
||||
/// Whether or not to drop the removed <see cref="IBodyPart"/>.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private bool RemoveBodyPart(string slotName, bool drop)
|
||||
{
|
||||
DebugTools.AssertNotNull(slotName);
|
||||
|
||||
if (!_parts.Remove(slotName, out var part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IEntity? dropped = null;
|
||||
if (drop)
|
||||
{
|
||||
part.SpawnDropped(out dropped);
|
||||
}
|
||||
|
||||
part.Body = null;
|
||||
|
||||
var args = new BodyPartRemovedEventArgs(part, slotName);
|
||||
|
||||
foreach (var component in Owner.GetAllComponents<IBodyPartRemoved>())
|
||||
{
|
||||
component.BodyPartRemoved(args);
|
||||
}
|
||||
|
||||
if (part.RSIMap != null)
|
||||
{
|
||||
var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid);
|
||||
SendNetworkMessage(message);
|
||||
}
|
||||
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum);
|
||||
|
||||
SendNetworkMessage(mechanismMessage);
|
||||
}
|
||||
|
||||
if (CurrentDamageState == DamageState.Dead) return true;
|
||||
|
||||
// creadth: fall down if no legs
|
||||
if (part.PartType == BodyPartType.Leg && Parts.Count(x => x.Value.PartType == BodyPartType.Leg) == 0)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
||||
}
|
||||
|
||||
// creadth: immediately kill entity if last vital part removed
|
||||
if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
|
||||
{
|
||||
CurrentDamageState = DamageState.Dead;
|
||||
ForceHealthChangedEvent();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the body part from this body, if one exists.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to remove from this body.</param>
|
||||
/// <param name="slotName">The slot that the part was in, if any.</param>
|
||||
/// <returns>True if <see cref="part"/> was removed, false otherwise.</returns>
|
||||
private bool RemoveBodyPart(IBodyPart part, [NotNullWhen(true)] out string? slotName)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
|
||||
|
||||
if (pair.Equals(default))
|
||||
{
|
||||
slotName = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
slotName = pair.Key;
|
||||
|
||||
return RemoveBodyPart(slotName, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BodyNetwork Functions
|
||||
|
||||
private bool EnsureNetwork(BodyNetwork network)
|
||||
@@ -854,81 +277,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Recursion Functions
|
||||
|
||||
/// <summary>
|
||||
/// Returns the combined length of the distance to the nearest <see cref="BodyPart"/> with a
|
||||
/// <see cref="FootProperty"/>. Returns <see cref="float.MinValue"/>
|
||||
/// if there is no foot found. If you consider a <see cref="BodyManagerComponent"/> a node map, then it will look for
|
||||
/// a foot node from the given node. It can
|
||||
/// only search through BodyParts with <see cref="ExtensionProperty"/>.
|
||||
/// </summary>
|
||||
private static float DistanceToNearestFoot(BodyManagerComponent body, IBodyPart source)
|
||||
{
|
||||
if (source.HasProperty<FootProperty>() && source.TryGetProperty<ExtensionProperty>(out var property))
|
||||
{
|
||||
return property.ReachDistance;
|
||||
}
|
||||
|
||||
return LookForFootRecursion(body, source, new List<BodyPart>());
|
||||
}
|
||||
|
||||
// TODO: Make this not static and not keep me up at night
|
||||
private static float LookForFootRecursion(BodyManagerComponent body, IBodyPart current,
|
||||
ICollection<BodyPart> searchedParts)
|
||||
{
|
||||
if (!current.TryGetProperty<ExtensionProperty>(out var extProperty))
|
||||
{
|
||||
return float.MinValue;
|
||||
}
|
||||
|
||||
// Get all connected parts if the current part has an extension property
|
||||
if (!body.TryGetBodyPartConnections(current, out var connections))
|
||||
{
|
||||
return float.MinValue;
|
||||
}
|
||||
|
||||
// If a connected BodyPart is a foot, return this BodyPart's length.
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedParts.Contains(connection) && connection.HasProperty<FootProperty>())
|
||||
{
|
||||
return extProperty.ReachDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, get the recursion values of all connected BodyParts and
|
||||
// store them in a list.
|
||||
var distances = new List<float>();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (!searchedParts.Contains(connection))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = LookForFootRecursion(body, connection, searchedParts);
|
||||
|
||||
if (Math.Abs(result - float.MinValue) > 0.001f)
|
||||
{
|
||||
distances.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
// If one or more of the searches found a foot, return the smallest one
|
||||
// and add this ones length.
|
||||
if (distances.Count > 0)
|
||||
{
|
||||
return distances.Min<float>() + extProperty.ReachDistance;
|
||||
}
|
||||
|
||||
return float.MinValue;
|
||||
|
||||
// No extension property, no go.
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface IBodyManagerHealthChangeParams
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
|
||||
/// <summary>
|
||||
/// Internal solution for reagent storage
|
||||
/// </summary>
|
||||
[ViewVariables] private SolutionComponent _internalSolution;
|
||||
[ViewVariables] private SolutionContainerComponent _internalSolution;
|
||||
|
||||
/// <summary>
|
||||
/// Empty volume of internal solution
|
||||
@@ -31,13 +31,13 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
|
||||
|
||||
[ViewVariables] public GasMixture Air { get; set; }
|
||||
|
||||
[ViewVariables] public SolutionComponent Solution => _internalSolution;
|
||||
[ViewVariables] public SolutionContainerComponent Solution => _internalSolution;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_internalSolution = Owner.EnsureComponent<SolutionComponent>();
|
||||
_internalSolution = Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
_internalSolution.MaxVolume = _initialMaxVolume;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||
/// </summary>
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
||||
get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
||||
set
|
||||
{
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
solution.MaxVolume = value;
|
||||
}
|
||||
@@ -64,9 +64,9 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (!Owner.EnsureComponent(out SolutionComponent solution))
|
||||
if (!Owner.EnsureComponent(out SolutionContainerComponent solution))
|
||||
{
|
||||
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
|
||||
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||
}
|
||||
|
||||
solution.MaxVolume = _initialMaxVolume;
|
||||
@@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||
|
||||
public bool TryTransferSolution(Solution solution)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||
/// <param name="frameTime">The time since the last update in seconds.</param>
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent) ||
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent) ||
|
||||
!Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
|
||||
typeResult != ContainedBodyPart?.PartType ||
|
||||
!bodyManager.TryGetBodyPartConnections(slot, out var parts))
|
||||
!bodyManager.TryGetPartConnections(slot, out var parts))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
var target = (string) targetObject!;
|
||||
string message;
|
||||
|
||||
if (_bodyManagerComponentCache.InstallDroppedBodyPart(this, target))
|
||||
if (_bodyManagerComponentCache.TryAddPart(target, this))
|
||||
{
|
||||
message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart);
|
||||
}
|
||||
|
||||
@@ -6,20 +6,14 @@ using Content.Shared.GameObjects.Components.Body;
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
// TODO: Merge with ISharedBodyManagerComponent
|
||||
public interface IBodyManagerComponent : ISharedBodyManagerComponent
|
||||
public interface IBodyManagerComponent : ISharedBodyManagerComponent, IBodyPartManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="BodyTemplate"/> that this <see cref="BodyManagerComponent"/>
|
||||
/// is adhering to.
|
||||
/// The <see cref="BodyTemplate"/> that this
|
||||
/// <see cref="BodyManagerComponent"/> is adhering to.
|
||||
/// </summary>
|
||||
public BodyTemplate Template { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="BodyPreset"/> that this <see cref="BodyManagerComponent"/>
|
||||
/// is adhering to.
|
||||
/// </summary>
|
||||
public BodyPreset Preset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Installs the given <see cref="IBodyPart"/> into the given slot.
|
||||
/// </summary>
|
||||
|
||||
154
Content.Server/GameObjects/Components/Body/IBodyPartManager.cs
Normal file
154
Content.Server/GameObjects/Components/Body/IBodyPartManager.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Body;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
public interface IBodyPartManager : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="BodyPreset"/> that this
|
||||
/// <see cref="BodyManagerComponent"/>
|
||||
/// is adhering to.
|
||||
/// </summary>
|
||||
public BodyPreset Preset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the
|
||||
/// given slot, deleting the <see cref="IEntity"/> afterwards.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
bool TryAddPart(string slot, DroppedBodyPartComponent part, bool force = false);
|
||||
|
||||
bool TryAddPart(string slot, IBodyPart part, bool force = false);
|
||||
|
||||
bool HasPart(string slot);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they
|
||||
/// were hanging off of it.
|
||||
/// </summary>
|
||||
void RemovePart(IBodyPart part, bool drop);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the body part in slot <see cref="slot"/> from this body,
|
||||
/// if one exists.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to remove it from.</param>
|
||||
/// <param name="drop">
|
||||
/// Whether or not to drop the removed <see cref="IBodyPart"/>.
|
||||
/// </param>
|
||||
/// <returns>True if the part was removed, false otherwise.</returns>
|
||||
bool RemovePart(string slot, bool drop);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the body part from this body, if one exists.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to remove from this body.</param>
|
||||
/// <param name="slotName">The slot that the part was in, if any.</param>
|
||||
/// <returns>True if <see cref="part"/> was removed, false otherwise.</returns>
|
||||
bool RemovePart(IBodyPart part, [NotNullWhen(true)] out string? slotName);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
|
||||
/// off of it.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="IEntity"/> representing the dropped
|
||||
/// <see cref="IBodyPart"/>, or null if none was dropped.
|
||||
/// </returns>
|
||||
IEntity? DropPart(IBodyPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Recursively searches for if <see cref="part"/> is connected to
|
||||
/// the center.
|
||||
/// </summary>
|
||||
/// <param name="part">The body part to find the center for.</param>
|
||||
/// <returns>True if it is connected to the center, false otherwise.</returns>
|
||||
bool ConnectedToCenter(IBodyPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the central <see cref="IBodyPart"/>, if any, of this body based on
|
||||
/// the <see cref="BodyTemplate"/>. For humans, this is the torso.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="BodyPart"/> if one exists, null otherwise.</returns>
|
||||
IBodyPart? CenterPart();
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given part slot name exists within the current
|
||||
/// <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to check for.</param>
|
||||
/// <returns>True if the slot exists in this body, false otherwise.</returns>
|
||||
bool HasSlot(string slot);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="IBodyPart"/> in the given <see cref="slot"/> if
|
||||
/// one exists.
|
||||
/// </summary>
|
||||
/// <param name="slot">The part slot to search in.</param>
|
||||
/// <param name="result">The body part in that slot, if any.</param>
|
||||
/// <returns>True if found, false otherwise.</returns>
|
||||
bool TryGetPart(string slot, [NotNullWhen(true)] out IBodyPart? result);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the slotName that the given <see cref="IBodyPart"/> resides in.
|
||||
/// </summary>
|
||||
/// <param name="part">The <see cref="IBodyPart"/> to find the slot for.</param>
|
||||
/// <param name="slot">The slot found, if any.</param>
|
||||
/// <returns>True if a slot was found, false otherwise</returns>
|
||||
bool TryGetSlot(IBodyPart part, [NotNullWhen(true)] out string? slot);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="BodyPartType"/> in the given
|
||||
/// <see cref="slot"/> if one exists.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to search in.</param>
|
||||
/// <param name="result">
|
||||
/// The <see cref="BodyPartType"/> of that slot, if any.
|
||||
/// </param>
|
||||
/// <returns>True if found, false otherwise.</returns>
|
||||
bool TryGetSlotType(string slot, out BodyPartType result);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the names of all slots connected to the given
|
||||
/// <see cref="slot"/> for the template.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to search in.</param>
|
||||
/// <param name="connections">The connections found, if any.</param>
|
||||
/// <returns>True if the connections are found, false otherwise.</returns>
|
||||
bool TryGetSlotConnections(string slot, [NotNullWhen(true)] out List<string>? connections);
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all occupied slots connected to the given slot,
|
||||
/// regardless of whether the given <see cref="slot"/> is occupied.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot name to find connections from.</param>
|
||||
/// <param name="connections">The connected body parts, if any.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if the slot couldn't be found on this body.
|
||||
/// </returns>
|
||||
bool TryGetPartConnections(string slot, [NotNullWhen(true)] out List<IBodyPart>? connections);
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all parts connected to the given <see cref="part"/>, regardless
|
||||
/// of whether the given <see cref="part"/> is occupied.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to find connections from.</param>
|
||||
/// <param name="connections">The connected body parts, if any.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if the part couldn't be found on this body.
|
||||
/// </returns>
|
||||
bool TryGetPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart>? connections);
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all <see cref="IBodyPart"/> of the given type in this body.
|
||||
/// </summary>
|
||||
List<IBodyPart> GetPartsOfType(BodyPartType type);
|
||||
}
|
||||
}
|
||||
@@ -42,14 +42,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
[ViewVariables] private ContainerSlot _beakerContainer = default!;
|
||||
[ViewVariables] private string _packPrototypeId = "";
|
||||
|
||||
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
|
||||
|
||||
[ViewVariables] private bool _bufferModeTransfer = true;
|
||||
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
|
||||
[ViewVariables] private readonly SolutionComponent BufferSolution = new SolutionComponent();
|
||||
[ViewVariables] private readonly SolutionContainerComponent BufferSolution = new SolutionContainerComponent();
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key);
|
||||
|
||||
@@ -179,7 +177,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
"", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
|
||||
}
|
||||
|
||||
var solution = beaker.GetComponent<SolutionComponent>();
|
||||
var solution = beaker.GetComponent<SolutionContainerComponent>();
|
||||
return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
|
||||
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
|
||||
}
|
||||
@@ -191,7 +189,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this component contains an entity with a <see cref="SolutionComponent"/>, eject it.
|
||||
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, eject it.
|
||||
/// Tries to eject into user's hands first, then ejects onto chem master if both hands are full.
|
||||
/// </summary>
|
||||
private void TryEject(IEntity user)
|
||||
@@ -213,7 +211,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (!HasBeaker && _bufferModeTransfer) return;
|
||||
var beaker = _beakerContainer.ContainedEntity;
|
||||
var beakerSolution = beaker.GetComponent<SolutionComponent>();
|
||||
var beakerSolution = beaker.GetComponent<SolutionContainerComponent>();
|
||||
if (isBuffer)
|
||||
{
|
||||
foreach (var reagent in BufferSolution.Solution.Contents)
|
||||
@@ -283,7 +281,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
var bufferSolution = BufferSolution.Solution.SplitSolution(actualVolume);
|
||||
|
||||
bottle.TryGetComponent<SolutionComponent>(out var bottleSolution);
|
||||
bottle.TryGetComponent<SolutionContainerComponent>(out var bottleSolution);
|
||||
bottleSolution?.Solution.AddSolution(bufferSolution);
|
||||
|
||||
//Try to give them the bottle
|
||||
@@ -317,7 +315,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
var bufferSolution = BufferSolution.Solution.SplitSolution(actualVolume);
|
||||
|
||||
pill.TryGetComponent<SolutionComponent>(out var pillSolution);
|
||||
pill.TryGetComponent<SolutionContainerComponent>(out var pillSolution);
|
||||
pillSolution?.Solution.AddSolution(bufferSolution);
|
||||
|
||||
//Try to give them the bottle
|
||||
@@ -368,7 +366,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
/// <summary>
|
||||
/// Called when you click the owner entity with something in your active hand. If the entity in your hand
|
||||
/// contains a <see cref="SolutionComponent"/>, if you have hands, and if the chem master doesn't already
|
||||
/// contains a <see cref="SolutionContainerComponent"/>, if you have hands, and if the chem master doesn't already
|
||||
/// hold a container, it will be added to the chem master.
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
@@ -377,27 +375,27 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands!"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hands.GetActiveHand == null)
|
||||
{
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have nothing on your hand."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have nothing in your hand!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var activeHandEntity = hands.GetActiveHand.Owner;
|
||||
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
|
||||
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution))
|
||||
{
|
||||
if (HasBeaker)
|
||||
{
|
||||
Owner.PopupMessage(args.User, Loc.GetString("This ChemMaster already has a container in it."));
|
||||
}
|
||||
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master...
|
||||
else if (!solution.CanUseWithChemDispenser)
|
||||
{
|
||||
//If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit.
|
||||
Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the ChemMaster."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("The {0:theName} is too large for the ChemMaster!", activeHandEntity));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -407,7 +405,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
else
|
||||
{
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You can't put this in the ChemMaster."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You can't put {0:theName} in the ChemMaster!", activeHandEntity));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -61,12 +61,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
Owner.EnsureComponent<SolutionComponent>();
|
||||
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
{
|
||||
solution.Capabilities |= SolutionCaps.Injector;
|
||||
}
|
||||
var solution = Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
solution.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom;
|
||||
|
||||
// Set _toggleState based on prototype
|
||||
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
|
||||
@@ -111,30 +107,51 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
|
||||
//Make sure we have the attacking entity
|
||||
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector)
|
||||
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetEntity = eventArgs.Target;
|
||||
//Handle injecting/drawing for solutions
|
||||
if (targetEntity.TryGetComponent<SolutionComponent>(out var targetSolution) && targetSolution.Injectable)
|
||||
|
||||
// Handle injecting/drawing for solutions
|
||||
if (targetEntity.TryGetComponent<SolutionContainerComponent>(out var targetSolution))
|
||||
{
|
||||
if (_toggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
TryInject(targetSolution, eventArgs.User);
|
||||
if (solution.CanRemoveSolutions && targetSolution.CanAddSolutions)
|
||||
{
|
||||
TryInject(targetSolution, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner));
|
||||
}
|
||||
}
|
||||
else if (_toggleState == InjectorToggleMode.Draw)
|
||||
{
|
||||
TryDraw(targetSolution, eventArgs.User);
|
||||
if (targetSolution.CanRemoveSolutions && solution.CanAddSolutions)
|
||||
{
|
||||
TryDraw(targetSolution, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to draw from {0:theName}!", targetSolution.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
else //Handle injecting into bloodstream
|
||||
else // Handle injecting into bloodstream
|
||||
{
|
||||
if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
|
||||
_toggleState == InjectorToggleMode.Inject)
|
||||
if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && _toggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
if (solution.CanRemoveSolutions)
|
||||
{
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to inject {0:theName}!", targetEntity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,88 +169,91 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
|
||||
solution.CurrentVolume == 0)
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("Container full."));
|
||||
Owner.PopupMessage(user, Loc.GetString("You aren't able to inject {0:theName}!", targetBloodstream.Owner));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = solution.SplitSolution(realTransferAmount);
|
||||
|
||||
if (!targetBloodstream.TryTransferSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, targetBloodstream.Owner));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void TryInject(SolutionComponent targetSolution, IEntity user)
|
||||
private void TryInject(SolutionContainerComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
|
||||
solution.CurrentVolume == 0)
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("Container full."));
|
||||
Owner.PopupMessage(user, Loc.GetString("{0:theName} is already full!", targetSolution.Owner));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = solution.SplitSolution(realTransferAmount);
|
||||
|
||||
if (!targetSolution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("You transfter {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void TryDraw(SolutionComponent targetSolution, IEntity user)
|
||||
private void TryDraw(SolutionContainerComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
|
||||
solution.EmptyVolume == 0)
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.EmptyVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("Container empty"));
|
||||
Owner.PopupMessage(user, Loc.GetString("{0:theName} is empty!", targetSolution.Owner));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = targetSolution.SplitSolution(realTransferAmount);
|
||||
|
||||
if (!solution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
Owner.TryGetComponent(out SolutionComponent? solution);
|
||||
Owner.TryGetComponent(out SolutionContainerComponent? solution);
|
||||
|
||||
var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero;
|
||||
var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
[ViewVariables]
|
||||
private string _trashPrototype;
|
||||
[ViewVariables]
|
||||
private SolutionComponent _contents;
|
||||
private SolutionContainerComponent _contents;
|
||||
[ViewVariables]
|
||||
private ReagentUnit _transferAmount;
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_contents = Owner.GetComponent<SolutionComponent>();
|
||||
|
||||
_contents = Owner.GetComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
|
||||
@@ -48,22 +48,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
//Get target solution component
|
||||
if (!Owner.TryGetComponent<SolutionComponent>(out var targetSolution))
|
||||
if (!Owner.TryGetComponent<SolutionContainerComponent>(out var targetSolution))
|
||||
return false;
|
||||
|
||||
//Get attack solution component
|
||||
var attackEntity = eventArgs.Using;
|
||||
if (!attackEntity.TryGetComponent<SolutionComponent>(out var attackSolution))
|
||||
if (!attackEntity.TryGetComponent<SolutionContainerComponent>(out var attackSolution))
|
||||
return false;
|
||||
|
||||
// Calculate possibe solution transfer
|
||||
if (targetSolution.CanPourIn && attackSolution.CanPourOut)
|
||||
if (targetSolution.CanAddSolutions && attackSolution.CanRemoveSolutions)
|
||||
{
|
||||
// default logic (beakers and glasses)
|
||||
// transfer solution from object in hand to attacked
|
||||
return TryTransfer(eventArgs, attackSolution, targetSolution);
|
||||
}
|
||||
else if (targetSolution.CanPourOut && attackSolution.CanPourIn)
|
||||
else if (targetSolution.CanRemoveSolutions && attackSolution.CanAddSolutions)
|
||||
{
|
||||
// storage tanks and sinks logic
|
||||
// drain solution from attacked object to object in hand
|
||||
@@ -74,26 +74,38 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryTransfer(InteractUsingEventArgs eventArgs, SolutionComponent fromSolution, SolutionComponent toSolution)
|
||||
bool TryTransfer(InteractUsingEventArgs eventArgs, SolutionContainerComponent fromSolution, SolutionContainerComponent toSolution)
|
||||
{
|
||||
var fromEntity = fromSolution.Owner;
|
||||
if (!fromEntity.TryGetComponent<PourableComponent>(out var fromPourable))
|
||||
return false;
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume);
|
||||
if (realTransferAmount <= 0) //Special message if container is full
|
||||
if (!fromEntity.TryGetComponent<PourableComponent>(out var fromPourable))
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Container is full"));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume);
|
||||
|
||||
if (realTransferAmount <= 0) // Special message if container is full
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("{0:theName} is full!", toSolution.Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = fromSolution.SplitSolution(realTransferAmount);
|
||||
if (!toSolution.TryAddSolution(removedSolution))
|
||||
return false;
|
||||
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
|
||||
if (removedSolution.TotalVolume <= ReagentUnit.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!toSolution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You transfer {0}u to {1:theName}.", removedSolution.TotalVolume, toSolution.Owner));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
|
||||
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
|
||||
|
||||
[ViewVariables]
|
||||
private SolutionComponent? Solution => _beakerContainer.ContainedEntity?.GetComponent<SolutionComponent>();
|
||||
[ViewVariables] private SolutionContainerComponent? Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionContainerComponent>();
|
||||
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
|
||||
@@ -210,7 +208,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
"", Inventory, Owner.Name, null, _dispenseAmount);
|
||||
}
|
||||
|
||||
var solution = beaker.GetComponent<SolutionComponent>();
|
||||
var solution = beaker.GetComponent<SolutionContainerComponent>();
|
||||
return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
|
||||
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), _dispenseAmount);
|
||||
}
|
||||
@@ -222,7 +220,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this component contains an entity with a <see cref="SolutionComponent"/>, eject it.
|
||||
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, eject it.
|
||||
/// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full.
|
||||
/// </summary>
|
||||
private void TryEject(IEntity user)
|
||||
@@ -241,26 +239,26 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this component contains an entity with a <see cref="SolutionComponent"/>, remove all of it's reagents / solutions.
|
||||
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, remove all of it's reagents / solutions.
|
||||
/// </summary>
|
||||
private void TryClear()
|
||||
{
|
||||
if (!HasBeaker) return;
|
||||
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
|
||||
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionContainerComponent>();
|
||||
solution.RemoveAllSolution();
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this component contains an entity with a <see cref="SolutionComponent"/>, attempt to dispense the specified reagent to it.
|
||||
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, attempt to dispense the specified reagent to it.
|
||||
/// </summary>
|
||||
/// <param name="dispenseIndex">The index of the reagent in <c>Inventory</c>.</param>
|
||||
private void TryDispense(int dispenseIndex)
|
||||
{
|
||||
if (!HasBeaker) return;
|
||||
|
||||
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
|
||||
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionContainerComponent>();
|
||||
solution.TryAddReagent(Inventory[dispenseIndex].ID, _dispenseAmount, out _);
|
||||
|
||||
UpdateUserInterface();
|
||||
@@ -292,7 +290,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
/// <summary>
|
||||
/// Called when you click the owner entity with something in your active hand. If the entity in your hand
|
||||
/// contains a <see cref="SolutionComponent"/>, if you have hands, and if the dispenser doesn't already
|
||||
/// contains a <see cref="SolutionContainerComponent"/>, if you have hands, and if the dispenser doesn't already
|
||||
/// hold a container, it will be added to the dispenser.
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
@@ -312,13 +310,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
var activeHandEntity = hands.GetActiveHand.Owner;
|
||||
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
|
||||
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution))
|
||||
{
|
||||
if (HasBeaker)
|
||||
{
|
||||
Owner.PopupMessage(args.User, Loc.GetString("This dispenser already has a container in it."));
|
||||
}
|
||||
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0)
|
||||
else if ((solution.Capabilities & SolutionContainerCaps.FitsInDispenser) == 0)
|
||||
{
|
||||
//If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit.
|
||||
Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the dispenser."));
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
@@ -27,41 +28,26 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// ECS component that manages a liquid solution of reagents.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class SolutionComponent : SharedSolutionComponent, IExamine
|
||||
public class SolutionContainerComponent : SharedSolutionContainerComponent, IExamine
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private IEnumerable<ReactionPrototype> _reactions;
|
||||
private AudioSystem _audioSystem;
|
||||
private ChemistrySystem _chemistrySystem;
|
||||
|
||||
private SpriteComponent _spriteComponent;
|
||||
|
||||
private Solution _containedSolution = new Solution();
|
||||
private ReagentUnit _maxVolume;
|
||||
private SolutionCaps _capabilities;
|
||||
private string _fillInitState;
|
||||
private int _fillInitSteps;
|
||||
private string _fillPathString = "Objects/Specific/Chemistry/fillings.rsi";
|
||||
private ResourcePath _fillPath;
|
||||
private SpriteSpecifier _fillSprite;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum volume of the container.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _maxVolume;
|
||||
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
|
||||
}
|
||||
private AudioSystem _audioSystem;
|
||||
private ChemistrySystem _chemistrySystem;
|
||||
private SpriteComponent _spriteComponent;
|
||||
|
||||
/// <summary>
|
||||
/// The total volume of all the of the reagents in the container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ReagentUnit CurrentVolume => _containedSolution.TotalVolume;
|
||||
public ReagentUnit CurrentVolume => Solution.TotalVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The volume without reagents remaining in the container.
|
||||
@@ -79,49 +65,35 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// 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 SolutionContainerCaps Capabilities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The contained solution.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Solution Solution
|
||||
{
|
||||
get => _containedSolution;
|
||||
set => _containedSolution = value;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
|
||||
public Solution Solution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
|
||||
/// The maximum volume of the container.
|
||||
/// </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;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume { get; set; }
|
||||
|
||||
public bool NoExamine => (Capabilities & SolutionCaps.NoExamine) != 0;
|
||||
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
|
||||
public bool CanExamineContents => (Capabilities & SolutionContainerCaps.NoExamine) == 0;
|
||||
public bool CanUseWithChemDispenser => (Capabilities & SolutionContainerCaps.FitsInDispenser) != 0;
|
||||
public bool CanAddSolutions => (Capabilities & SolutionContainerCaps.AddTo) != 0;
|
||||
public bool CanRemoveSolutions => (Capabilities & SolutionContainerCaps.RemoveFrom) != 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _maxVolume, "maxVol", ReagentUnit.New(0));
|
||||
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
|
||||
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
|
||||
serializer.DataField(ref _fillInitState, "fillingState", "");
|
||||
serializer.DataField(this, x => MaxVolume, "maxVol", ReagentUnit.New(0));
|
||||
serializer.DataField(this, x => Solution, "contents", new Solution());
|
||||
serializer.DataField(this, x => Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom);
|
||||
serializer.DataField(ref _fillInitState, "fillingState", string.Empty);
|
||||
serializer.DataField(ref _fillInitSteps, "fillingSteps", 7);
|
||||
}
|
||||
|
||||
@@ -149,15 +121,18 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
public void RemoveAllSolution()
|
||||
{
|
||||
_containedSolution.RemoveAllSolution();
|
||||
Solution.RemoveAllSolution();
|
||||
OnSolutionChanged(false);
|
||||
}
|
||||
|
||||
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
|
||||
{
|
||||
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
|
||||
if (!ContainsReagent(reagentId, out var currentQuantity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_containedSolution.RemoveReagent(reagentId, quantity);
|
||||
Solution.RemoveReagent(reagentId, quantity);
|
||||
OnSolutionChanged(false);
|
||||
return true;
|
||||
}
|
||||
@@ -170,23 +145,25 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
public bool TryRemoveSolution(ReagentUnit quantity)
|
||||
{
|
||||
if (CurrentVolume == 0)
|
||||
{
|
||||
return false;
|
||||
|
||||
_containedSolution.RemoveSolution(quantity);
|
||||
}
|
||||
|
||||
Solution.RemoveSolution(quantity);
|
||||
OnSolutionChanged(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Solution SplitSolution(ReagentUnit quantity)
|
||||
{
|
||||
var solutionSplit = _containedSolution.SplitSolution(quantity);
|
||||
var solutionSplit = Solution.SplitSolution(quantity);
|
||||
OnSolutionChanged(false);
|
||||
return solutionSplit;
|
||||
}
|
||||
|
||||
protected void RecalculateColor()
|
||||
{
|
||||
if (_containedSolution.TotalVolume == 0)
|
||||
if (Solution.TotalVolume == 0)
|
||||
{
|
||||
SubstanceColor = Color.Transparent;
|
||||
return;
|
||||
@@ -195,16 +172,23 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
Color mixColor = default;
|
||||
var runningTotalQuantity = ReagentUnit.New(0);
|
||||
|
||||
foreach (var reagent in _containedSolution)
|
||||
foreach (var reagent in Solution)
|
||||
{
|
||||
runningTotalQuantity += reagent.Quantity;
|
||||
|
||||
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mixColor == default)
|
||||
{
|
||||
mixColor = proto.SubstanceColor;
|
||||
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor,
|
||||
(1 / runningTotalQuantity.Float()) * reagent.Quantity.Float());
|
||||
continue;
|
||||
}
|
||||
|
||||
var interpolateValue = (1 / runningTotalQuantity.Float()) * reagent.Quantity.Float();
|
||||
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue);
|
||||
}
|
||||
|
||||
SubstanceColor = mixColor;
|
||||
@@ -214,56 +198,53 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class FillTargetVerb : Verb<SolutionComponent>
|
||||
private sealed class FillTargetVerb : Verb<SolutionContainerComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
|
||||
protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!user.TryGetComponent<HandsComponent>(out var hands) ||
|
||||
hands.GetActiveHand == null ||
|
||||
hands.GetActiveHand.Owner == component.Owner ||
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var solution) ||
|
||||
!solution.CanRemoveSolutions ||
|
||||
!component.CanAddSolutions)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 &&
|
||||
(component.Capabilities & SolutionCaps.PourIn) != 0)
|
||||
{
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locHeldEntityName, locMyName);
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locHeldEntityName, locMyName);
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionComponent component)
|
||||
protected override void Activate(IEntity user, SolutionContainerComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands))
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands) || hands.GetActiveHand == null)
|
||||
{
|
||||
return;
|
||||
|
||||
if (hands.GetActiveHand == null)
|
||||
return;
|
||||
|
||||
if (!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var handSolutionComp))
|
||||
return;
|
||||
|
||||
if ((handSolutionComp.Capabilities & SolutionCaps.PourOut) == 0 || (component.Capabilities & SolutionCaps.PourIn) == 0)
|
||||
}
|
||||
|
||||
if (!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var handSolutionComp) ||
|
||||
!handSolutionComp.CanRemoveSolutions ||
|
||||
!component.CanAddSolutions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume, ReagentUnit.New(10));
|
||||
|
||||
// nothing to transfer
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
var transferSolution = handSolutionComp.SplitSolution(transferQuantity);
|
||||
component.TryAddSolution(transferSolution);
|
||||
}
|
||||
@@ -271,44 +252,37 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (NoExamine)
|
||||
if (!CanExamineContents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message.AddText(Loc.GetString("Contains:\n"));
|
||||
if (ReagentList.Count == 0)
|
||||
{
|
||||
message.AddText("Nothing.\n");
|
||||
message.AddText(Loc.GetString("It's empty."));
|
||||
}
|
||||
foreach (var reagent in ReagentList)
|
||||
else if (ReagentList.Count == 1)
|
||||
{
|
||||
var reagent = ReagentList[0];
|
||||
|
||||
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
{
|
||||
if (inDetailsRange)
|
||||
{
|
||||
message.AddText($"{proto.Name}: {reagent.Quantity}u\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
//This is trash but it shows the general idea
|
||||
var color = proto.SubstanceColor;
|
||||
var colorIsh = "Red";
|
||||
if (color.G > color.R)
|
||||
{
|
||||
colorIsh = "Green";
|
||||
}
|
||||
if (color.B > color.G && color.B > color.R)
|
||||
{
|
||||
colorIsh = "Blue";
|
||||
}
|
||||
|
||||
message.AddText(Loc.GetString("A {0} liquid\n", colorIsh));
|
||||
}
|
||||
var colorStr = $" [color={proto.GetSubstanceTextColor().ToHexNoAlpha()}]";
|
||||
message.AddText(Loc.GetString("It contains a"));
|
||||
message.AddMarkup(colorStr + Loc.GetString(proto.PhysicalDescription) + "[/color] ");
|
||||
message.AddText(Loc.GetString("substance."));
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
var reagent = ReagentList.Max();
|
||||
|
||||
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
{
|
||||
message.AddText(Loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity));
|
||||
var colorStr = $" [color={SubstanceColor.ToHexNoAlpha()}]";
|
||||
message.AddText(Loc.GetString("It contains a"));
|
||||
message.AddMarkup(colorStr + Loc.GetString(proto.PhysicalDescription) + "[/color] ");
|
||||
message.AddText(Loc.GetString("mixture of substances."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,55 +291,53 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// Transfers solution from a target container to the held container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class EmptyTargetVerb : Verb<SolutionComponent>
|
||||
private sealed class EmptyTargetVerb : Verb<SolutionContainerComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
|
||||
protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!user.TryGetComponent<HandsComponent>(out var hands) ||
|
||||
hands.GetActiveHand == null ||
|
||||
hands.GetActiveHand.Owner == component.Owner ||
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var solution) ||
|
||||
!solution.CanAddSolutions ||
|
||||
!component.CanRemoveSolutions)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 &&
|
||||
(component.Capabilities & SolutionCaps.PourOut) != 0)
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locMyName, locHeldEntityName);
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionContainerComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands) || hands.GetActiveHand == null)
|
||||
{
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locMyName, locHeldEntityName);
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands))
|
||||
return;
|
||||
|
||||
if (hands.GetActiveHand == null)
|
||||
return;
|
||||
|
||||
if(!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var handSolutionComp))
|
||||
return;
|
||||
|
||||
if ((handSolutionComp.Capabilities & SolutionCaps.PourIn) == 0 || (component.Capabilities & SolutionCaps.PourOut) == 0)
|
||||
if(!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var handSolutionComp) ||
|
||||
!handSolutionComp.CanAddSolutions ||
|
||||
!component.CanRemoveSolutions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume, ReagentUnit.New(10));
|
||||
|
||||
// pulling from an empty container, pointless to continue
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferSolution = component.SplitSolution(transferQuantity);
|
||||
handSolutionComp.TryAddSolution(transferSolution);
|
||||
@@ -401,7 +373,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
|
||||
{
|
||||
var toAcceptQuantity = MaxVolume - _containedSolution.TotalVolume;
|
||||
var toAcceptQuantity = MaxVolume - Solution.TotalVolume;
|
||||
if (quantity > toAcceptQuantity)
|
||||
{
|
||||
acceptedQuantity = toAcceptQuantity;
|
||||
@@ -412,7 +384,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
acceptedQuantity = quantity;
|
||||
}
|
||||
|
||||
_containedSolution.AddReagent(reagentId, acceptedQuantity);
|
||||
Solution.AddReagent(reagentId, acceptedQuantity);
|
||||
if (!skipColor) {
|
||||
RecalculateColor();
|
||||
}
|
||||
@@ -424,10 +396,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
public bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
|
||||
{
|
||||
if (solution.TotalVolume > (MaxVolume - _containedSolution.TotalVolume))
|
||||
if (solution.TotalVolume > (MaxVolume - Solution.TotalVolume))
|
||||
return false;
|
||||
|
||||
_containedSolution.AddSolution(solution);
|
||||
Solution.AddSolution(solution);
|
||||
if (!skipColor) {
|
||||
RecalculateColor();
|
||||
}
|
||||
@@ -487,18 +459,20 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
TryRemoveReagent(reactant.Key, amountToRemove);
|
||||
}
|
||||
}
|
||||
//Add products
|
||||
|
||||
// Add products
|
||||
foreach (var product in reaction.Products)
|
||||
{
|
||||
TryAddReagent(product.Key, product.Value * unitReactions, out var acceptedQuantity, true);
|
||||
}
|
||||
//Trigger reaction effects
|
||||
|
||||
// Trigger reaction effects
|
||||
foreach (var effect in reaction.Effects)
|
||||
{
|
||||
effect.React(Owner, unitReactions.Double());
|
||||
}
|
||||
|
||||
//Play reaction sound client-side
|
||||
// Play reaction sound client-side
|
||||
_audioSystem.PlayAtCoords("/Audio/Effects/Chemistry/bubbles.ogg", Owner.Transform.Coordinates);
|
||||
}
|
||||
|
||||
@@ -510,7 +484,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// <returns>Return true if the solution contains the reagent.</returns>
|
||||
public bool ContainsReagent(string reagentId, out ReagentUnit quantity)
|
||||
{
|
||||
foreach (var reagent in _containedSolution.Contents)
|
||||
foreach (var reagent in Solution.Contents)
|
||||
{
|
||||
if (reagent.ReagentId == reagentId)
|
||||
{
|
||||
@@ -518,43 +492,50 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
quantity = ReagentUnit.New(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetMajorReagentId()
|
||||
{
|
||||
if (_containedSolution.Contents.Count == 0)
|
||||
if (Solution.Contents.Count == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
var majorReagent = _containedSolution.Contents.OrderByDescending(reagent => reagent.Quantity).First();;
|
||||
|
||||
var majorReagent = Solution.Contents.OrderByDescending(reagent => reagent.Quantity).First();;
|
||||
return majorReagent.ReagentId;
|
||||
}
|
||||
|
||||
protected void UpdateFillIcon()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_fillInitState)) return;
|
||||
if (string.IsNullOrEmpty(_fillInitState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var percentage = (CurrentVolume / MaxVolume).Double();
|
||||
var level = ContentHelpers.RoundToLevels(percentage * 100, 100, _fillInitSteps);
|
||||
|
||||
//Transformed glass uses special fancy sprites so we don't bother
|
||||
if (level == 0 || Owner.TryGetComponent<TransformableContainerComponent>(out var transformableContainerComponent)
|
||||
&& transformableContainerComponent.Transformed)
|
||||
if (level == 0 || (Owner.TryGetComponent<TransformableContainerComponent>(out var transformComp) && transformComp.Transformed))
|
||||
{
|
||||
_spriteComponent.LayerSetColor(1, Color.Transparent);
|
||||
return;
|
||||
}
|
||||
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState+level);
|
||||
|
||||
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState + level);
|
||||
_spriteComponent.LayerSetSprite(1, _fillSprite);
|
||||
_spriteComponent.LayerSetColor(1,SubstanceColor);
|
||||
_spriteComponent.LayerSetColor(1, SubstanceColor);
|
||||
}
|
||||
|
||||
protected virtual void OnSolutionChanged(bool skipColor)
|
||||
{
|
||||
if (!skipColor)
|
||||
{
|
||||
RecalculateColor();
|
||||
}
|
||||
|
||||
UpdateFillIcon();
|
||||
_chemistrySystem.HandleSolutionChange(Owner);
|
||||
@@ -42,13 +42,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (!Owner.EnsureComponent(out SolutionComponent solution))
|
||||
if (!Owner.EnsureComponent(out SolutionContainerComponent solution))
|
||||
{
|
||||
Logger.Warning(
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||
}
|
||||
|
||||
solution.Capabilities |= SolutionCaps.FitsInDispenser;
|
||||
solution.Capabilities |= SolutionContainerCaps.FitsInDispenser;
|
||||
}
|
||||
|
||||
public void CancelTransformation()
|
||||
@@ -68,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
|
||||
{
|
||||
var solution = eventArgs.Owner.GetComponent<SolutionComponent>();
|
||||
var solution = eventArgs.Owner.GetComponent<SolutionContainerComponent>();
|
||||
//Transform container into initial state when emptied
|
||||
if (_currentReagent != null && solution.ReagentList.Count == 0)
|
||||
{
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (!Owner.EnsureComponent(out SolutionComponent _))
|
||||
if (!Owner.EnsureComponent(out SolutionContainerComponent _))
|
||||
{
|
||||
Logger.Warning(
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent contents))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent contents))
|
||||
return;
|
||||
|
||||
if (!_running)
|
||||
@@ -94,7 +94,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Owner.TryGetComponent(out SolutionComponent contents))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent contents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,17 +24,17 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
||||
get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
||||
set
|
||||
{
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
solution.MaxVolume = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionComponent? solution)
|
||||
public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionContainerComponent? solution)
|
||||
? solution.CurrentVolume
|
||||
: ReagentUnit.Zero;
|
||||
|
||||
@@ -50,12 +50,12 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponent<SolutionComponent>();
|
||||
Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
private bool TryGiveToMop(MopComponent mopComponent)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? contents))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? contents))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class CanSpillComponent : Component
|
||||
{
|
||||
public override string Name => "CanSpill";
|
||||
// TODO: If the Owner doesn't have a SolutionComponent straight up just have this remove itself?
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class FillTargetVerb : Verb<CanSpillComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, CanSpillComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!component.Owner.TryGetComponent(out SolutionComponent solutionComponent))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Spill liquid");
|
||||
data.Visibility = solutionComponent.CurrentVolume > ReagentUnit.Zero
|
||||
? VerbVisibility.Visible
|
||||
: VerbVisibility.Disabled;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, CanSpillComponent component)
|
||||
{
|
||||
var solutionComponent = component.Owner.GetComponent<SolutionComponent>();
|
||||
// Need this as when we split the component's owner may be deleted
|
||||
var entityLocation = component.Owner.Transform.Coordinates;
|
||||
var solution = solutionComponent.SplitSolution(solutionComponent.CurrentVolume);
|
||||
solution.SpillAt(entityLocation, "PuddleSmear");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,14 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
public override string Name => "Mop";
|
||||
|
||||
public SolutionComponent? Contents => Owner.GetComponentOrNull<SolutionComponent>();
|
||||
public SolutionContainerComponent? Contents => Owner.GetComponentOrNull<SolutionContainerComponent>();
|
||||
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => Owner.GetComponentOrNull<SolutionComponent>()?.MaxVolume ?? ReagentUnit.Zero;
|
||||
get => Owner.GetComponentOrNull<SolutionContainerComponent>()?.MaxVolume ?? ReagentUnit.Zero;
|
||||
set
|
||||
{
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
solution.MaxVolume = value;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
}
|
||||
|
||||
public ReagentUnit CurrentVolume =>
|
||||
Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
|
||||
Owner.GetComponentOrNull<SolutionContainerComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
|
||||
|
||||
// Currently there's a separate amount for pickup and dropoff so
|
||||
// Picking up a puddle requires multiple clicks
|
||||
@@ -60,15 +60,15 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (!Owner.EnsureComponent(out SolutionComponent _))
|
||||
if (!Owner.EnsureComponent(out SolutionContainerComponent _))
|
||||
{
|
||||
Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
|
||||
Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||
}
|
||||
}
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? contents)) return;
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) return;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
|
||||
if (CurrentVolume <= 0)
|
||||
@@ -76,7 +76,6 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
return;
|
||||
}
|
||||
|
||||
//Solution solution;
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
// Drop the liquid on the mop on to the ground
|
||||
@@ -98,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
|
||||
if (transferAmount == 0)
|
||||
{
|
||||
if(puddleComponent.EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent.
|
||||
if (puddleComponent.EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent.
|
||||
{
|
||||
puddleComponent.Owner.Delete();
|
||||
transferAmount = ReagentUnit.Min(ReagentUnit.New(5), CurrentVolume);
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
private ReagentUnit _overflowVolume;
|
||||
private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume;
|
||||
|
||||
private SolutionComponent _contents;
|
||||
private SolutionContainerComponent _contents;
|
||||
public bool EmptyHolder => _contents.ReagentList.Count == 0;
|
||||
private int _spriteVariants;
|
||||
// Whether the underlying solution color should be used
|
||||
@@ -118,13 +118,13 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent solutionComponent))
|
||||
{
|
||||
_contents = solutionComponent;
|
||||
}
|
||||
else
|
||||
{
|
||||
_contents = Owner.AddComponent<SolutionComponent>();
|
||||
_contents = Owner.AddComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
_snapGrid = Owner.EnsureComponent<SnapGridComponent>();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class SpillableComponent : Component
|
||||
{
|
||||
public override string Name => "Spillable";
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from the held container to the floor.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class SpillTargetVerb : Verb<SpillableComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, SpillableComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!component.Owner.TryGetComponent(out SolutionContainerComponent solutionComponent) ||
|
||||
!solutionComponent.CanRemoveSolutions)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Spill liquid");
|
||||
data.Visibility = solutionComponent.CurrentVolume > ReagentUnit.Zero ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SpillableComponent component)
|
||||
{
|
||||
if (component.Owner.TryGetComponent<SolutionContainerComponent>(out var solutionComponent))
|
||||
{
|
||||
if (!solutionComponent.CanRemoveSolutions)
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("You can't pour anything from {0:theName}!", component.Owner));
|
||||
}
|
||||
|
||||
if (solutionComponent.CurrentVolume.Float() <= 0)
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("{0:theName} is empty!", component.Owner));
|
||||
}
|
||||
|
||||
// Need this as when we split the component's owner may be deleted
|
||||
var entityLocation = component.Owner.Transform.Coordinates;
|
||||
var solution = solutionComponent.SplitSolution(solutionComponent.CurrentVolume);
|
||||
solution.SpillAt(entityLocation, "PuddleSmear");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,16 +45,16 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
set => _sprayVelocity = value;
|
||||
}
|
||||
|
||||
public ReagentUnit CurrentVolume => Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
|
||||
public ReagentUnit CurrentVolume => Owner.GetComponentOrNull<SolutionContainerComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (!Owner.EnsureComponent(out SolutionComponent _))
|
||||
if (!Owner.EnsureComponent(out SolutionContainerComponent _))
|
||||
{
|
||||
Logger.Warning(
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
|
||||
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
if (eventArgs.ClickLocation.GetGridId(_serverEntityManager) != playerPos.GetGridId(_serverEntityManager))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out SolutionComponent contents))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent contents))
|
||||
return;
|
||||
|
||||
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
|
||||
|
||||
@@ -224,8 +224,10 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (!interactionSystem.TryDroppedInteraction(Owner, item.Owner))
|
||||
return false;
|
||||
}
|
||||
|
||||
interactionSystem.DroppedInteraction(Owner, item.Owner);
|
||||
else
|
||||
{
|
||||
interactionSystem.DroppedInteraction(Owner, item.Owner);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -248,7 +250,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true)
|
||||
{
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot) || hand?.Entity == null)
|
||||
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -260,12 +262,17 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DroppedInteraction(item, doMobChecks))
|
||||
if (!DroppedInteraction(item, false))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
item.Owner.Transform.Coordinates = coords;
|
||||
|
||||
if (item.Owner.TryGetComponent<SpriteComponent>(out var spriteComponent))
|
||||
{
|
||||
spriteComponent.RenderOrder = item.Owner.EntityManager.CurrentTick.Value;
|
||||
}
|
||||
|
||||
if (ContainerHelpers.TryGetContainer(Owner, out var container))
|
||||
{
|
||||
container.Insert(item.Owner);
|
||||
@@ -294,39 +301,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
public bool Drop(string slot, bool mobChecks = true)
|
||||
{
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot, mobChecks) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!DroppedInteraction(item, mobChecks))
|
||||
return false;
|
||||
|
||||
if (!hand.Container.Remove(hand.Entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
item.RemovedFromSlot();
|
||||
item.Owner.Transform.Coordinates = Owner.Transform.Coordinates;
|
||||
|
||||
if (item.Owner.TryGetComponent<SpriteComponent>(out var spriteComponent))
|
||||
{
|
||||
spriteComponent.RenderOrder = item.Owner.EntityManager.CurrentTick.Value;
|
||||
}
|
||||
|
||||
if (ContainerHelpers.TryGetContainer(Owner, out var container))
|
||||
{
|
||||
container.Insert(item.Owner);
|
||||
}
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
return true;
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks);
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, bool mobChecks = true)
|
||||
@@ -341,7 +316,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, mobChecks);
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true)
|
||||
@@ -357,16 +332,11 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot) || hand?.Entity == null)
|
||||
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!DroppedInteraction(item, doMobChecks))
|
||||
return false;
|
||||
|
||||
if (!hand.Container.CanRemove(hand.Entity))
|
||||
{
|
||||
return false;
|
||||
@@ -377,11 +347,16 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!hand.Container.Remove(hand.Entity))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (!DroppedInteraction(item, doMobChecks))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
|
||||
if (!targetContainer.Insert(item.Owner))
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
private bool _welderLit;
|
||||
private WelderSystem _welderSystem = default!;
|
||||
private SpriteComponent? _spriteComponent;
|
||||
private SolutionComponent? _solutionComponent;
|
||||
private SolutionContainerComponent? _solutionComponent;
|
||||
private PointLightComponent? _pointLightComponent;
|
||||
|
||||
public string? WeldSoundCollection { get; set; }
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
private uint _currentCookTimerTime = 1;
|
||||
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
private bool _hasContents => Owner.TryGetComponent(out SolutionComponent? solution) && (solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0);
|
||||
private bool _hasContents => Owner.TryGetComponent(out SolutionContainerComponent? solution) && (solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0);
|
||||
private bool _uiDirty = true;
|
||||
private bool _lostPower = false;
|
||||
private int _currentCookTimeButtonIndex = 0;
|
||||
@@ -88,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponent<SolutionComponent>();
|
||||
Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
|
||||
_storage = ContainerManagerComponent.Ensure<Container>("microwave_entity_container", Owner, out var existed);
|
||||
_audioSystem = EntitySystem.Get<AudioSystem>();
|
||||
@@ -165,7 +165,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
_uiDirty = true;
|
||||
}
|
||||
|
||||
if (_uiDirty && Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (_uiDirty && Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
UserInterface?.SetState(new MicrowaveUpdateUserInterfaceState
|
||||
(
|
||||
@@ -216,13 +216,13 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
if (itemEntity.TryGetComponent<PourableComponent>(out var attackPourable))
|
||||
{
|
||||
if (!itemEntity.TryGetComponent<SolutionComponent>(out var attackSolution)
|
||||
|| !attackSolution.CanPourOut)
|
||||
if (!itemEntity.TryGetComponent<SolutionContainerComponent>(out var attackSolution)
|
||||
|| !attackSolution.CanRemoveSolutions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -355,7 +355,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
private void VaporizeReagents()
|
||||
{
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
solution.RemoveAllSolution();
|
||||
}
|
||||
@@ -363,7 +363,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
private void VaporizeReagentQuantity(Solution.ReagentQuantity reagentQuantity)
|
||||
{
|
||||
if (Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
solution?.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity);
|
||||
}
|
||||
@@ -399,7 +399,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
private void SubtractContents(FoodRecipePrototype recipe)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -434,7 +434,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
private MicrowaveSuccessState CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary<string,int> solids)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return MicrowaveSuccessState.RecipeFail;
|
||||
}
|
||||
@@ -479,7 +479,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
var headCount = 0;
|
||||
if (victim.TryGetComponent<BodyManagerComponent>(out var bodyManagerComponent))
|
||||
{
|
||||
var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
|
||||
var heads = bodyManagerComponent.GetPartsOfType(BodyPartType.Head);
|
||||
foreach (var head in heads)
|
||||
{
|
||||
var droppedHead = bodyManagerComponent.DropPart(head);
|
||||
|
||||
@@ -101,8 +101,8 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
|
||||
var bodyManager = user.GetComponent<BodyManagerComponent>();
|
||||
|
||||
if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
|
||||
bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
|
||||
if (bodyManager.GetPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
|
||||
bodyManager.GetPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
|
||||
{
|
||||
reason = Loc.GetString("You are unable to climb!");
|
||||
return false;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
public override string Name => "Drink";
|
||||
|
||||
[ViewVariables]
|
||||
private SolutionComponent _contents;
|
||||
private SolutionContainerComponent _contents;
|
||||
[ViewVariables]
|
||||
private string _useSound;
|
||||
[ViewVariables]
|
||||
@@ -56,9 +56,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _useSound, "useSound", "/Audio/Items/drink.ogg");
|
||||
serializer.DataField(ref _defaultToOpened, "isOpen", false); //For things like cups of coffee.
|
||||
serializer.DataField(ref _soundCollection, "openSounds","canOpenSounds");
|
||||
serializer.DataField(ref _pressurized, "pressurized",false);
|
||||
serializer.DataField(ref _defaultToOpened, "isOpen", false); // For things like cups of coffee.
|
||||
serializer.DataField(ref _soundCollection, "openSounds", "canOpenSounds");
|
||||
serializer.DataField(ref _pressurized, "pressurized", false);
|
||||
serializer.DataField(ref _burstSound, "burstSound", "/Audio/Effects/flash_bang.ogg");
|
||||
}
|
||||
|
||||
@@ -66,14 +66,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.TryGetComponent(out _appearanceComponent);
|
||||
if(!Owner.TryGetComponent(out _contents))
|
||||
|
||||
if (!Owner.TryGetComponent(out _contents))
|
||||
{
|
||||
_contents = Owner.AddComponent<SolutionComponent>();
|
||||
_contents = Owner.AddComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
_contents.Capabilities = SolutionCaps.PourIn
|
||||
| SolutionCaps.PourOut
|
||||
| SolutionCaps.Injectable;
|
||||
_contents.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom;
|
||||
Opened = _defaultToOpened;
|
||||
UpdateAppearance();
|
||||
}
|
||||
@@ -83,11 +82,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.Visual, _contents.CurrentVolume.Float());
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs args)
|
||||
{
|
||||
if (!Opened)
|
||||
@@ -100,13 +99,20 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
Opened = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_contents.CurrentVolume.Float() <= 0)
|
||||
{
|
||||
args.User.PopupMessage(Loc.GetString("{0:theName} is empty!", Owner));
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryUseDrink(args.User);
|
||||
}
|
||||
|
||||
//Force feeding a drink to someone.
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryUseDrink(eventArgs.Target);
|
||||
TryUseDrink(eventArgs.Target, forced: true);
|
||||
}
|
||||
|
||||
public void Examine(FormattedMessage message, bool inDetailsRange)
|
||||
@@ -118,25 +124,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
var color = Empty ? "gray" : "yellow";
|
||||
var openedText = Loc.GetString(Empty ? "Empty" : "Opened");
|
||||
message.AddMarkup(Loc.GetString("[color={0}]{1}[/color]", color, openedText));
|
||||
|
||||
}
|
||||
|
||||
private bool TryUseDrink(IEntity target)
|
||||
private bool TryUseDrink(IEntity target, bool forced = false)
|
||||
{
|
||||
if (target == null)
|
||||
if (target == null || !_contents.CanRemoveSolutions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Opened)
|
||||
{
|
||||
target.PopupMessage(Loc.GetString("Open it first!"));
|
||||
target.PopupMessage(Loc.GetString("Open {0:theName} first!", Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_contents.CurrentVolume.Float() <= 0)
|
||||
{
|
||||
target.PopupMessage(Loc.GetString("It's empty!"));
|
||||
if (!forced)
|
||||
{
|
||||
target.PopupMessage(Loc.GetString("{0:theName} is empty!", Owner));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -147,18 +156,23 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
var transferAmount = ReagentUnit.Min(TransferAmount, _contents.CurrentVolume);
|
||||
var split = _contents.SplitSolution(transferAmount);
|
||||
|
||||
if (stomachComponent.TryTransferSolution(split))
|
||||
{
|
||||
if (_useSound == null) return false;
|
||||
if (_useSound == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f));
|
||||
target.PopupMessage(Loc.GetString("Slurp"));
|
||||
UpdateAppearance();
|
||||
return true;
|
||||
}
|
||||
|
||||
//Stomach was full or can't handle whatever solution we have.
|
||||
// Stomach was full or can't handle whatever solution we have.
|
||||
_contents.TryAddSolution(split);
|
||||
target.PopupMessage(Loc.GetString("You've had enough {0}!", Owner.Name));
|
||||
target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -167,7 +181,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
if (_pressurized &&
|
||||
!Opened &&
|
||||
_random.Prob(0.25f) &&
|
||||
Owner.TryGetComponent(out SolutionComponent component))
|
||||
Owner.TryGetComponent(out SolutionContainerComponent component))
|
||||
{
|
||||
Opened = true;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponent<SolutionComponent>();
|
||||
Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
@@ -110,7 +110,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
public virtual bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionComponent? solution))
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Maps;
|
||||
@@ -321,7 +322,7 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
public string Command => "mapping";
|
||||
public string Description => "Creates and teleports you to a new uninitialized map for mapping.";
|
||||
public string Help => $"Usage: {Command} <id> <mapname>";
|
||||
public string Help => $"Usage: {Command} <mapname> / {Command} <id> <mapname>";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||
{
|
||||
@@ -331,18 +332,45 @@ namespace Content.Server.GameTicking
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length != 2)
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
int mapId;
|
||||
string mapName;
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
shell.SendText(player, Help);
|
||||
return;
|
||||
case 1:
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.SendText(player, "The map name argument cannot be omitted if you have no entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
mapId = (int) mapManager.NextMapId();
|
||||
mapName = args[0];
|
||||
break;
|
||||
case 2:
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
shell.SendText(player, $"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
mapId = id;
|
||||
mapName = args[1];
|
||||
break;
|
||||
default:
|
||||
shell.SendText(player, Help);
|
||||
return;
|
||||
}
|
||||
|
||||
shell.ExecuteCommand(player, $"addmap {args[0]} false");
|
||||
shell.ExecuteCommand(player, $"loadbp {args[0]} \"{CommandParsing.Escape(args[1])}\"");
|
||||
shell.ExecuteCommand(player, $"addmap {mapId} false");
|
||||
shell.ExecuteCommand(player, $"loadbp {mapId} \"{CommandParsing.Escape(mapName)}\"");
|
||||
shell.ExecuteCommand(player, $"aghost");
|
||||
shell.ExecuteCommand(player, $"tp 0 0 {args[0]}");
|
||||
shell.ExecuteCommand(player, $"tp 0 0 {mapId}");
|
||||
|
||||
shell.SendText(player, $"Created unloaded map from file {args[1]} with id {args[0]}. Use \"savebp 4 foo.yml\" to save it.");
|
||||
var newGridId = mapManager.GetAllGrids().Max(g => (int) g.Index);
|
||||
|
||||
shell.SendText(player, $"Created unloaded map from file {mapName} with id {mapId}. Use \"savebp {newGridId} foo.yml\" to save the new grid as a map.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using System;
|
||||
|
||||
namespace Content.Shared.Chemistry
|
||||
{
|
||||
@@ -19,6 +20,7 @@ namespace Content.Shared.Chemistry
|
||||
private string _id;
|
||||
private string _name;
|
||||
private string _description;
|
||||
private string _physicalDescription;
|
||||
private Color _substanceColor;
|
||||
private List<IMetabolizable> _metabolism;
|
||||
private string _spritePath;
|
||||
@@ -26,7 +28,9 @@ namespace Content.Shared.Chemistry
|
||||
public string ID => _id;
|
||||
public string Name => _name;
|
||||
public string Description => _description;
|
||||
public string PhysicalDescription => _physicalDescription;
|
||||
public Color SubstanceColor => _substanceColor;
|
||||
|
||||
//List of metabolism effects this reagent has, should really only be used server-side.
|
||||
public List<IMetabolizable> Metabolism => _metabolism;
|
||||
public string SpriteReplacementPath => _spritePath;
|
||||
@@ -43,13 +47,36 @@ namespace Content.Shared.Chemistry
|
||||
serializer.DataField(ref _id, "id", string.Empty);
|
||||
serializer.DataField(ref _name, "name", string.Empty);
|
||||
serializer.DataField(ref _description, "desc", string.Empty);
|
||||
serializer.DataField(ref _physicalDescription, "physicalDesc", 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()});
|
||||
{
|
||||
serializer.DataField(ref _metabolism, "metabolism", new List<IMetabolizable> { new DefaultMetabolizable() });
|
||||
}
|
||||
else
|
||||
{
|
||||
_metabolism = new List<IMetabolizable> { new DefaultMetabolizable() };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the substance color is too dark we user a lighter version to make the text color readable when the user examines a solution.
|
||||
/// </summary>
|
||||
public Color GetSubstanceTextColor()
|
||||
{
|
||||
var highestValue = MathF.Max(SubstanceColor.R, MathF.Max(SubstanceColor.G, SubstanceColor.B));
|
||||
var difference = 0.5f - highestValue;
|
||||
|
||||
if (difference > 0f)
|
||||
{
|
||||
return new Color(SubstanceColor.R + difference,
|
||||
SubstanceColor.G + difference,
|
||||
SubstanceColor.B + difference);
|
||||
}
|
||||
|
||||
return SubstanceColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace Content.Shared.Chemistry
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ReagentQuantity
|
||||
public readonly struct ReagentQuantity: IComparable<ReagentQuantity>
|
||||
{
|
||||
public readonly string ReagentId;
|
||||
public readonly ReagentUnit Quantity;
|
||||
@@ -262,6 +262,8 @@ namespace Content.Shared.Chemistry
|
||||
{
|
||||
return $"{ReagentId}:{Quantity}";
|
||||
}
|
||||
|
||||
public int CompareTo(ReagentQuantity other) { return Quantity.Float().CompareTo(other.Quantity.Float()); }
|
||||
}
|
||||
|
||||
#region Enumeration
|
||||
|
||||
@@ -8,15 +8,19 @@ namespace Content.Shared.Chemistry
|
||||
/// </summary>
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum SolutionCaps
|
||||
public enum SolutionContainerCaps
|
||||
{
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
PourIn = 1,
|
||||
PourOut = 2,
|
||||
/// <summary>
|
||||
/// Can solutions be added into the container?
|
||||
/// </summary>
|
||||
AddTo = 1,
|
||||
|
||||
Injector = 4,
|
||||
Injectable = 8,
|
||||
/// <summary>
|
||||
/// Can solutions be removed from the container?
|
||||
/// </summary>
|
||||
RemoveFrom = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allows the container to be placed in a <c>ReagentDispenserComponent</c>.
|
||||
@@ -24,8 +28,11 @@ namespace Content.Shared.Chemistry
|
||||
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
|
||||
/// since they can't be placed directly in them.</para>
|
||||
/// </summary>
|
||||
FitsInDispenser = 16,
|
||||
FitsInDispenser = 4,
|
||||
|
||||
NoExamine = 32,
|
||||
/// <summary>
|
||||
/// Can people examine the solution in the container or is it impossible to see?
|
||||
/// </summary>
|
||||
NoExamine = 8,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
{
|
||||
public class SharedSolutionComponent : Component
|
||||
public class SharedSolutionContainerComponent : Component
|
||||
{
|
||||
public override string Name => "Solution";
|
||||
public override string Name => "SolutionContainer";
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
|
||||
@@ -28,13 +28,13 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if(curState == null)
|
||||
if (curState == null)
|
||||
{
|
||||
return;
|
||||
|
||||
var compState = (SolutionComponentState)curState;
|
||||
|
||||
//TODO: Make me work!
|
||||
}
|
||||
|
||||
// var compState = (SolutionComponentState)curState;
|
||||
// Is there anything we even need to sync with client?
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -580,8 +580,8 @@ entities:
|
||||
pos: -15.694785,24.608267
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: PourIn, PourOut, Injectable
|
||||
type: Solution
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- anchored: False
|
||||
type: Collidable
|
||||
- uid: 60
|
||||
@@ -718,8 +718,8 @@ entities:
|
||||
pos: -3.470539,16.956116
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: PourIn, PourOut, Injectable
|
||||
type: Solution
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- anchored: False
|
||||
type: Collidable
|
||||
- uid: 74
|
||||
@@ -1037,8 +1037,8 @@ entities:
|
||||
- parent: 857
|
||||
pos: 8.661116,25.513401
|
||||
type: Transform
|
||||
- caps: PourIn, PourOut, Injectable
|
||||
type: Solution
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- anchored: False
|
||||
type: Collidable
|
||||
- uid: 114
|
||||
@@ -22848,7 +22848,7 @@ entities:
|
||||
rot: 3.141592653589793 rad
|
||||
type: Transform
|
||||
- fillingSteps: 0
|
||||
type: Solution
|
||||
type: SolutionContainer
|
||||
- anchored: False
|
||||
type: Collidable
|
||||
- uid: 1219
|
||||
@@ -32479,8 +32479,8 @@ entities:
|
||||
pos: -8.476567,-17.420076
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: PourIn, PourOut, Injectable
|
||||
type: Solution
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- anchored: False
|
||||
type: Collidable
|
||||
- uid: 2454
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
- type: entity
|
||||
parent: GlovesBase
|
||||
id: GlovesGray
|
||||
name: gray gloves
|
||||
description: Regular gray gloves that do not keep you from frying.
|
||||
name: grey gloves
|
||||
description: Regular grey gloves that do not keep you from frying.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Gloves/gray.rsi
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
parent: HatBase
|
||||
id: HatMouseGray
|
||||
name: mouse gray
|
||||
description: This is a head of a gray mouse. Squeak!
|
||||
description: This is a head of a grey mouse. Squeak!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Head/mouse_gray.rsi
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
- type: Microwave
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: 1
|
||||
caps: AddTo
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: MicrowaveVisualizer
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
heavyImpactRange: 2
|
||||
lightImpactRange: 6
|
||||
flashRange: 5
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
Anchored: false
|
||||
- type: Destructible
|
||||
deadThreshold: 10
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 1500
|
||||
caps: 2
|
||||
caps: RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 100.0
|
||||
placement:
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
id: WaterTankFull
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.H2O
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
- type: Hunger
|
||||
- type: Thirst
|
||||
# Organs
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 250
|
||||
caps: 32
|
||||
caps: AddTo, RemoveFrom, NoExamine
|
||||
- type: Bloodstream
|
||||
max_volume: 100
|
||||
- type: Stomach
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
id: DrinkBase
|
||||
abstract: true
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
@@ -15,7 +16,7 @@
|
||||
state: icon
|
||||
- type: Icon
|
||||
state: icon
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
parent: DrinkBase
|
||||
@@ -35,10 +36,10 @@
|
||||
sprite: Objects/Consumable/Drinks/glass_clear.rsi
|
||||
- type: Icon
|
||||
sprite: Objects/Consumable/Drinks/glass_clear.rsi
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
fillingState: glass
|
||||
maxVol: 50
|
||||
caps: 16
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: TransformableContainer
|
||||
@@ -47,9 +48,9 @@
|
||||
parent: DrinkGlassBase
|
||||
id: DrinkAleglass
|
||||
name: ale
|
||||
description: A dark alchoholic beverage made by malted barley and yeast.
|
||||
description: A dark alchoholic beverage made with malted barley and yeast.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -66,7 +67,7 @@
|
||||
name: anti-freeze
|
||||
description: Ultimate refreshment.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -84,7 +85,7 @@
|
||||
name: atomic bomb glass
|
||||
description: Nuclear proliferation never tasted so good.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -102,7 +103,7 @@
|
||||
name: b-52 glass
|
||||
description: Coffee, Irish Cream, and cognac. You will get bombed.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -144,7 +145,7 @@
|
||||
name: beer # 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.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -161,7 +162,7 @@
|
||||
name: beer glass
|
||||
description: An alcoholic beverage made from malted grains, hops, yeast, and water.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -305,7 +306,7 @@
|
||||
name: coffee
|
||||
description: Coffee is a brewed drink prepared from roasted seeds, commonly called coffee beans, of the coffee plant.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -322,7 +323,7 @@
|
||||
name: cognac glass
|
||||
description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. Classy as fornication.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -339,7 +340,7 @@
|
||||
name: space cola bottle
|
||||
description: Cola. in space
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -356,7 +357,7 @@
|
||||
name: cream
|
||||
description: Dairy product composed of the higher-fat layer skimmed from the top of milk before homogenization.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -373,7 +374,7 @@
|
||||
name: cuba libre glass
|
||||
description: Rum, mixed with cola. Viva la revolucion.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -642,7 +643,7 @@
|
||||
name: milk
|
||||
description: An opaque white liquid produced by the mammary glands of mammals.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -804,7 +805,7 @@
|
||||
name: ice glass
|
||||
description: Water frozen into a solid state.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -821,7 +822,7 @@
|
||||
name: irish car bomb
|
||||
description: Mmm, tastes like chocolate cake...
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -838,7 +839,7 @@
|
||||
name: irish coffee glass
|
||||
description: Coffee, and alcohol. More fun than a Mimosa to drink in the morning.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -855,7 +856,7 @@
|
||||
name: irish cream glass
|
||||
description: Whiskey-imbued cream, what else would you expect from the Irish.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -908,7 +909,7 @@
|
||||
name: kahlua glass
|
||||
description: A widely known, Mexican coffee-flavoured liqueur.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1045,7 +1046,7 @@
|
||||
name: the manly dorf glass
|
||||
description: Beer and Ale, brought together in a delicious mix. Intended for true men only.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1098,7 +1099,7 @@
|
||||
name: milk jug
|
||||
description: An opaque white liquid produced by the mammary glands of mammals.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1597,7 +1598,7 @@
|
||||
name: syndicate bomb
|
||||
description: Tastes like terrorism!
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1614,7 +1615,7 @@
|
||||
name: tea glass
|
||||
description: Tasty black tea. Contains caffeine.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1631,7 +1632,7 @@
|
||||
name: teapot # short and stout
|
||||
description: An elegant teapot. It simply oozes class.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1822,7 +1823,7 @@
|
||||
name: whiskey cola glass
|
||||
description: Whiskey, mixed with cola. Surprisingly refreshing.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1839,7 +1840,7 @@
|
||||
name: special blend whiskey glass
|
||||
description: Just when you thought regular station whiskey was good... This silky, amber goodness has to come along and ruin everything.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -1892,7 +1893,7 @@
|
||||
name: wine glass
|
||||
description: An premium alchoholic beverage made from distilled grape juice.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
components:
|
||||
- type: Drink
|
||||
openSounds: bottleOpenSounds
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: Sprite
|
||||
state: icon
|
||||
- type: Icon
|
||||
state: icon
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
parent: DrinkBottleBaseFull
|
||||
@@ -44,7 +45,7 @@
|
||||
name: magm-ale
|
||||
description: A true dorf's drink of choice.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
@@ -73,7 +74,7 @@
|
||||
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: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
@@ -114,7 +115,7 @@
|
||||
name: kahlua bottle
|
||||
description: A widely known, Mexican coffee-flavoured liqueur. In production since 1936, HONK
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
@@ -191,7 +192,7 @@
|
||||
name: vodka bottle
|
||||
description: Aah, vodka. Prime choice of drink AND fuel by Russians worldwide.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
@@ -208,7 +209,7 @@
|
||||
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: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
@@ -225,7 +226,7 @@
|
||||
name: doublebearded bearded special wine bottle
|
||||
description: A faint aura of unease and asspainery surrounds the bottle.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 80
|
||||
contents:
|
||||
reagents:
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
- type: Drink
|
||||
openSounds: canOpenSounds
|
||||
pressurized: true
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
caps: AddTo, RemoveFrom
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.Cola
|
||||
@@ -18,7 +19,7 @@
|
||||
state: icon
|
||||
- type: Icon
|
||||
state: icon
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
parent: DrinkCanBaseFull
|
||||
@@ -26,7 +27,7 @@
|
||||
name: space cola
|
||||
description: A refreshing beverage.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/cola.rsi
|
||||
- type: Icon
|
||||
@@ -37,8 +38,8 @@
|
||||
- type: entity
|
||||
parent: DrinkCanBaseFull
|
||||
id: DrinkIceTeaCan
|
||||
name: ice tea can
|
||||
description: A can of refreshing ice tea.
|
||||
name: iced tea can
|
||||
description: A refreshing can of iced tea.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/ice_tea_can.rsi
|
||||
@@ -51,7 +52,7 @@
|
||||
parent: DrinkCanBaseFull
|
||||
id: DrinkLemonLimeCan
|
||||
name: lemon-lime can
|
||||
description: You wanted ORANGE. It gave you Lemon Lime.
|
||||
description: You wanted ORANGE. It gave you Lemon-Lime.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/lemon-lime.rsi
|
||||
@@ -64,7 +65,7 @@
|
||||
parent: DrinkCanBaseFull
|
||||
id: DrinkGrapeCan
|
||||
name: grape soda can
|
||||
description: 'Sweetened drink with a grape flavor and a deep purple color.'
|
||||
description: Sweetened drink with a grape flavor and a deep purple color.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/purple_can.rsi
|
||||
@@ -156,8 +157,8 @@
|
||||
- type: entity
|
||||
parent: DrinkCanBaseFull
|
||||
id: DrinkEnergyDrink
|
||||
name: Energy drink
|
||||
description: ''
|
||||
name: red bool energy drink
|
||||
description: A can of Red Bool, with enough caffeine to kill a horse.
|
||||
components:
|
||||
- type: Drink
|
||||
- type: Sprite
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
name: base cup
|
||||
abstract: true
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
@@ -15,7 +16,7 @@
|
||||
state: icon
|
||||
- type: Icon
|
||||
state: icon
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
parent: DrinkBaseCup
|
||||
@@ -23,7 +24,7 @@
|
||||
name: golden cup
|
||||
description: A golden cup
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/golden_cup.rsi
|
||||
@@ -36,7 +37,7 @@
|
||||
name: insulated pitcher
|
||||
description: A stainless steel insulated pitcher. Everyone's best friend in the morning.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 15
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/pitcher.rsi
|
||||
@@ -55,7 +56,7 @@
|
||||
name: mug
|
||||
description: A plain white mug.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug.rsi
|
||||
@@ -74,7 +75,7 @@
|
||||
name: mug black
|
||||
description: A sleek black mug.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_black.rsi
|
||||
@@ -93,7 +94,7 @@
|
||||
name: mug blue
|
||||
description: A blue and black mug.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_blue.rsi
|
||||
@@ -112,7 +113,7 @@
|
||||
name: mug green
|
||||
description: A pale green and pink mug.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_green.rsi
|
||||
@@ -131,7 +132,7 @@
|
||||
name: mug heart
|
||||
description: A white mug, it prominently features a red heart.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_heart.rsi
|
||||
@@ -150,7 +151,7 @@
|
||||
name: mug metal
|
||||
description: A metal mug. You're not sure which metal.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_metal.rsi
|
||||
@@ -169,7 +170,7 @@
|
||||
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: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_moebius.rsi
|
||||
@@ -188,7 +189,7 @@
|
||||
name: "#1 mug"
|
||||
description: "A white mug, it prominently features a #1."
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_one.rsi
|
||||
@@ -207,7 +208,7 @@
|
||||
name: mug rainbow
|
||||
description: A rainbow mug. The colors are almost as blinding as a welder.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_rainbow.rsi
|
||||
@@ -226,7 +227,7 @@
|
||||
name: mug red
|
||||
description: A red and black mug.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Drinks/mug_red.rsi
|
||||
@@ -257,7 +258,7 @@
|
||||
name: Coffee
|
||||
description: Coffee is a brewed drink prepared from roasted seeds, commonly called coffee beans, of the coffee plant.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -274,7 +275,7 @@
|
||||
name: Teacup
|
||||
description: A plain white porcelain teacup.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
@@ -295,9 +296,9 @@
|
||||
parent: DrinkBaseCup
|
||||
id: DrinkLean
|
||||
name: Lean
|
||||
description: Bro Drake has such a cool fade bro Drake is the best bro c'mon bro he's
|
||||
description: Bro Drake has such a cool fade bro Drake is the best bro c'mon bro.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
contents:
|
||||
reagents:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,9 @@
|
||||
name: flour
|
||||
description: Not to be confused with flower.
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: AddTo, RemoveFrom
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.Flour
|
||||
@@ -19,4 +20,4 @@
|
||||
- type: Icon
|
||||
sprite: Objects/Consumable/Food/flour.rsi
|
||||
state: icon
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
state: icon
|
||||
- type: Icon
|
||||
state: icon
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
isOpen: true
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
|
||||
# Containers
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
- type: Item
|
||||
sprite: Objects/Misc/fire_extinguisher.rsi
|
||||
size: 10
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 1000
|
||||
caps: 9
|
||||
caps: AddTo, RemoveFrom, NoExamine
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.H2O
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
- type: Item
|
||||
sprite: Objects/Specific/Chemistry/beakers.rsi
|
||||
HeldPrefix: beaker
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
fillingState: beaker
|
||||
maxVol: 50
|
||||
caps: 27
|
||||
caps: AddTo, RemoveFrom, FitsInDispenser # can add and remove solutions and fits in the chemmaster.
|
||||
- type: Pourable
|
||||
transferAmount: 5.0
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: large beaker
|
||||
@@ -32,13 +32,13 @@
|
||||
- type: Item
|
||||
sprite: Objects/Specific/Chemistry/beakers.rsi
|
||||
HeldPrefix: beaker
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
fillingState: beakerlarge
|
||||
maxVol: 100
|
||||
caps: 27
|
||||
caps: AddTo, RemoveFrom, FitsInDispenser
|
||||
- type: Pourable
|
||||
transferAmount: 5.0
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: dropper
|
||||
@@ -50,14 +50,14 @@
|
||||
texture: Objects/Specific/Chemistry/dropper.rsi/dropper.png
|
||||
- type: Icon
|
||||
texture: Objects/Specific/Chemistry/dropper.rsi/dropper.png
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
fillingState: dropper
|
||||
fillingSteps: 2
|
||||
maxVol: 5
|
||||
caps: 19
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5.0
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: syringe
|
||||
@@ -69,14 +69,14 @@
|
||||
texture: Objects/Specific/Chemistry/syringe.rsi/0.png
|
||||
- type: Icon
|
||||
texture: Objects/Specific/Chemistry/syringe.rsi/0.png
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
fillingState: syringe
|
||||
fillingSteps: 5
|
||||
maxVol: 15
|
||||
caps: 19
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Injector
|
||||
injectOnly: false
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: bottle
|
||||
@@ -84,15 +84,16 @@
|
||||
id: bottle
|
||||
components:
|
||||
- type: Drink
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 30
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
- type: Sprite
|
||||
texture: Objects/Specific/Chemistry/bottle.rsi/bottle.png
|
||||
- type: Icon
|
||||
texture: Objects/Specific/Chemistry/bottle.rsi/bottle.png
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: pill
|
||||
@@ -101,8 +102,9 @@
|
||||
description: It's not a suppository.
|
||||
components:
|
||||
- type: Pill
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: RemoveFrom
|
||||
- type: Sprite
|
||||
texture: Objects/Specific/Chemistry/pills.rsi/pill.png
|
||||
- type: Icon
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
size: 10
|
||||
sprite: Objects/Specific/Janitorial/mop.rsi
|
||||
- type: Mop
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
caps: 1
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: LoopingSound
|
||||
|
||||
- type: entity
|
||||
@@ -33,9 +33,9 @@
|
||||
- type: InteractionOutline
|
||||
- type: Bucket
|
||||
- type: LoopingSound
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 500
|
||||
caps: 3
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Collidable
|
||||
shapes:
|
||||
- !type:PhysShapeAabb
|
||||
@@ -49,14 +49,14 @@
|
||||
- type: Physics
|
||||
mass: 5
|
||||
anchored: false
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
- type: Pullable
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
name: bucket
|
||||
id: Bucket
|
||||
description: "Dear God."
|
||||
description: It's a boring old bucket.
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: Sprite
|
||||
@@ -66,9 +66,9 @@
|
||||
texture: Objects/Specific/Janitorial/bucket.png
|
||||
- type: Bucket
|
||||
- type: LoopingSound
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 500
|
||||
caps: 3
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Collidable
|
||||
shapes:
|
||||
- !type:PhysShapeAabb
|
||||
@@ -79,7 +79,7 @@
|
||||
- type: Physics
|
||||
mass: 5
|
||||
anchored: false
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
|
||||
- type: entity
|
||||
name: wet floor sign
|
||||
@@ -190,12 +190,12 @@
|
||||
texture: Objects/Specific/Janitorial/cleaner.png
|
||||
- type: Icon
|
||||
texture: Objects/Specific/Janitorial/cleaner.png
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: 1
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 5.0
|
||||
- type: CanSpill
|
||||
- type: Spillable
|
||||
- type: Spray
|
||||
transferAmount: 10
|
||||
sprayVelocity: 5
|
||||
|
||||
@@ -89,9 +89,9 @@
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
- type: ItemStatus
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: 9
|
||||
caps: AddTo, NoExamine
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
name: "reagent item"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 5
|
||||
|
||||
- type: entity
|
||||
@@ -14,7 +14,7 @@
|
||||
components:
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Solution
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
- type: Vapor
|
||||
- type: Physics
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
offset: Center
|
||||
- type: Sprite
|
||||
drawdepth: FloorObjects
|
||||
- type: Solution
|
||||
caps: 1
|
||||
- type: SolutionContainer
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Puddle
|
||||
spill_sound: /Audio/Effects/Fluids/splat.ogg
|
||||
recolor: true
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
id: chem.Nutriment
|
||||
name: nutriment
|
||||
desc: All the vitamins, minerals, and carbohydrates the body needs in pure form.
|
||||
physicalDesc: opaque
|
||||
color: "#664330"
|
||||
metabolism:
|
||||
- !type:DefaultFood
|
||||
@@ -11,6 +12,7 @@
|
||||
id: chem.H2SO4
|
||||
name: sulfuric acid
|
||||
desc: A highly corrosive, oily, colorless liquid.
|
||||
physicalDesc: oily
|
||||
color: "#BF8C00"
|
||||
boilingPoint: 337.0
|
||||
meltingPoint: 10.31
|
||||
@@ -18,7 +20,8 @@
|
||||
- type: reagent
|
||||
id: chem.H2O
|
||||
name: water
|
||||
desc: A tasty colorless liquid.
|
||||
desc: A colorless liquid that humans need in order to survive.
|
||||
physicalDesc: translucent
|
||||
color: "#c0e0ff20"
|
||||
boilingPoint: 100.0
|
||||
meltingPoint: 0.0
|
||||
@@ -30,6 +33,7 @@
|
||||
id: chem.Ice
|
||||
name: ice
|
||||
desc: Frozen water.
|
||||
physicalDesc: frosty
|
||||
color: "#bed8e6"
|
||||
meltingPoint: 0.0
|
||||
boilingPoint: 100.0
|
||||
@@ -38,7 +42,8 @@
|
||||
id: chem.Phoron
|
||||
name: phoron
|
||||
desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways.
|
||||
color: "#500064"
|
||||
physicalDesc: gaseous
|
||||
color: "#7e009e"
|
||||
boilingPoint: -127.3 # Random values picked between the actual values for CO2 and O2
|
||||
meltingPoint: -186.4
|
||||
|
||||
@@ -46,6 +51,7 @@
|
||||
id: chem.Ethanol
|
||||
name: ethanol
|
||||
desc: A simple alcohol, makes you drunk if consumed, flammable.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#b05b3c"
|
||||
boilingPoint: 78.2
|
||||
meltingPoint: -114.1
|
||||
@@ -54,6 +60,7 @@
|
||||
id: chem.Glucose
|
||||
name: glucose
|
||||
desc: A simple sugar found in many foods.
|
||||
physicalDesc: syrupy
|
||||
color: "#ffffff"
|
||||
boilingPoint: 340282300000000000000000000000000000000 #Fun fact: Glucose can't boil. So let's just set it to the maximum float value.
|
||||
meltingPoint: 146.0
|
||||
@@ -62,6 +69,7 @@
|
||||
id: chem.Ammonia
|
||||
name: ammonia
|
||||
desc: An effective fertilizer which is better than what is available to the botanist initially, though it isn't as powerful as Diethylamine
|
||||
physicalDesc: pungent
|
||||
color: "#77b58e"
|
||||
boilingPoint: -33.0
|
||||
meltingPoint: -77.7
|
||||
@@ -70,6 +78,7 @@
|
||||
id: chem.Bleach
|
||||
name: bleach
|
||||
desc: Heavy duty cleaner that can clean tiles the same as Space Cleaner and also decontaminate clothes. Extremely toxic when ingested.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#a1000b"
|
||||
boilingPoint: 111.0
|
||||
meltingPoint: -5.0
|
||||
@@ -78,6 +87,7 @@
|
||||
id: chem.Diethylamine
|
||||
name: diethylamine
|
||||
desc: A very potent fertilizer.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#a1000b"
|
||||
boilingPoint: 55.5
|
||||
meltingPoint: -50.0
|
||||
@@ -86,6 +96,7 @@
|
||||
id: chem.FoamingAgent
|
||||
name: foaming agent
|
||||
desc: Makes foam such as that required in metal foam grenades
|
||||
physicalDesc: foamy
|
||||
color: "#215263"
|
||||
boilingPoint: 418.0 # I went with ammonium lauryl sulfate as the basis for this
|
||||
meltingPoint: 7.4 # I made this up
|
||||
@@ -94,6 +105,7 @@
|
||||
id: chem.PolytrinicAcid
|
||||
name: polytrinic acid
|
||||
desc: An extremely corrosive chemical substance. The slightest touch of it will melt off most masks and headgear, and it deals extreme damage to anyone who comes directly into contact with it. Spraying it on other items will usually melt them too, which does make it useful if the clown has covered the entire hallway in banana peels.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#a1000b"
|
||||
boilingPoint: 78.2 # This isn't a real chemical...
|
||||
meltingPoint: -19.4
|
||||
@@ -102,7 +114,8 @@
|
||||
id: chem.SpaceCleaner
|
||||
name: space cleaner
|
||||
desc: This is able to clean almost all surfaces of almost anything that may dirty them. The janitor is likely to appreciate refills.
|
||||
color: "#215263"
|
||||
physicalDesc: lemony fresh
|
||||
color: "#c8ff69"
|
||||
boilingPoint: 147.0 # Made this up, loosely based on bleach
|
||||
meltingPoint: -11.0
|
||||
|
||||
@@ -110,6 +123,7 @@
|
||||
id: chem.SpaceLube
|
||||
name: space lube
|
||||
desc: Space Lube is a high performance lubricant intended for maintenance of extremely complex mechanical equipment (and certainly not used to make people slip).
|
||||
physicalDesc: shiny
|
||||
color: "#77b58e"
|
||||
boilingPoint: 290.0 # Glycerin
|
||||
meltingPoint: 18.2
|
||||
@@ -118,6 +132,7 @@
|
||||
id: chem.TableSalt
|
||||
name: table salt
|
||||
desc: Commonly known as salt, Sodium Chloride is often used to season food or kill borers instantly.
|
||||
physicalDesc: grainy
|
||||
color: "#a1000b"
|
||||
boilingPoint: 1465.0
|
||||
meltingPoint: 800.7
|
||||
@@ -126,7 +141,8 @@
|
||||
id: chem.Thermite
|
||||
name: thermite
|
||||
desc: A mixture that becomes extremely hot when ignited, and which can burn straight through walls when applied and ignited. It'll slowly inflict burn damage to anybody dumb enough to ingest it, but can't be ignited inside inside said dumb person.
|
||||
color: "#77b58e"
|
||||
physicalDesc: grainy
|
||||
color: "#757245"
|
||||
boilingPoint: 2977.0 # Aluminum oxide
|
||||
meltingPoint: 2030.0
|
||||
|
||||
@@ -134,7 +150,8 @@
|
||||
id: chem.UnstableMutagen
|
||||
name: unstable mutagen
|
||||
desc: Causes mutations when injected into living people or plants. High doses may be lethal, especially in humans.
|
||||
color: "#77b58e"
|
||||
physicalDesc: glowing
|
||||
color: "#00ff5f"
|
||||
boilingPoint: 340282300000000000000000000000000000000 # Ethidium bromide, which doesn't boil.
|
||||
meltingPoint: 261.0
|
||||
|
||||
@@ -142,6 +159,7 @@
|
||||
id: chem.WeldingFuel
|
||||
name: welding fuel
|
||||
desc: Used by welders to weld.
|
||||
physicalDesc: oily
|
||||
color: "#a76b1c"
|
||||
boilingPoint: -84.7 # Acetylene. Close enough.
|
||||
meltingPoint: -80.7
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
id: chem.Whiskey
|
||||
name: whiskey
|
||||
desc: An alcoholic beverage made from fermented grain mash
|
||||
physicalDesc: strong-smelling
|
||||
color: "#664300"
|
||||
spritePath: whiskeyglass.rsi
|
||||
|
||||
@@ -9,13 +10,15 @@
|
||||
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.
|
||||
color: "#664300"
|
||||
physicalDesc: bubbly
|
||||
color: "#663100"
|
||||
spritePath: aleglass.rsi
|
||||
|
||||
- type: reagent
|
||||
id: chem.Wine
|
||||
name: wine
|
||||
desc: An alcoholic drink made from fermented grapes
|
||||
physicalDesc: translucent
|
||||
color: "#7E4043"
|
||||
spritePath: wineglass.rsi
|
||||
|
||||
@@ -23,19 +26,36 @@
|
||||
id: chem.Beer
|
||||
name: beer
|
||||
desc: A cold pint of pale lager.
|
||||
color: "#664300"
|
||||
physicalDesc: bubbly
|
||||
color: "#cfa85f"
|
||||
spritePath: beerglass.rsi
|
||||
|
||||
- type: reagent
|
||||
id: chem.Vodka
|
||||
name: vodka
|
||||
desc: The glass contain wodka. Xynta.
|
||||
color: "#664300"
|
||||
physicalDesc: strong-smelling
|
||||
color: "#d1d1d155"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Moonshine
|
||||
name: moonshine
|
||||
desc: Artisanal homemade liquor. What could go wrong?
|
||||
physicalDesc: strong-smelling
|
||||
color: "#d1d7d155"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Tequila
|
||||
name: tequila
|
||||
desc: This stuff will get you fucked up.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#d7d1d155"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Kahlua
|
||||
name: kahlua
|
||||
desc: A widely known, Mexican coffee-flavoured liqueur. In production since 1936!
|
||||
physicalDesc: cloudy
|
||||
color: "#664300"
|
||||
spritePath: kahluaglass.rsi
|
||||
|
||||
@@ -43,6 +63,7 @@
|
||||
id: chem.Cognac
|
||||
name: cognac
|
||||
desc: A sweet and strongly alcoholic drink, twice distilled and left to mature for several years. Classy as fornication.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#AB3C05"
|
||||
spritePath: cognacglass.rsi
|
||||
|
||||
@@ -50,6 +71,7 @@
|
||||
id: chem.ManlyDorf
|
||||
name: manly dorf
|
||||
desc: A dwarfy concoction made from ale and beer. Intended for stout dwarves only.
|
||||
physicalDesc: bubbly
|
||||
color: "#664300"
|
||||
spritePath: manlydorfglass.rsi
|
||||
|
||||
@@ -57,6 +79,7 @@
|
||||
id: chem.CubaLibre
|
||||
name: cuba libre
|
||||
desc: A classic mix of rum and cola.
|
||||
physicalDesc: bubbly
|
||||
color: "#3E1B00"
|
||||
spritePath: cubalibreglass.rsi
|
||||
|
||||
@@ -64,6 +87,7 @@
|
||||
id: chem.IrishCarBomb
|
||||
name: irish car bomb
|
||||
desc: A troubling mixture of irish cream and ale.
|
||||
physicalDesc: bubbly
|
||||
color: "#2E6671"
|
||||
spritePath: irishcarbomb.rsi
|
||||
|
||||
@@ -71,6 +95,7 @@
|
||||
id: chem.IrishCoffee
|
||||
name: irish coffee
|
||||
desc: Coffee served with irish cream. Regular cream just isn't the same!
|
||||
physicalDesc: cloudy
|
||||
color: "#664300"
|
||||
spritePath: irishcoffeeglass.rsi
|
||||
|
||||
@@ -78,6 +103,7 @@
|
||||
id: chem.IrishCream
|
||||
name: irish cream
|
||||
desc: Whiskey-imbued cream. What else could you expect from the Irish.
|
||||
physicalDesc: creamy
|
||||
color: "#664300"
|
||||
spritePath: irishcreamglass.rsi
|
||||
|
||||
@@ -85,6 +111,7 @@
|
||||
id: chem.B52
|
||||
name: b-52
|
||||
desc: Coffee, irish cream, and cognac. You will get bombed.
|
||||
physicalDesc: bubbly
|
||||
color: "#664300"
|
||||
spritePath: b52glass.rsi
|
||||
|
||||
@@ -92,6 +119,7 @@
|
||||
id: chem.AtomicBomb
|
||||
name: atomic bomb
|
||||
desc: Nuclear proliferation never tasted so good.
|
||||
physicalDesc: cloudy
|
||||
color: "#666300"
|
||||
spritePath: atomicbombglass.rsi
|
||||
|
||||
@@ -99,6 +127,7 @@
|
||||
id: chem.WhiskeyCola
|
||||
name: whiskey cola
|
||||
desc: An innocent-looking mixture of cola and whiskey. Delicious.
|
||||
physicalDesc: bubbly
|
||||
color: "#3E1B00"
|
||||
spritePath: whiskeycolaglass.rsi
|
||||
|
||||
@@ -106,22 +135,24 @@
|
||||
id: chem.SyndicateBomb
|
||||
name: syndicate bomb
|
||||
desc: Somebody set us up the bomb!
|
||||
color: "#2E6671"
|
||||
physicalDesc: opaque
|
||||
color: "#2E6660"
|
||||
spritePath: syndicatebomb.rsi
|
||||
|
||||
- type: reagent
|
||||
id: chem.Antifreeze
|
||||
name: antifreeze
|
||||
desc: The ultimate refreshment.
|
||||
color: "#664300"
|
||||
physicalDesc: translucent
|
||||
color: "#ff7d63"
|
||||
spritePath: antifreeze.rsi
|
||||
|
||||
|
||||
- type: reagent
|
||||
id: chem.Cola
|
||||
name: cola
|
||||
desc: A sweet, carbonated soft drink. Caffeine free.
|
||||
color: "#100800"
|
||||
physicalDesc: fizzy
|
||||
color: "#422912"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
@@ -130,6 +161,7 @@
|
||||
id: chem.Coffee
|
||||
name: coffee
|
||||
desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine.
|
||||
physicalDesc: aromatic
|
||||
color: "#664300"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
@@ -139,7 +171,8 @@
|
||||
id: chem.Tea
|
||||
name: tea
|
||||
desc: A made by boiling leaves of the tea tree, Camellia sinensis.
|
||||
color: "#101000"
|
||||
physicalDesc: aromatic
|
||||
color: "#8a5a3a"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
@@ -148,6 +181,7 @@
|
||||
id: chem.Cream
|
||||
name: cream
|
||||
desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh?
|
||||
physicalDesc: creamy
|
||||
color: "#DFD7AF"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
@@ -157,16 +191,48 @@
|
||||
id: chem.Milk
|
||||
name: milk
|
||||
desc: An opaque white liquid produced by the mammary glands of mammals.
|
||||
physicalDesc: opaque
|
||||
color: "#DFDFDF"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
|
||||
- type: reagent
|
||||
id: chem.SpoiledMilk
|
||||
name: spoiled milk
|
||||
desc: This milk has gone rancid.
|
||||
physicalDesc: putrid
|
||||
color: "#faffba"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
|
||||
- type: reagent
|
||||
id: chem.CreamyDelight
|
||||
name: creamy delight
|
||||
desc: A combination of cream, wine and moonshine. Why would you do this?
|
||||
physicalDesc: foul
|
||||
color: "#a6969a"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
|
||||
- type: reagent
|
||||
id: chem.Lean
|
||||
name: lean
|
||||
desc: Turn up for days
|
||||
desc: Turn up for days.
|
||||
physicalDesc: bubbly
|
||||
color: "#9400D3"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
|
||||
- type: reagent
|
||||
id: chem.LeanShine
|
||||
name: leanshine
|
||||
desc: Lean mixed with moonshine. Turn up for months.
|
||||
physicalDesc: bubbly
|
||||
color: "#9d5fb8"
|
||||
metabolism:
|
||||
- !type:DefaultDrink
|
||||
rate: 1
|
||||
@@ -2,6 +2,7 @@
|
||||
id: chem.H
|
||||
name: hydrogen
|
||||
desc: A light, flammable gas.
|
||||
physicalDesc: gaseous
|
||||
color: "#808080"
|
||||
boilingPoint: -253.0
|
||||
meltingPoint: -259.2
|
||||
@@ -10,6 +11,7 @@
|
||||
id: chem.O
|
||||
name: oxygen
|
||||
desc: An oxidizing, colorless gas.
|
||||
physicalDesc: gaseous
|
||||
color: "#808080"
|
||||
boilingPoint: -183.0
|
||||
meltingPoint: -218.4
|
||||
@@ -19,7 +21,8 @@
|
||||
id: chem.S
|
||||
name: sulfur
|
||||
desc: A yellow, crystalline solid.
|
||||
color: "#FFFACD"
|
||||
physicalDesc: powdery
|
||||
color: "#fff385"
|
||||
boilingPoint: 445.0
|
||||
meltingPoint: 120.0
|
||||
|
||||
@@ -27,6 +30,7 @@
|
||||
id: chem.C
|
||||
name: carbon
|
||||
desc: A black, crystalline solid.
|
||||
physicalDesc: crystalline
|
||||
color: "#22282b"
|
||||
boilingPoint: 4200.0
|
||||
meltingPoint: 3550.0
|
||||
@@ -34,7 +38,8 @@
|
||||
- type: reagent
|
||||
id: chem.Al
|
||||
name: aluminum
|
||||
desc: A silvery-white, soft, non-magnetic, and ductile metal.
|
||||
desc: A silver, soft, non-magnetic, and ductile metal.
|
||||
physicalDesc: metallic
|
||||
color: "#848789"
|
||||
boilingPoint: 2327.0
|
||||
meltingPoint: 660.0
|
||||
@@ -43,6 +48,7 @@
|
||||
id: chem.Cu
|
||||
name: copper
|
||||
desc: A soft, malleable, and ductile metal with very high thermal and electrical conductivity.
|
||||
physicalDesc: metallic
|
||||
color: "#b05b3c"
|
||||
boilingPoint: 2595.0
|
||||
meltingPoint: 1083.0
|
||||
@@ -51,6 +57,7 @@
|
||||
id: chem.N
|
||||
name: nitrogen
|
||||
desc: A colorless, odorless unreactive gas. Highly stable.
|
||||
physicalDesc: gaseous
|
||||
color: "#808080"
|
||||
boilingPoint: -195.8
|
||||
meltingPoint: -210.0
|
||||
@@ -59,6 +66,7 @@
|
||||
id: chem.Fe
|
||||
name: iron
|
||||
desc: A silvery-grey metal which forms iron oxides (rust) with contact with air. Commonly alloyed with other elements to create alloys such as steel.
|
||||
physicalDesc: metallic
|
||||
color: "#434b4d"
|
||||
boilingPoint: 2862.0
|
||||
meltingPoint: 1538.0
|
||||
@@ -67,6 +75,7 @@
|
||||
id: chem.F
|
||||
name: fluorine
|
||||
desc: A highly toxic pale yellow gas. Extremely reactive.
|
||||
physicalDesc: gaseous
|
||||
color: "#808080"
|
||||
boilingPoint: -188.11
|
||||
meltingPoint: -219.67
|
||||
@@ -75,6 +84,7 @@
|
||||
id: chem.Si
|
||||
name: silicon
|
||||
desc: A hard and brittle crystalline solid with a blue-grey color.
|
||||
physicalDesc: crystalline
|
||||
color: "#364266"
|
||||
boilingPoint: 3265.0
|
||||
meltingPoint: 1414.0
|
||||
@@ -83,6 +93,7 @@
|
||||
id: chem.Cl
|
||||
name: chlorine
|
||||
desc: A yellow-green gas which is toxic to humans.
|
||||
physicalDesc: gaseous
|
||||
color: "#a2ff00"
|
||||
meltingPoint: -101.5
|
||||
boilingPoint: -34.04
|
||||
@@ -91,6 +102,7 @@
|
||||
id: chem.Li
|
||||
name: lithium
|
||||
desc: A soft, silvery-white alkali metal. It is highly reactive, and ignites if it makes contact with water.
|
||||
physicalDesc: shiny
|
||||
color: "#c6c8cc"
|
||||
meltingPoint: 180.5
|
||||
boilingPoint: 1330.0
|
||||
@@ -99,6 +111,7 @@
|
||||
id: chem.Hg
|
||||
name: mercury
|
||||
desc: A silver metal which is liquid at room temperature. It is highly toxic to humans.
|
||||
physicalDesc: shiny
|
||||
color: "#929296"
|
||||
meltingPoint: -38.83
|
||||
boilingPoint: 356.73
|
||||
@@ -107,14 +120,16 @@
|
||||
id: chem.P
|
||||
name: phosphorus
|
||||
desc: A reactive metal used in pyrotechnics and weapons.
|
||||
color: "#803330"
|
||||
physicalDesc: powdery
|
||||
color: "#ede4e4"
|
||||
meltingPoint: 44.2
|
||||
boilingPoint: 280.5
|
||||
|
||||
- type: reagent
|
||||
id: chem.K
|
||||
name: potassium
|
||||
desc: A soft, silvery-white metal. Even more reactive than lithium.
|
||||
desc: A soft, shiny grey metal. Even more reactive than lithium.
|
||||
physicalDesc: shiny
|
||||
color: "#c6c8cc"
|
||||
meltingPoint: 65.5
|
||||
boilingPoint: 759.0
|
||||
@@ -123,6 +138,7 @@
|
||||
id: chem.Ra
|
||||
name: radium
|
||||
desc: A radioactive metal, silvery-white in it's pure form. It glows due to it's radioactivity and is highly toxic.
|
||||
physicalDesc: glowing
|
||||
color: "#00ff04"
|
||||
meltingPoint: 700.0
|
||||
boilingPoint: 1737.0
|
||||
@@ -131,6 +147,7 @@
|
||||
id: chem.Na
|
||||
name: sodium
|
||||
desc: A silvery-white alkali metal. Highly reactive in it's pure form.
|
||||
physicalDesc: metallic
|
||||
color: "#c6c8cc"
|
||||
meltingPoint: 97.8
|
||||
boilingPoint: 883.0
|
||||
@@ -138,7 +155,8 @@
|
||||
- type: reagent
|
||||
id: chem.U
|
||||
name: uranium
|
||||
desc: A silvery-white metallic chemical element in the actinide series, weakly radioactive.
|
||||
color: "#00ff06"
|
||||
desc: A grey metallic chemical element in the actinide series, weakly radioactive.
|
||||
physicalDesc: metallic
|
||||
color: "#8fa191"
|
||||
meltingPoint: 1133.0
|
||||
boilingPoint: 4131.0
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
id: chem.Flour
|
||||
name: flour
|
||||
desc: Used for baking.
|
||||
physicalDesc: powdery
|
||||
color: "#FFFFFF"
|
||||
metabolism:
|
||||
- !type:DefaultFood
|
||||
|
||||
@@ -2,245 +2,285 @@
|
||||
id: chem.Alkycosine
|
||||
name: alkycosine
|
||||
desc: Lessens the damage to neurological tissue. More effective at treating brain damage than alkysine and also a fairly effective painkiller as well. Caution is needed in its creation to avoid mixing bleach and the chlorine needed to make alkysine.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#9e232b"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Alkysine
|
||||
name: alkysine
|
||||
desc: Lessens the damage to neurological tissue, effective even after catastrophic injury. Used for treating brain damage and also a fairly effective painkiller.
|
||||
color: "#a1000b"
|
||||
physicalDesc: oily
|
||||
color: "#ff8c00"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Dylovene
|
||||
name: dylovene
|
||||
desc: A broad-spectrum anti-toxin, which treats toxin damage in the blood stream. Overdosing will cause vomiting, dizzyness and pain.
|
||||
physicalDesc: translucent
|
||||
color: "#3a1d8a"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Arithrazine
|
||||
name: arithrazine
|
||||
desc: A slightly unstable medication used for the most extreme any serious case of radiation poisoning. Lowers radiation level at over twice the rate Hyronalin does and will heal toxin damage at the same time. Deals very minor brute damage to the patient over time, but the patient's body will typically out-regenerate it easily.
|
||||
physicalDesc: cloudy
|
||||
color: "#bd5902"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Bicaridine
|
||||
name: bicaridine
|
||||
desc: An analgesic which is highly effective at treating brute damage. It is useful for stabilizing people who have been severely beaten, as well as treating less life-threatening injuries. In the case of bleeding (internal or external), bicaridine will slow down the bleeding heavily. If the dosage exceeds the overdose limit, it'll stop it outright.
|
||||
physicalDesc: opaque
|
||||
color: "#ffaa00"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Cryoxadone
|
||||
name: cryoxadone
|
||||
desc: Required for the proper function of cryogenics. Heals all standard types of damage very swiftly, but only works in temperatures under 170K (usually this means cryo cells). Can also slowly heal clone damage, such as caused by cloning or Slimes.
|
||||
physicalDesc: fizzy
|
||||
color: "#0091ff"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Clonexadone
|
||||
name: clonexadone
|
||||
desc: Heals standard damage in the same as Cryoxadone, with the same temperature requirement. Significantly more effective than the former at treating clone damage, although both can be used simultaneously. Best used in cryo cells.
|
||||
color: "#0091ff"
|
||||
desc: Heals standard damage in the same as Cryoxadone, with the same temperature requirement. Significantly more effective than the former at treating cellular damage, although both can be used simultaneously. Best used in cryo cells.
|
||||
physicalDesc: bubbly
|
||||
color: "#0666ff"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Citalopram
|
||||
name: citalopram
|
||||
desc: Prevents hallucination slightly.
|
||||
physicalDesc: cloudy
|
||||
color: "#21693c"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Dermaline
|
||||
name: dermaline
|
||||
desc: An advanced chemical that is more effective at treating burns damage than Kelotane.
|
||||
desc: An advanced chemical that is more effective at treating burn damage than Kelotane.
|
||||
physicalDesc: translucent
|
||||
color: "#215263"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Dexalin
|
||||
name: dexalin
|
||||
desc: Used for treating oxygen deprivation. In most cases where it is likely to be needed, the strength of Dexalin Plus will probably be more useful (Results in 1 unit instead of 2).
|
||||
physicalDesc: opaque
|
||||
color: "#0041a8"
|
||||
|
||||
- type: reagent
|
||||
id: chem.DexalinPlus
|
||||
name: dexalin plus
|
||||
desc: Used in treatment of extreme cases of oxygen deprivation. Even a single unit immediately counters all oxygen loss, which is hugely useful in many circumstances. Any dose beyond this will continue to counter oxygen loss until it is metabolized, essentially removing the need to breathe.
|
||||
color: "#297691"
|
||||
physicalDesc: cloudy
|
||||
color: "#4da0bd"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Ethylredoxrazine
|
||||
name: ethylredoxrazine
|
||||
desc: Neutralises the effects of alcohol in the blood stream. Though it is commonly needed, it is rarely requested.
|
||||
physicalDesc: opaque
|
||||
color: "#2d5708"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Hyperzine
|
||||
name: hyperzine
|
||||
desc: A highly effective, long lasting muscle stimulant. It allows greater freedom of movement in bulky clothing although it has the side effect of causing some twitching. Dangerous in higher doses.
|
||||
physicalDesc: translucent
|
||||
color: "#17bd61"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Hyronalin
|
||||
name: hyronalin
|
||||
desc: A weak treatment for radiation damage. Considered to be useful mainly for genetic modification, where it reduces radiation levels, and thus the chance of genetic mutations. Largely outclassed by Arithrazine.
|
||||
color: "#17ac61"
|
||||
physicalDesc: cloudy
|
||||
color: "#4cb580"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Imidazoline
|
||||
name: imidazoline
|
||||
desc: Effective in treating eye trauma. It heals damage caused by physical or chemical trauma, though it is ineffective in treating genetic defects in the eyes.
|
||||
physicalDesc: pungent
|
||||
color: "#f7ef00"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Inacusiate
|
||||
name: inacusiate
|
||||
desc: You only need 1u for Inacusiate work. Cures deafness instantly. Useful after an explosion.
|
||||
color: "#f7ef00"
|
||||
desc: You only need 1u for Inacusiate to be effective. Cures deafness instantly. Useful after an explosion.
|
||||
physicalDesc: pungent
|
||||
color: "#c4c04b"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Inaprovaline
|
||||
name: inaprovaline
|
||||
desc: Inaprovaline is a synaptic stimulant and cardiostimulant. Commonly used to stabilize patients- it stops oxygen loss when the patient is in critical health. It'll also slow down bleeding (internal or external) by half while in the body. Acts as a decent painkiller.
|
||||
color: "#73103b"
|
||||
physicalDesc: opaque
|
||||
color: "#731024"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Kelotane
|
||||
name: kelotane
|
||||
desc: Treats burn damage and prevents infection.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#bf3d19"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Leporazine
|
||||
name: leporazine
|
||||
desc: This keeps a patient's body temperature stable. High doses can allow short periods of unprotected EVA, but prevents use of the cryogenics tubes.
|
||||
physicalDesc: pungent
|
||||
color: "#ff7db5"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Methylin
|
||||
name: methylin
|
||||
desc: An intelligence enhancer, also used in the treatment of attention deficit hyperactivity disorder. Also known as Ritalin. Allows monkeys (not diona nymphs) to understand human speech and improves their dexterity as long as they have some in their system. Causes toxin and brain damage in higher doses.
|
||||
physicalDesc: acrid
|
||||
color: "#a700c4"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Oxycodone
|
||||
name: oxycodone
|
||||
desc: A very effective painkiller, about 250% as strong as Tramadol.
|
||||
physicalDesc: acrid
|
||||
color: "#c4a300"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Phalanximine
|
||||
name: phalanximine
|
||||
desc: Used in the treatment of cancer, is as effective as Anti-Toxin. Causes moderate radiation and hair loss.
|
||||
physicalDesc: acrid
|
||||
color: "#c8ff75"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Paroxetine
|
||||
name: paroxetine
|
||||
desc: Prevents hallucination, but has a 10% chance of causing intense hallucinations.
|
||||
color: "#c8ff75"
|
||||
physicalDesc: acrid
|
||||
color: "#fffbad"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Ryetalyn
|
||||
name: ryetalyn
|
||||
desc: You only need 1u for Ryetalin to work. Deactivates genetic defects and powers, restoring a patient to an ideal state. May be useful if genetics is unable to function properly. Deactivated effects return if the patient's genes are modified again.
|
||||
physicalDesc: cloudy
|
||||
color: "#532fd4"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Spaceacillin
|
||||
name: spaceacillin
|
||||
desc: A theta-lactam antibiotic. A common and very useful medicine, effective against many diseases likely to be encountered in space. Slows progression of diseases.
|
||||
color: "#7f2fd4"
|
||||
physicalDesc: opaque
|
||||
color: "#9942f5"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Synaptizine
|
||||
name: synaptizine
|
||||
desc: Toxic, but treats hallucinations, drowsiness & halves the duration of paralysis, stuns and knockdowns. It is metabolized very slowly. One unit is enough to treat hallucinations; two units is deadly.
|
||||
physicalDesc: pungent
|
||||
color: "#d49a2f"
|
||||
|
||||
|
||||
- type: reagent
|
||||
id: chem.Tramadol
|
||||
name: tramadol
|
||||
desc: A simple, yet effective painkiller. Very effective for patients in shock.
|
||||
physicalDesc: strong-smelling
|
||||
color: "#2f6ed4"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Tricordrazine
|
||||
name: tricordrazine
|
||||
desc: A wide-spectrum stimulant, originally derived from Cordrazine. Is capable of healing all four main damage types simultaneously, however it only heals at half the rate of conventional healing chemicals. Because of its low potency, it's best used as a supplement to other medicines.
|
||||
physicalDesc: opaque
|
||||
color: "#00e5ff"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Vaccine
|
||||
name: vaccine
|
||||
desc: Introduces antigens to the body, allowing the immune system to produce enough antibodies to combat present or future infections. Is blank by default, vaccine carrying antigen is produced at a centrifuge. Each unit raises the associated antibody concentration by 20% so at most 5 units are needed to cure the strongest diseases.
|
||||
color: "#ba0b60"
|
||||
physicalDesc: translucent
|
||||
color: "#ffc9f6"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Albuterol
|
||||
name: albuterol
|
||||
desc: A bronchodilator that relaxes muscles in the airways and increases air flow to the lungs. It'll remove mucus from your system as long as it's inside your body. Only useful to people with the Asthma disability.
|
||||
color: "#00e5ff"
|
||||
physicalDesc: cloudy
|
||||
color: "#00b89f"
|
||||
|
||||
- type: reagent
|
||||
id: chem.ChloralHydrate
|
||||
name: chloral hydrate
|
||||
desc: A powerful sedative which causes death in doses upwards of 16.2 units. Sends the patient to sleep almost instantly.
|
||||
color: "#00e5ff"
|
||||
physicalDesc: acrid
|
||||
color: "#18c9b1"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Creatine
|
||||
name: creatine
|
||||
desc: A muscle-building drug that grants the user enormous strength, before their muscles seize and tear their own body to shreds. In practical terms, 1-25 units just causes toxin damage, and 26 units turns you into a hulk with all its associated benefits with the side effect of taking a little toxin damage over time. You'll remain as a hulk until it's all metabolized, at which point you'll take 200 brute damage and gib. With the correct administration, it can be the most potent drug there is, but even at the best of times it is best considered a double-edged sword.
|
||||
physicalDesc: cloudy
|
||||
color: "#a1000b"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Cryptobiolin
|
||||
name: cryptobiolin
|
||||
desc: Causes confusion and dizziness. This is essential to make Spaceacillin.
|
||||
color: "#00e5ff"
|
||||
physicalDesc: fizzy
|
||||
color: "#081a80"
|
||||
|
||||
- type: reagent
|
||||
id: chem.HeartbreakerToxin
|
||||
name: heartbreaker toxin
|
||||
desc: A hallucinogenic compound that is illegal under space law. A synthetic drug derived from Mindbreaker toxin, it blocks some neurological signals to the respiratory system which causes choking.
|
||||
color: "#00e5ff"
|
||||
physicalDesc: strong-smelling
|
||||
color: "#5f959c"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Impedrezene
|
||||
name: impedrezene
|
||||
desc: A narcotic that impedes one's ability by slowing down the higher brain cell functions. Causes massive brain damage.
|
||||
physicalDesc: acrid
|
||||
color: "#215263"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Lexorin
|
||||
name: lexorin
|
||||
desc: Temporarily stops respiration and causes tissue damage. Large doses are fatal, and will cause people to pass out very quickly. Dexalin and Dexalin Plus will both remove it, however.
|
||||
color: "#a1000b"
|
||||
physicalDesc: pungent
|
||||
color: "#6b0007"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Lipozine
|
||||
name: lipozine
|
||||
desc: Causes weight loss upon consumption.
|
||||
color: "#215263"
|
||||
physicalDesc: oily
|
||||
color: "#2690b5"
|
||||
|
||||
- type: reagent
|
||||
id: chem.MindbreakerToxin
|
||||
name: mindbreaker toxin
|
||||
desc: A potent hallucinogenic compound that is illegal under space law. Formerly known as LSD.
|
||||
physicalDesc: opaque
|
||||
color: "#77b58e"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Soporific
|
||||
name: soporific (sleep-toxin)
|
||||
desc: A less powerful sedative that takes a while to work, intended to help insomniacs and patients that are too aggressive to be treated normally. Takes roughly 50 seconds to make the patient fall asleep. Is safe in large quantities. Can be counteracted with Anti-Toxin.
|
||||
physicalDesc: acrid
|
||||
color: "#215263"
|
||||
|
||||
- type: reagent
|
||||
id: chem.Sterilizine
|
||||
name: sterilizine
|
||||
desc: Helps the patient when used during surgery, may also decontaminate objects and surfaces that bear pathogens. Is currently useless due to infections not being a thing.
|
||||
color: "#215263"
|
||||
physicalDesc: strong-smelling
|
||||
color: "#7cad37"
|
||||
|
||||
- type: reagent
|
||||
id: chem.SpaceDrugs
|
||||
name: space drugs
|
||||
desc: An illegal compound which induces a number of effects such as loss of balance and visual artefacts.
|
||||
color: "#a1000b"
|
||||
physicalDesc: syrupy
|
||||
color: "#63806e"
|
||||
|
||||
@@ -100,4 +100,38 @@
|
||||
chem.Ice:
|
||||
amount: 1
|
||||
products:
|
||||
chem.Antifreeze: 4
|
||||
chem.Antifreeze: 4
|
||||
|
||||
- type: reaction
|
||||
id: react.Moonshine
|
||||
reactants:
|
||||
chem.Vodka:
|
||||
amount: 1
|
||||
chem.Tequila:
|
||||
amount: 1
|
||||
chem.Whiskey:
|
||||
amount: 1
|
||||
products:
|
||||
chem.Moonshine: 3
|
||||
|
||||
- type: reaction
|
||||
id: react.CreamyDelight
|
||||
reactants:
|
||||
chem.Moonshine:
|
||||
amount: 1
|
||||
chem.Cream:
|
||||
amount: 1
|
||||
chem.Wine:
|
||||
amount: 1
|
||||
products:
|
||||
chem.CreamyDelight: 3
|
||||
|
||||
- type: reaction
|
||||
id: react.LeanShine
|
||||
reactants:
|
||||
chem.Lean:
|
||||
amount: 1
|
||||
chem.Moonshine:
|
||||
amount: 1
|
||||
products:
|
||||
chem.LeanShine: 2
|
||||
Submodule RobustToolbox updated: 9cc9b68e09...4cbdde6ec3
Reference in New Issue
Block a user