Merge branch 'space-wizards:master' into master
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Client.Chemistry.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HyposprayComponent : SharedHyposprayComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
public FixedPoint2 CurrentVolume;
|
||||
[ViewVariables]
|
||||
public FixedPoint2 TotalVolume;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool UiUpdateNeeded;
|
||||
}
|
||||
}
|
||||
15
Content.Client/Chemistry/EntitySystems/HypospraySystem.cs
Normal file
15
Content.Client/Chemistry/EntitySystems/HypospraySystem.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Content.Client.Chemistry.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
namespace Content.Client.Chemistry.EntitySystems;
|
||||
|
||||
public sealed class HypospraySystem : SharedHypospraySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Subs.ItemStatus<HyposprayComponent>(ent => new HyposprayStatusControl(ent, _solutionContainers));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Client.Chemistry.Components;
|
||||
using Content.Client.Chemistry.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -13,17 +12,5 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
{
|
||||
base.Initialize();
|
||||
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainers));
|
||||
SubscribeLocalEvent<HyposprayComponent, ComponentHandleState>(OnHandleHyposprayState);
|
||||
Subs.ItemStatus<HyposprayComponent>(ent => new HyposprayStatusControl(ent));
|
||||
}
|
||||
|
||||
private void OnHandleHyposprayState(EntityUid uid, HyposprayComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HyposprayComponentState cState)
|
||||
return;
|
||||
|
||||
component.CurrentVolume = cState.CurVolume;
|
||||
component.TotalVolume = cState.MaxVolume;
|
||||
component.UiUpdateNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Client.Chemistry.Components;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -9,34 +11,48 @@ namespace Content.Client.Chemistry.UI;
|
||||
|
||||
public sealed class HyposprayStatusControl : Control
|
||||
{
|
||||
private readonly HyposprayComponent _parent;
|
||||
private readonly Entity<HyposprayComponent> _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
private readonly SharedSolutionContainerSystem _solutionContainers;
|
||||
|
||||
public HyposprayStatusControl(HyposprayComponent parent)
|
||||
private FixedPoint2 PrevVolume;
|
||||
private FixedPoint2 PrevMaxVolume;
|
||||
private bool PrevOnlyAffectsMobs;
|
||||
|
||||
public HyposprayStatusControl(Entity<HyposprayComponent> parent, SharedSolutionContainerSystem solutionContainers)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
|
||||
_solutionContainers = solutionContainers;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
if (!_parent.UiUpdateNeeded)
|
||||
|
||||
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
|
||||
return;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// only updates the UI if any of the details are different than they previously were
|
||||
if (PrevVolume == solution.Volume
|
||||
&& PrevMaxVolume == solution.MaxVolume
|
||||
&& PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs)
|
||||
return;
|
||||
|
||||
_parent.UiUpdateNeeded = false;
|
||||
PrevVolume = solution.Volume;
|
||||
PrevMaxVolume = solution.MaxVolume;
|
||||
PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
|
||||
|
||||
_label.SetMarkup(Loc.GetString(
|
||||
"hypospray-volume-text",
|
||||
("currentVolume", _parent.CurrentVolume),
|
||||
("totalVolume", _parent.TotalVolume)));
|
||||
var modeStringLocalized = Loc.GetString(_parent.Comp.OnlyAffectsMobs switch
|
||||
{
|
||||
false => "hypospray-all-mode-text",
|
||||
true => "hypospray-mobs-only-mode-text",
|
||||
});
|
||||
|
||||
_label.SetMarkup(Loc.GetString("hypospray-volume-label",
|
||||
("currentVolume", solution.Volume),
|
||||
("totalVolume", solution.MaxVolume),
|
||||
("modeString", modeStringLocalized)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Shuttles.UI.MapObjects;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -38,16 +39,11 @@ public sealed partial class MapScreen : BoxContainer
|
||||
private EntityUid? _shuttleEntity;
|
||||
|
||||
private FTLState _state;
|
||||
private float _ftlDuration;
|
||||
private StartEndTime _ftlTime;
|
||||
|
||||
private List<ShuttleBeaconObject> _beacons = new();
|
||||
private List<ShuttleExclusionObject> _exclusions = new();
|
||||
|
||||
/// <summary>
|
||||
/// When the next FTL state change happens.
|
||||
/// </summary>
|
||||
private TimeSpan _nextFtlTime;
|
||||
|
||||
private TimeSpan _nextPing;
|
||||
private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
|
||||
private TimeSpan _nextMapDequeue;
|
||||
@@ -114,8 +110,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||
_beacons = state.Destinations;
|
||||
_exclusions = state.Exclusions;
|
||||
_state = state.FTLState;
|
||||
_ftlDuration = state.FTLDuration;
|
||||
_nextFtlTime = _timing.CurTime + TimeSpan.FromSeconds(_ftlDuration);
|
||||
_ftlTime = state.FTLTime;
|
||||
MapRadar.InFtl = true;
|
||||
MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
|
||||
|
||||
@@ -511,20 +506,8 @@ public sealed partial class MapScreen : BoxContainer
|
||||
MapRebuildButton.Disabled = false;
|
||||
}
|
||||
|
||||
var ftlDiff = (float) (_nextFtlTime - _timing.CurTime).TotalSeconds;
|
||||
|
||||
float ftlRatio;
|
||||
|
||||
if (_ftlDuration.Equals(0f))
|
||||
{
|
||||
ftlRatio = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ftlRatio = Math.Clamp(1f - (ftlDiff / _ftlDuration), 0f, 1f);
|
||||
}
|
||||
|
||||
FTLBar.Value = ftlRatio;
|
||||
var progress = _ftlTime.ProgressAt(curTime);
|
||||
FTLBar.Value = float.IsFinite(progress) ? progress : 1;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed class AirFilterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -31,7 +32,7 @@ public sealed class AirFilterSystem : EntitySystem
|
||||
if (air.Pressure >= intake.Pressure)
|
||||
return;
|
||||
|
||||
var environment = _atmosphere.GetContainingMixture(uid, true, true);
|
||||
var environment = _atmosphere.GetContainingMixture(uid, args.Grid, args.Map, true, true);
|
||||
// nothing to intake from
|
||||
if (environment == null)
|
||||
return;
|
||||
@@ -63,12 +64,11 @@ public sealed class AirFilterSystem : EntitySystem
|
||||
var oxygen = air.GetMoles(filter.Oxygen) / air.TotalMoles;
|
||||
var gases = oxygen >= filter.TargetOxygen ? filter.Gases : filter.OverflowGases;
|
||||
|
||||
var coordinates = Transform(uid).MapPosition;
|
||||
GasMixture? destination = null;
|
||||
if (_map.TryFindGridAt(coordinates, out _, out var grid))
|
||||
if (args.Grid is {} grid)
|
||||
{
|
||||
var tile = grid.GetTileRef(coordinates);
|
||||
destination = _atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true);
|
||||
var position = _transform.GetGridTilePositionOrDefault(uid);
|
||||
destination = _atmosphere.GetTileMixture(grid, args.Map, position, true);
|
||||
}
|
||||
|
||||
if (destination != null)
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
/// <summary>
|
||||
/// Event that tries to query the mixture a certain entity is exposed to.
|
||||
/// This is mainly intended for use with entities inside of containers.
|
||||
/// This event is not raised for entities that are directly parented to the grid.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct AtmosExposedGetAirEvent
|
||||
@@ -31,7 +33,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// <summary>
|
||||
/// The entity we want to query this for.
|
||||
/// </summary>
|
||||
public readonly EntityUid Entity;
|
||||
public readonly Entity<TransformComponent> Entity;
|
||||
|
||||
/// <summary>
|
||||
/// The mixture that the entity is exposed to. Output parameter.
|
||||
@@ -39,9 +41,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public GasMixture? Gas = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to invalidate the mixture, if possible.
|
||||
/// Whether to excite the mixture, if possible.
|
||||
/// </summary>
|
||||
public bool Invalidate = false;
|
||||
public readonly bool Excite = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this event has been handled or not.
|
||||
@@ -49,10 +51,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public bool Handled = false;
|
||||
|
||||
public AtmosExposedGetAirEvent(EntityUid entity, bool invalidate = false)
|
||||
public AtmosExposedGetAirEvent(Entity<TransformComponent> entity, bool excite = false)
|
||||
{
|
||||
Entity = entity;
|
||||
Invalidate = invalidate;
|
||||
Excite = excite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -11,41 +12,39 @@ namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public partial class AtmosphereSystem
|
||||
{
|
||||
public GasMixture? GetContainingMixture(EntityUid uid, bool ignoreExposed = false, bool excite = false, TransformComponent? transform = null)
|
||||
public GasMixture? GetContainingMixture(Entity<TransformComponent?> ent, bool ignoreExposed = false, bool excite = false)
|
||||
{
|
||||
if (!ignoreExposed)
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return null;
|
||||
|
||||
return GetContainingMixture(ent, ent.Comp.GridUid, ent.Comp.MapUid, ignoreExposed, excite);
|
||||
}
|
||||
|
||||
public GasMixture? GetContainingMixture(
|
||||
Entity<TransformComponent?> ent,
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
Entity<MapAtmosphereComponent?>? map,
|
||||
bool ignoreExposed = false,
|
||||
bool excite = false)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return null;
|
||||
|
||||
if (!ignoreExposed && !ent.Comp.Anchored)
|
||||
{
|
||||
// Used for things like disposals/cryo to change which air people are exposed to.
|
||||
var ev = new AtmosExposedGetAirEvent(uid, excite);
|
||||
|
||||
// Give the entity itself a chance to handle this.
|
||||
RaiseLocalEvent(uid, ref ev, false);
|
||||
|
||||
var ev = new AtmosExposedGetAirEvent((ent, ent.Comp), excite);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
if (ev.Handled)
|
||||
return ev.Gas;
|
||||
|
||||
// We need to get the parent now, so we need the transform... If the parent is invalid, we can't do much else.
|
||||
if(!Resolve(uid, ref transform) || !transform.ParentUid.IsValid() || transform.MapUid == null)
|
||||
return GetTileMixture(null, null, Vector2i.Zero, excite);
|
||||
|
||||
// Give the parent entity a chance to handle the event...
|
||||
RaiseLocalEvent(transform.ParentUid, ref ev, false);
|
||||
|
||||
if (ev.Handled)
|
||||
return ev.Gas;
|
||||
}
|
||||
// Oops, we did a little bit of code duplication...
|
||||
else if(!Resolve(uid, ref transform))
|
||||
{
|
||||
return GetTileMixture(null, null, Vector2i.Zero, excite);
|
||||
// TODO ATMOS: recursively iterate up through parents
|
||||
// This really needs recursive InContainer metadata flag for performance
|
||||
// And ideally some fast way to get the innermost airtight container.
|
||||
}
|
||||
|
||||
|
||||
var gridUid = transform.GridUid;
|
||||
var mapUid = transform.MapUid;
|
||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
||||
|
||||
return GetTileMixture(gridUid, mapUid, position, excite);
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault((ent, ent.Comp));
|
||||
return GetTileMixture(grid, map, position, excite);
|
||||
}
|
||||
|
||||
public bool HasAtmosphere(EntityUid gridUid) => _atmosQuery.HasComponent(gridUid);
|
||||
@@ -84,21 +83,28 @@ public partial class AtmosphereSystem
|
||||
entity.Comp.InvalidatedCoords.Add(tile);
|
||||
}
|
||||
|
||||
public GasMixture?[]? GetTileMixtures(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, List<Vector2i> tiles, bool excite = false)
|
||||
public GasMixture?[]? GetTileMixtures(
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
Entity<MapAtmosphereComponent?>? map,
|
||||
List<Vector2i> tiles,
|
||||
bool excite = false)
|
||||
{
|
||||
GasMixture?[]? mixtures = null;
|
||||
var handled = false;
|
||||
|
||||
// If we've been passed a grid, try to let it handle it.
|
||||
if (grid is {} gridEnt && Resolve(gridEnt, ref gridEnt.Comp))
|
||||
if (grid is {} gridEnt && Resolve(gridEnt, ref gridEnt.Comp1))
|
||||
{
|
||||
if (excite)
|
||||
Resolve(gridEnt, ref gridEnt.Comp2);
|
||||
|
||||
handled = true;
|
||||
mixtures = new GasMixture?[tiles.Count];
|
||||
|
||||
for (var i = 0; i < tiles.Count; i++)
|
||||
{
|
||||
var tile = tiles[i];
|
||||
if (!gridEnt.Comp.Tiles.TryGetValue(tile, out var atmosTile))
|
||||
if (!gridEnt.Comp1.Tiles.TryGetValue(tile, out var atmosTile))
|
||||
{
|
||||
// need to get map atmosphere
|
||||
handled = false;
|
||||
@@ -108,7 +114,10 @@ public partial class AtmosphereSystem
|
||||
mixtures[i] = atmosTile.Air;
|
||||
|
||||
if (excite)
|
||||
gridEnt.Comp.InvalidatedCoords.Add(tile);
|
||||
{
|
||||
AddActiveTile(gridEnt.Comp1, atmosTile);
|
||||
InvalidateVisuals((gridEnt.Owner, gridEnt.Comp2), tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,15 +155,22 @@ public partial class AtmosphereSystem
|
||||
return GetTileMixture(entity.Comp.GridUid, entity.Comp.MapUid, indices, excite);
|
||||
}
|
||||
|
||||
public GasMixture? GetTileMixture(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, Vector2i gridTile, bool excite = false)
|
||||
public GasMixture? GetTileMixture(
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
Entity<MapAtmosphereComponent?>? map,
|
||||
Vector2i gridTile,
|
||||
bool excite = false)
|
||||
{
|
||||
// If we've been passed a grid, try to let it handle it.
|
||||
if (grid is {} gridEnt
|
||||
&& Resolve(gridEnt, ref gridEnt.Comp, false)
|
||||
&& gridEnt.Comp.Tiles.TryGetValue(gridTile, out var tile))
|
||||
&& Resolve(gridEnt, ref gridEnt.Comp1, false)
|
||||
&& gridEnt.Comp1.Tiles.TryGetValue(gridTile, out var tile))
|
||||
{
|
||||
if (excite)
|
||||
gridEnt.Comp.InvalidatedCoords.Add(gridTile);
|
||||
{
|
||||
AddActiveTile(gridEnt.Comp1, tile);
|
||||
InvalidateVisuals((grid.Value.Owner, grid.Value.Comp2), gridTile);
|
||||
}
|
||||
|
||||
return tile.Air;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
@@ -64,10 +66,12 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
excitedGroup.DismantleCooldown = 0;
|
||||
}
|
||||
|
||||
private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
|
||||
private void ExcitedGroupSelfBreakdown(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
ExcitedGroup excitedGroup)
|
||||
{
|
||||
DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
|
||||
DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
|
||||
DebugTools.Assert(ent.Comp1.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
|
||||
var combined = new GasMixture(Atmospherics.CellVolume);
|
||||
|
||||
var tileSize = excitedGroup.Tiles.Count;
|
||||
@@ -77,7 +81,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (tileSize == 0)
|
||||
{
|
||||
ExcitedGroupDispose(gridAtmosphere, excitedGroup);
|
||||
ExcitedGroupDispose(ent.Comp1, excitedGroup);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +107,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
continue;
|
||||
|
||||
tile.Air.CopyFromMutable(combined);
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
|
||||
InvalidateVisuals(ent, tile);
|
||||
}
|
||||
|
||||
excitedGroup.BreakdownCooldown = 0;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -160,7 +159,7 @@ public sealed partial class AtmosphereSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
|
||||
/// Update array of adjacent tiles and the adjacency flags.
|
||||
/// </summary>
|
||||
private void UpdateAdjacentTiles(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
@@ -195,14 +194,16 @@ public sealed partial class AtmosphereSystem
|
||||
if (activate)
|
||||
AddActiveTile(atmos, adjacent);
|
||||
|
||||
var oppositeDirection = direction.GetOpposite();
|
||||
var oppositeIndex = i.ToOppositeIndex();
|
||||
var oppositeDirection = (AtmosDirection) (1 << oppositeIndex);
|
||||
|
||||
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
|
||||
{
|
||||
// Adjacency is blocked by some airtight entity.
|
||||
tile.AdjacentBits &= ~direction;
|
||||
adjacent.AdjacentBits &= ~oppositeDirection;
|
||||
tile.AdjacentTiles[i] = null;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
|
||||
adjacent.AdjacentTiles[oppositeIndex] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -210,7 +211,7 @@ public sealed partial class AtmosphereSystem
|
||||
tile.AdjacentBits |= direction;
|
||||
adjacent.AdjacentBits |= oppositeDirection;
|
||||
tile.AdjacentTiles[i] = adjacent;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
|
||||
adjacent.AdjacentTiles[oppositeIndex] = tile;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
@@ -18,18 +20,18 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? HotspotSound { get; private set; } = "/Audio/Effects/fire.ogg";
|
||||
|
||||
private void ProcessHotspot(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
|
||||
private void ProcessHotspot(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile)
|
||||
{
|
||||
var gridAtmosphere = ent.Comp1;
|
||||
if (!tile.Hotspot.Valid)
|
||||
{
|
||||
gridAtmosphere.HotspotTiles.Remove(tile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tile.Excited)
|
||||
{
|
||||
AddActiveTile(gridAtmosphere, tile);
|
||||
}
|
||||
AddActiveTile(gridAtmosphere, tile);
|
||||
|
||||
if (!tile.Hotspot.SkippedFirstProcess)
|
||||
{
|
||||
@@ -44,7 +46,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|| tile.Air == null || tile.Air.GetMoles(Gas.Oxygen) < 0.5f || (tile.Air.GetMoles(Gas.Plasma) < 0.5f && tile.Air.GetMoles(Gas.Tritium) < 0.5f))
|
||||
{
|
||||
tile.Hotspot = new Hotspot();
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
|
||||
InvalidateVisuals(ent, tile);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals)
|
||||
private void ProcessCell(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile, int fireCount)
|
||||
{
|
||||
var gridAtmosphere = ent.Comp1;
|
||||
// Can't process a tile without air
|
||||
if (tile.Air == null)
|
||||
{
|
||||
@@ -52,11 +56,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
shouldShareAir = true;
|
||||
} else if (CompareExchange(tile.Air, enemyTile.Air) != GasCompareResult.NoExchange)
|
||||
{
|
||||
if (!enemyTile.Excited)
|
||||
{
|
||||
AddActiveTile(gridAtmosphere, enemyTile);
|
||||
}
|
||||
|
||||
AddActiveTile(gridAtmosphere, enemyTile);
|
||||
if (ExcitedGroups)
|
||||
{
|
||||
var excitedGroup = tile.ExcitedGroup;
|
||||
@@ -91,7 +91,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsiderPressureDifference(gridAtmosphere, enemyTile, direction.GetOpposite(), -difference);
|
||||
ConsiderPressureDifference(gridAtmosphere, enemyTile, i.ToOppositeDir(), -difference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if(tile.Air != null)
|
||||
React(tile.Air, tile);
|
||||
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
|
||||
InvalidateVisuals(ent, tile);
|
||||
|
||||
var remove = true;
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// <param name="tile">Tile Atmosphere to be activated.</param>
|
||||
private void AddActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
|
||||
{
|
||||
if (tile.Air == null)
|
||||
if (tile.Air == null || tile.Excited)
|
||||
return;
|
||||
|
||||
tile.Excited = true;
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
||||
_equalizeQueue[queueLength++] = otherTile2;
|
||||
otherTile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
|
||||
otherTile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
|
||||
otherTile2.MonstermosInfo.CurrentTransferDirection = k.ToOppositeDir();
|
||||
otherTile2.MonstermosInfo.CurrentTransferAmount = 0;
|
||||
if (otherTile2.MonstermosInfo.MoleDelta < 0)
|
||||
{
|
||||
@@ -296,7 +296,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
||||
_equalizeQueue[queueLength++] = otherTile2;
|
||||
otherTile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
|
||||
otherTile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
|
||||
otherTile2.MonstermosInfo.CurrentTransferDirection = k.ToOppositeDir();
|
||||
otherTile2.MonstermosInfo.CurrentTransferAmount = 0;
|
||||
|
||||
if (otherTile2.MonstermosInfo.MoleDelta > 0)
|
||||
@@ -338,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for (var i = 0; i < tileCount; i++)
|
||||
{
|
||||
var otherTile = _equalizeTiles[i]!;
|
||||
FinalizeEq(gridAtmosphere, otherTile, ent);
|
||||
FinalizeEq(ent, otherTile);
|
||||
}
|
||||
|
||||
for (var i = 0; i < tileCount; i++)
|
||||
@@ -473,7 +473,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if(tile2.Space)
|
||||
continue;
|
||||
|
||||
tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
|
||||
tile2.MonstermosInfo.CurrentTransferDirection = j.ToOppositeDir();
|
||||
tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
|
||||
tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
|
||||
tile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
|
||||
@@ -549,7 +549,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
otherTile.Air.Temperature = Atmospherics.TCMB;
|
||||
}
|
||||
|
||||
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
|
||||
InvalidateVisuals(ent, otherTile);
|
||||
HandleDecompressionFloorRip(mapGrid, otherTile, otherTile.MonstermosInfo.CurrentTransferAmount);
|
||||
}
|
||||
|
||||
@@ -598,11 +598,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
UpdateAdjacentTiles(ent, tile);
|
||||
UpdateAdjacentTiles(ent, other);
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
|
||||
InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
|
||||
InvalidateVisuals(ent, tile);
|
||||
InvalidateVisuals(ent, other);
|
||||
}
|
||||
|
||||
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
|
||||
private void FinalizeEq(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile)
|
||||
{
|
||||
Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
|
||||
var hasTransferDirs = false;
|
||||
@@ -629,17 +631,19 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
// Everything that calls this method already ensures that Air will not be null.
|
||||
if (tile.Air!.TotalMoles < amount)
|
||||
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections, visuals);
|
||||
FinalizeEqNeighbors(ent, tile, transferDirections);
|
||||
|
||||
otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
|
||||
otherTile.MonstermosInfo[i.ToOppositeDir()] = 0;
|
||||
Merge(otherTile.Air, tile.Air.Remove(amount));
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
|
||||
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
|
||||
ConsiderPressureDifference(gridAtmosphere, tile, direction, amount);
|
||||
InvalidateVisuals(ent, tile);
|
||||
InvalidateVisuals(ent, otherTile);
|
||||
ConsiderPressureDifference(ent, tile, direction, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void FinalizeEqNeighbors(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, ReadOnlySpan<float> transferDirs, GasTileOverlayComponent? visuals)
|
||||
private void FinalizeEqNeighbors(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile, ReadOnlySpan<float> transferDirs)
|
||||
{
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
@@ -647,7 +651,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var amount = transferDirs[i];
|
||||
// Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air.
|
||||
if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction))
|
||||
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!, visuals); // A bit of recursion if needed.
|
||||
FinalizeEq(ent, tile.AdjacentTiles[i]!); // A bit of recursion if needed.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,7 +668,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
Log.Error($"Encountered null-tile in {nameof(AdjustEqMovement)}. Trace: {Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
var adj = tile.AdjacentTiles[direction.ToIndex()];
|
||||
|
||||
var idx = direction.ToIndex();
|
||||
var adj = tile.AdjacentTiles[idx];
|
||||
if (adj == null)
|
||||
{
|
||||
var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
|
||||
@@ -673,7 +679,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
|
||||
tile.MonstermosInfo[direction] += amount;
|
||||
adj.MonstermosInfo[direction.GetOpposite()] -= amount;
|
||||
adj.MonstermosInfo[idx.ToOppositeDir()] -= amount;
|
||||
}
|
||||
|
||||
private void HandleDecompressionFloorRip(MapGridComponent mapGrid, TileAtmosphere tile, float sum)
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
|
||||
UpdateAdjacentTiles(ent, tile, activate: true);
|
||||
UpdateTileAir(ent, tile, volume);
|
||||
InvalidateVisuals(uid, tile.GridIndices, visuals);
|
||||
InvalidateVisuals(ent, tile);
|
||||
|
||||
if (number++ < InvalidCoordinatesLagCheckIterations)
|
||||
continue;
|
||||
@@ -313,15 +313,17 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
|
||||
private bool ProcessActiveTiles(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var atmosphere = ent.Comp1;
|
||||
if(!atmosphere.ProcessingPaused)
|
||||
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
|
||||
|
||||
var number = 0;
|
||||
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
|
||||
{
|
||||
ProcessCell(atmosphere, tile, atmosphere.UpdateCounter, visuals);
|
||||
ProcessCell(ent, tile, atmosphere.UpdateCounter);
|
||||
|
||||
if (number++ < LagCheckIterations)
|
||||
continue;
|
||||
@@ -337,8 +339,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessExcitedGroups(GridAtmosphereComponent gridAtmosphere)
|
||||
private bool ProcessExcitedGroups(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var gridAtmosphere = ent.Comp1;
|
||||
if (!gridAtmosphere.ProcessingPaused)
|
||||
{
|
||||
gridAtmosphere.CurrentRunExcitedGroups.Clear();
|
||||
@@ -356,7 +360,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
excitedGroup.DismantleCooldown++;
|
||||
|
||||
if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
|
||||
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
|
||||
ExcitedGroupSelfBreakdown(ent, excitedGroup);
|
||||
else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
|
||||
DeactivateGroupTiles(gridAtmosphere, excitedGroup);
|
||||
// TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
|
||||
@@ -411,15 +415,17 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessHotspots(GridAtmosphereComponent atmosphere)
|
||||
private bool ProcessHotspots(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var atmosphere = ent.Comp1;
|
||||
if(!atmosphere.ProcessingPaused)
|
||||
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.HotspotTiles);
|
||||
|
||||
var number = 0;
|
||||
while (atmosphere.CurrentRunTiles.TryDequeue(out var hotspot))
|
||||
{
|
||||
ProcessHotspot(atmosphere, hotspot);
|
||||
ProcessHotspot(ent, hotspot);
|
||||
|
||||
if (number++ < LagCheckIterations)
|
||||
continue;
|
||||
@@ -507,8 +513,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return num * AtmosTime;
|
||||
}
|
||||
|
||||
private bool ProcessAtmosDevices(GridAtmosphereComponent atmosphere)
|
||||
private bool ProcessAtmosDevices(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
Entity<MapAtmosphereComponent?> map)
|
||||
{
|
||||
var atmosphere = ent.Comp1;
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
{
|
||||
atmosphere.CurrentRunAtmosDevices.Clear();
|
||||
@@ -521,7 +530,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
var time = _gameTiming.CurTime;
|
||||
var number = 0;
|
||||
var ev = new AtmosDeviceUpdateEvent(RealAtmosTime());
|
||||
var ev = new AtmosDeviceUpdateEvent(RealAtmosTime(), (ent, ent.Comp1, ent.Comp2), map);
|
||||
while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device))
|
||||
{
|
||||
RaiseLocalEvent(device, ref ev);
|
||||
@@ -565,12 +574,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
|
||||
var (owner, atmosphere, visuals, grid, xform) = ent;
|
||||
|
||||
if (!TryComp(owner, out TransformComponent? x)
|
||||
|| x.MapUid == null
|
||||
|| TerminatingOrDeleted(x.MapUid.Value)
|
||||
|| x.MapID == MapId.Nullspace)
|
||||
if (xform.MapUid == null
|
||||
|| TerminatingOrDeleted(xform.MapUid.Value)
|
||||
|| xform.MapID == MapId.Nullspace)
|
||||
{
|
||||
Log.Error($"Attempted to process atmos without a map? Entity: {ToPrettyString(owner)}. Map: {ToPrettyString(x?.MapUid)}. MapId: {x?.MapID}");
|
||||
Log.Error($"Attempted to process atmos without a map? Entity: {ToPrettyString(owner)}. Map: {ToPrettyString(xform?.MapUid)}. MapId: {xform?.MapID}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -585,6 +593,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// We subtract it so it takes lost time into account.
|
||||
atmosphere.Timer -= AtmosTime;
|
||||
|
||||
var map = new Entity<MapAtmosphereComponent?>(xform.MapUid.Value, _mapAtmosQuery.CompOrNull(xform.MapUid.Value));
|
||||
|
||||
switch (atmosphere.State)
|
||||
{
|
||||
case AtmosphereProcessingState.Revalidate:
|
||||
@@ -614,7 +624,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
|
||||
continue;
|
||||
case AtmosphereProcessingState.ActiveTiles:
|
||||
if (!ProcessActiveTiles(ent, ent))
|
||||
if (!ProcessActiveTiles(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
@@ -625,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = ExcitedGroups ? AtmosphereProcessingState.ExcitedGroups : AtmosphereProcessingState.HighPressureDelta;
|
||||
continue;
|
||||
case AtmosphereProcessingState.ExcitedGroups:
|
||||
if (!ProcessExcitedGroups(atmosphere))
|
||||
if (!ProcessExcitedGroups(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
@@ -645,7 +655,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = AtmosphereProcessingState.Hotspots;
|
||||
continue;
|
||||
case AtmosphereProcessingState.Hotspots:
|
||||
if (!ProcessHotspots(atmosphere))
|
||||
if (!ProcessHotspots(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
@@ -680,7 +690,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = AtmosphereProcessingState.AtmosDevices;
|
||||
continue;
|
||||
case AtmosphereProcessingState.AtmosDevices:
|
||||
if (!ProcessAtmosDevices(atmosphere))
|
||||
if (!ProcessAtmosDevices(ent, map))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!directions.IsFlagSet(direction))
|
||||
continue;
|
||||
|
||||
var adjacent = tile.AdjacentTiles[direction.ToIndex()];
|
||||
var adjacent = tile.AdjacentTiles[i];
|
||||
|
||||
// TODO ATMOS handle adjacent being null.
|
||||
if (adjacent == null || adjacent.ThermalConductivity == 0f)
|
||||
|
||||
@@ -36,9 +36,17 @@ public partial class AtmosphereSystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void InvalidateVisuals(EntityUid gridUid, Vector2i tile, GasTileOverlayComponent? comp = null)
|
||||
public void InvalidateVisuals(Entity<GasTileOverlayComponent?> grid, Vector2i tile)
|
||||
{
|
||||
_gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
|
||||
_gasTileOverlaySystem.Invalidate(grid, tile);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void InvalidateVisuals(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile)
|
||||
{
|
||||
_gasTileOverlaySystem.Invalidate((ent.Owner, ent.Comp2), tile.GridIndices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
var query = EntityQueryEnumerator<AtmosExposedComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var transform))
|
||||
{
|
||||
var air = GetContainingMixture(uid, transform:transform);
|
||||
var air = GetContainingMixture((uid, transform));
|
||||
|
||||
if (air == null)
|
||||
continue;
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private void ReleaseGas(Entity<GasTankComponent> gasTank)
|
||||
{
|
||||
var removed = RemoveAirVolume(gasTank, gasTank.Comp.ValveOutputRate * TimerDelay);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(gasTank, false, true);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(gasTank.Owner, false, true);
|
||||
if (environment != null)
|
||||
{
|
||||
_atmosphereSystem.Merge(environment, removed);
|
||||
|
||||
@@ -52,12 +52,15 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
new DefaultObjectPool<Dictionary<NetEntity, HashSet<Vector2i>>>(
|
||||
new DefaultPooledObjectPolicy<Dictionary<NetEntity, HashSet<Vector2i>>>(), 64);
|
||||
|
||||
private bool _doSessionUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay update interval, in seconds.
|
||||
/// </summary>
|
||||
private float _updateInterval;
|
||||
|
||||
private int _thresholds;
|
||||
private EntityQuery<GasTileOverlayComponent> _query;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -82,6 +85,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentStartup>(OnStartup);
|
||||
_query = GetEntityQuery<GasTileOverlayComponent>();
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, GasTileOverlayComponent component, ComponentStartup args)
|
||||
@@ -130,10 +134,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private void UpdateThresholds(int value) => _thresholds = value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invalidate(EntityUid grid, Vector2i index, GasTileOverlayComponent? comp = null)
|
||||
public void Invalidate(Entity<GasTileOverlayComponent?> grid, Vector2i index)
|
||||
{
|
||||
if (Resolve(grid, ref comp))
|
||||
comp.InvalidTiles.Add(index);
|
||||
if (_query.Resolve(grid.Owner, ref grid.Comp))
|
||||
grid.Comp.InvalidTiles.Add(index);
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
@@ -192,7 +196,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// <summary>
|
||||
/// Updates the visuals for a tile on some grid chunk. Returns true if the visuals have changed.
|
||||
/// </summary>
|
||||
private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayChunk chunk, Vector2i index, GameTick curTick)
|
||||
private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayChunk chunk, Vector2i index)
|
||||
{
|
||||
ref var oldData = ref chunk.TileData[chunk.GetDataIndex(index)];
|
||||
if (!gridAtmosphere.Tiles.TryGetValue(index, out var tile))
|
||||
@@ -200,7 +204,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (oldData.Equals(default))
|
||||
return false;
|
||||
|
||||
chunk.LastUpdate = curTick;
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
oldData = default;
|
||||
return true;
|
||||
}
|
||||
@@ -258,11 +262,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!changed)
|
||||
return false;
|
||||
|
||||
chunk.LastUpdate = curTick;
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateOverlayData(GameTick curTick)
|
||||
private void UpdateOverlayData()
|
||||
{
|
||||
// TODO parallelize?
|
||||
var query = EntityQueryEnumerator<GasTileOverlayComponent, GridAtmosphereComponent, MetaDataComponent>();
|
||||
@@ -276,7 +280,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!overlay.Chunks.TryGetValue(chunkIndex, out var chunk))
|
||||
overlay.Chunks[chunkIndex] = chunk = new GasOverlayChunk(chunkIndex);
|
||||
|
||||
changed |= UpdateChunkTile(gam, chunk, index, curTick);
|
||||
changed |= UpdateChunkTile(gam, chunk, index);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
@@ -291,13 +295,28 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
base.Update(frameTime);
|
||||
AccumulatedFrameTime += frameTime;
|
||||
|
||||
if (AccumulatedFrameTime < _updateInterval) return;
|
||||
if (_doSessionUpdate)
|
||||
{
|
||||
UpdateSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (AccumulatedFrameTime < _updateInterval)
|
||||
return;
|
||||
|
||||
AccumulatedFrameTime -= _updateInterval;
|
||||
|
||||
var curTick = _gameTiming.CurTick;
|
||||
|
||||
// First, update per-chunk visual data for any invalidated tiles.
|
||||
UpdateOverlayData(curTick);
|
||||
UpdateOverlayData();
|
||||
|
||||
// Then, next tick we send the data to players.
|
||||
// This is to avoid doing all the work in the same tick.
|
||||
_doSessionUpdate = true;
|
||||
}
|
||||
|
||||
public void UpdateSessions()
|
||||
{
|
||||
_doSessionUpdate = false;
|
||||
|
||||
if (!PvsEnabled)
|
||||
return;
|
||||
@@ -315,11 +334,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_sessions.Add(player);
|
||||
}
|
||||
|
||||
if (_sessions.Count > 0)
|
||||
{
|
||||
_updateJob.CurrentTick = curTick;
|
||||
_parMan.ProcessNow(_updateJob, _sessions.Count);
|
||||
}
|
||||
if (_sessions.Count == 0)
|
||||
return;
|
||||
|
||||
_parMan.ProcessNow(_updateJob, _sessions.Count);
|
||||
_updateJob.LastSessionUpdate = _gameTiming.CurTick;
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
@@ -352,7 +371,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public ObjectPool<HashSet<Vector2i>> ChunkIndexPool;
|
||||
public ObjectPool<Dictionary<NetEntity, HashSet<Vector2i>>> ChunkViewerPool;
|
||||
|
||||
public GameTick CurrentTick;
|
||||
public GameTick LastSessionUpdate;
|
||||
public Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> LastSentChunks;
|
||||
public List<ICommonSession> Sessions;
|
||||
|
||||
@@ -415,7 +434,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (previousChunks != null &&
|
||||
previousChunks.Contains(gIndex) &&
|
||||
value.LastUpdate != CurrentTick)
|
||||
value.LastUpdate > LastSessionUpdate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -40,24 +40,16 @@ public sealed class HeatExchangerSystem : EntitySystem
|
||||
|
||||
private void OnAtmosUpdate(EntityUid uid, HeatExchangerComponent comp, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !TryComp(uid, out AtmosDeviceComponent? device)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.OutletName, out PipeNode? outlet))
|
||||
// make sure that the tile the device is on isn't blocked by a wall or something similar.
|
||||
if (args.Grid is {} grid
|
||||
&& _transform.TryGetGridTilePosition(uid, out var tile)
|
||||
&& _atmosphereSystem.IsTileAirBlocked(grid, tile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that the tile the device is on isn't blocked by a wall or something similar.
|
||||
var xform = Transform(uid);
|
||||
if (_transform.TryGetGridTilePosition(uid, out var tile))
|
||||
{
|
||||
// TryGetGridTilePosition() already returns false if GridUid is null, but the null checker isn't smart enough yet
|
||||
if (xform.GridUid != null && _atmosphereSystem.IsTileAirBlocked(xform.GridUid.Value, tile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!_nodeContainer.TryGetNodes(uid, comp.InletName, comp.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
return;
|
||||
|
||||
var dt = args.dt;
|
||||
|
||||
|
||||
@@ -204,11 +204,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
// can't hurt
|
||||
// (in case something is making AtmosDeviceUpdateEvents
|
||||
// outside the typical device loop)
|
||||
if (!TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent)
|
||||
|| atmosDeviceComponent.JoinedGrid == null)
|
||||
if (args.Grid == null)
|
||||
return;
|
||||
|
||||
// if we're not monitoring atmos, don't bother
|
||||
|
||||
@@ -26,11 +26,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
|
||||
private void OnPassiveGateUpdated(EntityUid uid, GasPassiveGateComponent gate, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, gate.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, gate.OutletName, out PipeNode? outlet))
|
||||
if (!_nodeContainer.TryGetNodes(uid, gate.InletName, gate.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
return;
|
||||
|
||||
var n1 = inlet.Air.TotalMoles;
|
||||
|
||||
@@ -66,9 +66,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!pump.Enabled
|
||||
|| !EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, pump.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, pump.OutletName, out PipeNode? outlet))
|
||||
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
return;
|
||||
|
||||
@@ -41,12 +41,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
if (!EntityManager.GetComponent<TransformComponent>(ent).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status.
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(ent, out NodeContainerComponent? nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.OutletName, out PipeNode? _))
|
||||
{
|
||||
if (!_nodeContainer.TryGetNode(ent.Owner, comp.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
}
|
||||
|
||||
using (args.PushGroup(nameof(GasRecyclerComponent)))
|
||||
{
|
||||
@@ -72,9 +68,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
private void OnUpdate(Entity<GasRecyclerComponent> ent, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
if (!EntityManager.TryGetComponent(ent, out NodeContainerComponent? nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.OutletName, out PipeNode? outlet))
|
||||
if (!_nodeContainer.TryGetNodes(ent.Owner, comp.InletName, comp.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(ent, false);
|
||||
return;
|
||||
|
||||
@@ -59,9 +59,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
public void Set(EntityUid uid, GasValveComponent component, bool value)
|
||||
{
|
||||
component.Open = value;
|
||||
if (TryComp(uid, out NodeContainerComponent? nodeContainer)
|
||||
&& _nodeContainer.TryGetNode(nodeContainer, component.InletName, out PipeNode? inlet)
|
||||
&& _nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet))
|
||||
|
||||
if (_nodeContainer.TryGetNodes(uid, component.InletName, component.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
|
||||
@@ -71,11 +71,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
|
||||
private void OnVolumePumpUpdated(EntityUid uid, GasVolumePumpComponent pump, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!pump.Enabled
|
||||
|| !TryComp(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !TryComp(uid, out AtmosDeviceComponent? device)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, pump.InletName, out PipeNode? inlet)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, pump.OutletName, out PipeNode? outlet))
|
||||
if (!pump.Enabled ||
|
||||
!_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Components;
|
||||
|
||||
@@ -46,18 +47,25 @@ public sealed partial class AtmosDeviceComponent : Component
|
||||
/// Use this for atmos devices instead of <see cref="EntitySystem.Update"/>.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct AtmosDeviceUpdateEvent
|
||||
public readonly struct AtmosDeviceUpdateEvent(float dt, Entity<GridAtmosphereComponent, GasTileOverlayComponent>? grid, Entity<MapAtmosphereComponent?>? map)
|
||||
{
|
||||
/// <summary>
|
||||
/// Time elapsed since last update, in seconds. Multiply values used in the update handler
|
||||
/// by this number to make them tickrate-invariant. Use this number instead of AtmosphereSystem.AtmosTime.
|
||||
/// </summary>
|
||||
public readonly float dt;
|
||||
public readonly float dt = dt;
|
||||
|
||||
public AtmosDeviceUpdateEvent(float dt)
|
||||
{
|
||||
this.dt = dt;
|
||||
}
|
||||
/// <summary>
|
||||
/// The grid that this device is currently on.
|
||||
/// </summary>
|
||||
public readonly Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? Grid = grid == null
|
||||
? null
|
||||
: (grid.Value, grid.Value, grid.Value);
|
||||
|
||||
/// <summary>
|
||||
/// The map that the device & grid is on.
|
||||
/// </summary>
|
||||
public readonly Entity<MapAtmosphereComponent?>? Map = map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -129,9 +129,10 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
_timer -= _atmosphereSystem.AtmosTime;
|
||||
|
||||
var time = _gameTiming.CurTime;
|
||||
var ev = new AtmosDeviceUpdateEvent(_atmosphereSystem.AtmosTime);
|
||||
var ev = new AtmosDeviceUpdateEvent(_atmosphereSystem.AtmosTime, null, null);
|
||||
foreach (var device in _joinedDevices)
|
||||
{
|
||||
DebugTools.Assert(!HasComp<GridAtmosphereComponent>(Transform(device).GridUid));
|
||||
RaiseLocalEvent(device, ref ev);
|
||||
device.Comp.LastProcess = time;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems
|
||||
private bool CheckMinerOperation(Entity<GasMinerComponent> ent, [NotNullWhen(true)] out GasMixture? environment)
|
||||
{
|
||||
var (uid, miner) = ent;
|
||||
environment = _atmosphereSystem.GetContainingMixture(uid, true, true);
|
||||
|
||||
var transform = Transform(uid);
|
||||
environment = _atmosphereSystem.GetContainingMixture((uid, transform), true, true);
|
||||
|
||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
||||
|
||||
// Space.
|
||||
|
||||
@@ -53,11 +53,7 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!filter.Enabled
|
||||
|| !EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosDeviceComponent? device)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, filter.InletName, out PipeNode? inletNode)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, filter.FilterName, out PipeNode? filterNode)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, filter.OutletName, out PipeNode? outletNode)
|
||||
|| !_nodeContainer.TryGetNodes(uid, filter.InletName, filter.OutletName, filter.FilterName, out PipeNode? inletNode, out PipeNode? filterNode, out PipeNode? outletNode)
|
||||
|| outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full.
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
@@ -187,16 +183,15 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
var gasMixDict = new Dictionary<string, GasMixture?>();
|
||||
args.GasMixtures ??= new Dictionary<string, GasMixture?>();
|
||||
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.InletName, out PipeNode? inlet))
|
||||
gasMixDict.Add(Loc.GetString("gas-analyzer-window-text-inlet"), inlet.Air);
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-inlet"), inlet.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.FilterName, out PipeNode? filterNode))
|
||||
gasMixDict.Add(Loc.GetString("gas-analyzer-window-text-filter"), filterNode.Air);
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-filter"), filterNode.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet))
|
||||
gasMixDict.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air);
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air);
|
||||
|
||||
args.GasMixtures = gasMixDict;
|
||||
args.DeviceFlipped = inlet != null && filterNode != null && inlet.CurrentPipeDirection.ToDirection() == filterNode.CurrentPipeDirection.ToDirection().GetClockwise90Degrees();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,18 +54,8 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
{
|
||||
// TODO ATMOS: Cache total moles since it's expensive.
|
||||
|
||||
if (!mixer.Enabled)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, mixer.InletOneName, out PipeNode? inletOne)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, mixer.InletTwoName, out PipeNode? inletTwo)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, mixer.OutletName, out PipeNode? outlet))
|
||||
if (!mixer.Enabled
|
||||
|| !_nodeContainer.TryGetNodes(uid, mixer.InletOneName, mixer.InletTwoName, mixer.OutletName, out PipeNode? inletOne, out PipeNode? inletTwo, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
return;
|
||||
|
||||
@@ -33,11 +33,7 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
|
||||
private void OnUpdate(EntityUid uid, PressureControlledValveComponent comp, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosDeviceComponent? device)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.InletName, out PipeNode? inletNode)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.ControlName, out PipeNode? controlNode)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, comp.OutletName, out PipeNode? outletNode))
|
||||
if (!_nodeContainer.TryGetNodes(uid, comp.InletName, comp.ControlName, comp.OutletName, out PipeNode? inletNode, out PipeNode? controlNode, out PipeNode? outletNode))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
comp.Enabled = false;
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
if (!Resolve(uid, ref canister, ref transform))
|
||||
return;
|
||||
|
||||
var environment = _atmos.GetContainingMixture(uid, false, true);
|
||||
var environment = _atmos.GetContainingMixture((uid, transform), false, true);
|
||||
|
||||
if (environment is not null)
|
||||
_atmos.Merge(environment, canister.Air);
|
||||
@@ -168,7 +168,7 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
var environment = _atmos.GetContainingMixture(uid, false, true);
|
||||
var environment = _atmos.GetContainingMixture(uid, args.Grid, args.Map, false, true);
|
||||
_atmos.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,8 @@ public sealed class GasCondenserSystem : EntitySystem
|
||||
|
||||
private void OnCondenserUpdated(Entity<GasCondenserComponent> entity, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!(_power.IsPowered(entity) && TryComp<ApcPowerReceiverComponent>(entity, out var receiver))
|
||||
|| !TryComp<NodeContainerComponent>(entity, out var nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, entity.Comp.Inlet, out PipeNode? inlet)
|
||||
if (!(TryComp<ApcPowerReceiverComponent>(entity, out var receiver) && _power.IsPowered(entity, receiver))
|
||||
|| !_nodeContainer.TryGetNode(entity.Owner, entity.Comp.Inlet, out PipeNode? inlet)
|
||||
|| !_solution.ResolveSolution(entity.Owner, entity.Comp.SolutionId, ref entity.Comp.Solution, out var solution))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -50,16 +50,10 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!injector.Enabled)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
if (!_nodeContainer.TryGetNode(uid, injector.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out AtmosDeviceComponent? device))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, injector.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, true, true);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, true, true);
|
||||
|
||||
if (environment == null)
|
||||
return;
|
||||
|
||||
@@ -24,15 +24,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
private void OnPassiveVentUpdated(EntityUid uid, GasPassiveVentComponent vent, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, true, true);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, true, true);
|
||||
|
||||
if (environment == null)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, vent.InletName, out PipeNode? inlet))
|
||||
if (!_nodeContainer.TryGetNode(uid, vent.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
|
||||
var inletAir = inlet.Air.RemoveRatio(1f);
|
||||
|
||||
@@ -39,10 +39,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
private void OnAnchorChanged(EntityUid uid, GasPortableComponent portable, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, portable.PortName, out PipeNode? portableNode))
|
||||
if (!_nodeContainer.TryGetNode(uid, portable.PortName, out PipeNode? portableNode))
|
||||
return;
|
||||
|
||||
portableNode.ConnectionsEnabled = args.Anchored;
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
_atmosphereSystem.AddHeat(heatExchangeGasMixture, dQPipe);
|
||||
thermoMachine.LastEnergyDelta = dQPipe;
|
||||
|
||||
if (dQLeak != 0f && _atmosphereSystem.GetContainingMixture(uid, excite: true) is { } containingMixture)
|
||||
if (dQLeak != 0f && _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, excite: true) is { } containingMixture)
|
||||
_atmosphereSystem.AddHeat(containingMixture, dQLeak);
|
||||
}
|
||||
|
||||
@@ -130,8 +130,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryComp<NodeContainerComponent>(uid, out var nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, thermoMachine.InletName, out PipeNode? inlet))
|
||||
if (!_nodeContainer.TryGetNode(uid, thermoMachine.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
heatExchangeGasMixture = inlet.Air;
|
||||
}
|
||||
|
||||
@@ -67,15 +67,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
if (!vent.Enabled
|
||||
|| !TryComp(uid, out AtmosDeviceComponent? device)
|
||||
|| !TryComp(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, nodeName, out PipeNode? pipe))
|
||||
if (!vent.Enabled || !_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, true, true);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, true, true);
|
||||
|
||||
// We're in an air-blocked tile... Do nothing.
|
||||
if (environment == null)
|
||||
@@ -295,9 +292,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
/// </summary>
|
||||
private void OnAnalyzed(EntityUid uid, GasVentPumpComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
var gasMixDict = new Dictionary<string, GasMixture?>();
|
||||
|
||||
// these are both called pipe, above it switches using this so I duplicated that...?
|
||||
@@ -307,7 +301,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
VentPumpDirection.Siphoning => component.Outlet,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
if (_nodeContainer.TryGetNode(nodeContainer, nodeName, out PipeNode? pipe))
|
||||
if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe))
|
||||
gasMixDict.Add(nodeName, pipe.Air);
|
||||
|
||||
args.GasMixtures = gasMixDict;
|
||||
|
||||
@@ -49,27 +49,18 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
private void OnVentScrubberUpdated(EntityUid uid, GasVentScrubberComponent scrubber, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (_weldable.IsWelded(uid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp(uid, out AtmosDeviceComponent? device))
|
||||
return;
|
||||
|
||||
var timeDelta = args.dt;
|
||||
|
||||
if (!scrubber.Enabled
|
||||
|| !EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode(nodeContainer, scrubber.OutletName, out PipeNode? outlet))
|
||||
if (!scrubber.Enabled || !_nodeContainer.TryGetNode(uid, scrubber.OutletName, out PipeNode? outlet))
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid == null)
|
||||
if (args.Grid is not {} grid)
|
||||
return;
|
||||
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault((uid,xform));
|
||||
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault(uid);
|
||||
var environment = _atmosphereSystem.GetTileMixture(grid, args.Map, position, true);
|
||||
|
||||
Scrub(timeDelta, scrubber, environment, outlet);
|
||||
|
||||
@@ -77,7 +68,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
|
||||
// Scrub adjacent tiles too.
|
||||
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true);
|
||||
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(grid, position, false, true);
|
||||
while (enumerator.MoveNext(out var adjacent))
|
||||
{
|
||||
Scrub(timeDelta, scrubber, adjacent, outlet);
|
||||
|
||||
@@ -47,17 +47,13 @@ namespace Content.Server.Atmos.Portable
|
||||
|
||||
private void OnDeviceUpdated(EntityUid uid, PortableScrubberComponent component, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AtmosDeviceComponent? device))
|
||||
return;
|
||||
|
||||
var timeDelta = args.dt;
|
||||
|
||||
if (!component.Enabled)
|
||||
return;
|
||||
|
||||
// If we are on top of a connector port, empty into it.
|
||||
if (TryComp<NodeContainerComponent>(uid, out var nodeContainer)
|
||||
&& _nodeContainer.TryGetNode(nodeContainer, component.PortName, out PortablePipeNode? portableNode)
|
||||
if (_nodeContainer.TryGetNode(uid, component.PortName, out PortablePipeNode? portableNode)
|
||||
&& portableNode.ConnectionsEnabled)
|
||||
{
|
||||
_atmosphereSystem.React(component.Air, portableNode);
|
||||
@@ -71,13 +67,11 @@ namespace Content.Server.Atmos.Portable
|
||||
return;
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid == null)
|
||||
if (args.Grid is not {} grid)
|
||||
return;
|
||||
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault((uid,xform));
|
||||
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault(uid);
|
||||
var environment = _atmosphereSystem.GetTileMixture(grid, args.Map, position, true);
|
||||
|
||||
var running = Scrub(timeDelta, component, environment);
|
||||
|
||||
@@ -85,8 +79,9 @@ namespace Content.Server.Atmos.Portable
|
||||
// We scrub once to see if we can and set the animation
|
||||
if (!running)
|
||||
return;
|
||||
|
||||
// widenet
|
||||
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true);
|
||||
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(grid, position, false, true);
|
||||
while (enumerator.MoveNext(out var adjacent))
|
||||
{
|
||||
Scrub(timeDelta, component, adjacent);
|
||||
@@ -98,10 +93,7 @@ namespace Content.Server.Atmos.Portable
|
||||
/// </summary>
|
||||
private void OnAnchorChanged(EntityUid uid, PortableScrubberComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, component.PortName, out PipeNode? portableNode))
|
||||
if (!_nodeContainer.TryGetNode(uid, component.PortName, out PipeNode? portableNode))
|
||||
return;
|
||||
|
||||
portableNode.ConnectionsEnabled = (args.Anchored && _gasPortableSystem.FindGasPortIn(Transform(uid).GridUid, Transform(uid).Coordinates, out _));
|
||||
@@ -159,14 +151,10 @@ namespace Content.Server.Atmos.Portable
|
||||
/// </summary>
|
||||
private void OnScrubberAnalyzed(EntityUid uid, PortableScrubberComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
var gasMixDict = new Dictionary<string, GasMixture?> { { Name(uid), component.Air } };
|
||||
args.GasMixtures ??= new Dictionary<string, GasMixture?> { { Name(uid), component.Air } };
|
||||
// If it's connected to a port, include the port side
|
||||
if (TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||
{
|
||||
if (_nodeContainer.TryGetNode(nodeContainer, component.PortName, out PipeNode? port))
|
||||
gasMixDict.Add(component.PortName, port.Air);
|
||||
}
|
||||
args.GasMixtures = gasMixDict;
|
||||
if (_nodeContainer.TryGetNode(uid, component.PortName, out PipeNode? port))
|
||||
args.GasMixtures.Add(component.PortName, port.Air);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public sealed class SpaceHeaterSystem : EntitySystem
|
||||
// If in automatic temperature mode, check if we need to adjust the heat exchange direction
|
||||
if (spaceHeater.Mode == SpaceHeaterMode.Auto)
|
||||
{
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map);
|
||||
if (environment == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
{
|
||||
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
|
||||
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
|
||||
Dictionary<Vector2i, TileAtmosphere> tiles;
|
||||
Dictionary<Vector2i, TileAtmosphere> tiles = new();
|
||||
|
||||
// Backwards compatability
|
||||
if (version == 1)
|
||||
@@ -36,8 +36,6 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
var mixies = serializationManager.Read<Dictionary<Vector2i, int>?>(tile2, hookCtx, context);
|
||||
var unique = serializationManager.Read<List<GasMixture>?>(node["uniqueMixes"], hookCtx, context);
|
||||
|
||||
tiles = new Dictionary<Vector2i, TileAtmosphere>();
|
||||
|
||||
if (unique != null && mixies != null)
|
||||
{
|
||||
foreach (var (indices, mix) in mixies)
|
||||
@@ -58,15 +56,14 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
else
|
||||
{
|
||||
var dataNode = (MappingDataNode) node["data"];
|
||||
var tileNode = (MappingDataNode) dataNode["tiles"];
|
||||
var chunkSize = serializationManager.Read<int>(dataNode["chunkSize"], hookCtx, context);
|
||||
|
||||
var unique = serializationManager.Read<List<GasMixture>?>(dataNode["uniqueMixes"], hookCtx, context);
|
||||
|
||||
tiles = new Dictionary<Vector2i, TileAtmosphere>();
|
||||
dataNode.TryGetValue(new ValueDataNode("uniqueMixes"), out var mixNode);
|
||||
var unique = mixNode == null ? null : serializationManager.Read<List<GasMixture>?>(mixNode, hookCtx, context);
|
||||
|
||||
if (unique != null)
|
||||
{
|
||||
var tileNode = (MappingDataNode) dataNode["tiles"];
|
||||
foreach (var (chunkNode, valueNode) in tileNode)
|
||||
{
|
||||
var chunkOrigin = serializationManager.Read<Vector2i>(chunkNode, hookCtx, context);
|
||||
|
||||
@@ -11,7 +11,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Body.Components
|
||||
{
|
||||
[RegisterComponent, Access(typeof(BloodstreamSystem), (typeof(ChemistrySystem)))]
|
||||
[RegisterComponent, Access(typeof(BloodstreamSystem), typeof(ReactionMixerSystem))]
|
||||
public sealed partial class BloodstreamComponent : Component
|
||||
{
|
||||
public static string DefaultChemicalsSolutionName = "chemicals";
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Chemistry.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HyposprayComponent : SharedHyposprayComponent
|
||||
{
|
||||
// TODO: This should be on clumsycomponent.
|
||||
[DataField("clumsyFailChance")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ClumsyFailChance = 0.5f;
|
||||
|
||||
[DataField("transferAmount")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 TransferAmount = FixedPoint2.New(5);
|
||||
|
||||
[DataField("injectSound")]
|
||||
public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the hypo is able to inject only into mobs. On false you can inject into beakers/jugs
|
||||
/// </summary>
|
||||
[DataField("onlyMobs")]
|
||||
public bool OnlyMobs = true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
|
||||
public sealed partial class ChemistrySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainers = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
// Why ChemMaster duplicates reagentdispenser nobody knows.
|
||||
InitializeHypospray();
|
||||
InitializeMixing();
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.GameStates;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems
|
||||
{
|
||||
public sealed partial class ChemistrySystem
|
||||
{
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
|
||||
private void InitializeHypospray()
|
||||
{
|
||||
SubscribeLocalEvent<HyposprayComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<HyposprayComponent, MeleeHitEvent>(OnAttack);
|
||||
SubscribeLocalEvent<HyposprayComponent, SolutionContainerChangedEvent>(OnSolutionChange);
|
||||
SubscribeLocalEvent<HyposprayComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<HyposprayComponent, ComponentGetState>(OnHypoGetState);
|
||||
}
|
||||
|
||||
private void OnHypoGetState(Entity<HyposprayComponent> entity, ref ComponentGetState args)
|
||||
{
|
||||
args.State = _solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out _, out var solution)
|
||||
? new HyposprayComponentState(solution.Volume, solution.MaxVolume)
|
||||
: new HyposprayComponentState(FixedPoint2.Zero, FixedPoint2.Zero);
|
||||
}
|
||||
|
||||
private void OnUseInHand(Entity<HyposprayComponent> entity, ref UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
TryDoInject(entity, args.User, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSolutionChange(Entity<HyposprayComponent> entity, ref SolutionContainerChangedEvent args)
|
||||
{
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
public void OnAfterInteract(Entity<HyposprayComponent> entity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
var target = args.Target;
|
||||
var user = args.User;
|
||||
|
||||
TryDoInject(entity, target, user);
|
||||
}
|
||||
|
||||
public void OnAttack(Entity<HyposprayComponent> entity, ref MeleeHitEvent args)
|
||||
{
|
||||
if (!args.HitEntities.Any())
|
||||
return;
|
||||
|
||||
TryDoInject(entity, args.HitEntities.First(), args.User);
|
||||
}
|
||||
|
||||
public bool TryDoInject(Entity<HyposprayComponent> hypo, EntityUid? target, EntityUid user)
|
||||
{
|
||||
var (uid, component) = hypo;
|
||||
|
||||
if (!EligibleEntity(target, _entMan, component))
|
||||
return false;
|
||||
|
||||
if (TryComp(uid, out UseDelayComponent? delayComp))
|
||||
{
|
||||
if (_useDelay.IsDelayed((uid, delayComp)))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
string? msgFormat = null;
|
||||
|
||||
if (target == user)
|
||||
msgFormat = "hypospray-component-inject-self-message";
|
||||
else if (EligibleEntity(user, _entMan, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance))
|
||||
{
|
||||
msgFormat = "hypospray-component-inject-self-clumsy-message";
|
||||
target = user;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0)
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("hypospray-component-empty-message"), user);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetInjectableSolution(target.Value, out var targetSoln, out var targetSolution))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target.Value, _entMan))), user);
|
||||
return false;
|
||||
}
|
||||
|
||||
_popup.PopupCursor(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target)), user);
|
||||
|
||||
if (target != user)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target.Value, target.Value);
|
||||
// TODO: This should just be using melee attacks...
|
||||
// meleeSys.SendLunge(angle, user);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(component.InjectSound, user);
|
||||
|
||||
// Medipens and such use this system and don't have a delay, requiring extra checks
|
||||
// BeginDelay function returns if item is already on delay
|
||||
if (delayComp != null)
|
||||
_useDelay.TryResetDelay((uid, delayComp));
|
||||
|
||||
// Get transfer amount. May be smaller than component.TransferAmount if not enough room
|
||||
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), user);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = _solutionContainers.SplitSolution(hypoSpraySoln.Value, realTransferAmount);
|
||||
|
||||
if (!targetSolution.CanAddSolution(removedSolution))
|
||||
return true;
|
||||
_reactiveSystem.DoEntityReaction(target.Value, removedSolution, ReactionMethod.Injection);
|
||||
_solutionContainers.TryAddSolution(targetSoln.Value, removedSolution);
|
||||
|
||||
var ev = new TransferDnaEvent { Donor = target.Value, Recipient = uid };
|
||||
RaiseLocalEvent(target.Value, ref ev);
|
||||
|
||||
// same LogType as syringes...
|
||||
_adminLogger.Add(LogType.ForceFeed, $"{_entMan.ToPrettyString(user):user} injected {_entMan.ToPrettyString(target.Value):target} with a solution {SolutionContainerSystem.ToPrettyString(removedSolution):removedSolution} using a {_entMan.ToPrettyString(uid):using}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EligibleEntity([NotNullWhen(true)] EntityUid? entity, IEntityManager entMan, HyposprayComponent component)
|
||||
{
|
||||
// TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag?
|
||||
// In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else.
|
||||
// But this is 14, we dont do what SS13 does just because SS13 does it.
|
||||
return component.OnlyMobs
|
||||
? entMan.HasComponent<SolutionContainerManagerComponent>(entity) &&
|
||||
entMan.HasComponent<MobStateComponent>(entity)
|
||||
: entMan.HasComponent<SolutionContainerManagerComponent>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
197
Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
Normal file
197
Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
|
||||
public sealed class HypospraySystem : SharedHypospraySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HyposprayComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<HyposprayComponent, MeleeHitEvent>(OnAttack);
|
||||
SubscribeLocalEvent<HyposprayComponent, UseInHandEvent>(OnUseInHand);
|
||||
}
|
||||
|
||||
private void UseHypospray(Entity<HyposprayComponent> entity, EntityUid target, EntityUid user)
|
||||
{
|
||||
// if target is ineligible but is a container, try to draw from the container
|
||||
if (!EligibleEntity(target, EntityManager, entity)
|
||||
&& _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
|
||||
{
|
||||
TryDraw(entity, target, drawableSolution.Value, user);
|
||||
}
|
||||
|
||||
TryDoInject(entity, target, user);
|
||||
}
|
||||
|
||||
private void OnUseInHand(Entity<HyposprayComponent> entity, ref UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
TryDoInject(entity, args.User, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void OnAfterInteract(Entity<HyposprayComponent> entity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
UseHypospray(entity, args.Target.Value, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void OnAttack(Entity<HyposprayComponent> entity, ref MeleeHitEvent args)
|
||||
{
|
||||
if (!args.HitEntities.Any())
|
||||
return;
|
||||
|
||||
TryDoInject(entity, args.HitEntities.First(), args.User);
|
||||
}
|
||||
|
||||
public bool TryDoInject(Entity<HyposprayComponent> entity, EntityUid target, EntityUid user)
|
||||
{
|
||||
var (uid, component) = entity;
|
||||
|
||||
if (!EligibleEntity(target, EntityManager, component))
|
||||
return false;
|
||||
|
||||
if (TryComp(uid, out UseDelayComponent? delayComp))
|
||||
{
|
||||
if (_useDelay.IsDelayed((uid, delayComp)))
|
||||
return false;
|
||||
}
|
||||
|
||||
string? msgFormat = null;
|
||||
|
||||
if (target == user)
|
||||
msgFormat = "hypospray-component-inject-self-message";
|
||||
else if (EligibleEntity(user, EntityManager, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance))
|
||||
{
|
||||
msgFormat = "hypospray-component-inject-self-clumsy-message";
|
||||
target = user;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-empty-message"), target, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetInjectableSolution(target, out var targetSoln, out var targetSolution))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target, EntityManager))), target, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target)), target, user);
|
||||
|
||||
if (target != user)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target);
|
||||
// TODO: This should just be using melee attacks...
|
||||
// meleeSys.SendLunge(angle, user);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(component.InjectSound, user);
|
||||
|
||||
// Medipens and such use this system and don't have a delay, requiring extra checks
|
||||
// BeginDelay function returns if item is already on delay
|
||||
if (delayComp != null)
|
||||
_useDelay.TryResetDelay((uid, delayComp));
|
||||
|
||||
// Get transfer amount. May be smaller than component.TransferAmount if not enough room
|
||||
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), target, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = _solutionContainers.SplitSolution(hypoSpraySoln.Value, realTransferAmount);
|
||||
|
||||
if (!targetSolution.CanAddSolution(removedSolution))
|
||||
return true;
|
||||
_reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection);
|
||||
_solutionContainers.TryAddSolution(targetSoln.Value, removedSolution);
|
||||
|
||||
var ev = new TransferDnaEvent { Donor = target, Recipient = uid };
|
||||
RaiseLocalEvent(target, ref ev);
|
||||
|
||||
// same LogType as syringes...
|
||||
_adminLogger.Add(LogType.ForceFeed, $"{EntityManager.ToPrettyString(user):user} injected {EntityManager.ToPrettyString(target):target} with a solution {SolutionContainerSystem.ToPrettyString(removedSolution):removedSolution} using a {EntityManager.ToPrettyString(uid):using}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryDraw(Entity<HyposprayComponent> entity, Entity<BloodstreamComponent?> target, Entity<SolutionComponent> targetSolution, EntityUid user)
|
||||
{
|
||||
if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln,
|
||||
out var solution) || solution.AvailableVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
|
||||
var realTransferAmount = FixedPoint2.Min(entity.Comp.TransferAmount, targetSolution.Comp.Solution.Volume,
|
||||
solution.AvailableVolume);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_popup.PopupEntity(
|
||||
Loc.GetString("injector-component-target-is-empty-message",
|
||||
("target", Identity.Entity(target, EntityManager))),
|
||||
entity.Owner, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var removedSolution = _solutionContainers.Draw(target.Owner, targetSolution, realTransferAmount);
|
||||
|
||||
if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
|
||||
("amount", removedSolution.Volume),
|
||||
("target", Identity.Entity(target, EntityManager))), entity.Owner, user);
|
||||
}
|
||||
|
||||
private bool EligibleEntity(EntityUid entity, IEntityManager entMan, HyposprayComponent component)
|
||||
{
|
||||
// TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag?
|
||||
// In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else.
|
||||
// But this is 14, we dont do what SS13 does just because SS13 does it.
|
||||
return component.OnlyAffectsMobs
|
||||
? entMan.HasComponent<SolutionContainerManagerComponent>(entity) &&
|
||||
entMan.HasComponent<MobStateComponent>(entity)
|
||||
: entMan.HasComponent<SolutionContainerManagerComponent>(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
|
||||
public sealed partial class ChemistrySystem
|
||||
public sealed partial class ReactionMixerSystem : EntitySystem
|
||||
{
|
||||
public void InitializeMixing()
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainers = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReactionMixerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Content.Server.Electrocution
|
||||
public sealed partial class ElectrocutionNode : Node
|
||||
{
|
||||
[DataField("cable")]
|
||||
public EntityUid CableEntity;
|
||||
public EntityUid? CableEntity;
|
||||
[DataField("node")]
|
||||
public string? NodeName;
|
||||
|
||||
@@ -19,12 +19,11 @@ namespace Content.Server.Electrocution
|
||||
MapGridComponent? grid,
|
||||
IEntityManager entMan)
|
||||
{
|
||||
var _nodeContainer = entMan.System<NodeContainerSystem>();
|
||||
|
||||
if (!nodeQuery.TryGetComponent(CableEntity, out var nodeContainer))
|
||||
if (CableEntity == null || NodeName == null)
|
||||
yield break;
|
||||
|
||||
if (_nodeContainer.TryGetNode(nodeContainer, NodeName, out Node? node))
|
||||
var _nodeContainer = entMan.System<NodeContainerSystem>();
|
||||
if (_nodeContainer.TryGetNode(CableEntity.Value, NodeName, out Node? node))
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
if (ignoreTileBlockers || !blockedDirections.IsFlagSet(direction))
|
||||
{
|
||||
ProcessNewTile(iteration, tile.Offset(direction), direction.GetOpposite());
|
||||
ProcessNewTile(iteration, tile.Offset(direction), i.ToOppositeDir());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
if (blockedDirections.IsFlagSet(direction))
|
||||
{
|
||||
list.Add((tile.Offset(direction), direction.GetOpposite()));
|
||||
list.Add((tile.Offset(direction), i.ToOppositeDir()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class ExplosionSpaceTileFlood : ExplosionTileFlood
|
||||
if (!unblockedDirections.IsFlagSet(direction))
|
||||
continue; // explosion cannot propagate in this direction. Ever.
|
||||
|
||||
ProcessNewTile(iteration, tile.Offset(direction), direction.GetOpposite());
|
||||
ProcessNewTile(iteration, tile.Offset(direction), i.ToOppositeDir());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,15 @@ public sealed partial class ExplosionSystem
|
||||
/// Queue for delayed processing of explosions. If there is an explosion that covers more than <see
|
||||
/// cref="TilesPerTick"/> tiles, other explosions will actually be delayed slightly. Unless it's a station
|
||||
/// nuke, this delay should never really be noticeable.
|
||||
/// This is also used to combine explosion intensities of the same kind.
|
||||
/// </summary>
|
||||
private Queue<Func<Explosion?>> _explosionQueue = new();
|
||||
private Queue<QueuedExplosion> _explosionQueue = new();
|
||||
|
||||
/// <summary>
|
||||
/// All queued explosions that will be processed in <see cref="_explosionQueue"/>.
|
||||
/// These always have the same contents.
|
||||
/// </summary>
|
||||
private HashSet<QueuedExplosion> _queuedExplosions = new();
|
||||
|
||||
/// <summary>
|
||||
/// The explosion currently being processed.
|
||||
@@ -93,10 +100,11 @@ public sealed partial class ExplosionSystem
|
||||
if (MathF.Max(MaxProcessingTime - 1, 0.1f) < Stopwatch.Elapsed.TotalMilliseconds)
|
||||
break;
|
||||
|
||||
if (!_explosionQueue.TryDequeue(out var spawnNextExplosion))
|
||||
if (!_explosionQueue.TryDequeue(out var queued))
|
||||
break;
|
||||
|
||||
_activeExplosion = spawnNextExplosion();
|
||||
_queuedExplosions.Remove(queued);
|
||||
_activeExplosion = SpawnExplosion(queued);
|
||||
|
||||
// explosion spawning can be null if something somewhere went wrong. (e.g., negative explosion
|
||||
// intensity).
|
||||
@@ -867,3 +875,15 @@ sealed class Explosion
|
||||
_tileUpdateDict.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data needed to spawn an explosion with <see cref="ExplosionSystem.SpawnExplosion"/>.
|
||||
/// </summary>
|
||||
public sealed class QueuedExplosion
|
||||
{
|
||||
public MapCoordinates Epicenter;
|
||||
public ExplosionPrototype Proto = new();
|
||||
public float TotalIntensity, Slope, MaxTileIntensity, TileBreakScale;
|
||||
public int MaxTileBreak;
|
||||
public bool CanCreateVacuum;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ public sealed partial class ExplosionSystem : EntitySystem
|
||||
private void OnReset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_explosionQueue.Clear();
|
||||
_queuedExplosions.Clear();
|
||||
if (_activeExplosion != null)
|
||||
QueueDel(_activeExplosion.VisualEnt);
|
||||
_activeExplosion = null;
|
||||
@@ -297,8 +298,36 @@ public sealed partial class ExplosionSystem : EntitySystem
|
||||
if (addLog) // dont log if already created a separate, more detailed, log.
|
||||
_adminLogger.Add(LogType.Explosion, LogImpact.High, $"Explosion ({typeId}) spawned at {epicenter:coordinates} with intensity {totalIntensity} slope {slope}");
|
||||
|
||||
_explosionQueue.Enqueue(() => SpawnExplosion(epicenter, type, totalIntensity,
|
||||
slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum));
|
||||
// try to combine explosions on the same tile if they are the same type
|
||||
foreach (var queued in _queuedExplosions)
|
||||
{
|
||||
// ignore different types or those on different maps
|
||||
if (queued.Proto.ID != type.ID || queued.Epicenter.MapId != epicenter.MapId)
|
||||
continue;
|
||||
|
||||
var dst2 = queued.Proto.MaxCombineDistance * queued.Proto.MaxCombineDistance;
|
||||
var direction = queued.Epicenter.Position - epicenter.Position;
|
||||
if (direction.LengthSquared() > dst2)
|
||||
continue;
|
||||
|
||||
// they are close enough to combine so just add total intensity and prevent queuing another one
|
||||
queued.TotalIntensity += totalIntensity;
|
||||
return;
|
||||
}
|
||||
|
||||
var boom = new QueuedExplosion()
|
||||
{
|
||||
Epicenter = epicenter,
|
||||
Proto = type,
|
||||
TotalIntensity = totalIntensity,
|
||||
Slope = slope,
|
||||
MaxTileIntensity = maxTileIntensity,
|
||||
TileBreakScale = tileBreakScale,
|
||||
MaxTileBreak = maxTileBreak,
|
||||
CanCreateVacuum = canCreateVacuum
|
||||
};
|
||||
_explosionQueue.Enqueue(boom);
|
||||
_queuedExplosions.Add(boom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -306,32 +335,26 @@ public sealed partial class ExplosionSystem : EntitySystem
|
||||
/// information about the affected tiles for the explosion system to process. It will also trigger the
|
||||
/// camera shake and sound effect.
|
||||
/// </summary>
|
||||
private Explosion? SpawnExplosion(MapCoordinates epicenter,
|
||||
ExplosionPrototype type,
|
||||
float totalIntensity,
|
||||
float slope,
|
||||
float maxTileIntensity,
|
||||
float tileBreakScale,
|
||||
int maxTileBreak,
|
||||
bool canCreateVacuum)
|
||||
private Explosion? SpawnExplosion(QueuedExplosion queued)
|
||||
{
|
||||
if (!_mapManager.MapExists(epicenter.MapId))
|
||||
var pos = queued.Epicenter;
|
||||
if (!_mapManager.MapExists(pos.MapId))
|
||||
return null;
|
||||
|
||||
var results = GetExplosionTiles(epicenter, type.ID, totalIntensity, slope, maxTileIntensity);
|
||||
var results = GetExplosionTiles(pos, queued.Proto.ID, queued.TotalIntensity, queued.Slope, queued.MaxTileIntensity);
|
||||
|
||||
if (results == null)
|
||||
return null;
|
||||
|
||||
var (area, iterationIntensity, spaceData, gridData, spaceMatrix) = results.Value;
|
||||
|
||||
var visualEnt = CreateExplosionVisualEntity(epicenter, type.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||
|
||||
// camera shake
|
||||
CameraShake(iterationIntensity.Count * 4f, epicenter, totalIntensity);
|
||||
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
||||
|
||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||
var mapEntityCoords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(epicenter.MapId), epicenter, _transformSystem, EntityManager);
|
||||
var mapEntityCoords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(pos.MapId), pos, _transformSystem, EntityManager);
|
||||
|
||||
// play sound.
|
||||
// for the normal audio, we want everyone in pvs range
|
||||
@@ -339,34 +362,35 @@ public sealed partial class ExplosionSystem : EntitySystem
|
||||
// this is capped to 30 because otherwise really huge bombs
|
||||
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
|
||||
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
|
||||
var filter = Filter.Pvs(epicenter).AddInRange(epicenter, audioRange);
|
||||
var sound = iterationIntensity.Count < type.SmallSoundIterationThreshold
|
||||
? type.SmallSound
|
||||
: type.Sound;
|
||||
var filter = Filter.Pvs(pos).AddInRange(pos, audioRange);
|
||||
var sound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? queued.Proto.SmallSound
|
||||
: queued.Proto.Sound;
|
||||
|
||||
_audio.PlayStatic(sound, filter, mapEntityCoords, true, sound.Params);
|
||||
|
||||
// play far sound
|
||||
// far sound should play for anyone who wasn't in range of any of the effects of the bomb
|
||||
var farAudioRange = iterationIntensity.Count * 5;
|
||||
var farFilter = Filter.Empty().AddInRange(epicenter, farAudioRange).RemoveInRange(epicenter, audioRange);
|
||||
var farSound = iterationIntensity.Count < type.SmallSoundIterationThreshold
|
||||
? type.SmallSoundFar
|
||||
: type.SoundFar;
|
||||
var farFilter = Filter.Empty().AddInRange(pos, farAudioRange).RemoveInRange(pos, audioRange);
|
||||
var farSound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? queued.Proto.SmallSoundFar
|
||||
: queued.Proto.SoundFar;
|
||||
|
||||
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
||||
|
||||
return new Explosion(this,
|
||||
type,
|
||||
queued.Proto,
|
||||
spaceData,
|
||||
gridData.Values.ToList(),
|
||||
iterationIntensity,
|
||||
epicenter,
|
||||
pos,
|
||||
spaceMatrix,
|
||||
area,
|
||||
tileBreakScale,
|
||||
maxTileBreak,
|
||||
canCreateVacuum,
|
||||
// TODO: instead of le copy paste fields refactor so it has QueuedExplosion as a field?
|
||||
queued.TileBreakScale,
|
||||
queued.MaxTileBreak,
|
||||
queued.CanCreateVacuum,
|
||||
EntityManager,
|
||||
_mapManager,
|
||||
visualEnt);
|
||||
|
||||
@@ -256,7 +256,7 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta == null || component.Host == null || component.DamageShare > 0)
|
||||
if (args.DamageDelta == null || component.Host == null || component.DamageShare == 0)
|
||||
return;
|
||||
|
||||
_damageSystem.TryChangeDamage(
|
||||
|
||||
@@ -139,7 +139,6 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
||||
break;
|
||||
}
|
||||
_xform.SetWorldPosition(ent, targetCoords.Position);
|
||||
_xform.AttachToGridOrMap(ent, xform);
|
||||
_audio.PlayPvs(implant.TeleportSound, ent);
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
@@ -404,14 +404,17 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<MechComponent>(component.Mech, out var mech) ||
|
||||
!TryComp<MechAirComponent>(component.Mech, out var mechAir))
|
||||
if (!TryComp(component.Mech, out MechComponent? mech))
|
||||
return;
|
||||
|
||||
if (mech.Airtight && TryComp(component.Mech, out MechAirComponent? air))
|
||||
{
|
||||
args.Handled = true;
|
||||
args.Gas = air.Air;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Gas = mech.Airtight ? mechAir.Air : _atmosphere.GetContainingMixture(component.Mech);
|
||||
|
||||
args.Gas = _atmosphere.GetContainingMixture(component.Mech, excite: args.Excite);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,10 +257,7 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
||||
|
||||
private void OnCryoPodUpdateAtmosphere(Entity<CryoPodComponent> entity, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp(entity, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, entity.Comp.PortName, out PortablePipeNode? portNode))
|
||||
if (!_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PortablePipeNode? portNode))
|
||||
return;
|
||||
|
||||
if (!TryComp(entity, out CryoPodAirComponent? cryoPodAir))
|
||||
@@ -279,14 +276,10 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
||||
if (!TryComp(entity, out CryoPodAirComponent? cryoPodAir))
|
||||
return;
|
||||
|
||||
var gasMixDict = new Dictionary<string, GasMixture?> { { Name(entity.Owner), cryoPodAir.Air } };
|
||||
args.GasMixtures ??= new Dictionary<string, GasMixture?> { { Name(entity.Owner), cryoPodAir.Air } };
|
||||
// If it's connected to a port, include the port side
|
||||
if (TryComp(entity, out NodeContainerComponent? nodeContainer))
|
||||
{
|
||||
if (_nodeContainer.TryGetNode(nodeContainer, entity.Comp.PortName, out PipeNode? port))
|
||||
gasMixDict.Add(entity.Comp.PortName, port.Air);
|
||||
}
|
||||
args.GasMixtures = gasMixDict;
|
||||
if (_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PipeNode? port))
|
||||
args.GasMixtures.Add(entity.Comp.PortName, port.Air);
|
||||
}
|
||||
|
||||
private void OnEjected(Entity<CryoPodComponent> cryoPod, ref EntRemovedFromContainerMessage args)
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Content.Server.NodeContainer.EntitySystems
|
||||
public sealed class NodeContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
|
||||
private EntityQuery<NodeContainerComponent> _query;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -26,6 +27,8 @@ namespace Content.Server.NodeContainer.EntitySystems
|
||||
SubscribeLocalEvent<NodeContainerComponent, ReAnchorEvent>(OnReAnchor);
|
||||
SubscribeLocalEvent<NodeContainerComponent, MoveEvent>(OnMoveEvent);
|
||||
SubscribeLocalEvent<NodeContainerComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
_query = GetEntityQuery<NodeContainerComponent>();
|
||||
}
|
||||
|
||||
public bool TryGetNode<T>(NodeContainerComponent component, string? identifier, [NotNullWhen(true)] out T? node) where T : Node
|
||||
@@ -46,6 +49,77 @@ namespace Content.Server.NodeContainer.EntitySystems
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetNode<T>(Entity<NodeContainerComponent?> ent, string identifier, [NotNullWhen(true)] out T? node) where T : Node
|
||||
{
|
||||
if (_query.Resolve(ent, ref ent.Comp, false)
|
||||
&& ent.Comp.Nodes.TryGetValue(identifier, out var n)
|
||||
&& n is T t)
|
||||
{
|
||||
node = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetNodes<T1, T2>(
|
||||
Entity<NodeContainerComponent?> ent,
|
||||
string id1,
|
||||
string id2,
|
||||
[NotNullWhen(true)] out T1? node1,
|
||||
[NotNullWhen(true)] out T2? node2)
|
||||
where T1 : Node
|
||||
where T2 : Node
|
||||
{
|
||||
if (_query.Resolve(ent, ref ent.Comp, false)
|
||||
&& ent.Comp.Nodes.TryGetValue(id1, out var n1)
|
||||
&& n1 is T1 t1
|
||||
&& ent.Comp.Nodes.TryGetValue(id2, out var n2)
|
||||
&& n2 is T2 t2)
|
||||
{
|
||||
node1 = t1;
|
||||
node2 = t2;
|
||||
return true;
|
||||
}
|
||||
|
||||
node1 = null;
|
||||
node2 = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetNodes<T1, T2, T3>(
|
||||
Entity<NodeContainerComponent?> ent,
|
||||
string id1,
|
||||
string id2,
|
||||
string id3,
|
||||
[NotNullWhen(true)] out T1? node1,
|
||||
[NotNullWhen(true)] out T2? node2,
|
||||
[NotNullWhen(true)] out T3? node3)
|
||||
where T1 : Node
|
||||
where T2 : Node
|
||||
where T3 : Node
|
||||
{
|
||||
if (_query.Resolve(ent, ref ent.Comp, false)
|
||||
&& ent.Comp.Nodes.TryGetValue(id1, out var n1)
|
||||
&& n1 is T1 t1
|
||||
&& ent.Comp.Nodes.TryGetValue(id2, out var n2)
|
||||
&& n2 is T2 t2
|
||||
&& ent.Comp.Nodes.TryGetValue(id3, out var n3)
|
||||
&& n2 is T3 t3)
|
||||
{
|
||||
node1 = t1;
|
||||
node2 = t2;
|
||||
node3 = t3;
|
||||
return true;
|
||||
}
|
||||
|
||||
node1 = null;
|
||||
node2 = null;
|
||||
node3 = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args)
|
||||
{
|
||||
foreach (var (key, node) in component.Nodes)
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem
|
||||
return;
|
||||
|
||||
// this should always be possible, as we'll eject the gas tank when it no longer is
|
||||
var environment = _atmos.GetContainingMixture(cannon, false, true);
|
||||
var environment = _atmos.GetContainingMixture(cannon.Owner, false, true);
|
||||
var removed = _gasTank.RemoveAir(gas.Value, component.GasUsage);
|
||||
if (environment != null && removed != null)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
public sealed partial class CableVisComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("node")]
|
||||
public string? Node;
|
||||
[DataField("node", required:true)]
|
||||
public string Node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, CableVisComponent cableVis, ref NodeGroupsRebuilt args)
|
||||
{
|
||||
if (!TryComp(uid, out NodeContainerComponent? nodeContainer) || !TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode<CableNode>(nodeContainer, cableVis.Node, out var node))
|
||||
if (!_nodeContainer.TryGetNode(uid, cableVis.Node, out CableNode? node))
|
||||
return;
|
||||
|
||||
var transform = Transform(uid);
|
||||
@@ -55,7 +52,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
};
|
||||
}
|
||||
|
||||
_appearance.SetData(uid, WireVisVisuals.ConnectedMask, mask, appearance);
|
||||
_appearance.SetData(uid, WireVisVisuals.ConnectedMask, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,8 @@ public sealed class GasPowerReceiverSystem : EntitySystem
|
||||
{
|
||||
var timeDelta = args.dt;
|
||||
|
||||
if (!HasComp<AtmosDeviceComponent>(uid)
|
||||
|| !TryComp<NodeContainerComponent>(uid, out var nodeContainer)
|
||||
|| !_nodeContainer.TryGetNode<PipeNode>(nodeContainer, "pipe", out var pipe))
|
||||
{
|
||||
if (!_nodeContainer.TryGetNode(uid, "pipe", out PipeNode? pipe))
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're below the max temperature, then we are simply consuming our target gas
|
||||
if (pipe.Air.Temperature <= component.MaxTemperature)
|
||||
@@ -57,7 +53,7 @@ public sealed class GasPowerReceiverSystem : EntitySystem
|
||||
if (component.OffVentGas)
|
||||
{
|
||||
// eject the gas into the atmosphere
|
||||
var mix = _atmosphereSystem.GetContainingMixture(uid, false, true);
|
||||
var mix = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, false, true);
|
||||
if (mix is not null)
|
||||
_atmosphereSystem.Merge(res, mix);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -16,15 +17,15 @@ public sealed partial class FTLComponent : Component
|
||||
[ViewVariables]
|
||||
public FTLState State = FTLState.Available;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public StartEndTime StateTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StartupTime = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TravelTime = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Shuttles.UI.MapObjects;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -257,7 +258,11 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
else
|
||||
{
|
||||
navState = new NavInterfaceState(0f, null, null, new Dictionary<NetEntity, List<DockingPortState>>());
|
||||
mapState = new ShuttleMapInterfaceState(FTLState.Invalid, 0f, new List<ShuttleBeaconObject>(), new List<ShuttleExclusionObject>());
|
||||
mapState = new ShuttleMapInterfaceState(
|
||||
FTLState.Invalid,
|
||||
default,
|
||||
new List<ShuttleBeaconObject>(),
|
||||
new List<ShuttleExclusionObject>());
|
||||
}
|
||||
|
||||
if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui))
|
||||
@@ -408,12 +413,12 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
public ShuttleMapInterfaceState GetMapState(Entity<FTLComponent?> shuttle)
|
||||
{
|
||||
FTLState ftlState = FTLState.Available;
|
||||
float stateDuration = 0f;
|
||||
StartEndTime stateDuration = default;
|
||||
|
||||
if (Resolve(shuttle, ref shuttle.Comp, false) && shuttle.Comp.LifeStage < ComponentLifeStage.Stopped)
|
||||
{
|
||||
ftlState = shuttle.Comp.State;
|
||||
stateDuration = _shuttle.GetStateDuration(shuttle.Comp);
|
||||
stateDuration = _shuttle.GetStateTime(shuttle.Comp);
|
||||
}
|
||||
|
||||
List<ShuttleBeaconObject>? beacons = null;
|
||||
@@ -422,7 +427,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
GetExclusions(ref exclusions);
|
||||
|
||||
return new ShuttleMapInterfaceState(
|
||||
ftlState, stateDuration,
|
||||
ftlState,
|
||||
stateDuration,
|
||||
beacons ?? new List<ShuttleBeaconObject>(),
|
||||
exclusions ?? new List<ShuttleExclusionObject>());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Parallax;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Whitelist;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -131,7 +132,7 @@ public sealed partial class ShuttleSystem
|
||||
return mapUid;
|
||||
}
|
||||
|
||||
public float GetStateDuration(FTLComponent component)
|
||||
public StartEndTime GetStateTime(FTLComponent component)
|
||||
{
|
||||
var state = component.State;
|
||||
|
||||
@@ -141,9 +142,9 @@ public sealed partial class ShuttleSystem
|
||||
case FTLState.Travelling:
|
||||
case FTLState.Arriving:
|
||||
case FTLState.Cooldown:
|
||||
return component.Accumulator;
|
||||
return component.StateTime;
|
||||
case FTLState.Available:
|
||||
return 0f;
|
||||
return default;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -251,7 +252,9 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
hyperspace.StartupTime = startupTime;
|
||||
hyperspace.TravelTime = hyperspaceTime;
|
||||
hyperspace.Accumulator = hyperspace.StartupTime;
|
||||
hyperspace.StateTime = StartEndTime.FromStartDuration(
|
||||
_gameTiming.CurTime,
|
||||
TimeSpan.FromSeconds(hyperspace.StartupTime));
|
||||
hyperspace.TargetCoordinates = coordinates;
|
||||
hyperspace.TargetAngle = angle;
|
||||
hyperspace.PriorityTag = priorityTag;
|
||||
@@ -282,7 +285,9 @@ public sealed partial class ShuttleSystem
|
||||
var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag);
|
||||
hyperspace.StartupTime = startupTime;
|
||||
hyperspace.TravelTime = hyperspaceTime;
|
||||
hyperspace.Accumulator = hyperspace.StartupTime;
|
||||
hyperspace.StateTime = StartEndTime.FromStartDuration(
|
||||
_gameTiming.CurTime,
|
||||
TimeSpan.FromSeconds(hyperspace.StartupTime));
|
||||
hyperspace.PriorityTag = priorityTag;
|
||||
|
||||
_console.RefreshShuttleConsoles(shuttleUid);
|
||||
@@ -366,7 +371,7 @@ public sealed partial class ShuttleSystem
|
||||
// Reset rotation so they always face the same direction.
|
||||
xform.LocalRotation = Angle.Zero;
|
||||
_index += width + Buffer;
|
||||
comp.Accumulator += comp.TravelTime - DefaultArrivalTime;
|
||||
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, comp.TravelTime - DefaultArrivalTime);
|
||||
|
||||
Enable(uid, component: body);
|
||||
_physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body);
|
||||
@@ -401,7 +406,7 @@ public sealed partial class ShuttleSystem
|
||||
{
|
||||
var shuttle = entity.Comp2;
|
||||
var comp = entity.Comp1;
|
||||
comp.Accumulator += DefaultArrivalTime;
|
||||
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime);
|
||||
comp.State = FTLState.Arriving;
|
||||
// TODO: Arrival effects
|
||||
// For now we'll just use the ss13 bubbles but we can do fancier.
|
||||
@@ -504,7 +509,7 @@ public sealed partial class ShuttleSystem
|
||||
}
|
||||
|
||||
comp.State = FTLState.Cooldown;
|
||||
comp.Accumulator += FTLCooldown;
|
||||
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, FTLCooldown);
|
||||
_console.RefreshShuttleConsoles(uid);
|
||||
_mapManager.SetMapPaused(mapId, false);
|
||||
Smimsh(uid, xform: xform);
|
||||
@@ -519,15 +524,14 @@ public sealed partial class ShuttleSystem
|
||||
_console.RefreshShuttleConsoles(entity);
|
||||
}
|
||||
|
||||
private void UpdateHyperspace(float frameTime)
|
||||
private void UpdateHyperspace()
|
||||
{
|
||||
var curTime = _gameTiming.CurTime;
|
||||
var query = EntityQueryEnumerator<FTLComponent, ShuttleComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var shuttle))
|
||||
{
|
||||
comp.Accumulator -= frameTime;
|
||||
|
||||
if (comp.Accumulator > 0f)
|
||||
if (curTime < comp.StateTime.End)
|
||||
continue;
|
||||
|
||||
var entity = (uid, comp, shuttle);
|
||||
|
||||
@@ -19,6 +19,7 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
@@ -30,6 +31,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly BiomeSystem _biomes = default!;
|
||||
[Dependency] private readonly BodySystem _bobby = default!;
|
||||
[Dependency] private readonly DockingSystem _dockSystem = default!;
|
||||
@@ -68,7 +70,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
UpdateHyperspace(frameTime);
|
||||
UpdateHyperspace();
|
||||
}
|
||||
|
||||
private void OnGridFixtureChange(EntityUid uid, FixturesComponent manager, GridFixtureChangeEvent args)
|
||||
|
||||
@@ -231,10 +231,9 @@ public sealed class SpreaderSystem : EntitySystem
|
||||
// Add the normal neighbors.
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var direction = (Direction) (i * 2);
|
||||
var atmosDir = direction.ToAtmosDirection();
|
||||
var neighborPos = SharedMapSystem.GetDirection(tile, direction);
|
||||
neighborTiles.Add((comp.GridUid.Value, grid, neighborPos, atmosDir, atmosDir.GetOpposite()));
|
||||
var atmosDir = (AtmosDirection) (1 << i);
|
||||
var neighborPos = tile.Offset(atmosDir);
|
||||
neighborTiles.Add((comp.GridUid.Value, grid, neighborPos, atmosDir, i.ToOppositeDir()));
|
||||
}
|
||||
|
||||
foreach (var (neighborEnt, neighborGrid, neighborPos, ourAtmosDir, otherAtmosDir) in neighborTiles)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos
|
||||
@@ -15,6 +16,8 @@ namespace Content.Shared.Atmos
|
||||
South = 1 << 1, // 2
|
||||
East = 1 << 2, // 4
|
||||
West = 1 << 3, // 8
|
||||
// If more directions are added, note that AtmosDirectionHelpers.ToOppositeIndex() expects opposite directions
|
||||
// to come in pairs
|
||||
|
||||
NorthEast = North | East, // 5
|
||||
SouthEast = South | East, // 6
|
||||
@@ -42,6 +45,22 @@ namespace Content.Shared.Atmos
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns the index that corresponds to the opposite direction of some other direction index.
|
||||
/// I.e., <c>1<<OppositeIndex(i) == (1<<i).GetOpposite()</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToOppositeIndex(this int index)
|
||||
{
|
||||
return index ^ 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AtmosDirection ToOppositeDir(this int index)
|
||||
{
|
||||
return (AtmosDirection) (1 << (index ^ 1));
|
||||
}
|
||||
|
||||
public static Direction ToDirection(this AtmosDirection direction)
|
||||
{
|
||||
return direction switch
|
||||
@@ -119,10 +138,11 @@ namespace Content.Shared.Atmos
|
||||
return angle.GetDir().ToAtmosDirection();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToIndex(this AtmosDirection direction)
|
||||
{
|
||||
// This will throw if you pass an invalid direction. Not this method's fault, but yours!
|
||||
return (int) Math.Log2((int) direction);
|
||||
return BitOperations.Log2((uint)direction);
|
||||
}
|
||||
|
||||
public static AtmosDirection WithFlag(this AtmosDirection direction, AtmosDirection other)
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Content.Server.Bed.Sleep
|
||||
_actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid);
|
||||
|
||||
// TODO remove hardcoded time.
|
||||
_actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(15));
|
||||
_actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args)
|
||||
|
||||
33
Content.Shared/Chemistry/Components/HyposprayComponent.cs
Normal file
33
Content.Shared/Chemistry/Components/HyposprayComponent.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HyposprayComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public string SolutionName = "hypospray";
|
||||
|
||||
// TODO: This should be on clumsycomponent.
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ClumsyFailChance = 0.5f;
|
||||
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 TransferAmount = FixedPoint2.New(5);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Decides whether you can inject everything or just mobs.
|
||||
/// When you can only affect mobs, you're capable of drawing from beakers.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField(required: true)]
|
||||
public bool OnlyAffectsMobs = false;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components;
|
||||
|
||||
[NetworkedComponent()]
|
||||
public abstract partial class SharedHyposprayComponent : Component
|
||||
{
|
||||
[DataField("solutionName")]
|
||||
public string SolutionName = "hypospray";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HyposprayComponentState : ComponentState
|
||||
{
|
||||
public FixedPoint2 CurVolume { get; }
|
||||
public FixedPoint2 MaxVolume { get; }
|
||||
|
||||
public HyposprayComponentState(FixedPoint2 curVolume, FixedPoint2 maxVolume)
|
||||
{
|
||||
CurVolume = curVolume;
|
||||
MaxVolume = maxVolume;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Administration.Logs;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
public abstract class SharedHypospraySystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] protected readonly SharedSolutionContainerSystem _solutionContainers = default!;
|
||||
[Dependency] protected readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] protected readonly ReactiveSystem _reactiveSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HyposprayComponent, GetVerbsEvent<AlternativeVerb>>(AddToggleModeVerb);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// Uses the OnlyMobs field as a check to implement the ability
|
||||
// to draw from jugs and containers with the hypospray
|
||||
// Toggleable to allow people to inject containers if they prefer it over drawing
|
||||
// </summary>
|
||||
private void AddToggleModeVerb(Entity<HyposprayComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||
return;
|
||||
|
||||
var (_, component) = entity;
|
||||
var user = args.User;
|
||||
var verb = new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("hypospray-verb-mode-label"),
|
||||
Act = () =>
|
||||
{
|
||||
ToggleMode(entity, user);
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void ToggleMode(Entity<HyposprayComponent> entity, EntityUid user)
|
||||
{
|
||||
SetMode(entity, !entity.Comp.OnlyAffectsMobs);
|
||||
string msg = entity.Comp.OnlyAffectsMobs ? "hypospray-verb-mode-inject-mobs-only" : "hypospray-verb-mode-inject-all";
|
||||
_popup.PopupClient(Loc.GetString(msg), entity, user);
|
||||
}
|
||||
|
||||
public void SetMode(Entity<HyposprayComponent> entity, bool onlyAffectsMobs)
|
||||
{
|
||||
if (entity.Comp.OnlyAffectsMobs == onlyAffectsMobs)
|
||||
return;
|
||||
|
||||
entity.Comp.OnlyAffectsMobs = onlyAffectsMobs;
|
||||
Dirty(entity);
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,7 @@ public abstract class SharedInjectorSystem : EntitySystem
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||
return;
|
||||
|
||||
if (!HasComp<ActorComponent>(args.User))
|
||||
return;
|
||||
var user = args.User;
|
||||
|
||||
var (_, component) = entity;
|
||||
|
||||
var min = component.MinimumTransferAmount;
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (ev.Cancelled)
|
||||
return false;
|
||||
|
||||
return _containers.CanInsert(usedUid, slot.ContainerSlot, assumeEmpty: true);
|
||||
return _containers.CanInsert(usedUid, slot.ContainerSlot, assumeEmpty: swap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -77,6 +77,13 @@ public sealed partial class ExplosionPrototype : IPrototype
|
||||
[DataField("smallSoundIterationThreshold")]
|
||||
public int SmallSoundIterationThreshold = 6;
|
||||
|
||||
/// <summary>
|
||||
/// How far away another explosion in the same tick can be and be combined.
|
||||
/// Total intensity is added to the original queued explosion.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxCombineDistance = 1f;
|
||||
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier Sound = new SoundCollectionSpecifier("Explosion");
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
}
|
||||
|
||||
Dirty(player, pullerComp);
|
||||
_throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false);
|
||||
_throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false, doSpin: false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Shuttles.UI.MapObjects;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.BUIStates;
|
||||
@@ -16,9 +17,9 @@ public sealed class ShuttleMapInterfaceState
|
||||
public readonly FTLState FTLState;
|
||||
|
||||
/// <summary>
|
||||
/// How long the FTL state takes.
|
||||
/// When the current FTL state starts and ends.
|
||||
/// </summary>
|
||||
public float FTLDuration;
|
||||
public StartEndTime FTLTime;
|
||||
|
||||
public List<ShuttleBeaconObject> Destinations;
|
||||
|
||||
@@ -26,12 +27,12 @@ public sealed class ShuttleMapInterfaceState
|
||||
|
||||
public ShuttleMapInterfaceState(
|
||||
FTLState ftlState,
|
||||
float ftlDuration,
|
||||
StartEndTime ftlTime,
|
||||
List<ShuttleBeaconObject> destinations,
|
||||
List<ShuttleExclusionObject> exclusions)
|
||||
{
|
||||
FTLState = ftlState;
|
||||
FTLDuration = ftlDuration;
|
||||
FTLTime = ftlTime;
|
||||
Destinations = destinations;
|
||||
Exclusions = exclusions;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed partial class IonStormTargetComponent : Component
|
||||
/// Chance for this borg to be affected at all.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Chance = 0.5f;
|
||||
public float Chance = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance to replace the lawset with a random one
|
||||
@@ -32,19 +32,19 @@ public sealed partial class IonStormTargetComponent : Component
|
||||
/// Chance to remove a random law.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RemoveChance = 0.1f;
|
||||
public float RemoveChance = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance to replace a random law with the new one, rather than have it be a glitched-order law.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ReplaceChance = 0.1f;
|
||||
public float ReplaceChance = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance to shuffle laws after everything is done.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ShuffleChance = 0.1f;
|
||||
public float ShuffleChance = 0.2f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -49,7 +49,8 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
float pushbackRatio = PushbackDefault,
|
||||
bool recoil = true,
|
||||
bool animated = true,
|
||||
bool playSound = true)
|
||||
bool playSound = true,
|
||||
bool doSpin = true)
|
||||
{
|
||||
var thrownPos = _transform.GetMapCoordinates(uid);
|
||||
var mapPos = _transform.ToMapCoordinates(coordinates);
|
||||
@@ -57,7 +58,7 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
if (mapPos.MapId != thrownPos.MapId)
|
||||
return;
|
||||
|
||||
TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, recoil: recoil, animated: animated, playSound: playSound);
|
||||
TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,6 +68,7 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
/// <param name="direction">A vector pointing from the entity to its destination.</param>
|
||||
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
|
||||
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
|
||||
/// <param name="doSpin">Whether spin will be applied to the thrown entity.</param>
|
||||
public void TryThrow(EntityUid uid,
|
||||
Vector2 direction,
|
||||
float strength = 1.0f,
|
||||
@@ -74,7 +76,8 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
float pushbackRatio = PushbackDefault,
|
||||
bool recoil = true,
|
||||
bool animated = true,
|
||||
bool playSound = true)
|
||||
bool playSound = true,
|
||||
bool doSpin = true)
|
||||
{
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
if (!physicsQuery.TryGetComponent(uid, out var physics))
|
||||
@@ -90,7 +93,7 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
projectileQuery,
|
||||
strength,
|
||||
user,
|
||||
pushbackRatio, recoil: recoil, animated: animated, playSound: playSound);
|
||||
pushbackRatio, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +103,7 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
/// <param name="direction">A vector pointing from the entity to its destination.</param>
|
||||
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
|
||||
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
|
||||
/// <param name="doSpin">Whether spin will be applied to the thrown entity.</param>
|
||||
public void TryThrow(EntityUid uid,
|
||||
Vector2 direction,
|
||||
PhysicsComponent physics,
|
||||
@@ -110,7 +114,8 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
float pushbackRatio = PushbackDefault,
|
||||
bool recoil = true,
|
||||
bool animated = true,
|
||||
bool playSound = true)
|
||||
bool playSound = true,
|
||||
bool doSpin = true)
|
||||
{
|
||||
if (strength <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero)
|
||||
return;
|
||||
@@ -147,17 +152,20 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
ThrowingAngleComponent? throwingAngle = null;
|
||||
|
||||
// Give it a l'il spin.
|
||||
if (physics.InvI > 0f && (!TryComp(uid, out throwingAngle) || throwingAngle.AngularVelocity))
|
||||
if (doSpin)
|
||||
{
|
||||
_physics.ApplyAngularImpulse(uid, ThrowAngularImpulse / physics.InvI, body: physics);
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(uid, ref throwingAngle, false);
|
||||
var gridRot = _transform.GetWorldRotation(transform.ParentUid);
|
||||
var angle = direction.ToWorldAngle() - gridRot;
|
||||
var offset = throwingAngle?.Angle ?? Angle.Zero;
|
||||
_transform.SetLocalRotation(uid, angle + offset);
|
||||
if (physics.InvI > 0f && (!TryComp(uid, out throwingAngle) || throwingAngle.AngularVelocity))
|
||||
{
|
||||
_physics.ApplyAngularImpulse(uid, ThrowAngularImpulse / physics.InvI, body: physics);
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(uid, ref throwingAngle, false);
|
||||
var gridRot = _transform.GetWorldRotation(transform.ParentUid);
|
||||
var angle = direction.ToWorldAngle() - gridRot;
|
||||
var offset = throwingAngle?.Angle ?? Angle.Zero;
|
||||
_transform.SetLocalRotation(uid, angle + offset);
|
||||
}
|
||||
}
|
||||
|
||||
var throwEvent = new ThrownEvent(user, uid);
|
||||
|
||||
68
Content.Shared/Timing/StartEndTime.cs
Normal file
68
Content.Shared/Timing/StartEndTime.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Timing;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a range of an "action" in time, as start/end times.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Positions in time are represented as <see cref="TimeSpan"/>s, usually from <see cref="IGameTiming.CurTime"/>
|
||||
/// or <see cref="IGameTiming.RealTime"/>.
|
||||
/// </remarks>
|
||||
/// <param name="Start">The time the action starts.</param>
|
||||
/// <param name="End">The time action ends.</param>
|
||||
[Serializable]
|
||||
public record struct StartEndTime(TimeSpan Start, TimeSpan End)
|
||||
{
|
||||
/// <summary>
|
||||
/// How long the action takes.
|
||||
/// </summary>
|
||||
public TimeSpan Length => End - Start;
|
||||
|
||||
/// <summary>
|
||||
/// Get how far the action has progressed relative to a time value.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to get the current progress value for.</param>
|
||||
/// <param name="clamp">If true, clamp values outside the time range to 0 through 1.</param>
|
||||
/// <returns>
|
||||
/// <para>
|
||||
/// A progress value. Zero means <paramref name="time"/> is at <see cref="Start"/>,
|
||||
/// one means <paramref name="time"/> is at <see cref="End"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This function returns <see cref="float.NaN"/> if <see cref="Start"/> and <see cref="End"/> are identical.
|
||||
/// </para>
|
||||
/// </returns>
|
||||
public float ProgressAt(TimeSpan time, bool clamp = true)
|
||||
{
|
||||
var length = Length;
|
||||
if (length == default)
|
||||
return float.NaN;
|
||||
|
||||
var progress = (float) ((time - Start) / length);
|
||||
if (clamp)
|
||||
progress = MathHelper.Clamp01(progress);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
public static StartEndTime FromStartDuration(TimeSpan start, TimeSpan duration)
|
||||
{
|
||||
return new StartEndTime(start, start + duration);
|
||||
}
|
||||
|
||||
public static StartEndTime FromStartDuration(TimeSpan start, float durationSeconds)
|
||||
{
|
||||
return new StartEndTime(start, start + TimeSpan.FromSeconds(durationSeconds));
|
||||
}
|
||||
|
||||
public static StartEndTime FromCurTime(IGameTiming gameTiming, TimeSpan duration)
|
||||
{
|
||||
return FromStartDuration(gameTiming.CurTime, duration);
|
||||
}
|
||||
|
||||
public static StartEndTime FromCurTime(IGameTiming gameTiming, float durationSeconds)
|
||||
{
|
||||
return FromStartDuration(gameTiming.CurTime, durationSeconds);
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,7 @@ public sealed class WeldableSystem : EntitySystem
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
public bool IsWelded(EntityUid uid, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
return component.IsWelded;
|
||||
}
|
||||
private EntityQuery<WeldableComponent> _query;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -31,6 +24,13 @@ public sealed class WeldableSystem : EntitySystem
|
||||
SubscribeLocalEvent<WeldableComponent, WeldFinishedEvent>(OnWeldFinished);
|
||||
SubscribeLocalEvent<LayerChangeOnWeldComponent, WeldableChangedEvent>(OnWeldChanged);
|
||||
SubscribeLocalEvent<WeldableComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
_query = GetEntityQuery<WeldableComponent>();
|
||||
}
|
||||
|
||||
public bool IsWelded(EntityUid uid, WeldableComponent? component = null)
|
||||
{
|
||||
return _query.Resolve(uid, ref component, false) && component.IsWelded;
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, WeldableComponent component, ExaminedEvent args)
|
||||
@@ -49,7 +49,7 @@ public sealed class WeldableSystem : EntitySystem
|
||||
|
||||
private bool CanWeld(EntityUid uid, EntityUid tool, EntityUid user, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!_query.Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
// Other component systems
|
||||
@@ -63,7 +63,7 @@ public sealed class WeldableSystem : EntitySystem
|
||||
|
||||
private bool TryWeld(EntityUid uid, EntityUid tool, EntityUid user, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!_query.Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!CanWeld(uid, tool, user, component))
|
||||
@@ -115,17 +115,13 @@ public sealed class WeldableSystem : EntitySystem
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
_appearance.SetData(uid, WeldableVisuals.IsWelded, component.IsWelded, appearance);
|
||||
if (_query.Resolve(uid, ref component))
|
||||
_appearance.SetData(uid, WeldableVisuals.IsWelded, component.IsWelded);
|
||||
}
|
||||
|
||||
public void SetWeldedState(EntityUid uid, bool state, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!_query.Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.IsWelded == state)
|
||||
@@ -141,7 +137,7 @@ public sealed class WeldableSystem : EntitySystem
|
||||
|
||||
public void SetWeldingTime(EntityUid uid, TimeSpan time, WeldableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!_query.Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.WeldingTime.Equals(time))
|
||||
|
||||
@@ -1,79 +1,4 @@
|
||||
Entries:
|
||||
- author: Agoichi
|
||||
changes:
|
||||
- message: Rebalanced Lobbying Bundle
|
||||
type: Tweak
|
||||
id: 5751
|
||||
time: '2024-01-20T02:35:44.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24303
|
||||
- author: Dygon
|
||||
changes:
|
||||
- message: Storage objects can't be opened anymore while stored in a container.
|
||||
type: Fix
|
||||
id: 5752
|
||||
time: '2024-01-20T02:50:14.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24253
|
||||
- author: FairlySadPanda
|
||||
changes:
|
||||
- message: Lobby restart sound effects no longer cut-off.
|
||||
type: Fix
|
||||
id: 5753
|
||||
time: '2024-01-20T03:40:01.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24044
|
||||
- author: casperr04
|
||||
changes:
|
||||
- message: Fixed players being able to re-anchor items after fultoning them.
|
||||
type: Fix
|
||||
- message: Changed which objects can be fultoned.
|
||||
type: Tweak
|
||||
id: 5754
|
||||
time: '2024-01-20T04:57:05.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/20628
|
||||
- author: Blackern5000
|
||||
changes:
|
||||
- message: Crushers can no longer be researched.
|
||||
type: Remove
|
||||
id: 5755
|
||||
time: '2024-01-20T05:11:02.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24319
|
||||
- author: metalgearsloth
|
||||
changes:
|
||||
- message: Fix buckle sound playing twice in some instances.
|
||||
type: Fix
|
||||
id: 5756
|
||||
time: '2024-01-20T06:22:19.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24321
|
||||
- author: tday
|
||||
changes:
|
||||
- message: Added admin log messages for adding and ending game rules, and for the
|
||||
commands to do so.
|
||||
type: Add
|
||||
- message: Added admin log messages for secret mode rule selection.
|
||||
type: Add
|
||||
id: 5757
|
||||
time: '2024-01-20T18:02:13.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24092
|
||||
- author: Nimfar11
|
||||
changes:
|
||||
- message: Adds snake kebab and its recipe.
|
||||
type: Add
|
||||
id: 5758
|
||||
time: '2024-01-20T23:38:11.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24341
|
||||
- author: Alekshhh
|
||||
changes:
|
||||
- message: Cerberus now has a wideswing that works similarly to spears.
|
||||
type: Tweak
|
||||
id: 5759
|
||||
time: '2024-01-20T23:38:27.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24328
|
||||
- author: TheShuEd
|
||||
changes:
|
||||
- message: Added new Floral anomaly!
|
||||
type: Add
|
||||
id: 5760
|
||||
time: '2024-01-21T01:31:12.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24351
|
||||
- author: Menshin
|
||||
changes:
|
||||
- message: The PA control box should now properly detect the PA parts in all situations
|
||||
@@ -3793,3 +3718,79 @@
|
||||
id: 6250
|
||||
time: '2024-03-29T06:30:51.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26290
|
||||
- author: deltanedas
|
||||
changes:
|
||||
- message: Multiple bombs exploding at once on a tile now combine to have a larger
|
||||
explosion rather than stacking the same small explosion.
|
||||
type: Tweak
|
||||
id: 6251
|
||||
time: '2024-03-29T23:46:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25664
|
||||
- author: Mephisto72
|
||||
changes:
|
||||
- message: Ion Storms are now more likely to alter a Borg's laws.
|
||||
type: Tweak
|
||||
id: 6252
|
||||
time: '2024-03-30T00:01:39.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26539
|
||||
- author: arimah
|
||||
changes:
|
||||
- message: Holoparasites, holoclowns and other guardians correctly transfer damage
|
||||
to their hosts again.
|
||||
type: Fix
|
||||
id: 6253
|
||||
time: '2024-03-30T01:25:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26541
|
||||
- author: Boaz1111
|
||||
changes:
|
||||
- message: Added an industrial reagent grinder to the basic hydroponics research.
|
||||
It grinds things into reagents like a recycler.
|
||||
type: Add
|
||||
id: 6254
|
||||
time: '2024-03-30T02:46:20.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25020
|
||||
- author: SonicHDC
|
||||
changes:
|
||||
- message: Added unzipping for lab coats!
|
||||
type: Add
|
||||
id: 6255
|
||||
time: '2024-03-30T03:31:32.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26494
|
||||
- author: Zealith-Gamer
|
||||
changes:
|
||||
- message: Items being pulled no longer spin when being thrown.
|
||||
type: Fix
|
||||
id: 6256
|
||||
time: '2024-03-30T03:35:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26504
|
||||
- author: Plykiya
|
||||
changes:
|
||||
- message: Hyposprays can now be toggled to draw from solution containers like jugs
|
||||
and beakers.
|
||||
type: Tweak
|
||||
id: 6257
|
||||
time: '2024-03-30T03:59:17.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25544
|
||||
- author: EdenTheLiznerd
|
||||
changes:
|
||||
- message: Amanita toxin now kills you slightly slower, providing you time to seek
|
||||
charcoal before it's too late
|
||||
type: Tweak
|
||||
id: 6258
|
||||
time: '2024-03-30T04:00:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25830
|
||||
- author: liltenhead
|
||||
changes:
|
||||
- message: Changed the syndicate hardbomb to have less of a chance to completely
|
||||
destroy tiles.
|
||||
type: Tweak
|
||||
id: 6259
|
||||
time: '2024-03-30T04:36:33.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26548
|
||||
- author: takemysoult
|
||||
changes:
|
||||
- message: stimulants removes chloral hydrate from body
|
||||
type: Tweak
|
||||
id: 6260
|
||||
time: '2024-03-30T06:52:27.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25886
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
## UI
|
||||
|
||||
hypospray-volume-text = Volume: [color=white]{$currentVolume}/{$totalVolume}[/color]
|
||||
hypospray-all-mode-text = Only Injects
|
||||
hypospray-mobs-only-mode-text = Draws and Injects
|
||||
hypospray-invalid-text = Invalid
|
||||
hypospray-volume-label = Volume: [color=white]{$currentVolume}/{$totalVolume}u[/color]
|
||||
Mode: [color=white]{$modeString}[/color]
|
||||
|
||||
## Entity
|
||||
|
||||
hypospray-component-inject-other-message = You inject {$other}.
|
||||
hypospray-component-inject-self-message = You inject yourself.
|
||||
hypospray-component-inject-self-clumsy-message = Oops! You injected yourself.
|
||||
hypospray-component-empty-message = It's empty!
|
||||
hypospray-component-empty-message = Nothing to inject.
|
||||
hypospray-component-feel-prick-message = You feel a tiny prick!
|
||||
hypospray-component-transfer-already-full-message = {$owner} is already full!
|
||||
hypospray-cant-inject = Can't inject into {$target}!
|
||||
|
||||
hypospray-verb-mode-label = Toggle Container Draw
|
||||
hypospray-verb-mode-inject-all = You cannot draw from containers anymore.
|
||||
hypospray-verb-mode-inject-mobs-only = You can now draw from containers.
|
||||
|
||||
@@ -4,4 +4,7 @@ foldable-deploy-fail = You can't deploy the {$object} here.
|
||||
fold-verb = Fold
|
||||
unfold-verb = Unfold
|
||||
|
||||
fold-flip-verb = Flip
|
||||
fold-flip-verb = Flip
|
||||
|
||||
fold-zip-verb = Zip up
|
||||
fold-unzip-verb = Unzip
|
||||
|
||||
@@ -43,6 +43,48 @@
|
||||
- type: StaticPrice
|
||||
price: 80
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: [ClothingOuterStorageBase, BaseFoldable]
|
||||
id: ClothingOuterStorageFoldableBase
|
||||
components:
|
||||
- type: Appearance
|
||||
- type: Foldable
|
||||
canFoldInsideContainer: true
|
||||
unfoldVerbText: fold-zip-verb
|
||||
foldVerbText: fold-unzip-verb
|
||||
- type: FoldableClothing
|
||||
foldedEquippedPrefix: open
|
||||
foldedHeldPrefix: open
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: icon
|
||||
map: [ "unfoldedLayer" ]
|
||||
- state: icon-open
|
||||
map: ["foldedLayer"]
|
||||
visible: false
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterStorageFoldableBaseOpened
|
||||
suffix: opened
|
||||
components:
|
||||
- type: Foldable
|
||||
folded: true
|
||||
- type: Clothing
|
||||
equippedPrefix: open
|
||||
- type: Item
|
||||
heldPrefix: open
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: icon
|
||||
map: [ "unfoldedLayer" ]
|
||||
visible: false
|
||||
- state: icon-open
|
||||
map: ["foldedLayer"]
|
||||
visible: true
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: ClothingOuterStorageBase
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatLab
|
||||
name: lab coat
|
||||
description: A suit that protects against minor chemical spills.
|
||||
@@ -129,7 +129,12 @@
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLab]
|
||||
id: ClothingOuterCoatLabOpened
|
||||
name: lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatLabChem
|
||||
name: chemist lab coat
|
||||
description: A suit that protects against minor chemical spills. Has an orange stripe on the shoulder.
|
||||
@@ -144,7 +149,12 @@
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLabChem]
|
||||
id: ClothingOuterCoatLabChemOpened
|
||||
name: chemist lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatLabViro
|
||||
name: virologist lab coat
|
||||
description: A suit that protects against bacteria and viruses. Has an green stripe on the shoulder.
|
||||
@@ -158,9 +168,13 @@
|
||||
coefficients:
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLabViro]
|
||||
id: ClothingOuterCoatLabViroOpened
|
||||
name: virologist lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatLabGene
|
||||
name: geneticist lab coat
|
||||
description: A suit that protects against minor chemical spills. Has an blue stripe on the shoulder.
|
||||
@@ -174,9 +188,13 @@
|
||||
coefficients:
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLabGene]
|
||||
id: ClothingOuterCoatLabGeneOpened
|
||||
name: geneticist lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatLabCmo
|
||||
name: chief medical officer's lab coat
|
||||
description: Bluer than the standard model.
|
||||
@@ -191,7 +209,12 @@
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLabCmo]
|
||||
id: ClothingOuterCoatLabCmoOpened
|
||||
name: chief medical officer's lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatRnd
|
||||
name: scientist lab coat
|
||||
description: A suit that protects against minor chemical spills. Has a purple stripe on the shoulder.
|
||||
@@ -206,7 +229,12 @@
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatRnd]
|
||||
id: ClothingOuterCoatRndOpened
|
||||
name: scientist lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatRobo
|
||||
name: roboticist lab coat
|
||||
description: More like an eccentric coat than a labcoat. Helps pass off bloodstains as part of the aesthetic. Comes with red shoulder pads.
|
||||
@@ -221,7 +249,12 @@
|
||||
Caustic: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatRobo]
|
||||
id: ClothingOuterCoatRoboOpened
|
||||
name: roboticist lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageFoldableBase
|
||||
id: ClothingOuterCoatRD
|
||||
name: research director lab coat
|
||||
description: Woven with top of the line technology, this labcoat helps protect against radiation in similar way to the experimental hardsuit.
|
||||
@@ -236,6 +269,11 @@
|
||||
Caustic: 0.75
|
||||
Radiation: 0.9
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatRD]
|
||||
id: ClothingOuterCoatRDOpened
|
||||
name: research director lab coat
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterStorageBase
|
||||
id: ClothingOuterCoatPirate
|
||||
|
||||
@@ -1357,3 +1357,16 @@
|
||||
materialRequirements:
|
||||
Steel: 5
|
||||
CableHV: 2
|
||||
|
||||
- type: entity
|
||||
parent: BaseMachineCircuitboard
|
||||
id: ReagentGrinderIndustrialMachineCircuitboard
|
||||
name: industrial reagent grinder machine board
|
||||
components:
|
||||
- type: MachineBoard
|
||||
prototype: ReagentGrinderIndustrial
|
||||
requirements:
|
||||
MatterBin: 1
|
||||
Manipulator: 3
|
||||
materialRequirements:
|
||||
Glass: 1
|
||||
@@ -18,7 +18,7 @@
|
||||
- type: ExaminableSolution
|
||||
solution: hypospray
|
||||
- type: Hypospray
|
||||
onlyMobs: false
|
||||
onlyAffectsMobs: false
|
||||
- type: UseDelay
|
||||
delay: 0.5
|
||||
- type: StaticPrice
|
||||
@@ -49,7 +49,7 @@
|
||||
- type: ExaminableSolution
|
||||
solution: hypospray
|
||||
- type: Hypospray
|
||||
onlyMobs: false
|
||||
onlyAffectsMobs: false
|
||||
- type: UseDelay
|
||||
delay: 0.5
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
- type: ExaminableSolution
|
||||
solution: hypospray
|
||||
- type: Hypospray
|
||||
onlyAffectsMobs: false
|
||||
- type: UseDelay
|
||||
delay: 0.5
|
||||
|
||||
@@ -113,6 +114,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 15
|
||||
onlyAffectsMobs: false
|
||||
- type: Appearance
|
||||
- type: SolutionContainerVisuals
|
||||
maxFillLevels: 1
|
||||
@@ -202,6 +204,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 20
|
||||
onlyAffectsMobs: false
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
pen:
|
||||
@@ -232,6 +235,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 20
|
||||
onlyAffectsMobs: false
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
pen:
|
||||
@@ -262,6 +266,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 20
|
||||
onlyAffectsMobs: false
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
pen:
|
||||
@@ -293,6 +298,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 30
|
||||
onlyAffectsMobs: false
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
pen:
|
||||
@@ -330,6 +336,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 30
|
||||
onlyAffectsMobs: false
|
||||
- type: StaticPrice
|
||||
price: 500
|
||||
- type: Tag
|
||||
@@ -389,6 +396,7 @@
|
||||
- type: Hypospray
|
||||
solutionName: pen
|
||||
transferAmount: 30
|
||||
onlyAffectsMobs: false
|
||||
- type: StaticPrice
|
||||
price: 500
|
||||
- type: Tag
|
||||
@@ -410,7 +418,7 @@
|
||||
- type: ExaminableSolution
|
||||
solution: hypospray
|
||||
- type: Hypospray
|
||||
onlyMobs: false
|
||||
onlyAffectsMobs: false
|
||||
- type: UseDelay
|
||||
delay: 0.5
|
||||
- type: StaticPrice # A new shitcurity meta
|
||||
|
||||
@@ -451,6 +451,7 @@
|
||||
- ArtifactCrusherMachineCircuitboard
|
||||
- TelecomServerCircuitboard
|
||||
- MassMediaCircuitboard
|
||||
- ReagentGrinderIndustrialMachineCircuitboard
|
||||
- type: MaterialStorage
|
||||
whitelist:
|
||||
tags:
|
||||
|
||||
@@ -56,3 +56,45 @@
|
||||
inputContainer: !type:Container
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
|
||||
- type: entity
|
||||
parent: Recycler #too different so different parent
|
||||
id: ReagentGrinderIndustrial
|
||||
name: industrial reagent grinder
|
||||
description: An industrial reagent grinder.
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
output:
|
||||
maxVol: 400 #*slaps roof of machine* This baby can fit so much omnizine in it
|
||||
- type: MaterialReclaimer
|
||||
whitelist:
|
||||
components:
|
||||
- Extractable #same as reagent grinder
|
||||
blacklist:
|
||||
tags:
|
||||
- HighRiskItem #ian meat
|
||||
efficiency: 0.9
|
||||
- type: Sprite
|
||||
sprite: Structures/Machines/recycling.rsi
|
||||
layers:
|
||||
- state: grinder-b0
|
||||
- type: Machine
|
||||
board: ReagentGrinderIndustrialMachineCircuitboard
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.ConveyorVisuals.State:
|
||||
enum.RecyclerVisualLayers.Main:
|
||||
Forward: { state: grinder-b1 }
|
||||
Reverse: { state: grinder-b1 }
|
||||
Off: { state: grinder-b0 }
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
- type: Construction
|
||||
graph: Machine
|
||||
node: machine
|
||||
containers:
|
||||
- machine_parts
|
||||
- machine_board
|
||||
@@ -116,4 +116,4 @@
|
||||
interactSuccessString: petting-success-recycler
|
||||
interactFailureString: petting-failure-generic
|
||||
interactSuccessSound:
|
||||
path: /Audio/Items/drill_hit.ogg
|
||||
path: /Audio/Items/drill_hit.ogg
|
||||
@@ -1,7 +1,7 @@
|
||||
# Special entity used to attach to power networks as load when somebody gets electrocuted.
|
||||
- type: entity
|
||||
id: VirtualElectrocutionLoadBase
|
||||
noSpawn: true
|
||||
abstract: true
|
||||
components:
|
||||
- type: Electrocution
|
||||
- type: Icon
|
||||
|
||||
@@ -116,6 +116,13 @@
|
||||
damage:
|
||||
types:
|
||||
Poison: 1
|
||||
- !type:AdjustReagent
|
||||
conditions:
|
||||
- !type:ReagentThreshold
|
||||
reagent: ChloralHydrate
|
||||
min: 1
|
||||
reagent: ChloralHydrate
|
||||
amount: -10
|
||||
- !type:GenericStatusEffect
|
||||
key: Stun
|
||||
time: 3
|
||||
@@ -129,6 +136,10 @@
|
||||
component: StaminaModifier
|
||||
time: 3
|
||||
type: Add
|
||||
- !type:GenericStatusEffect
|
||||
key: ForcedSleep
|
||||
time: 3
|
||||
type: Remove
|
||||
Medicine:
|
||||
metabolismRate: 1.0
|
||||
effects:
|
||||
|
||||
@@ -425,11 +425,12 @@
|
||||
color: "#D6CE7B"
|
||||
metabolisms:
|
||||
Poison:
|
||||
metabolismRate: 0.2
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damage:
|
||||
types:
|
||||
Poison: 6
|
||||
Poison: 3
|
||||
|
||||
- type: reagent
|
||||
id: VentCrud
|
||||
|
||||
@@ -956,3 +956,12 @@
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ReagentGrinderIndustrialMachineCircuitboard
|
||||
result: ReagentGrinderIndustrialMachineCircuitboard
|
||||
completetime: 5
|
||||
materials:
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
- BorgModuleHarvesting
|
||||
- SeedExtractorMachineCircuitboard
|
||||
- HydroponicsTrayMachineCircuitboard
|
||||
- ReagentGrinderIndustrialMachineCircuitboard
|
||||
|
||||
- type: technology
|
||||
id: CritterMechs
|
||||
|
||||
@@ -110,9 +110,9 @@
|
||||
Blunt: 15
|
||||
Piercing: 6
|
||||
Structural: 40
|
||||
tileBreakChance: [ 0.75, 0.95, 1 ]
|
||||
tileBreakIntensity: [ 1, 10, 15 ]
|
||||
tileBreakRerollReduction: 30
|
||||
tileBreakChance: [ 0, 0.5, 1 ]
|
||||
tileBreakIntensity: [ 0, 10, 30 ]
|
||||
tileBreakRerollReduction: 10
|
||||
intensityPerState: 20
|
||||
lightColor: Orange
|
||||
texturePath: /Textures/Effects/fire.rsi
|
||||
|
||||
@@ -28,6 +28,14 @@
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "open-inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "open-inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 390 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user