diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index ae73f0f92a..8453efe74c 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -129,6 +129,7 @@ namespace Content.Client factory.Register(); prototypes.RegisterIgnore("material"); + prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side. IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Server/Chemistry/ExplosionReactionEffect.cs b/Content.Server/Chemistry/ExplosionReactionEffect.cs new file mode 100644 index 0000000000..0efa242f5d --- /dev/null +++ b/Content.Server/Chemistry/ExplosionReactionEffect.cs @@ -0,0 +1,70 @@ +using System; +using Content.Server.Explosions; +using Content.Server.GameObjects.Components.Explosive; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Shared.Interfaces; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry +{ + class ExplosionReactionEffect : IReactionEffect + { + private float _devastationRange; + private float _heavyImpactRange; + private float _lightImpactRange; + private float _flashRange; + + /// + /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount. + /// + private bool _scaled; + /// + /// Maximum scaling on ranges. For example, if it equals 5, then it won't scaled anywhere past + /// 5 times the minimum reactant amount. + /// + private float _maxScale; + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _devastationRange, "devastationRange", 1); + serializer.DataField(ref _heavyImpactRange, "heavyImpactRange", 2); + serializer.DataField(ref _lightImpactRange, "lightImpactRange", 3); + serializer.DataField(ref _flashRange, "flashRange", 0); + + serializer.DataField(ref _scaled, "scaled", false); + serializer.DataField(ref _maxScale, "maxScale", 1); + } + + public void React(IEntity solutionEntity, int intensity) + { + float floatIntensity = intensity; //Use float to avoid truncation in scaling + if (solutionEntity == null) + return; + if(!solutionEntity.TryGetComponent(out SolutionComponent solution)) + return; + + //Handle scaling + if (_scaled) + { + floatIntensity = Math.Min(floatIntensity, _maxScale); + } + else + { + floatIntensity = 1; + } + + //Calculate intensities + int finalDevastationRange = (int)Math.Round(_devastationRange * floatIntensity); + int finalHeavyImpactRange = (int)Math.Round(_heavyImpactRange * floatIntensity); + int finalLightImpactRange = (int)Math.Round(_lightImpactRange * floatIntensity); + int finalFlashRange = (int)Math.Round(_flashRange * floatIntensity); + ExplosionHelper.SpawnExplosion(solutionEntity.Transform.GridPosition, finalDevastationRange, + finalHeavyImpactRange, finalLightImpactRange, finalFlashRange); + } + } +} + + diff --git a/Content.Server/Chemistry/ReactionPrototype.cs b/Content.Server/Chemistry/ReactionPrototype.cs new file mode 100644 index 0000000000..f5c9c8974f --- /dev/null +++ b/Content.Server/Chemistry/ReactionPrototype.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using Content.Shared.Interfaces; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.Chemistry +{ + /// + /// Prototype for chemical reaction definitions + /// + [Prototype("reaction")] + public class ReactionPrototype : IPrototype, IIndexedPrototype + { + private string _id; + private string _name; + private Dictionary _reactants; + private Dictionary _products; + private List _effects; + + public string ID => _id; + public string Name => _name; + /// + /// Reactants required for the reaction to occur. + /// + public IReadOnlyDictionary Reactants => _reactants; + /// + /// Reagents created when the reaction occurs. + /// + public IReadOnlyDictionary Products => _products; + /// + /// Effects to be triggered when the reaction occurs. + /// + public IReadOnlyList Effects => _effects; + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _reactants, "reactants", new Dictionary()); + serializer.DataField(ref _products, "products", new Dictionary()); + serializer.DataField(ref _effects, "effects", new List()); + } + } + + /// + /// Prototype for chemical reaction reactants. + /// + public class ReactantPrototype : IExposeData + { + private int _amount; + private bool _catalyst; + + /// + /// Minimum amount of the reactant needed for the reaction to occur. + /// + public int Amount => _amount; + /// + /// Whether or not the reactant is a catalyst. Catalysts aren't removed when a reaction occurs. + /// + public bool Catalyst => _catalyst; + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _amount, "amount", 1); + serializer.DataField(ref _catalyst, "catalyst", false); + } + } +} diff --git a/Content.Server/Explosions/ExplosionHelper.cs b/Content.Server/Explosions/ExplosionHelper.cs new file mode 100644 index 0000000000..a67d1a35da --- /dev/null +++ b/Content.Server/Explosions/ExplosionHelper.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Maps; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects.EntitySystemMessages; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; + +namespace Content.Server.Explosions +{ + public static class ExplosionHelper + { + public static void SpawnExplosion(GridCoordinates coords, int devastationRange, int heavyImpactRange, int lightImpactRange, int flashRange) + { + var tileDefinitionManager = IoCManager.Resolve(); + var serverEntityManager = IoCManager.Resolve(); + var entitySystemManager = IoCManager.Resolve(); + var mapManager = IoCManager.Resolve(); + var robustRandom = IoCManager.Resolve(); + + var maxRange = MathHelper.Max(devastationRange, heavyImpactRange, lightImpactRange, 0f); + //Entity damage calculation + var entitiesAll = serverEntityManager.GetEntitiesInRange(coords, maxRange).ToList(); + + foreach (var entity in entitiesAll) + { + //if (entity == Owner) + // continue; + if (!entity.Transform.IsMapTransform) + continue; + var distanceFromEntity = (int)entity.Transform.GridPosition.Distance(mapManager, coords); + var exAct = entitySystemManager.GetEntitySystem(); + var severity = ExplosionSeverity.Destruction; + if (distanceFromEntity < devastationRange) + { + severity = ExplosionSeverity.Destruction; + } + else if (distanceFromEntity < heavyImpactRange) + { + severity = ExplosionSeverity.Heavy; + } + else if (distanceFromEntity < lightImpactRange) + { + severity = ExplosionSeverity.Light; + } + else + { + continue; + } + //exAct.HandleExplosion(Owner, entity, severity); + exAct.HandleExplosion(null, entity, severity); + } + + //Tile damage calculation mockup + //TODO: make it into some sort of actual damage component or whatever the boys think is appropriate + var mapGrid = mapManager.GetGrid(coords.GridID); + var circle = new Circle(coords.Position, maxRange); + var tiles = mapGrid.GetTilesIntersecting(circle); + foreach (var tile in tiles) + { + var tileLoc = mapGrid.GridTileToLocal(tile.GridIndices); + var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.Tile.TypeId]; + var distanceFromTile = (int)tileLoc.Distance(mapManager, coords); + if (!string.IsNullOrWhiteSpace(tileDef.SubFloor)) + { + if (distanceFromTile < devastationRange) + mapGrid.SetTile(tileLoc, new Tile(tileDefinitionManager["space"].TileId)); + if (distanceFromTile < heavyImpactRange) + { + if (robustRandom.Prob(80)) + { + mapGrid.SetTile(tileLoc, new Tile(tileDefinitionManager[tileDef.SubFloor].TileId)); + } + else + { + mapGrid.SetTile(tileLoc, new Tile(tileDefinitionManager["space"].TileId)); + } + } + if (distanceFromTile < lightImpactRange) + { + if (robustRandom.Prob(50)) + { + mapGrid.SetTile(tileLoc, new Tile(tileDefinitionManager[tileDef.SubFloor].TileId)); + } + } + } + } + + //Effects and sounds + var time = IoCManager.Resolve().CurTime; + var message = new EffectSystemMessage + { + EffectSprite = "Effects/explosion.rsi", + RsiState = "explosionfast", + Born = time, + DeathTime = time + TimeSpan.FromSeconds(5), + Size = new Vector2(flashRange / 2, flashRange / 2), + Coordinates = coords, + //Rotated from east facing + Rotation = 0f, + ColorDelta = new Vector4(0, 0, 0, -1500f), + Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), 0.5f), + Shaded = false + }; + entitySystemManager.GetEntitySystem().CreateParticle(message); + entitySystemManager.GetEntitySystem().Play("/Audio/effects/explosion.ogg", coords); + + // Knock back cameras of all players in the area. + + var playerManager = IoCManager.Resolve(); + //var selfPos = Owner.Transform.WorldPosition; //vec2 + var selfPos = coords.ToWorld(mapManager).Position; + foreach (var player in playerManager.GetAllPlayers()) + { + if (player.AttachedEntity == null + || player.AttachedEntity.Transform.MapID != mapGrid.ParentMapId + || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent recoil)) + { + continue; + } + + var playerPos = player.AttachedEntity.Transform.WorldPosition; + var delta = selfPos - playerPos; + var distance = delta.LengthSquared; + + var effect = 1 / (1 + 0.2f * distance); + if (effect > 0.01f) + { + var kick = -delta.Normalized * effect; + recoil.Kick(kick); + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index 9e30b2f288..ca49803cc0 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -9,7 +9,6 @@ using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Prototypes; @@ -40,6 +39,8 @@ namespace Content.Server.GameObjects.Components.Chemistry public bool HasBeaker => _beakerContainer.ContainedEntity != null; public int DispenseAmount = 10; + private SolutionComponent _solution => _beakerContainer.ContainedEntity.GetComponent(); + /// /// Shows the serializer how to save/load this components yaml prototype. /// @@ -153,7 +154,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// Gets current component data as a and sends it to the client. /// - private void UpdateUserInterface() + public void UpdateUserInterface() { var state = GetUserInterfaceState(); _userInterface.SetState(state); @@ -162,9 +163,10 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// If this component contains an entity with a , eject it. /// - private void TryEject() + public void TryEject() { if(!HasBeaker) return; + _solution.SolutionChanged -= HandleSolutionChangedEvent; _beakerContainer.Remove(_beakerContainer.ContainedEntity); UpdateUserInterface(); @@ -253,6 +255,7 @@ namespace Content.Server.GameObjects.Components.Chemistry else { _beakerContainer.Insert(activeHandEntity); + _solution.SolutionChanged += HandleSolutionChangedEvent; UpdateUserInterface(); } } @@ -264,5 +267,10 @@ namespace Content.Server.GameObjects.Components.Chemistry return true; } + + void HandleSolutionChangedEvent() + { + UpdateUserInterface(); + } } } diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 195fda0e20..1c5f0a0d77 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; +using Content.Server.Chemistry; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Chemistry; using Content.Shared.GameObjects; +using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -20,14 +23,26 @@ namespace Content.Server.GameObjects.Components.Chemistry #pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager; [Dependency] private readonly ILocalizationManager _localizationManager; + [Dependency] private readonly IEntitySystemManager _entitySystemManager; #pragma warning restore 649 + private IEnumerable _reactions; + private AudioSystem _audioSystem; + + protected override void Startup() + { + base.Startup(); + + _reactions = _prototypeManager.EnumeratePrototypes(); + _audioSystem = _entitySystemManager.GetEntitySystem(); + } + /// /// Transfers solution from the held container to the target container. /// [Verb] private sealed class FillTargetVerb : Verb - { + { protected override string GetText(IEntity user, SolutionComponent component) { if(!user.TryGetComponent(out var hands)) @@ -164,5 +179,116 @@ namespace Content.Server.GameObjects.Components.Chemistry handSolutionComp.TryAddSolution(transferSolution); } } + + private void CheckForReaction() + { + //Check the solution for every reaction + foreach (var reaction in _reactions) + { + if (SolutionValidReaction(reaction, out int unitReactions)) + { + PerformReaction(reaction, unitReactions); + break; //Only perform one reaction per solution per update. + } + } + } + + public bool TryAddReagent(string reagentId, int quantity, out int acceptedQuantity, bool skipReactionCheck = false) + { + if (quantity > _maxVolume - _containedSolution.TotalVolume) + { + acceptedQuantity = _maxVolume - _containedSolution.TotalVolume; + if (acceptedQuantity == 0) return false; + } + else + { + acceptedQuantity = quantity; + } + + _containedSolution.AddReagent(reagentId, acceptedQuantity); + RecalculateColor(); + if(!skipReactionCheck) + CheckForReaction(); + OnSolutionChanged(); + return true; + } + + public bool TryAddSolution(Solution solution, bool skipReactionCheck = false) + { + if (solution.TotalVolume > (_maxVolume - _containedSolution.TotalVolume)) + return false; + + _containedSolution.AddSolution(solution); + RecalculateColor(); + if(!skipReactionCheck) + CheckForReaction(); + OnSolutionChanged(); + return true; + } + + /// + /// Checks if a solution has the reactants required to cause a specified reaction. + /// + /// The solution to check for reaction conditions. + /// The reaction whose reactants will be checked for in the solution. + /// The number of times the reaction can occur with the given solution. + /// + private bool SolutionValidReaction(ReactionPrototype reaction, out int unitReactions) + { + unitReactions = int.MaxValue; //Set to some impossibly large number initially + foreach (var reactant in reaction.Reactants) + { + if (!ContainsReagent(reactant.Key, out int reagentQuantity)) + { + return false; + } + int currentUnitReactions = reagentQuantity / reactant.Value.Amount; + if (currentUnitReactions < unitReactions) + { + unitReactions = currentUnitReactions; + } + } + + if (unitReactions == 0) + { + return false; + } + else + { + return true; + } + } + + /// + /// Perform a reaction on a solution. This assumes all reaction criteria have already been checked and are met. + /// + /// Solution to be reacted. + /// Reaction to occur. + /// The number of times to cause this reaction. + private void PerformReaction(ReactionPrototype reaction, int unitReactions) + { + //Remove non-catalysts + foreach (var reactant in reaction.Reactants) + { + if (!reactant.Value.Catalyst) + { + int amountToRemove = unitReactions * reactant.Value.Amount; + TryRemoveReagent(reactant.Key, amountToRemove); + } + } + //Add products + foreach (var product in reaction.Products) + { + TryAddReagent(product.Key, (int)(unitReactions * product.Value), out int acceptedQuantity, true); + } + //Trigger reaction effects + foreach (var effect in reaction.Effects) + { + effect.React(Owner, unitReactions); + } + + //Play reaction sound client-side + _audioSystem.Play("/Audio/effects/chemistry/bubbles.ogg", Owner.Transform.GridPosition); + } } } diff --git a/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs b/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs index f091f32246..a21a5e8730 100644 --- a/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs +++ b/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs @@ -1,22 +1,12 @@ -using System; -using System.Linq; -using Content.Server.GameObjects.Components.Mobs; -using Content.Server.GameObjects.EntitySystems; -using Content.Shared.Maps; -using Robust.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; -using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.EntitySystemMessages; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; -using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Random; using Robust.Shared.Serialization; +using Content.Server.Explosions; namespace Content.Server.GameObjects.Components.Explosive { @@ -32,7 +22,7 @@ namespace Content.Server.GameObjects.Components.Explosive #pragma warning restore 649 public override string Name => "Explosive"; - + public int DevastationRange = 0; public int HeavyImpactRange = 0; public int LightImpactRange = 0; @@ -50,121 +40,13 @@ namespace Content.Server.GameObjects.Components.Explosive serializer.DataField(ref FlashRange, "flashRange", 0); } - private bool Explosion() + public bool Explosion() { //Prevent adjacent explosives from infinitely blowing each other up. if (_beingExploded) return true; _beingExploded = true; - var maxRange = MathHelper.Max(DevastationRange, HeavyImpactRange, LightImpactRange, 0f); - //Entity damage calculation - var entitiesAll = _serverEntityManager.GetEntitiesInRange(Owner.Transform.GridPosition, maxRange).ToList(); - - foreach (var entity in entitiesAll) - { - if (entity == Owner) - continue; - if (!entity.Transform.IsMapTransform) - continue; - var distanceFromEntity = (int)entity.Transform.GridPosition.Distance(_mapManager, Owner.Transform.GridPosition); - var exAct = _entitySystemManager.GetEntitySystem(); - var severity = ExplosionSeverity.Destruction; - if (distanceFromEntity < DevastationRange) - { - severity = ExplosionSeverity.Destruction; - } - else if (distanceFromEntity < HeavyImpactRange) - { - severity = ExplosionSeverity.Heavy; - } - else if (distanceFromEntity < LightImpactRange) - { - severity = ExplosionSeverity.Light; - } - else - { - continue; - } - exAct.HandleExplosion(Owner, entity, severity); - } - - //Tile damage calculation mockup - //TODO: make it into some sort of actual damage component or whatever the boys think is appropriate - var mapGrid = _mapManager.GetGrid(Owner.Transform.GridPosition.GridID); - var circle = new Circle(Owner.Transform.GridPosition.Position, maxRange); - var tiles = mapGrid.GetTilesIntersecting(circle); - foreach (var tile in tiles) - { - var tileLoc = mapGrid.GridTileToLocal(tile.GridIndices); - var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId]; - var distanceFromTile = (int)tileLoc.Distance(_mapManager, Owner.Transform.GridPosition); - if (!string.IsNullOrWhiteSpace(tileDef.SubFloor)) { - if (distanceFromTile < DevastationRange) - mapGrid.SetTile(tileLoc, new Tile(_tileDefinitionManager["space"].TileId)); - if (distanceFromTile < HeavyImpactRange) - { - if (_robustRandom.Prob(80)) - { - mapGrid.SetTile(tileLoc, new Tile(_tileDefinitionManager[tileDef.SubFloor].TileId)); - } - else - { - mapGrid.SetTile(tileLoc, new Tile(_tileDefinitionManager["space"].TileId)); - } - } - if (distanceFromTile < LightImpactRange) - { - if (_robustRandom.Prob(50)) - { - mapGrid.SetTile(tileLoc, new Tile(_tileDefinitionManager[tileDef.SubFloor].TileId)); - } - } - } - } - - //Effects and sounds - var time = IoCManager.Resolve().CurTime; - var message = new EffectSystemMessage - { - EffectSprite = "Effects/explosion.rsi", - RsiState = "explosionfast", - Born = time, - DeathTime = time + TimeSpan.FromSeconds(5), - Size = new Vector2(FlashRange / 2, FlashRange / 2), - Coordinates = Owner.Transform.GridPosition, - //Rotated from east facing - Rotation = 0f, - ColorDelta = new Vector4(0, 0, 0, -1500f), - Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), 0.5f), - Shaded = false - }; - _entitySystemManager.GetEntitySystem().CreateParticle(message); - _entitySystemManager.GetEntitySystem().Play("/Audio/effects/explosion.ogg", Owner); - - // Knock back cameras of all players in the area. - - var playerManager = IoCManager.Resolve(); - var selfPos = Owner.Transform.WorldPosition; - foreach (var player in playerManager.GetAllPlayers()) - { - if (player.AttachedEntity == null - || player.AttachedEntity.Transform.MapID != mapGrid.ParentMapId - || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent recoil)) - { - continue; - } - - var playerPos = player.AttachedEntity.Transform.WorldPosition; - var delta = selfPos - playerPos; - var distance = delta.LengthSquared; - - var effect = 1 / (1 + 0.2f * distance); - if (effect > 0.01f) - { - var kick = -delta.Normalized * effect; - recoil.Kick(kick); - } - } + ExplosionHelper.SpawnExplosion(Owner.Transform.GridPosition, DevastationRange, HeavyImpactRange, LightImpactRange, FlashRange); Owner.Delete(); return true; diff --git a/Content.Server/Interfaces/Chemistry/IReactionEffect.cs b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs new file mode 100644 index 0000000000..b3e388bb8a --- /dev/null +++ b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Interfaces +{ + /// + /// Chemical reaction effect on the world such as an explosion, EMP, or fire. + /// + public interface IReactionEffect : IExposeData + { + void React(IEntity solutionEntity, int intensity ); + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs index cc1df081b9..a81bb976ba 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -17,10 +17,15 @@ namespace Content.Shared.GameObjects.Components.Chemistry #pragma warning restore 649 [ViewVariables] - private Solution _containedSolution; - private int _maxVolume; + protected Solution _containedSolution; + protected int _maxVolume; private SolutionCaps _capabilities; + /// + /// Triggered when the solution contents change. + /// + public event Action SolutionChanged; + /// /// The maximum volume of the container. /// @@ -94,41 +99,35 @@ namespace Content.Shared.GameObjects.Components.Chemistry public void RemoveAllSolution() { _containedSolution.RemoveAllSolution(); + OnSolutionChanged(); } - public bool TryAddReagent(string reagentId, int quantity, out int acceptedQuantity) + public bool TryRemoveReagent(string reagentId, int quantity) { - if (quantity > _maxVolume - _containedSolution.TotalVolume) - { - acceptedQuantity = _maxVolume - _containedSolution.TotalVolume; - if (acceptedQuantity == 0) return false; - } - else - { - acceptedQuantity = quantity; - } + if (!ContainsReagent(reagentId, out var currentQuantity)) return false; - _containedSolution.AddReagent(reagentId, acceptedQuantity); - RecalculateColor(); + _containedSolution.RemoveReagent(reagentId, quantity); + OnSolutionChanged(); return true; } - public bool TryAddSolution(Solution solution) + public bool TryRemoveSolution(int quantity) { - if (solution.TotalVolume > (_maxVolume - _containedSolution.TotalVolume)) - return false; + if (CurrentVolume == 0) return false; - _containedSolution.AddSolution(solution); - RecalculateColor(); + _containedSolution.RemoveSolution(quantity); + OnSolutionChanged(); return true; } public Solution SplitSolution(int quantity) { - return _containedSolution.SplitSolution(quantity); + var solutionSplit = _containedSolution.SplitSolution(quantity); + OnSolutionChanged(); + return solutionSplit; } - private void RecalculateColor() + protected void RecalculateColor() { if(_containedSolution.TotalVolume == 0) SubstanceColor = Color.White; @@ -184,5 +183,30 @@ namespace Content.Shared.GameObjects.Components.Chemistry { public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { } } + + /// + /// Check if the solution contains the specified reagent. + /// + /// The reagent to check for. + /// Output the quantity of the reagent if it is contained, 0 if it isn't. + /// Return true if the solution contains the reagent. + public bool ContainsReagent(string reagentId, out int quantity) + { + foreach (var reagent in _containedSolution.Contents) + { + if (reagent.ReagentId == reagentId) + { + quantity = reagent.Quantity; + return true; + } + } + quantity = 0; + return false; + } + + protected virtual void OnSolutionChanged() + { + SolutionChanged?.Invoke(); + } } } diff --git a/Resources/Audio/effects/chemistry/bubbles.ogg b/Resources/Audio/effects/chemistry/bubbles.ogg new file mode 100644 index 0000000000..da117c4eea Binary files /dev/null and b/Resources/Audio/effects/chemistry/bubbles.ogg differ diff --git a/Resources/Prototypes/Entities/buildings/chem_dispenser.yml b/Resources/Prototypes/Entities/buildings/chem_dispenser.yml index 7b4c88c45c..042b6796b0 100644 --- a/Resources/Prototypes/Entities/buildings/chem_dispenser.yml +++ b/Resources/Prototypes/Entities/buildings/chem_dispenser.yml @@ -31,14 +31,24 @@ - type: reagentDispenserInventory id: ChemDispenserStandardInventory inventory: - - chem.H2 - - chem.O2 - - chem.S8 + - chem.H2SO4 + - chem.H2O + - chem.Ethanol + - chem.Glucose + - chem.H + - chem.O + - chem.S - chem.C - chem.Cu - - chem.N2 + - chem.N - chem.Fe - - chem.F2 + - chem.F - chem.Al - - chem.H2SO4 - - chem.H2O \ No newline at end of file + - chem.Si + - chem.Cl + - chem.Li + - chem.Hg + - chem.P + - chem.K + - chem.Ra + - chem.Na diff --git a/Resources/Prototypes/Reactions/chemicals.yml b/Resources/Prototypes/Reactions/chemicals.yml new file mode 100644 index 0000000000..36bfa2aa97 --- /dev/null +++ b/Resources/Prototypes/Reactions/chemicals.yml @@ -0,0 +1,136 @@ +- type: reaction + id: react.H2O + reactants: + chem.H: + amount: 1 + chem.O: + amount: 1 + products: + chem.H2O: 2 + +- type: reaction + id: react.Ammonia + reactants: + chem.H: + amount: 3 + chem.N: + amount: 1 + products: + chem.Ammonia: 4 + +- type: reaction + id: react.Bleach + reactants: + chem.TableSalt: + amount: 2 + chem.SpaceCleaner: + amount: 2 + chem.O: + amount: 1 + products: + chem.Bleach: 5 + +- type: reaction + id: react.Diethylamine + reactants: + chem.Ammonia: + amount: 1 + chem.Ethanol: + amount: 1 + products: + chem.Diethylamine: 2 + +- type: reaction + id: react.FoamingAgent + reactants: + chem.Li: + amount: 1 + chem.H: + amount: 1 + products: + chem.FoamingAgent: 2 + +- type: reaction + id: react.PolytrinicAcid + reactants: + chem.H2SO4: + amount: 1 + chem.Cl: + amount: 1 + chem.K: + amount: 1 + products: + chem.PolytrinicAcid: 3 + +- type: reaction + id: react.SpaceCleaner + reactants: + chem.Ammonia: + amount: 1 + chem.H2O: + amount: 1 + products: + chem.SpaceCleaner: 2 + +- type: reaction + id: react.SpaceLube + reactants: + chem.H2O: + amount: 1 + chem.Si: + amount: 1 + chem.O: + amount: 1 + products: + chem.SpaceLube: 3 + +- type: reaction + id: react.TableSalt + reactants: + chem.Cl: + amount: 1 + chem.Na: + amount: 1 + products: + chem.TableSalt: 2 + +- type: reaction + id: react.Thermite + reactants: + chem.Fe: + amount: 1 + chem.Al: + amount: 1 + chem.O: + amount: 1 + products: + chem.Thermite: 3 + +- type: reaction + id: react.UnstableMutagen + reactants: + chem.Ra: + amount: 1 + chem.P: + amount: 1 + chem.Cl: + amount: 1 + products: + chem.UnstableMutagen: 3 + +- type: reaction + id: react.PotassiumExplosion + reactants: + chem.H2O: + amount: 1 + chem.K: + amount: 1 + effects: + - !type:ExplosionReactionEffect + #Ranges used when 1 potassium + 1 water react (A unit reaction) + devastationRange: 0.05 + heavyImpactRange: 0.1 + lightImpactRange: 0.15 + flashRange: 0.2 + scaled: true #Scaled proportionally to amount of potassium and water + maxScale: 30 #Explosion strength stops scaling at 30 potassium + 30 water diff --git a/Resources/Prototypes/Reactions/medicine.yml b/Resources/Prototypes/Reactions/medicine.yml new file mode 100644 index 0000000000..7675eae989 --- /dev/null +++ b/Resources/Prototypes/Reactions/medicine.yml @@ -0,0 +1,471 @@ +- type: reaction + id: react.Alkycosine + reactants: + chem.Alkysine: + amount: 1 + chem.Bleach: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.Alkycosine: 3 + +- type: reaction + id: react.Alkysine + reactants: + chem.Cl: + amount: 1 + chem.N: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.Alkysine: 3 + +- type: reaction + id: react.Dylovene + reactants: + chem.Si: + amount: 1 + chem.N: + amount: 1 + chem.K: + amount: 1 + products: + chem.Dylovene: 3 + +- type: reaction + id: react.Arithrazine + reactants: + chem.Hyronalin: + amount: 1 + chem.H: + amount: 1 + products: + chem.Arithrazine: 2 + +- type: reaction + id: react.Bicaridine + reactants: + chem.Inaprovaline: + amount: 1 + chem.C: + amount: 1 + products: + chem.Bicaridine: 2 + +- type: reaction + id: react.Cryoxadone + reactants: + chem.Dexalin: + amount: 1 + chem.H2O: + amount: 1 + chem.O: + amount: 1 + products: + chem.Cryoxadone: 3 + +- type: reaction + id: react.Clonexadone + reactants: + chem.Cryoxadone: + amount: 1 + chem.Na: + amount: 1 + chem.Plasma: + amount: 5 + catalyst: true + products: + chem.Clonexadone: 2 + +- type: reaction + id: react.Citalopram + reactants: + chem.MindbreakerToxin: + amount: 5 + chem.C: + amount: 5 + products: + chem.Citalopram: 10 + +- type: reaction + id: react.Dermaline + reactants: + chem.Kelotane: + amount: 1 + chem.O: + amount: 1 + chem.P: + amount: 1 + products: + chem.Dermaline: 3 + +- type: reaction + id: react.Dexalin + reactants: + chem.O: + amount: 2 + chem.Plasma: + amount: 5 + catalyst: true + products: + chem.Dexalin: 3 + +- type: reaction + id: react.DexalinPlus + reactants: + chem.Dexalin: + amount: 1 + chem.C: + amount: 1 + chem.Fe: + amount: 1 + products: + chem.DexalinPlus: 3 + +- type: reaction + id: react.Ethylredoxrazine + reactants: + chem.O: + amount: 1 + chem.Dylovene: + amount: 1 + chem.C: + amount: 1 + products: + chem.Ethylredoxrazine: 3 + +- type: reaction + id: react.Hyperzine + reactants: + chem.Glucose: + amount: 1 + chem.P: + amount: 1 + chem.S: + amount: 1 + products: + chem.Hyperzine: 3 + +- type: reaction + id: react.Hyronalin + reactants: + chem.Ra: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.Hyronalin: 2 + +- type: reaction + id: react.Imidazoline + reactants: + chem.H: + amount: 1 + chem.C: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.Imidazoline: 3 + +- type: reaction + id: react.Inacusiate + reactants: + chem.Dylovene: + amount: 1 + chem.C: + amount: 1 + chem.H2O: + amount: 1 + products: + chem.Inacusiate: 3 + +- type: reaction + id: react.Inaprovaline + reactants: + chem.O: + amount: 1 + chem.C: + amount: 1 + chem.Glucose: + amount: 1 + products: + chem.Inaprovaline: 3 + +- type: reaction + id: react.Kelotane + reactants: + chem.Si: + amount: 1 + chem.C: + amount: 1 + products: + chem.Kelotane: 2 + +- type: reaction + id: react.Leporazine + reactants: + chem.Si: + amount: 1 + chem.Cu: + amount: 1 + chem.Plasma: + amount: 5 + catalyst: true + products: + chem.Leporazine: 2 + +- type: reaction + id: react.Methylin + reactants: + chem.H: + amount: 1 + chem.Cl: + amount: 1 + chem.Ethanol: + amount: 1 + chem.F: + amount: 5 + catalyst: true + products: + chem.Methylin: 3 + +- type: reaction + id: react.Oxycodone + reactants: + chem.Ethanol: + amount: 1 + chem.Tramadol: + amount: 1 + chem.Plasma: + amount: 1 + products: + chem.Oxycodone: 3 + +- type: reaction + id: react.Phalanximine + reactants: + chem.Hyronalin: + amount: 1 + chem.Ethanol: + amount: 1 + chem.UnstableMutagen: + amount: 1 + products: + chem.Phalanximine: 3 + +- type: reaction + id: react.Paroxetine + reactants: + chem.MindbreakerToxin: + amount: 5 + chem.O: + amount: 5 + chem.Inaprovaline: + amount: 5 + products: + chem.Paroxetine: 15 + +- type: reaction + id: react.Ryetalyn + reactants: + chem.Arithrazine: + amount: 1 + chem.C: + amount: 1 + products: + chem.Ryetalyn: 2 + +- type: reaction + id: react.Spaceacillin + reactants: + chem.Cryptobiolin: + amount: 1 + chem.Inaprovaline: + amount: 1 + products: + chem.Spaceacillin: 2 + +- type: reaction + id: react.Synaptizine + reactants: + chem.Li: + amount: 1 + chem.Glucose: + amount: 1 + chem.H2O: + amount: 1 + products: + chem.Synaptizine: 3 + +- type: reaction + id: react.Tramadol + reactants: + chem.Inaprovaline: + amount: 1 + chem.Ethanol: + amount: 1 + chem.O: + amount: 1 + products: + chem.Tramadol: 3 + +- type: reaction + id: react.Tricordrazine + reactants: + chem.Inaprovaline: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.Tricordrazine: 2 + +- type: reaction + id: react.Vaccine + reactants: + chem.Al: + amount: 1 + chem.Glucose: + amount: 1 + chem.H2O: + amount: 1 + products: + chem.Vaccine: 3 + +- type: reaction + id: react.Albuterol + reactants: + chem.Hyperzine: + amount: 1 + chem.Tramadol: + amount: 1 + products: + chem.Albuterol: 2 + +- type: reaction + id: react.ChloralHydrate + reactants: + chem.Ethanol: + amount: 1 + chem.Cl: + amount: 3 + chem.H2O: + amount: 1 + products: + chem.ChloralHydrate: 5 + +- type: reaction + id: react.Creatine #Add nutriment as ingredient (amount = 1) once that's a thing + reactants: + chem.Bicaridine: + amount: 1 + chem.Hyperzine: + amount: 1 + chem.UnstableMutagen: + amount: 1 + products: + chem.Creatine: 3 + +- type: reaction + id: react.Cryptobiolin + reactants: + chem.K: + amount: 1 + chem.O: + amount: 1 + chem.Glucose: + amount: 1 + products: + chem.Cryptobiolin: 3 + +- type: reaction + id: react.HeartbreakerToxin #heat to 37C once temperatures supported + reactants: + chem.Dexalin: + amount: 1 + chem.MindbreakerToxin: + amount: 1 + products: + chem.HeartbreakerToxin: 2 + +- type: reaction + id: react.Impedrezene + reactants: + chem.Hg: + amount: 1 + chem.O: + amount: 1 + chem.Glucose: + amount: 1 + products: + chem.Impedrezene: 3 + +- type: reaction + id: react.Lexorin + reactants: + chem.Ammonia: + amount: 1 + chem.Plasma: + amount: 1 + products: + chem.Lexorin: 2 + +- type: reaction + id: react.Lipozine + reactants: + chem.TableSalt: + amount: 1 + chem.Ethanol: + amount: 1 + chem.Ra: + amount: 1 + products: + chem.Lipozine: 3 + +- type: reaction + id: react.MindbreakerToxin + reactants: + chem.Si: + amount: 1 + chem.H: + amount: 1 + chem.Dylovene: + amount: 1 + products: + chem.MindbreakerToxin: 3 + +- type: reaction + id: react.Soporific + reactants: + chem.ChloralHydrate: + amount: 1 + chem.Glucose: + amount: 4 + products: + chem.Soporific: 5 + +- type: reaction + id: react.Sterilizine + reactants: + chem.Ethanol: + amount: 1 + chem.Dylovene: + amount: 1 + chem.Cl: + amount: 1 + products: + chem.Sterilizine: 3 + +- type: reaction + id: react.SpaceDrugs + reactants: + chem.Hg: + amount: 1 + chem.Glucose: + amount: 1 + chem.Li: + amount: 1 + products: + chem.SpaceDrugs: 3 diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index 3f67e7eac1..97271e737d 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -2,7 +2,7 @@ id: chem.H2SO4 name: Sulfuric Acid desc: A highly corrosive, oily, colorless liquid. - + - type: reagent id: chem.H2O name: Water @@ -12,4 +12,81 @@ id: chem.Ice name: Ice desc: Frozen water. - color: "#bed8e6" \ No newline at end of file + color: "#bed8e6" + +- type: reagent + id: chem.Plasma + name: Plasma + desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways. + +- type: reagent + id: chem.Ethanol + name: Ethanol + desc: A simple alcohol, makes you drunk if consumed, flammable. + color: "#b05b3c" + +- type: reagent + id: chem.Glucose + name: Glucose + desc: A simple sugar found in many foods. + color: "#ffffff" + +- type: reagent + 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 + color: "#77b58e" + +- type: reagent + 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. + color: "#a1000b" + +- type: reagent + id: chem.Diethylamine + name: Diethylamine + desc: A very potent fertilizer. + color: "#a1000b" + +- type: reagent + id: chem.FoamingAgent + name: Foaming Agent + desc: Makes foam such as that required in metal foam grenades + color: "#215263" + +- type: reagent + 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. + color: "#a1000b" + +- type: reagent + 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" + +- type: reagent + 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). + color: "#77b58e" + +- type: reagent + id: chem.TableSalt + name: Table Salt + desc: Commonly known as salt, Sodium Chloride is often used to season food or kill borers instantly. + color: "#a1000b" + +- type: reagent + 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" + +- type: reagent + 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" diff --git a/Resources/Prototypes/Reagents/elements.yml b/Resources/Prototypes/Reagents/elements.yml index 7a5fd241f7..938a27a4c7 100644 --- a/Resources/Prototypes/Reagents/elements.yml +++ b/Resources/Prototypes/Reagents/elements.yml @@ -1,19 +1,19 @@ - type: reagent - id: chem.H2 + id: chem.H name: Hydrogen desc: A light, flammable gas. - + - type: reagent - id: chem.O2 + id: chem.O name: Oxygen desc: An oxidizing, colorless gas. - + - type: reagent - id: chem.S8 + id: chem.S name: Sulfur desc: A yellow, crystalline solid. color: "#FFFACD" - + - type: reagent id: chem.C name: Carbon @@ -33,7 +33,7 @@ color: "#b05b3c" - type: reagent - id: chem.N2 + id: chem.N name: Nitrogen desc: A colorless, odorless unreactive gas. Highly stable. @@ -44,6 +44,54 @@ color: "#434b4d" - type: reagent - id: chem.F2 + id: chem.F name: Fluorine - desc: A highly toxic pale yellow gas. Extremely reactive. \ No newline at end of file + desc: A highly toxic pale yellow gas. Extremely reactive. + +- type: reagent + id: chem.Si + name: Silicon + desc: A hard and brittle crystalline solid with a blue-grey color. + color: "#364266" + +- type: reagent + id: chem.Cl + name: Chlorine + desc: A yellow-green gas which is toxic to humans. + color: "#a2ff00" + +- type: reagent + id: chem.Li + name: Lithium + desc: A soft, silvery-white alkali metal. It is highly reactive, and ignites if it makes contact with water. + color: "#c6c8cc" + +- type: reagent + id: chem.Hg + name: Mercury + desc: A silver metal which is liquid at room temperature. It is highly toxic to humans. + color: "#929296" + +- type: reagent + id: chem.P + name: Phosphorus + desc: A reactive metal used in pyrotechnics and weapons. + color: "#803330" + +- type: reagent + id: chem.K + name: Potassium + desc: A soft, silvery-white metal. Even more reactive than lithium. + color: "#c6c8cc" + +- type: reagent + 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. + color: "#00ff04" + +- type: reagent + id: chem.Na + name: Sodium + desc: A silvery-white alkali metal. Highly reactive in it's pure form. + color: "#c6c8cc" diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml new file mode 100644 index 0000000000..fc86317f2d --- /dev/null +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -0,0 +1,246 @@ +- type: reagent + 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. + 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" + +- 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. + 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. + 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. + 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. + 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" + +- type: reagent + id: chem.Citalopram + name: Citalopram + desc: Prevents hallucination slightly. + color: "#21693c" + +- type: reagent + id: chem.Dermaline + name: Dermaline + desc: An advanced chemical that is more effective at treating burns damage than Kelotane. + 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). + 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" + +- 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. + 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. + 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" + +- 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. + 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" + +- 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" + +- type: reagent + id: chem.Kelotane + name: Kelotane + desc: Treats burn damage and prevents infection. + 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. + 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. + color: "#a700c4" + +- type: reagent + id: chem.Oxycodone + name: Oxycodone + desc: A very effective painkiller, about 250% as strong as Tramadol. + 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. + color: "#c8ff75" + +- type: reagent + id: chem.Paroxetine + name: Paroxetine + desc: Prevents hallucination, but has a 10% chance of causing intense hallucinations. + color: "#c8ff75" + +- 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. + 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" + +- 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. + color: "#d49a2f" + + +- type: reagent + id: chem.Tramadol + name: Tramadol + desc: A simple, yet effective painkiller. Very effective for patients in shock. + 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. + 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" + +- 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" + +- 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" + +- 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. + color: "#a1000b" + +- type: reagent + id: chem.Cryptobiolin + name: Cryptobiolin + desc: Causes confusion and dizziness. This is essential to make Spaceacillin. + color: "#00e5ff" + +- 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" + +- 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. + 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" + +- type: reagent + id: chem.Lipozine + name: Lipozine + desc: Causes weight loss upon consumption. + color: "#215263" + +- type: reagent + id: chem.MindbreakerToxin + name: Mindbreaker Toxin + desc: A potent hallucinogenic compound that is illegal under space law. Formerly known as LSD. + 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. + 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" + +- 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"