diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index d5e3573148..28b79254c0 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -127,12 +127,17 @@
HorizontalExpand="True"
Orientation="Vertical">
-
-
-
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Materials/OreSiloSystem.cs b/Content.Client/Materials/OreSiloSystem.cs
new file mode 100644
index 0000000000..076a546d37
--- /dev/null
+++ b/Content.Client/Materials/OreSiloSystem.cs
@@ -0,0 +1,6 @@
+using Content.Shared.Materials.OreSilo;
+
+namespace Content.Client.Materials;
+
+///
+public sealed class OreSiloSystem : SharedOreSiloSystem;
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml b/Content.Client/Materials/UI/MaterialStorageControl.xaml
index 2be0f40aa5..d7503a61f3 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml
@@ -2,7 +2,10 @@
SizeFlagsStretchRatio="8"
HorizontalExpand="True"
VerticalExpand="True">
-
-
+
+
+
+
+
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
index 3cf1792c14..fd698d890f 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Materials;
+using Content.Shared.Materials.OreSilo;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -15,6 +16,7 @@ namespace Content.Client.Materials.UI;
public sealed partial class MaterialStorageControl : ScrollContainer
{
[Dependency] private readonly IEntityManager _entityManager = default!;
+ private readonly MaterialStorageSystem _materialStorage;
private EntityUid? _owner;
@@ -24,6 +26,8 @@ public sealed partial class MaterialStorageControl : ScrollContainer
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+
+ _materialStorage = _entityManager.System();
}
public void SetOwner(EntityUid owner)
@@ -45,7 +49,8 @@ public sealed partial class MaterialStorageControl : ScrollContainer
}
var canEject = materialStorage.CanEjectStoredMaterials;
- var mats = materialStorage.Storage;
+ var mats = _materialStorage.GetStoredMaterials((_owner.Value, materialStorage));
+
if (_currentMaterials.Equals(mats))
return;
@@ -89,5 +94,6 @@ public sealed partial class MaterialStorageControl : ScrollContainer
_currentMaterials = mats;
NoMatsLabel.Visible = MaterialList.ChildCount == 1;
+ SiloLinkedLabel.Visible = _entityManager.TryGetComponent(_owner.Value, out var client) && client.Silo != null;
}
}
diff --git a/Content.Client/Materials/UI/OreSiloBoundUserInterface.cs b/Content.Client/Materials/UI/OreSiloBoundUserInterface.cs
new file mode 100644
index 0000000000..32eed56d80
--- /dev/null
+++ b/Content.Client/Materials/UI/OreSiloBoundUserInterface.cs
@@ -0,0 +1,34 @@
+using Content.Shared.Materials.OreSilo;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Materials.UI;
+
+[UsedImplicitly]
+public sealed class OreSiloBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+ [ViewVariables]
+ private OreSiloMenu? _menu;
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = this.CreateWindow();
+ _menu.SetEntity(Owner);
+
+ _menu.OnClientEntryPressed += netEnt =>
+ {
+ SendPredictedMessage(new ToggleOreSiloClientMessage(netEnt));
+ };
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (state is not OreSiloBuiState msg)
+ return;
+ _menu?.Update(msg);
+ }
+}
diff --git a/Content.Client/Materials/UI/OreSiloMenu.xaml b/Content.Client/Materials/UI/OreSiloMenu.xaml
new file mode 100644
index 0000000000..3a8e0ef48a
--- /dev/null
+++ b/Content.Client/Materials/UI/OreSiloMenu.xaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Materials/UI/OreSiloMenu.xaml.cs b/Content.Client/Materials/UI/OreSiloMenu.xaml.cs
new file mode 100644
index 0000000000..69d1fa328e
--- /dev/null
+++ b/Content.Client/Materials/UI/OreSiloMenu.xaml.cs
@@ -0,0 +1,64 @@
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Materials.OreSilo;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Materials.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class OreSiloMenu : FancyWindow
+{
+ public event Action? OnClientEntryPressed;
+
+ public OreSiloMenu()
+ {
+ RobustXamlLoader.Load(this);
+
+ ClientList.OnItemSelected += args =>
+ {
+ var item = ClientList[args.ItemIndex];
+ // a little bit of null suppression makes me feel great! :-)
+ OnClientEntryPressed?.Invoke((NetEntity) item.Metadata!);
+ };
+ }
+
+ public void SetEntity(EntityUid uid)
+ {
+ Materials.SetOwner(uid);
+ }
+
+ public void Update(OreSiloBuiState state)
+ {
+ var items = new List();
+ var orderedClients = state.Clients.OrderBy(t => t.Item3).ThenBy(t => t.Item1.Id);
+ foreach (var (ent, _, _) in orderedClients)
+ {
+ items.Add(new ItemList.Item(ClientList)
+ {
+ Metadata = ent
+ });
+ }
+
+ ClientList.SetItems(items,
+ (item1, item2) =>
+ {
+ var ent1 = (NetEntity) item1.Metadata!;
+ var ent2 = (NetEntity) item2.Metadata!;
+ return ent1.CompareTo(ent2);
+ });
+
+ var entTextDict = state.Clients.Select(t => (t.Item1, t.Item2)).ToDictionary();
+ using var enumerator = ClientList.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ if (enumerator.Current.Metadata is not NetEntity ent)
+ continue;
+
+ if (entTextDict.TryGetValue(ent, out var text))
+ enumerator.Current.Text = text;
+ }
+ }
+}
+
diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
index 0676aea8e9..1a0097f17d 100644
--- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
+++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
@@ -61,14 +61,14 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
var oldProgress = component.Progress.ShallowClone();
component.Progress.Clear();
- var water = solution.GetTotalPrototypeQuantity(PuddleSystem.EvaporationReagents);
- if (water > FixedPoint2.Zero)
+ var mopReagent = solution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(solution));
+ if (mopReagent > FixedPoint2.Zero)
{
- component.Progress[solution.GetColorWithOnly(_prototype, PuddleSystem.EvaporationReagents)] = water.Float();
+ component.Progress[solution.GetColorWithOnly(_prototype, _puddleSystem.GetAbsorbentReagents(solution))] = mopReagent.Float();
}
- var otherColor = solution.GetColorWithout(_prototype, PuddleSystem.EvaporationReagents);
- var other = (solution.Volume - water).Float();
+ var otherColor = solution.GetColorWithout(_prototype, _puddleSystem.GetAbsorbentReagents(solution));
+ var other = (solution.Volume - mopReagent).Float();
if (other > 0f)
{
@@ -189,7 +189,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
}
// Prioritize transferring non-evaporatives if absorbent has any
- var contaminants = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, transferAmount, PuddleSystem.EvaporationReagents);
+ var contaminants = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, transferAmount, _puddleSystem.GetAbsorbentReagents(absorbentSoln.Comp.Solution));
if (contaminants.Volume > 0)
{
_solutionContainerSystem.TryAddSolution(refillableSoln, contaminants);
@@ -214,7 +214,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
Entity absorbentSoln,
Entity refillableSoln)
{
- var contaminantsFromAbsorbent = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, component.PickupAmount, PuddleSystem.EvaporationReagents);
+ var contaminantsFromAbsorbent = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, component.PickupAmount, _puddleSystem.GetAbsorbentReagents(absorbentSoln.Comp.Solution));
var absorbentSolution = absorbentSoln.Comp.Solution;
if (contaminantsFromAbsorbent.Volume == FixedPoint2.Zero && absorbentSolution.AvailableVolume == FixedPoint2.Zero)
@@ -231,7 +231,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
absorbentSolution.AvailableVolume;
var refillableSolution = refillableSoln.Comp.Solution;
- var waterFromRefillable = refillableSolution.SplitSolutionWithOnly(waterPulled, PuddleSystem.EvaporationReagents);
+ var waterFromRefillable = refillableSolution.SplitSolutionWithOnly(waterPulled, _puddleSystem.GetAbsorbentReagents(refillableSoln.Comp.Solution));
_solutionContainerSystem.UpdateChemicals(refillableSoln);
if (waterFromRefillable.Volume == FixedPoint2.Zero && contaminantsFromAbsorbent.Volume == FixedPoint2.Zero)
@@ -293,7 +293,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
// Check if we have any evaporative reagents on our absorber to transfer
var absorberSolution = absorberSoln.Comp.Solution;
- var available = absorberSolution.GetTotalPrototypeQuantity(PuddleSystem.EvaporationReagents);
+ var available = absorberSolution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(absorberSolution));
// No material
if (available == FixedPoint2.Zero)
@@ -305,8 +305,8 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
var transferMax = absorber.PickupAmount;
var transferAmount = available > transferMax ? transferMax : available;
- var puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, PuddleSystem.EvaporationReagents);
- var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, PuddleSystem.EvaporationReagents);
+ var puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution));
+ var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, _puddleSystem.GetAbsorbentReagents(absorberSolution));
// Do tile reactions first
var transform = Transform(target);
diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs
index d78950b1d5..f92504e74c 100644
--- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs
+++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs
@@ -1,4 +1,5 @@
using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
@@ -20,7 +21,7 @@ public sealed partial class PuddleSystem
return;
}
- if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero)
+ if (solution.GetTotalPrototypeQuantity(GetEvaporatingReagents(solution)) > FixedPoint2.Zero)
{
var evaporation = AddComp(uid);
evaporation.NextTick = _timing.CurTime + EvaporationCooldown;
@@ -45,8 +46,11 @@ public sealed partial class PuddleSystem
if (!_solutionContainerSystem.ResolveSolution(uid, puddle.SolutionName, ref puddle.Solution, out var puddleSolution))
continue;
- var reagentTick = evaporation.EvaporationAmount * EvaporationCooldown.TotalSeconds;
- puddleSolution.SplitSolutionWithOnly(reagentTick, EvaporationReagents);
+ foreach ((string evaporatingReagent, FixedPoint2 evaporatingSpeed) in GetEvaporationSpeeds(puddleSolution))
+ {
+ var reagentTick = evaporation.EvaporationAmount * EvaporationCooldown.TotalSeconds * evaporatingSpeed;
+ puddleSolution.SplitSolutionWithOnly(reagentTick, evaporatingReagent);
+ }
// Despawn if we're done
if (puddleSolution.Volume == FixedPoint2.Zero)
diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs
index 8c50d5fe9a..3a462dd4d5 100644
--- a/Content.Server/Materials/MaterialStorageSystem.cs
+++ b/Content.Server/Materials/MaterialStorageSystem.cs
@@ -102,14 +102,18 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
return false;
_audio.PlayPvs(storage.InsertingSound, receiver);
- _popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver),
- ("item", toInsert)), receiver);
+ _popup.PopupEntity(Loc.GetString("machine-insert-item",
+ ("user", user),
+ ("machine", receiver),
+ ("item", toInsert)),
+ receiver);
QueueDel(toInsert);
// Logging
TryComp(toInsert, out var stack);
var count = stack?.Count ?? 1;
- _adminLogger.Add(LogType.Action, LogImpact.Low,
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Low,
$"{ToPrettyString(user):player} inserted {count} {ToPrettyString(toInsert):inserted} into {ToPrettyString(receiver):receiver}");
return true;
}
diff --git a/Content.Server/Materials/OreSiloSystem.cs b/Content.Server/Materials/OreSiloSystem.cs
new file mode 100644
index 0000000000..87e6db16cf
--- /dev/null
+++ b/Content.Server/Materials/OreSiloSystem.cs
@@ -0,0 +1,119 @@
+using Content.Server.Pinpointer;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Materials.OreSilo;
+using Robust.Server.GameStates;
+using Robust.Shared.Player;
+
+namespace Content.Server.Materials;
+
+///
+public sealed class OreSiloSystem : SharedOreSiloSystem
+{
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly NavMapSystem _navMap = default!;
+ [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
+ [Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
+
+ private const float OreSiloPreloadRangeSquared = 225f; // ~1 screen
+
+ private readonly HashSet> _clientLookup = new();
+ private readonly HashSet<(NetEntity, string, string)> _clientInformation = new();
+ private readonly HashSet _silosToAdd = new();
+ private readonly HashSet _silosToRemove = new();
+
+ protected override void UpdateOreSiloUi(Entity ent)
+ {
+ if (!_userInterface.IsUiOpen(ent.Owner, OreSiloUiKey.Key))
+ return;
+ _clientLookup.Clear();
+ _clientInformation.Clear();
+
+ var xform = Transform(ent);
+
+ // Sneakily uses override with TComponent parameter
+ _entityLookup.GetEntitiesInRange(xform.Coordinates, ent.Comp.Range, _clientLookup);
+
+ foreach (var client in _clientLookup)
+ {
+ // don't show already-linked clients.
+ if (client.Comp.Silo is not null)
+ continue;
+
+ var netEnt = GetNetEntity(client);
+ var name = Identity.Name(client, EntityManager);
+ var beacon = _navMap.GetNearestBeaconString(client.Owner, onlyName: true);
+
+ var txt = Loc.GetString("ore-silo-ui-itemlist-entry",
+ ("name", name),
+ ("beacon", beacon),
+ ("linked", ent.Comp.Clients.Contains(client)),
+ ("inRange", true));
+
+ _clientInformation.Add((netEnt, txt, beacon));
+ }
+
+ // Get all clients of this silo, including those out of range.
+ foreach (var client in ent.Comp.Clients)
+ {
+ var netEnt = GetNetEntity(client);
+ var name = Identity.Name(client, EntityManager);
+ var beacon = _navMap.GetNearestBeaconString(client, onlyName: true);
+ var inRange = CanTransmitMaterials((ent, ent), client);
+
+ var txt = Loc.GetString("ore-silo-ui-itemlist-entry",
+ ("name", name),
+ ("beacon", beacon),
+ ("linked", ent.Comp.Clients.Contains(client)),
+ ("inRange", inRange));
+
+ _clientInformation.Add((netEnt, txt, beacon));
+ }
+
+ _userInterface.SetUiState(ent.Owner, OreSiloUiKey.Key, new OreSiloBuiState(_clientInformation));
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // Solving an annoying problem: we need to send the silo to people who are near the silo so that
+ // Things don't start wildly mispredicting. We do this as cheaply as possible via grid-based local-pos checks.
+ // Sloth okay-ed this in the interim until a better solution comes around.
+
+ var actorQuery = EntityQueryEnumerator();
+ while (actorQuery.MoveNext(out _, out var actorComp, out var actorXform))
+ {
+ _silosToAdd.Clear();
+ _silosToRemove.Clear();
+
+ var clientQuery = EntityQueryEnumerator();
+ while (clientQuery.MoveNext(out _, out var clientComp, out var clientXform))
+ {
+ if (clientComp.Silo == null)
+ continue;
+
+ // We limit it to same-grid checks only for peak perf
+ if (actorXform.GridUid != clientXform.GridUid)
+ continue;
+
+ if ((actorXform.LocalPosition - clientXform.LocalPosition).LengthSquared() <= OreSiloPreloadRangeSquared)
+ {
+ _silosToAdd.Add(clientComp.Silo.Value);
+ }
+ else
+ {
+ _silosToRemove.Add(clientComp.Silo.Value);
+ }
+ }
+
+ foreach (var toRemove in _silosToRemove)
+ {
+ _pvsOverride.RemoveSessionOverride(toRemove, actorComp.PlayerSession);
+ }
+ foreach (var toAdd in _silosToAdd)
+ {
+ _pvsOverride.AddSessionOverride(toAdd, actorComp.PlayerSession);
+ }
+ }
+ }
+}
diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs
index da9ab20a39..3ab0e8eff1 100644
--- a/Content.Server/Pinpointer/NavMapSystem.cs
+++ b/Content.Server/Pinpointer/NavMapSystem.cs
@@ -436,12 +436,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
/// to the position of from the nearest beacon.
///
[PublicAPI]
- public string GetNearestBeaconString(Entity ent)
+ public string GetNearestBeaconString(Entity ent, bool onlyName = false)
{
if (!Resolve(ent, ref ent.Comp))
return Loc.GetString("nav-beacon-pos-no-beacons");
- return GetNearestBeaconString(_transformSystem.GetMapCoordinates(ent, ent.Comp));
+ return GetNearestBeaconString(_transformSystem.GetMapCoordinates(ent, ent.Comp), onlyName);
}
///
@@ -449,11 +449,14 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
/// to from the nearest beacon.
///
- public string GetNearestBeaconString(MapCoordinates coordinates)
+ public string GetNearestBeaconString(MapCoordinates coordinates, bool onlyName = false)
{
if (!TryGetNearestBeacon(coordinates, out var beacon, out var pos))
return Loc.GetString("nav-beacon-pos-no-beacons");
+ if (onlyName)
+ return beacon.Value.Comp.Text!;
+
var gridOffset = Angle.Zero;
if (_mapManager.TryFindGridAt(pos.Value, out var grid, out _))
gridOffset = Transform(grid).LocalRotation;
diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
index 6a41ec1044..3b83e667e0 100644
--- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
+++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
@@ -243,7 +243,7 @@ public sealed partial class ShuttleSystem
}
}
- if (HasComp(shuttleUid))
+ if (HasComp(shuttleUid) || HasComp(shuttleUid))
{
reason = Loc.GetString("shuttle-console-prevent");
return false;
diff --git a/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs b/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs
index 0e9b0a34ef..5906bf3ed4 100644
--- a/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs
+++ b/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs
@@ -14,7 +14,7 @@ public sealed class MobsterAccentSystem : EntitySystem
private static readonly Regex RegexUpperAr = new(@"(?<=\w)A[Rr](?=\w)");
private static readonly Regex RegexFirstWord = new(@"^(\S+)");
private static readonly Regex RegexLastWord = new(@"(\S+)$");
-
+ private static readonly Regex RegexLastPunctuation = new(@"([.!?]+$)(?!.*[.!?])|(?(uid) || HasComp(uid))
+ return;
+
+ EnsureComp(uid, out PendingZombieComponent pendingComp);
+
+ pendingComp.GracePeriod = _random.Next(pendingComp.MinInitialInfectedGrace, pendingComp.MaxInitialInfectedGrace);
}
private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, MapInitEvent args)
@@ -210,7 +217,6 @@ namespace Content.Server.Zombies
}
component.NextTick = _timing.CurTime + TimeSpan.FromSeconds(1f);
- component.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace);
}
public override void Update(float frameTime)
diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
index 84cdb47494..cfca682e9b 100644
--- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
+++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
@@ -1,5 +1,6 @@
using System.Collections.Frozen;
using System.Linq;
+using Content.Shared.FixedPoint;
using System.Text.Json.Serialization;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Prototypes;
@@ -7,14 +8,12 @@ using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.EntityEffects;
using Content.Shared.Database;
-using Content.Shared.FixedPoint;
using Content.Shared.Nutrition;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Utility;
@@ -105,6 +104,18 @@ namespace Content.Shared.Chemistry.Reagent
[DataField]
public bool Slippery;
+ ///
+ /// The speed at which the reagent evaporates over time.
+ ///
+ [DataField]
+ public FixedPoint2 EvaporationSpeed = FixedPoint2.Zero;
+
+ ///
+ /// If this reagent can be used to mop up other reagents.
+ ///
+ [DataField]
+ public bool Absorbent = false;
+
///
/// How easily this reagent becomes fizzy when aggitated.
/// 0 - completely flat, 1 - fizzes up when nudged.
@@ -206,7 +217,7 @@ namespace Content.Shared.Chemistry.Reagent
.ToDictionary(x => x.Key, x => x.Item2);
if (proto.PlantMetabolisms.Count > 0)
{
- PlantMetabolisms = new List (proto.PlantMetabolisms
+ PlantMetabolisms = new List(proto.PlantMetabolisms
.Select(x => x.GuidebookEffectDescription(prototype, entSys))
.Where(x => x is not null)
.Select(x => x!)
diff --git a/Content.Shared/Fluids/Components/EvaporationComponent.cs b/Content.Shared/Fluids/Components/EvaporationComponent.cs
index 5545bd7be5..9b46629439 100644
--- a/Content.Shared/Fluids/Components/EvaporationComponent.cs
+++ b/Content.Shared/Fluids/Components/EvaporationComponent.cs
@@ -18,8 +18,8 @@ public sealed partial class EvaporationComponent : Component
public TimeSpan NextTick = TimeSpan.Zero;
///
- /// How much evaporation per second.
+ /// Evaporation factor. Multiplied by the evaporating speed of the reagent.
///
[DataField("evaporationAmount")]
- public FixedPoint2 EvaporationAmount = FixedPoint2.New(0.3);
+ public FixedPoint2 EvaporationAmount = FixedPoint2.New(1);
}
diff --git a/Content.Shared/Fluids/SharedPuddleSystem.Evaporation.cs b/Content.Shared/Fluids/SharedPuddleSystem.Evaporation.cs
index 023d024a05..8b937ed1a7 100644
--- a/Content.Shared/Fluids/SharedPuddleSystem.Evaporation.cs
+++ b/Content.Shared/Fluids/SharedPuddleSystem.Evaporation.cs
@@ -1,17 +1,52 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
namespace Content.Shared.Fluids;
public abstract partial class SharedPuddleSystem
{
- [ValidatePrototypeId]
- private const string Water = "Water";
+ public string[] GetEvaporatingReagents(Solution solution)
+ {
+ var evaporatingReagents = new List();
+ foreach (ReagentPrototype solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
+ {
+ if (solProto.EvaporationSpeed > FixedPoint2.Zero)
+ evaporatingReagents.Add(solProto.ID);
+ }
+ return evaporatingReagents.ToArray();
+ }
- public static readonly string[] EvaporationReagents = [Water];
+ public string[] GetAbsorbentReagents(Solution solution)
+ {
+ var absorbentReagents = new List();
+ foreach (ReagentPrototype solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
+ {
+ if (solProto.Absorbent)
+ absorbentReagents.Add(solProto.ID);
+ }
+ return absorbentReagents.ToArray();
+ }
public bool CanFullyEvaporate(Solution solution)
{
- return solution.GetTotalPrototypeQuantity(EvaporationReagents) == solution.Volume;
+ return solution.GetTotalPrototypeQuantity(GetEvaporatingReagents(solution)) == solution.Volume;
+ }
+
+ ///
+ /// Gets the evaporating speed of the reagents within a solution.
+ /// The speed at which a solution evaporates is the sum of the speed of all evaporating reagents in it.
+ ///
+ public Dictionary GetEvaporationSpeeds(Solution solution)
+ {
+ var evaporatingSpeeds = new Dictionary();
+ foreach (ReagentPrototype solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
+ {
+ if (solProto.EvaporationSpeed > FixedPoint2.Zero)
+ {
+ evaporatingSpeeds.Add(solProto.ID, solProto.EvaporationSpeed);
+ }
+ }
+ return evaporatingSpeeds;
}
}
diff --git a/Content.Shared/Fluids/SharedPuddleSystem.cs b/Content.Shared/Fluids/SharedPuddleSystem.cs
index f573c042c5..34ba8ba803 100644
--- a/Content.Shared/Fluids/SharedPuddleSystem.cs
+++ b/Content.Shared/Fluids/SharedPuddleSystem.cs
@@ -100,7 +100,7 @@ public abstract partial class SharedPuddleSystem : EntitySystem
{
if (CanFullyEvaporate(solution))
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating"));
- else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero)
+ else if (solution.GetTotalPrototypeQuantity(GetEvaporatingReagents(solution)) > FixedPoint2.Zero)
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial"));
else
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
diff --git a/Content.Shared/Materials/MaterialStorageComponent.cs b/Content.Shared/Materials/MaterialStorageComponent.cs
index 7d8dd5c749..1911b3de6f 100644
--- a/Content.Shared/Materials/MaterialStorageComponent.cs
+++ b/Content.Shared/Materials/MaterialStorageComponent.cs
@@ -75,6 +75,24 @@ public enum MaterialStorageVisuals : byte
Inserting
}
+///
+/// Collects all the materials stored on a
+///
+/// The entity holding all these materials
+/// A dictionary of all materials held
+/// An optional specifier. Non-local sources (silo, etc.) should not add materials when this is false.
+[ByRefEvent]
+public readonly record struct GetStoredMaterialsEvent(Entity Entity, Dictionary, int> Materials, bool LocalOnly);
+
+///
+/// After using materials, removes them from storage.
+///
+/// The entity that held the materials and is being used up
+/// A dictionary of the difference of materials left.
+/// An optional specifier. Non-local sources (silo, etc.) should not consume materials when this is false.
+[ByRefEvent]
+public readonly record struct ConsumeStoredMaterialsEvent(Entity Entity, Dictionary, int> Materials, bool LocalOnly);
+
///
/// event raised on the materialStorage when a material entity is inserted into it.
///
diff --git a/Content.Shared/Materials/OreSilo/OreSiloClientComponent.cs b/Content.Shared/Materials/OreSilo/OreSiloClientComponent.cs
new file mode 100644
index 0000000000..ff43c9c05a
--- /dev/null
+++ b/Content.Shared/Materials/OreSilo/OreSiloClientComponent.cs
@@ -0,0 +1,18 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Materials.OreSilo;
+
+///
+/// An entity with that interfaces with an .
+/// Used for tracking the connected silo.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedOreSiloSystem))]
+public sealed partial class OreSiloClientComponent : Component
+{
+ ///
+ /// The silo that this client pulls materials from.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? Silo;
+}
diff --git a/Content.Shared/Materials/OreSilo/OreSiloComponent.cs b/Content.Shared/Materials/OreSilo/OreSiloComponent.cs
new file mode 100644
index 0000000000..45c86b1845
--- /dev/null
+++ b/Content.Shared/Materials/OreSilo/OreSiloComponent.cs
@@ -0,0 +1,55 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Materials.OreSilo;
+
+///
+/// Provides additional materials to linked clients across long distances.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedOreSiloSystem))]
+public sealed partial class OreSiloComponent : Component
+{
+ ///
+ /// The that are connected to this silo.
+ ///
+ [DataField, AutoNetworkedField]
+ public HashSet Clients = new();
+
+ ///
+ /// The maximum distance you can be to the silo and still receive transmission.
+ ///
+ ///
+ /// Default value should be big enough to span a single large department.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Range = 20f;
+}
+
+[Serializable, NetSerializable]
+public sealed class OreSiloBuiState : BoundUserInterfaceState
+{
+ public readonly HashSet<(NetEntity, string, string)> Clients;
+
+ public OreSiloBuiState(HashSet<(NetEntity, string, string)> clients)
+ {
+ Clients = clients;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class ToggleOreSiloClientMessage : BoundUserInterfaceMessage
+{
+ public readonly NetEntity Client;
+
+ public ToggleOreSiloClientMessage(NetEntity client)
+ {
+ Client = client;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum OreSiloUiKey : byte
+{
+ Key
+}
diff --git a/Content.Shared/Materials/OreSilo/SharedOreSiloSystem.cs b/Content.Shared/Materials/OreSilo/SharedOreSiloSystem.cs
new file mode 100644
index 0000000000..729a710065
--- /dev/null
+++ b/Content.Shared/Materials/OreSilo/SharedOreSiloSystem.cs
@@ -0,0 +1,168 @@
+using Content.Shared.Power.EntitySystems;
+using JetBrains.Annotations;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Materials.OreSilo;
+
+public abstract class SharedOreSiloSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!;
+ [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ private EntityQuery _clientQuery;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnToggleOreSiloClient);
+ SubscribeLocalEvent(OnSiloShutdown);
+ Subs.BuiEvents(OreSiloUiKey.Key,
+ subs =>
+ {
+ subs.Event(OnBoundUIOpened);
+ });
+
+
+ SubscribeLocalEvent(OnGetStoredMaterials);
+ SubscribeLocalEvent(OnConsumeStoredMaterials);
+ SubscribeLocalEvent(OnClientShutdown);
+
+ _clientQuery = GetEntityQuery();
+ }
+
+ private void OnToggleOreSiloClient(Entity ent, ref ToggleOreSiloClientMessage args)
+ {
+ var client = GetEntity(args.Client);
+
+ if (!_clientQuery.TryComp(client, out var clientComp))
+ return;
+
+ if (ent.Comp.Clients.Contains(client)) // remove client
+ {
+ clientComp.Silo = null;
+ Dirty(client, clientComp);
+ ent.Comp.Clients.Remove(client);
+ Dirty(ent);
+
+ UpdateOreSiloUi(ent);
+ }
+ else // add client
+ {
+ if (!CanTransmitMaterials((ent, ent), client))
+ return;
+
+ var clientMats = _materialStorage.GetStoredMaterials(client, true);
+ var inverseMats = new Dictionary();
+ foreach (var (mat, amount) in clientMats)
+ {
+ inverseMats.Add(mat, -amount);
+ }
+ _materialStorage.TryChangeMaterialAmount(client, inverseMats, localOnly: true);
+ _materialStorage.TryChangeMaterialAmount(ent.Owner, clientMats);
+
+ ent.Comp.Clients.Add(client);
+ Dirty(ent);
+ clientComp.Silo = ent;
+ Dirty(client, clientComp);
+
+ UpdateOreSiloUi(ent);
+ }
+ }
+
+ private void OnBoundUIOpened(Entity ent, ref BoundUIOpenedEvent args)
+ {
+ UpdateOreSiloUi(ent);
+ }
+
+ private void OnSiloShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ foreach (var client in ent.Comp.Clients)
+ {
+ if (!_clientQuery.TryComp(client, out var comp))
+ continue;
+
+ comp.Silo = null;
+ Dirty(client, comp);
+ }
+ }
+
+ protected virtual void UpdateOreSiloUi(Entity ent)
+ {
+
+ }
+
+ private void OnGetStoredMaterials(Entity ent, ref GetStoredMaterialsEvent args)
+ {
+ if (args.LocalOnly)
+ return;
+
+ if (ent.Comp.Silo is not { } silo)
+ return;
+
+ if (!CanTransmitMaterials(silo, ent))
+ return;
+
+ var materials = _materialStorage.GetStoredMaterials(silo);
+
+ foreach (var (mat, amount) in materials)
+ {
+ // Don't supply materials that they don't usually have access to.
+ if (!_materialStorage.IsMaterialWhitelisted((args.Entity, args.Entity), mat))
+ continue;
+
+ var existing = args.Materials.GetOrNew(mat);
+ args.Materials[mat] = existing + amount;
+ }
+ }
+
+ private void OnConsumeStoredMaterials(Entity ent, ref ConsumeStoredMaterialsEvent args)
+ {
+ if (args.LocalOnly)
+ return;
+
+ if (ent.Comp.Silo is not { } silo || !TryComp(silo, out var materialStorage))
+ return;
+
+ if (!CanTransmitMaterials(silo, ent))
+ return;
+
+ foreach (var (mat, amount) in args.Materials)
+ {
+ if (!_materialStorage.TryChangeMaterialAmount(silo, mat, amount, materialStorage))
+ continue;
+ args.Materials[mat] = 0;
+ }
+ }
+
+ private void OnClientShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ if (!TryComp(ent.Comp.Silo, out var silo))
+ return;
+
+ silo.Clients.Remove(ent);
+ Dirty(ent.Comp.Silo.Value, silo);
+ UpdateOreSiloUi((ent.Comp.Silo.Value, silo));
+ }
+
+ ///
+ /// Checks if a given client fulfills the criteria to link/receive materials from an ore silo.
+ ///
+ [PublicAPI]
+ public bool CanTransmitMaterials(Entity silo, EntityUid client)
+ {
+ if (!Resolve(silo, ref silo.Comp))
+ return false;
+
+ if (!_powerReceiver.IsPowered(silo.Owner))
+ return false;
+
+ if (_transform.GetGrid(client) != _transform.GetGrid(silo.Owner))
+ return false;
+
+ if (!_transform.InRange(silo.Owner, client, silo.Comp.Range))
+ return false;
+
+ return true;
+ }
+}
diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
index 2544aacadd..2fc03dd997 100644
--- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs
+++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
@@ -1,7 +1,6 @@
using System.Linq;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
-using Content.Shared.Mobs;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
using JetBrains.Annotations;
@@ -58,16 +57,22 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
}
///
- /// Gets the volume of a specified material contained in this storage.
+ /// Gets all the materials stored on this entity
///
- ///
- ///
- ///
- /// The volume of the material
- [PublicAPI]
- public int GetMaterialAmount(EntityUid uid, MaterialPrototype material, MaterialStorageComponent? component = null)
+ ///
+ /// Include only materials held "locally", as determined by event subscribers
+ ///
+ public Dictionary, int> GetStoredMaterials(Entity ent, bool localOnly = false)
{
- return GetMaterialAmount(uid, material.ID, component);
+ if (!Resolve(ent, ref ent.Comp, false))
+ return new();
+
+ // clone so we don't modify by accident.
+ var mats = new Dictionary, int>(ent.Comp.Storage);
+ var ev = new GetStoredMaterialsEvent((ent, ent.Comp), mats, localOnly);
+ RaiseLocalEvent(ent, ref ev, true);
+
+ return ev.Materials;
}
///
@@ -76,12 +81,27 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
///
+ ///
/// The volume of the material
- public int GetMaterialAmount(EntityUid uid, string material, MaterialStorageComponent? component = null)
+ [PublicAPI]
+ public int GetMaterialAmount(EntityUid uid, MaterialPrototype material, MaterialStorageComponent? component = null, bool localOnly = false)
+ {
+ return GetMaterialAmount(uid, material.ID, component, localOnly);
+ }
+
+ ///
+ /// Gets the volume of a specified material contained in this storage.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The volume of the material
+ public int GetMaterialAmount(EntityUid uid, string material, MaterialStorageComponent? component = null, bool localOnly = false)
{
if (!Resolve(uid, ref component))
return 0; //you have nothing
- return component.Storage.GetValueOrDefault(material, 0);
+ return GetStoredMaterials((uid, component), localOnly).GetValueOrDefault(material, 0);
}
///
@@ -89,26 +109,43 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
///
+ ///
/// The volume of all materials in the storage
- public int GetTotalMaterialAmount(EntityUid uid, MaterialStorageComponent? component = null)
+ public int GetTotalMaterialAmount(EntityUid uid, MaterialStorageComponent? component = null, bool localOnly = false)
{
if (!Resolve(uid, ref component))
return 0;
- return component.Storage.Values.Sum();
+ return GetStoredMaterials((uid, component), localOnly).Values.Sum();
}
+ // TODO: Revisit this if we ever decide to do things with storage limits. As it stands, the feature is unused.
///
/// Tests if a specific amount of volume will fit in the storage.
///
///
///
///
+ ///
/// If the specified volume will fit
- public bool CanTakeVolume(EntityUid uid, int volume, MaterialStorageComponent? component = null)
+ public bool CanTakeVolume(EntityUid uid, int volume, MaterialStorageComponent? component = null, bool localOnly = false)
{
if (!Resolve(uid, ref component))
return false;
- return component.StorageLimit == null || GetTotalMaterialAmount(uid, component) + volume <= component.StorageLimit;
+ return component.StorageLimit == null || GetTotalMaterialAmount(uid, component, true) + volume <= component.StorageLimit;
+ }
+
+ ///
+ /// Checks if a certain material prototype is supported by this entity.
+ ///
+ public bool IsMaterialWhitelisted(Entity ent, ProtoId material)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (ent.Comp.MaterialWhiteList == null)
+ return true;
+
+ return ent.Comp.MaterialWhiteList.Contains(material);
}
///
@@ -118,8 +155,9 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
///
+ ///
/// If the amount can be changed
- public bool CanChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null)
+ public bool CanChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null, bool localOnly = false)
{
if (!Resolve(uid, ref component))
return false;
@@ -127,10 +165,10 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
if (!CanTakeVolume(uid, volume, component))
return false;
- if (component.MaterialWhiteList == null ? false : !component.MaterialWhiteList.Contains(materialId))
+ if (!IsMaterialWhitelisted((uid, component), materialId))
return false;
- var amount = component.Storage.GetValueOrDefault(materialId);
+ var amount = GetMaterialAmount(uid, materialId, component, localOnly);
return amount + volume >= 0;
}
@@ -140,14 +178,24 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
/// If the amount can be changed
- public bool CanChangeMaterialAmount(Entity entity, Dictionary materials)
+ ///
+ public bool CanChangeMaterialAmount(Entity entity, Dictionary materials, bool localOnly = false)
{
if (!Resolve(entity, ref entity.Comp))
return false;
+ var inVolume = materials.Values.Sum();
+ var stored = GetStoredMaterials((entity, entity.Comp), localOnly);
+
+ if (!CanTakeVolume(entity, inVolume, entity.Comp))
+ return false;
+
foreach (var (material, amount) in materials)
{
- if (!CanChangeMaterialAmount(entity, material, amount, entity.Comp))
+ if (!IsMaterialWhitelisted(entity, material))
+ return false;
+
+ if (stored.GetValueOrDefault(material) + amount < 0)
return false;
}
@@ -163,16 +211,27 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
///
+ ///
/// If it was successful
- public bool TryChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null, bool dirty = true)
+ public bool TryChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null, bool dirty = true, bool localOnly = false)
{
if (!Resolve(uid, ref component))
return false;
- if (!CanChangeMaterialAmount(uid, materialId, volume, component))
+
+ if (!CanChangeMaterialAmount(uid, materialId, volume, component, localOnly))
return false;
+ var changeEv = new ConsumeStoredMaterialsEvent((uid, component), new() {{materialId, volume}}, localOnly);
+ RaiseLocalEvent(uid, ref changeEv);
+ var remaining = changeEv.Materials.Values.First();
+
var existing = component.Storage.GetOrNew(materialId);
- existing += volume;
+
+ var localUpperLimit = component.StorageLimit == null ? int.MaxValue : component.StorageLimit.Value - existing;
+ var localLowerLimit = -existing;
+ var localChange = Math.Clamp(remaining, localLowerLimit, localUpperLimit);
+
+ existing += localChange;
if (existing == 0)
component.Storage.Remove(materialId);
@@ -191,23 +250,54 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
/// Changes the amount of a specific material in the storage.
/// Still respects the filters in place.
///
- ///
- ///
/// If the amount can be changed
- public bool TryChangeMaterialAmount(Entity entity, Dictionary materials)
+ public bool TryChangeMaterialAmount(Entity entity, Dictionary materials, bool localOnly = false)
+ {
+ return TryChangeMaterialAmount(entity, materials.Select(p => (new ProtoId(p.Key), p.Value)).ToDictionary(), localOnly);
+ }
+
+ ///
+ /// Changes the amount of a specific material in the storage.
+ /// Still respects the filters in place.
+ ///
+ /// If the amount can be changed
+ public bool TryChangeMaterialAmount(
+ Entity entity,
+ Dictionary, int> materials,
+ bool localOnly = false)
{
if (!Resolve(entity, ref entity.Comp))
return false;
- if (!CanChangeMaterialAmount(entity, materials))
- return false;
-
foreach (var (material, amount) in materials)
{
- if (!TryChangeMaterialAmount(entity, material, amount, entity.Comp, false))
+ if (!CanChangeMaterialAmount(entity, material, amount, entity))
return false;
}
+ var changeEv = new ConsumeStoredMaterialsEvent((entity, entity.Comp), materials, localOnly);
+ RaiseLocalEvent(entity, ref changeEv);
+
+ foreach (var (material, remaining) in changeEv.Materials)
+ {
+ var existing = entity.Comp.Storage.GetOrNew(material);
+
+ var localUpperLimit = entity.Comp.StorageLimit == null ? int.MaxValue : entity.Comp.StorageLimit.Value - existing;
+ var localLowerLimit = -existing;
+ var localChange = Math.Clamp(remaining, localLowerLimit, localUpperLimit);
+
+ existing += localChange;
+
+ if (existing == 0)
+ entity.Comp.Storage.Remove(material);
+ else
+ entity.Comp.Storage[material] = existing;
+
+ }
+
+ var ev = new MaterialAmountChangedEvent();
+ RaiseLocalEvent(entity, ref ev);
+
Dirty(entity, entity.Comp);
return true;
}
@@ -221,6 +311,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
/// The stored material volume to set the storage to.
/// The storage component on . Resolved automatically if not given.
/// True if it was successful (enough space etc).
+ [PublicAPI]
public bool TrySetMaterialAmount(
EntityUid uid,
string materialId,
@@ -268,7 +359,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
totalVolume += vol * multiplier;
}
- if (!CanTakeVolume(receiver, totalVolume, storage))
+ if (!CanTakeVolume(receiver, totalVolume, storage, localOnly: true))
return false;
foreach (var (mat, vol) in composition.MaterialComposition)
diff --git a/Content.Shared/Shuttles/Components/PreventFTLComponent.cs b/Content.Shared/Shuttles/Components/PreventFTLComponent.cs
new file mode 100644
index 0000000000..343e1ffb06
--- /dev/null
+++ b/Content.Shared/Shuttles/Components/PreventFTLComponent.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Shuttles.Components;
+
+///
+/// Add to grids that you do not want to FTL, but still might want to pilot.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PreventFTLComponent : Component;
diff --git a/Content.Shared/Zombies/PendingZombieComponent.cs b/Content.Shared/Zombies/PendingZombieComponent.cs
index 0fb61c84df..b199edeb00 100644
--- a/Content.Shared/Zombies/PendingZombieComponent.cs
+++ b/Content.Shared/Zombies/PendingZombieComponent.cs
@@ -17,7 +17,7 @@ public sealed partial class PendingZombieComponent : Component
{
DamageDict = new ()
{
- { "Poison", 0.2 },
+ { "Poison", 0.4 },
}
};
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index f491491794..1748858c5d 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -1,57 +1,4 @@
Entries:
-- author: Beck Thompson
- changes:
- - message: Defibrillator cooldowns are now clearly visible.
- type: Tweak
- id: 7740
- time: '2024-12-20T21:26:56.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/31251
-- author: J C Denton
- changes:
- - message: Syndicate and NanoTrasen NPCs and turrets are now hostile to Dragons
- and Carps
- type: Tweak
- id: 7741
- time: '2024-12-20T21:38:27.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/32515
-- author: lzk228
- changes:
- - message: Item's tool function now shows on examine.
- type: Add
- id: 7742
- time: '2024-12-20T22:15:40.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/32436
-- author: Pinkbat5
- changes:
- - message: Diona can now chirp.
- type: Add
- - message: Nymphs can scream and laugh like adult diona.
- type: Add
- id: 7743
- time: '2024-12-20T22:34:44.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/32511
-- author: ScarKy0
- changes:
- - message: Syndicate uplinks now have 6 discounts instead of 3.
- type: Tweak
- id: 7744
- time: '2024-12-21T02:14:08.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/33950
-- author: JustinWinningham
- changes:
- - message: Wood walls no longer require girders and are built from barricades instead.
- type: Tweak
- id: 7745
- time: '2024-12-21T03:28:46.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/33902
-- author: ElectroJr
- changes:
- - message: Toolshed console commands have been reworked. Some old commands may no
- longer work.
- type: Tweak
- id: 7746
- time: '2024-12-21T06:45:48.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/33598
- author: muburu
changes:
- message: Space Ninjas now have silent footsteps.
@@ -3912,3 +3859,56 @@
id: 8240
time: '2025-04-18T23:26:44.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32396
+- author: ScarKy0
+ changes:
+ - message: Changed the air vent and scrubber sprites.
+ type: Tweak
+ id: 8241
+ time: '2025-04-18T23:34:59.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/34223
+- author: EmoGarbage404
+ changes:
+ - message: Added the material silo. This machine allows nearby machines to share
+ a pool of resources, cutting down on micromanagement.
+ type: Add
+ id: 8242
+ time: '2025-04-18T23:43:17.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/36492
+- author: Onezero0
+ changes:
+ - message: Modular Grenades can now fit inside Security belts and carriers.
+ type: Tweak
+ id: 8243
+ time: '2025-04-19T00:04:44.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/34169
+- author: Alpaccalypse
+ changes:
+ - message: The cocktail "Irish slammer" has been renamed to "Grenade Penguin".
+ type: Tweak
+ id: 8244
+ time: '2025-04-19T00:24:26.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/34181
+- author: ScarKy0
+ changes:
+ - message: Added a couple more tools to the artifact borg module.
+ type: Tweak
+ id: 8245
+ time: '2025-04-19T00:25:29.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/33338
+- author: august-sun
+ changes:
+ - message: Resprited the Chief Engineer's mantle.
+ type: Tweak
+ id: 8246
+ time: '2025-04-19T00:26:24.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/36697
+- author: IProduceWidgets
+ changes:
+ - message: Vox are no longer immune to the zombie virus under certain circumstances
+ type: Fix
+ - message: The zombie virus no longer provides non-Initial Infected an undue grace
+ period before poisoning them.
+ type: Fix
+ id: 8247
+ time: '2025-04-19T00:38:33.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/34472
diff --git a/Resources/Locale/en-US/_strings/lathe/ui/lathe-menu.ftl b/Resources/Locale/en-US/_strings/lathe/ui/lathe-menu.ftl
index 907baf4ffc..076a70447c 100644
--- a/Resources/Locale/en-US/_strings/lathe/ui/lathe-menu.ftl
+++ b/Resources/Locale/en-US/_strings/lathe/ui/lathe-menu.ftl
@@ -25,6 +25,7 @@ lathe-menu-material-amount-missing = { $amount ->
*[other] {NATURALFIXED($amount, 2)} {MAKEPLURAL($unit)} of {$material} ([color=red]{NATURALFIXED($missingAmount, 2)} {MAKEPLURAL($unit)} missing[/color])
}
lathe-menu-no-materials-message = No materials loaded.
+lathe-menu-silo-linked-message = Silo Linked
lathe-menu-fabricating-message = Fabricating...
lathe-menu-materials-title = Materials
lathe-menu-queue-title = Build Queue
diff --git a/Resources/Locale/en-US/_strings/materials/silo.ftl b/Resources/Locale/en-US/_strings/materials/silo.ftl
new file mode 100644
index 0000000000..32d7de8b2e
--- /dev/null
+++ b/Resources/Locale/en-US/_strings/materials/silo.ftl
@@ -0,0 +1,10 @@
+ore-silo-ui-title = Material Silo
+ore-silo-ui-label-clients = Machines
+ore-silo-ui-label-mats = Materials
+ore-silo-ui-itemlist-entry = {$linked ->
+ [true] {"[Linked] "}
+ *[False] {""}
+} {$name} ({$beacon}) {$inRange ->
+ [true] {""}
+ *[false] (Out of Range)
+}
diff --git a/Resources/Locale/en-US/_strings/reagents/meta/consumable/drink/alcohol.ftl b/Resources/Locale/en-US/_strings/reagents/meta/consumable/drink/alcohol.ftl
index 962f0898b1..5a22328900 100644
--- a/Resources/Locale/en-US/_strings/reagents/meta/consumable/drink/alcohol.ftl
+++ b/Resources/Locale/en-US/_strings/reagents/meta/consumable/drink/alcohol.ftl
@@ -157,8 +157,8 @@ reagent-desc-hooch = Either someone's failure at cocktail making or attempt in a
reagent-name-iced-beer = iced beer
reagent-desc-iced-beer = A beer which is so cold the air around it freezes.
-reagent-name-irish-slammer = Irish slammer
-reagent-desc-irish-slammer = An unconventional mixture of irish cream and stout.
+reagent-name-irish-slammer = Grenade Penguin
+reagent-desc-irish-slammer = What's black and white and red all over?
reagent-name-irish-cream = Irish cream
reagent-desc-irish-cream = Whiskey-imbued cream. What else could you expect from the Irish.
diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml b/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml
index 480cb0b7a4..9ec0726de0 100644
--- a/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml
+++ b/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml
@@ -98,6 +98,16 @@
category: cargoproduct-category-name-materials
group: market
+- type: cargoProduct
+ id: MaterialSilo
+ icon:
+ sprite: Structures/Machines/silo.rsi
+ state: silo
+ product: CrateMaterialSilo
+ cost: 5000
+ category: cargoproduct-category-name-materials
+ group: market
+
- type: cargoProduct
id: MaterialFuelTank
icon:
diff --git a/Resources/Prototypes/Catalog/Fills/Crates/materials.yml b/Resources/Prototypes/Catalog/Fills/Crates/materials.yml
index f6eb9a17af..80c6311f13 100644
--- a/Resources/Prototypes/Catalog/Fills/Crates/materials.yml
+++ b/Resources/Prototypes/Catalog/Fills/Crates/materials.yml
@@ -153,6 +153,22 @@
# for some reason, the selector here adds 1 to whatever value it generates,
# so this is actually 2-4
+- type: entity
+ id: CrateMaterialSilo
+ parent: CrateGenericSteel
+ name: material silo crate
+ description: A package including all the materials to create a material silo.
+ components:
+ - type: StorageFill
+ contents:
+ - id: MaterialSiloMachineCircuitboard
+ - id: SheetSteel1
+ amount: 5
+ - id: MatterBinStockPart
+ amount: 4
+ - id: CableApcStack1
+ amount: 2
+
- type: entity
id: CrateMaterialBasicResource
parent: CrateGenericSteel
diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml
index a5702cba11..f57db69dc0 100644
--- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml
+++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml
@@ -513,6 +513,7 @@
- MagazineMagnum
- CombatKnife
- Truncheon
+ - HandGrenade
components:
- Stunbaton
- FlashOnTrigger
diff --git a/Resources/Prototypes/Entities/Clothing/Neck/mantles.yml b/Resources/Prototypes/Entities/Clothing/Neck/mantles.yml
index e5d4c53163..586cc5e665 100644
--- a/Resources/Prototypes/Entities/Clothing/Neck/mantles.yml
+++ b/Resources/Prototypes/Entities/Clothing/Neck/mantles.yml
@@ -12,8 +12,8 @@
- type: entity
parent: [ClothingNeckBase, BaseCommandContraband]
id: ClothingNeckMantleCE
- name: chief engineer's mantle
- description: High visibility, check. RIG system, check. High capacity cell, check. Everything a chief engineer could need in a stylish mantle.
+ name: chief engineer's manica
+ description: A segmented arm covering for the experienced Chief Engineer, to signify the hazards of the job and the dangers faced to earn it.
components:
- type: Sprite
sprite: Clothing/Neck/mantles/cemantle.rsi
diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
index b88211e02b..55543c8cc0 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
@@ -46,7 +46,7 @@
damage:
types:
Heat: -0.07
- Poison: -0.2
+ Poison: -0.2 # needs to be less than the PendingZombieComponent does or they never become zombies by the disease.
groups:
Brute: -0.07
- type: DamageVisuals
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml
index 3a6dd7344e..2a908f6b04 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml
@@ -1124,7 +1124,7 @@
- type: entity
parent: DrinkGlass
id: DrinkIrishSlammer
- suffix: irish slammer
+ suffix: grenade penguin
components:
- type: SolutionContainerManager
solutions:
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index 812b495ee9..48af55da3f 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -1005,6 +1005,19 @@
Manipulator: 1
Steel: 1
+- type: entity
+ id: MaterialSiloMachineCircuitboard
+ parent: BaseMachineCircuitboard
+ name: material silo machine board
+ components:
+ - type: Sprite
+ state: supply
+ - type: MachineBoard
+ prototype: MachineMaterialSilo
+ stackRequirements:
+ MatterBin: 4
+ Cable: 1
+
- type: entity
id: OreProcessorMachineCircuitboard
parent: BaseMachineCircuitboard
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
index 2284208c25..34d122e9c5 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
@@ -601,6 +601,11 @@
- type: ItemBorgModule
items:
- NodeScanner
+ - Multitool
+ - Wrench
+ - SprayBottle
+ - Beaker
+ - Syringe
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: node-scanner-module }
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
index 23a5cd4013..eb251f596c 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
@@ -35,6 +35,9 @@
enum.ConstructionVisuals.Layer:
Primed: { state: primed }
Unprimed: { state: icon }
+ - type: Tag
+ tags:
+ - HandGrenade
- type: entity
name: explosive grenade
@@ -85,6 +88,7 @@
- Antagonists
- type: Tag
tags:
+ - HandGrenade
- GrenadeFlashBang
- type: entity
@@ -314,6 +318,9 @@
map: [ "enum.ConstructionVisuals.Layer" ]
- type: Item
size: Small
+ - type: Tag
+ tags:
+ - HandGrenade
- type: PayloadCase
- type: Construction
graph: ModularGrenadeGraph
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml
index 6a111ef1c8..41fad224c4 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml
@@ -25,6 +25,9 @@
enum.ConstructionVisuals.Layer:
Primed: { state: primed }
Unprimed: { state: icon }
+ - type: Tag
+ tags:
+ - HandGrenade
- type: entity
parent: [ProjectileGrenadeBase, BaseSecurityContraband]
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml
index 38fde5c540..a3087f1f9e 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml
@@ -18,6 +18,9 @@
behaviors:
- !type:TriggerBehavior
- type: ScatteringGrenade
+ - type: Tag
+ tags:
+ - HandGrenade
- type: entity
parent: [ScatteringGrenadeBase, BaseSecurityContraband]
diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml
index 3bc961eb2f..464664faa3 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml
@@ -270,6 +270,7 @@
- Sheet
materialWhiteList:
- Plasma
+ - type: OreSiloClient
- type: Fixtures
fixtures:
fix1:
diff --git a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
index 78f1504003..f7f1bcba1b 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
@@ -44,6 +44,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: AmbientSound
enabled: false
volume: 5
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 9d716888c0..79a5d1047e 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -112,6 +112,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: Lathe
idleState: icon
runningState: building
@@ -185,6 +186,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: Lathe
idleState: icon
runningState: building
@@ -283,6 +285,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: RequireProjectileTarget
- type: entity
@@ -333,6 +336,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: GuideHelp
guides:
- Robotics
@@ -422,6 +426,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: LatheAnnouncing
channels: [Security]
@@ -457,6 +462,7 @@
- Sheet
- RawMaterial
- Ingot
+ - type: OreSiloClient
- type: entity
id: MedicalTechFab
@@ -497,6 +503,7 @@
board: MedicalTechFabCircuitboard
- type: StealTarget
stealGroup: MedicalTechFabCircuitboard
+ - type: OreSiloClient
- type: LatheAnnouncing
channels: [Medical]
diff --git a/Resources/Prototypes/Entities/Structures/Machines/silo.yml b/Resources/Prototypes/Entities/Structures/Machines/silo.yml
new file mode 100644
index 0000000000..31bfe42572
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Machines/silo.yml
@@ -0,0 +1,61 @@
+- type: entity
+ id: MachineMaterialSilo
+ parent: [ BaseMachinePowered, ConstructibleMachine ]
+ name: material silo
+ description: An advanced machine, capable of using bluespace technology to transmit materials to nearby machines.
+ components:
+ - type: Sprite
+ sprite: Structures/Machines/silo.rsi
+ layers:
+ - state: silo
+ map: [ "base" ]
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.PowerDeviceVisuals.Powered:
+ base:
+ True: { state: silo_active }
+ False: { state: silo }
+ - type: OreSilo
+ - type: MaterialStorage
+ whitelist:
+ tags:
+ - Sheet
+ - Ingot
+ - type: ActivatableUI
+ key: enum.OreSiloUiKey.Key
+ - type: ActivatableUIRequiresPower
+ - type: UserInterface
+ interfaces:
+ enum.OreSiloUiKey.Key:
+ type: OreSiloBoundUserInterface
+ - type: Machine
+ board: MaterialSiloMachineCircuitboard
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.4,-0.4,0.4,0.4"
+ density: 190
+ mask:
+ - MachineMask
+ layer:
+ - MachineLayer
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 300
+ behaviors:
+ - !type:PlaySoundBehavior
+ sound:
+ collection: MetalBreak
+ - !type:ChangeConstructionNodeBehavior
+ node: machineFrame
+ - !type:DoActsBehavior
+ acts: ["Destruction"]
+ - type: WiresVisuals
+ - type: WiresPanel
+ - type: StaticPrice
+ price: 1500
diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
index 6c1451f1fd..21e53910c5 100644
--- a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
+++ b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
@@ -500,6 +500,8 @@
parent: BaseDrink
desc: reagent-desc-water
slippery: true
+ evaporationSpeed: 0.3
+ absorbent: true
physicalDesc: reagent-physical-desc-translucent
flavor: water
color: "#75b1f0"
diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml
index a46dfa2969..b307166aa8 100644
--- a/Resources/Prototypes/Recipes/Reactions/drinks.yml
+++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml
@@ -573,6 +573,8 @@
amount: 1
products:
IrishSlammer: 2
+ sound:
+ path: /Audio/Animals/penguin_squawk.ogg # Cocktail is called grenade penguin in localisation
- type: reaction
id: IrishCoffee
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index 475f505699..4fa1b7b2b2 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -641,6 +641,9 @@
- type: Tag
id: Handcuffs
+- type: Tag
+ id: HandGrenade
+
- type: Tag
id: HappyHonk
diff --git a/Resources/Textures/Structures/Machines/silo.rsi/meta.json b/Resources/Textures/Structures/Machines/silo.rsi/meta.json
new file mode 100644
index 0000000000..aa78616897
--- /dev/null
+++ b/Resources/Textures/Structures/Machines/silo.rsi/meta.json
@@ -0,0 +1,40 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/d74b67828394a9842578279a6b8ab2955bb08216. Created by MrDoomBringer (github)",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "silo"
+ },
+ {
+ "name": "silo_active",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "overlay_active",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Structures/Machines/silo.rsi/overlay_active.png b/Resources/Textures/Structures/Machines/silo.rsi/overlay_active.png
new file mode 100644
index 0000000000..0a06e45676
Binary files /dev/null and b/Resources/Textures/Structures/Machines/silo.rsi/overlay_active.png differ
diff --git a/Resources/Textures/Structures/Machines/silo.rsi/silo.png b/Resources/Textures/Structures/Machines/silo.rsi/silo.png
new file mode 100644
index 0000000000..45fe37ac5f
Binary files /dev/null and b/Resources/Textures/Structures/Machines/silo.rsi/silo.png differ
diff --git a/Resources/Textures/Structures/Machines/silo.rsi/silo_active.png b/Resources/Textures/Structures/Machines/silo.rsi/silo_active.png
new file mode 100644
index 0000000000..f25218b2d2
Binary files /dev/null and b/Resources/Textures/Structures/Machines/silo.rsi/silo_active.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/meta.json
index 191aa398a9..88eeace3d8 100644
--- a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/meta.json
+++ b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/meta.json
@@ -1,34 +1,133 @@
{
- "version":1,
- "size":{
- "x":32,
- "y":32
- },
- "license":"CC-BY-SA-3.0",
- "copyright":"Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da",
- "states":[
- {
- "name":"scrub_off",
- "directions": 4
- },
- {
- "name":"scrub_welded",
- "directions": 4
- },
- {
- "name":"scrub_on",
- "directions": 4,
- "delays": [[0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08], [0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08], [0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08], [0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08]]
- },
- {
- "name": "scrub_purge",
- "directions": 4,
- "delays": [[0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04]]
- },
- {
- "name": "scrub_wide",
- "directions": 4,
- "delays": [[0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04], [0.2, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04]]
- }
- ]
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/32742/commits/44820f5b6e2c864a35bfad448e0a9437f01ad2e2",
+ "states": [
+ {
+ "name": "scrub_off",
+ "directions": 4
+ },
+ {
+ "name": "scrub_welded",
+ "directions": 4
+ },
+ {
+ "name": "scrub_on",
+ "directions": 4,
+ "delays": [
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ]
+ ]
+ },
+ {
+ "name": "scrub_purge",
+ "directions": 4,
+ "delays": [
+ [
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06
+ ],
+ [
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06
+ ],
+ [
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06
+ ],
+ [
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06,
+ 0.06
+ ]
+ ]
+ },
+ {
+ "name": "scrub_wide",
+ "directions": 4,
+ "delays": [
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ],
+ [
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10,
+ 0.10
+ ]
+ ]
+ }
+ ]
}
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_off.png b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_off.png
index efc173d893..8f7e5e6782 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_off.png and b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_off.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_on.png b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_on.png
index 9bc5d8c0bf..bdef533ae3 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_on.png and b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_on.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_purge.png b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_purge.png
index 75ba6d6de0..6bbc79c31b 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_purge.png and b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_purge.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_wide.png b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_wide.png
index 087777a888..c7fd087c81 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_wide.png and b/Resources/Textures/Structures/Piping/Atmospherics/scrubber.rsi/scrub_wide.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/meta.json
index 74e1688748..d22d006684 100644
--- a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/meta.json
+++ b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/meta.json
@@ -1,37 +1,103 @@
{
- "version":1,
- "size":{
- "x":32,
- "y":32
- },
- "license":"CC-BY-SA-3.0",
- "copyright":"Taken from https://github.com/BeeStation/BeeStation-Hornet at commit 4ccd79de285e79e504308bcd6fa5908d6b7685f7",
- "states":[
- {
- "name":"vent_passive",
- "directions" : 4
- },
- {
- "name":"vent_off",
- "directions" : 4
- },
- {
- "name":"vent_welded",
- "directions" : 4
- },
- {
- "name":"vent_out",
- "directions" : 4,
- "delays":[ [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ] ]
- },
- {
- "name":"vent_in",
- "directions" : 4,
- "delays":[ [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ], [ 0.08, 0.08, 0.08, 0.08 ] ]
- },
- {
- "name":"vent_lockout",
- "directions" : 4
- }
- ]
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/32742/commits/44820f5b6e2c864a35bfad448e0a9437f01ad2e2",
+ "states": [
+ {
+ "name": "vent_passive",
+ "directions": 4
+ },
+ {
+ "name": "vent_off",
+ "directions": 4
+ },
+ {
+ "name": "vent_welded",
+ "directions": 4
+ },
+ {
+ "name": "vent_out",
+ "directions": 4,
+ "delays": [
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ]
+ ]
+ },
+ {
+ "name": "vent_in",
+ "directions": 4,
+ "delays": [
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ],
+ [
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05,
+ 0.05
+ ]
+ ]
+ },
+ {
+ "name": "vent_lockout",
+ "directions": 4
+ }
+ ]
}
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_in.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_in.png
index cf1b050c02..17a740920f 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_in.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_in.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_lockout.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_lockout.png
index bf0b9c928e..b32003c698 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_lockout.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_lockout.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_off.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_off.png
index 6208c59db7..8380b47e7c 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_off.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_off.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_out.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_out.png
index e038706b0e..67890d4be2 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_out.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_out.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_passive.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_passive.png
index 5ff27ef759..9c732a4e7a 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_passive.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_passive.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_welded.png b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_welded.png
index b45ae1cb5f..0ecc5b4c9c 100644
Binary files a/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_welded.png and b/Resources/Textures/Structures/Piping/Atmospherics/vent.rsi/vent_welded.png differ