Merge branch 'master' into replace-sounds-with-sound-specifier
This commit is contained in:
@@ -37,7 +37,7 @@ namespace Content.Client.Animations
|
|||||||
new AnimationTrackComponentProperty
|
new AnimationTrackComponentProperty
|
||||||
{
|
{
|
||||||
ComponentType = typeof(ITransformComponent),
|
ComponentType = typeof(ITransformComponent),
|
||||||
Property = nameof(ITransformComponent.WorldPosition),
|
Property = nameof(ITransformComponent.LocalPosition),
|
||||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||||
KeyFrames =
|
KeyFrames =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ namespace Content.Client.Atmos.Overlays
|
|||||||
if (!_gasTileOverlaySystem.HasData(mapGrid.Index))
|
if (!_gasTileOverlaySystem.HasData(mapGrid.Index))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight));
|
foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
|
||||||
|
|
||||||
foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds))
|
|
||||||
{
|
{
|
||||||
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,22 +9,13 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public abstract class EnabledAtmosDeviceVisualizer : AppearanceVisualizer
|
public abstract class EnabledAtmosDeviceVisualizer : AppearanceVisualizer
|
||||||
{
|
{
|
||||||
|
[DataField("disabledState")]
|
||||||
|
private string _disabledState = string.Empty;
|
||||||
[DataField("enabledState")]
|
[DataField("enabledState")]
|
||||||
private string _enabledState = string.Empty;
|
private string _enabledState = string.Empty;
|
||||||
protected abstract object LayerMap { get; }
|
protected abstract object LayerMap { get; }
|
||||||
protected abstract Enum DataKey { get; }
|
protected abstract Enum DataKey { get; }
|
||||||
|
|
||||||
public override void InitializeEntity(IEntity entity)
|
|
||||||
{
|
|
||||||
base.InitializeEntity(entity);
|
|
||||||
|
|
||||||
if (!entity.TryGetComponent(out ISpriteComponent? sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
sprite.LayerMapSet(LayerMap, sprite.AddLayerState(_enabledState));
|
|
||||||
sprite.LayerSetVisible(LayerMap, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
{
|
{
|
||||||
base.OnChangeData(component);
|
base.OnChangeData(component);
|
||||||
@@ -32,8 +23,8 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(component.TryGetData(DataKey, out bool enabled))
|
if(component.TryGetData(DataKey, out bool enabled) && sprite.LayerMapTryGet(LayerMap, out var layer))
|
||||||
sprite.LayerSetVisible(LayerMap, enabled);
|
sprite.LayerSetState(layer, enabled ? _enabledState : _disabledState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs
Normal file
18
Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.Atmos.Piping;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Client.Atmos.Visualizers
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class GasFilterVisualizer : EnabledAtmosDeviceVisualizer
|
||||||
|
{
|
||||||
|
protected override object LayerMap => Layers.Enabled;
|
||||||
|
protected override Enum DataKey => FilterVisuals.Enabled;
|
||||||
|
|
||||||
|
enum Layers : byte
|
||||||
|
{
|
||||||
|
Enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Layers
|
private enum Layers : byte
|
||||||
{
|
{
|
||||||
ConnectedToPort,
|
ConnectedToPort,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
protected override object LayerMap => Layers.Enabled;
|
protected override object LayerMap => Layers.Enabled;
|
||||||
protected override Enum DataKey => OutletInjectorVisuals.Enabled;
|
protected override Enum DataKey => OutletInjectorVisuals.Enabled;
|
||||||
|
|
||||||
enum Layers
|
enum Layers : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
protected override object LayerMap => Layers.Enabled;
|
protected override object LayerMap => Layers.Enabled;
|
||||||
protected override Enum DataKey => PassiveVentVisuals.Enabled;
|
protected override Enum DataKey => PassiveVentVisuals.Enabled;
|
||||||
|
|
||||||
enum Layers
|
enum Layers : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Atmos.Piping;
|
using Content.Shared.Atmos.Piping;
|
||||||
|
using Content.Shared.SubFloor;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
@@ -69,13 +70,16 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualState state))
|
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualState state))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(!component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor))
|
||||||
|
subfloor = true;
|
||||||
|
|
||||||
foreach (Layer layerKey in Enum.GetValues(typeof(Layer)))
|
foreach (Layer layerKey in Enum.GetValues(typeof(Layer)))
|
||||||
{
|
{
|
||||||
var dir = (PipeDirection) layerKey;
|
var dir = (PipeDirection) layerKey;
|
||||||
var layerVisible = state.ConnectedDirections.HasDirection(dir);
|
var layerVisible = state.ConnectedDirections.HasDirection(dir);
|
||||||
|
|
||||||
var layer = sprite.LayerMapGet(layerKey);
|
var layer = sprite.LayerMapGet(layerKey);
|
||||||
sprite.LayerSetVisible(layer, layerVisible);
|
sprite.LayerSetVisible(layer, layerVisible && subfloor);
|
||||||
sprite.LayerSetColor(layer, color);
|
sprite.LayerSetColor(layer, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
protected override object LayerMap => Layers.Enabled;
|
protected override object LayerMap => Layers.Enabled;
|
||||||
protected override Enum DataKey => PressurePumpVisuals.Enabled;
|
protected override Enum DataKey => PressurePumpVisuals.Enabled;
|
||||||
|
|
||||||
enum Layers
|
enum Layers : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ScrubberVisualLayers
|
public enum ScrubberVisualLayers : byte
|
||||||
{
|
{
|
||||||
Scrubber,
|
Scrubber,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
protected override object LayerMap => Layers.Enabled;
|
protected override object LayerMap => Layers.Enabled;
|
||||||
protected override Enum DataKey => ThermoMachineVisuals.Enabled;
|
protected override Enum DataKey => ThermoMachineVisuals.Enabled;
|
||||||
|
|
||||||
enum Layers
|
enum Layers : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Content.Client.Atmos.Visualizers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VentVisualLayers
|
public enum VentVisualLayers : byte
|
||||||
{
|
{
|
||||||
Vent,
|
Vent,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Client.Cuffs.Components;
|
using Content.Client.Cuffs.Components;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
using Content.Shared.CharacterAppearance;
|
using Content.Shared.CharacterAppearance;
|
||||||
@@ -120,15 +120,10 @@ namespace Content.Client.CharacterAppearance
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var layer = args.Part.ToHumanoidLayer();
|
var layers = args.Part.ToHumanoidLayers();
|
||||||
|
|
||||||
if (layer == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO BODY Layer color, sprite and state
|
// TODO BODY Layer color, sprite and state
|
||||||
sprite.LayerSetVisible(layer, true);
|
foreach (var layer in layers)
|
||||||
|
sprite.LayerSetVisible(layer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BodyPartRemoved(BodyPartRemovedEventArgs args)
|
public void BodyPartRemoved(BodyPartRemovedEventArgs args)
|
||||||
@@ -143,15 +138,10 @@ namespace Content.Client.CharacterAppearance
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var layer = args.Part.ToHumanoidLayer();
|
var layers = args.Part.ToHumanoidLayers();
|
||||||
|
|
||||||
if (layer == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO BODY Layer color, sprite and state
|
// TODO BODY Layer color, sprite and state
|
||||||
sprite.LayerSetVisible(layer, false);
|
foreach (var layer in layers)
|
||||||
|
sprite.LayerSetVisible(layer, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,18 +17,26 @@ namespace Content.Client.Doors
|
|||||||
{
|
{
|
||||||
private const string AnimationKey = "airlock_animation";
|
private const string AnimationKey = "airlock_animation";
|
||||||
|
|
||||||
[DataField("open_sound", required: true)]
|
[DataField("animationTime")]
|
||||||
private SoundSpecifier _openSound = default!;
|
|
||||||
|
|
||||||
[DataField("close_sound", required: true)]
|
|
||||||
private SoundSpecifier _closeSound = default!;
|
|
||||||
|
|
||||||
[DataField("deny_sound", required: true)]
|
|
||||||
private SoundSpecifier _denySound = default!;
|
|
||||||
|
|
||||||
[DataField("animation_time")]
|
|
||||||
private float _delay = 0.8f;
|
private float _delay = 0.8f;
|
||||||
|
|
||||||
|
[DataField("denyAnimationTime")]
|
||||||
|
private float _denyDelay = 0.3f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the maintenance panel is animated or stays static.
|
||||||
|
/// False for windoors.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("animatedPanel")]
|
||||||
|
private bool _animatedPanel = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the BaseUnlit layer should still be visible when the airlock
|
||||||
|
/// is opened.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("openUnlitVisible")]
|
||||||
|
private bool _openUnlitVisible = false;
|
||||||
|
|
||||||
private Animation CloseAnimation = default!;
|
private Animation CloseAnimation = default!;
|
||||||
private Animation OpenAnimation = default!;
|
private Animation OpenAnimation = default!;
|
||||||
private Animation DenyAnimation = default!;
|
private Animation DenyAnimation = default!;
|
||||||
@@ -47,15 +55,13 @@ namespace Content.Client.Doors
|
|||||||
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
||||||
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing_unlit", 0f));
|
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing_unlit", 0f));
|
||||||
|
|
||||||
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
if (_animatedPanel)
|
||||||
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
{
|
||||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
|
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
||||||
var sound = new AnimationTrackPlaySound();
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
||||||
CloseAnimation.AnimationTracks.Add(sound);
|
}
|
||||||
|
|
||||||
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_closeSound.GetSound(), 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(_delay)};
|
OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(_delay)};
|
||||||
@@ -70,28 +76,21 @@ namespace Content.Client.Doors
|
|||||||
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
||||||
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening_unlit", 0f));
|
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening_unlit", 0f));
|
||||||
|
|
||||||
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
if (_animatedPanel)
|
||||||
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
{
|
||||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
|
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
|
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
||||||
var sound = new AnimationTrackPlaySound();
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
|
||||||
OpenAnimation.AnimationTracks.Add(sound);
|
}
|
||||||
|
|
||||||
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_openSound.GetSound(), 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(0.3f)};
|
DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(_denyDelay)};
|
||||||
{
|
{
|
||||||
var flick = new AnimationTrackSpriteFlick();
|
var flick = new AnimationTrackSpriteFlick();
|
||||||
DenyAnimation.AnimationTracks.Add(flick);
|
DenyAnimation.AnimationTracks.Add(flick);
|
||||||
flick.LayerKey = DoorVisualLayers.BaseUnlit;
|
flick.LayerKey = DoorVisualLayers.BaseUnlit;
|
||||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny_unlit", 0f));
|
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny_unlit", 0f));
|
||||||
|
|
||||||
var sound = new AnimationTrackPlaySound();
|
|
||||||
DenyAnimation.AnimationTracks.Add(sound);
|
|
||||||
|
|
||||||
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_denySound.GetSound(), 0, () => AudioHelpers.WithVariation(0.05f)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,13 +125,16 @@ namespace Content.Client.Doors
|
|||||||
{
|
{
|
||||||
case DoorVisualState.Open:
|
case DoorVisualState.Open:
|
||||||
sprite.LayerSetState(DoorVisualLayers.Base, "open");
|
sprite.LayerSetState(DoorVisualLayers.Base, "open");
|
||||||
unlitVisible = false;
|
unlitVisible = _openUnlitVisible;
|
||||||
|
if (_openUnlitVisible)
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Closed:
|
case DoorVisualState.Closed:
|
||||||
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
||||||
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "closed_unlit");
|
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "closed_unlit");
|
||||||
sprite.LayerSetState(DoorVisualLayers.BaseBolted, "bolted_unlit");
|
sprite.LayerSetState(DoorVisualLayers.BaseBolted, "bolted_unlit");
|
||||||
sprite.LayerSetState(WiresVisualizer.WiresVisualLayers.MaintenancePanel, "panel_open");
|
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Opening:
|
case DoorVisualState.Opening:
|
||||||
animPlayer.Play(OpenAnimation, AnimationKey);
|
animPlayer.Play(OpenAnimation, AnimationKey);
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ namespace Content.Client.Entry
|
|||||||
"GasPassiveGate",
|
"GasPassiveGate",
|
||||||
"GasValve",
|
"GasValve",
|
||||||
"GasThermoMachine",
|
"GasThermoMachine",
|
||||||
"Metabolism",
|
"Respirator",
|
||||||
|
"Metabolizer",
|
||||||
"AiFactionTag",
|
"AiFactionTag",
|
||||||
"PressureProtection",
|
"PressureProtection",
|
||||||
"AMEPart",
|
"AMEPart",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Content.Client.Hands
|
|||||||
if (!_gameTiming.IsFirstTimePredicted)
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection);
|
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.FinalPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HandsGuiState GetGuiState()
|
public HandsGuiState GetGuiState()
|
||||||
|
|||||||
@@ -19,7 +19,18 @@ namespace Content.Client.Physics.Controllers
|
|||||||
!player.TryGetComponent(out IMoverComponent? mover) ||
|
!player.TryGetComponent(out IMoverComponent? mover) ||
|
||||||
!player.TryGetComponent(out PhysicsComponent? body)) return;
|
!player.TryGetComponent(out PhysicsComponent? body)) return;
|
||||||
|
|
||||||
body.Predict = true; // TODO: equal prediction instead of true?
|
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
|
||||||
|
// (i.e. only see what the server has sent us).
|
||||||
|
// The exception to this is joints.
|
||||||
|
body.Predict = true;
|
||||||
|
|
||||||
|
// We set joints to predicted given these can affect how our mob moves.
|
||||||
|
// I would only recommend disabling this if you make pulling not use joints anymore (someday maybe?)
|
||||||
|
foreach (var joint in body.Joints)
|
||||||
|
{
|
||||||
|
joint.BodyA.Predict = true;
|
||||||
|
joint.BodyB.Predict = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Server-side should just be handled on its own so we'll just do this shizznit
|
// Server-side should just be handled on its own so we'll just do this shizznit
|
||||||
if (player.TryGetComponent(out IMobMoverComponent? mobMover))
|
if (player.TryGetComponent(out IMobMoverComponent? mobMover))
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace Content.Client.Storage
|
|||||||
|
|
||||||
if (Owner.EntityManager.TryGetEntity(entityId, out var entity))
|
if (Owner.EntityManager.TryGetEntity(entityId, out var entity))
|
||||||
{
|
{
|
||||||
ReusableAnimations.AnimateEntityPickup(entity, initialPosition, Owner.Transform.WorldPosition);
|
ReusableAnimations.AnimateEntityPickup(entity, initialPosition, Owner.Transform.LocalPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs
Normal file
40
Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Shared.SubFloor;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.SubFloor
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SubFloorShowLayerVisualizer : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
if (!component.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor))
|
||||||
|
{
|
||||||
|
sprite.Visible = true;
|
||||||
|
|
||||||
|
// Due to the way this visualizer works, you might want to specify it before any other
|
||||||
|
// visualizer that hides/shows layers depending on certain conditions, such as PipeConnectorVisualizer.
|
||||||
|
foreach (var layer in sprite.AllLayers)
|
||||||
|
{
|
||||||
|
layer.Visible = subfloor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sprite.LayerMapTryGet(Layers.FirstLayer, out var firstLayer))
|
||||||
|
{
|
||||||
|
sprite.LayerSetVisible(firstLayer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Layers : byte
|
||||||
|
{
|
||||||
|
FirstLayer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Body.Behavior;
|
using Content.Server.Body.Behavior;
|
||||||
using Content.Server.Body.Circulatory;
|
using Content.Server.Body.Circulatory;
|
||||||
using Content.Server.Metabolism;
|
using Content.Server.Body.Respiratory;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
template: HumanoidTemplate
|
template: HumanoidTemplate
|
||||||
preset: HumanPreset
|
preset: HumanPreset
|
||||||
centerSlot: torso
|
centerSlot: torso
|
||||||
- type: Metabolism
|
- type: Respirator
|
||||||
metabolismHeat: 5000
|
metabolismHeat: 5000
|
||||||
radiatedHeat: 400
|
radiatedHeat: 400
|
||||||
implicitHeatRegulation: 5000
|
implicitHeatRegulation: 5000
|
||||||
@@ -148,7 +148,7 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
|
|
||||||
MapId mapId;
|
MapId mapId;
|
||||||
IMapGrid grid = null;
|
IMapGrid grid = null;
|
||||||
MetabolismComponent metabolism = null;
|
RespiratorComponent respirator = null;
|
||||||
IEntity human = null;
|
IEntity human = null;
|
||||||
|
|
||||||
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
|
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
|
||||||
@@ -169,8 +169,8 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
|
|
||||||
Assert.True(human.TryGetComponent(out SharedBodyComponent body));
|
Assert.True(human.TryGetComponent(out SharedBodyComponent body));
|
||||||
Assert.True(body.HasMechanismBehavior<LungBehavior>());
|
Assert.True(body.HasMechanismBehavior<LungBehavior>());
|
||||||
Assert.True(human.TryGetComponent(out metabolism));
|
Assert.True(human.TryGetComponent(out respirator));
|
||||||
Assert.False(metabolism.Suffocating);
|
Assert.False(respirator.Suffocating);
|
||||||
});
|
});
|
||||||
|
|
||||||
var increment = 10;
|
var increment = 10;
|
||||||
@@ -178,7 +178,7 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
for (var tick = 0; tick < 600; tick += increment)
|
for (var tick = 0; tick < 600; tick += increment)
|
||||||
{
|
{
|
||||||
await server.WaitRunTicks(increment);
|
await server.WaitRunTicks(increment);
|
||||||
Assert.False(metabolism.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
|
Assert.False(respirator.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
|
||||||
}
|
}
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Client.Clickable;
|
using Content.Client.Clickable;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests
|
namespace Content.IntegrationTests.Tests
|
||||||
{
|
{
|
||||||
@@ -57,12 +60,19 @@ namespace Content.IntegrationTests.Tests
|
|||||||
[TestCase("ClickTestRotatingCornerInvisibleNoRot", 0.25f, 0.25f, DirSouthEastJustShy, 1, ExpectedResult = true)]
|
[TestCase("ClickTestRotatingCornerInvisibleNoRot", 0.25f, 0.25f, DirSouthEastJustShy, 1, ExpectedResult = true)]
|
||||||
public async Task<bool> Test(string prototype, float clickPosX, float clickPosY, double angle, float scale)
|
public async Task<bool> Test(string prototype, float clickPosX, float clickPosY, double angle, float scale)
|
||||||
{
|
{
|
||||||
|
Vector2? worldPos = null;
|
||||||
EntityUid entity = default;
|
EntityUid entity = default;
|
||||||
|
var clientEntManager = _client.ResolveDependency<IEntityManager>();
|
||||||
|
var serverEntManager = _server.ResolveDependency<IEntityManager>();
|
||||||
|
var mapManager = _server.ResolveDependency<IMapManager>();
|
||||||
|
var gameTicker = _server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
||||||
|
|
||||||
await _server.WaitPost(() =>
|
await _server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
var entMgr = IoCManager.Resolve<IEntityManager>();
|
var gridEnt = mapManager.GetGrid(gameTicker.DefaultGridId).GridEntityId;
|
||||||
var ent = entMgr.SpawnEntity(prototype, new MapCoordinates(0, 0, new MapId(1)));
|
worldPos = serverEntManager.GetEntity(gridEnt).Transform.WorldPosition;
|
||||||
|
|
||||||
|
var ent = serverEntManager.SpawnEntity(prototype, new EntityCoordinates(gridEnt, 0f, 0f));
|
||||||
ent.Transform.LocalRotation = angle;
|
ent.Transform.LocalRotation = angle;
|
||||||
ent.GetComponent<SpriteComponent>().Scale = (scale, scale);
|
ent.GetComponent<SpriteComponent>().Scale = (scale, scale);
|
||||||
entity = ent.Uid;
|
entity = ent.Uid;
|
||||||
@@ -75,17 +85,15 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
await _client.WaitPost(() =>
|
await _client.WaitPost(() =>
|
||||||
{
|
{
|
||||||
var entMgr = IoCManager.Resolve<IEntityManager>();
|
var ent = clientEntManager.GetEntity(entity);
|
||||||
var ent = entMgr.GetEntity(entity);
|
|
||||||
var clickable = ent.GetComponent<ClickableComponent>();
|
var clickable = ent.GetComponent<ClickableComponent>();
|
||||||
|
|
||||||
hit = clickable.CheckClick((clickPosX, clickPosY), out _, out _);
|
hit = clickable.CheckClick((clickPosX, clickPosY) + worldPos!.Value, out _, out _);
|
||||||
});
|
});
|
||||||
|
|
||||||
await _server.WaitPost(() =>
|
await _server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
var entMgr = IoCManager.Resolve<IEntityManager>();
|
serverEntManager.DeleteEntity(entity);
|
||||||
entMgr.DeleteEntity(entity);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return hit;
|
return hit;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Shared.Spawning;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Broadphase;
|
using Robust.Shared.Physics.Broadphase;
|
||||||
|
|
||||||
@@ -44,7 +45,11 @@ namespace Content.IntegrationTests.Tests.Utility
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
|
var mapId = new MapId(1);
|
||||||
var grid = sMapManager.GetGrid(new GridId(1));
|
var grid = sMapManager.GetGrid(new GridId(1));
|
||||||
|
grid.SetTile(new Vector2i(0, 0), new Tile(1));
|
||||||
|
var gridEnt = sEntityManager.GetEntity(grid.GridEntityId);
|
||||||
|
var gridPos = gridEnt.Transform.WorldPosition;
|
||||||
var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0);
|
var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0);
|
||||||
|
|
||||||
// Nothing blocking it, only entity is the grid
|
// Nothing blocking it, only entity is the grid
|
||||||
@@ -52,8 +57,7 @@ namespace Content.IntegrationTests.Tests.Utility
|
|||||||
Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity));
|
Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity));
|
||||||
Assert.NotNull(entity);
|
Assert.NotNull(entity);
|
||||||
|
|
||||||
var mapId = new MapId(1);
|
var mapCoordinates = new MapCoordinates(gridPos.X, gridPos.Y, mapId);
|
||||||
var mapCoordinates = new MapCoordinates(0, 0, mapId);
|
|
||||||
|
|
||||||
// Nothing blocking it, only entity is the grid
|
// Nothing blocking it, only entity is the grid
|
||||||
Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable));
|
Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -55,7 +56,8 @@ namespace Content.Server.Administration.Commands
|
|||||||
mind.TransferTo(ghost);
|
mind.TransferTo(ghost);
|
||||||
}
|
}
|
||||||
|
|
||||||
ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn;
|
var comp = ghost.GetComponent<GhostComponent>();
|
||||||
|
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(comp, canReturn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grid.HasComponent<IGridAtmosphereComponent>())
|
if (grid.HasComponent<IAtmosphereComponent>())
|
||||||
{
|
{
|
||||||
shell.WriteLine("Grid already has an atmosphere.");
|
shell.WriteLine("Grid already has an atmosphere.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grid.HasComponent<IGridAtmosphereComponent>())
|
if (grid.HasComponent<IAtmosphereComponent>())
|
||||||
{
|
{
|
||||||
shell.WriteLine("Grid already has an atmosphere.");
|
shell.WriteLine("Grid already has an atmosphere.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -23,23 +23,20 @@ namespace Content.Server.Atmos.Components
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
[ComponentDependency] private readonly FlammableComponent? _flammableComponent = null;
|
[ComponentDependency] private readonly FlammableComponent? _flammableComponent = null;
|
||||||
|
|
||||||
public void Update(TileAtmosphere tile, float frameDelta, AtmosphereSystem atmosphereSystem)
|
public void Update(GasMixture air, float frameDelta, AtmosphereSystem atmosphereSystem)
|
||||||
{
|
{
|
||||||
if (_temperatureComponent != null)
|
if (_temperatureComponent != null)
|
||||||
{
|
{
|
||||||
if (tile.Air != null)
|
var temperatureDelta = air.Temperature - _temperatureComponent.CurrentTemperature;
|
||||||
{
|
var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(air);
|
||||||
var temperatureDelta = tile.Air.Temperature - _temperatureComponent.CurrentTemperature;
|
var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity));
|
||||||
var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(tile.Air);
|
_temperatureComponent.ReceiveHeat(heat);
|
||||||
var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity));
|
|
||||||
_temperatureComponent.ReceiveHeat(heat);
|
|
||||||
}
|
|
||||||
_temperatureComponent.Update();
|
_temperatureComponent.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
_barotraumaComponent?.Update(tile.Air?.Pressure ?? 0);
|
_barotraumaComponent?.Update(air.Pressure);
|
||||||
|
|
||||||
_flammableComponent?.Update(tile);
|
_flammableComponent?.Update(air);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace Content.Server.Atmos.Components
|
|||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(TileAtmosphere tile)
|
public void Update(GasMixture air)
|
||||||
{
|
{
|
||||||
// Slowly dry ourselves off if wet.
|
// Slowly dry ourselves off if wet.
|
||||||
if (FireStacks < 0)
|
if (FireStacks < 0)
|
||||||
@@ -104,13 +104,13 @@ namespace Content.Server.Atmos.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're in an oxygenless environment, put the fire out.
|
// If we're in an oxygenless environment, put the fire out.
|
||||||
if (tile.Air?.GetMoles(Gas.Oxygen) < 1f)
|
if (air.GetMoles(Gas.Oxygen) < 1f)
|
||||||
{
|
{
|
||||||
Extinguish();
|
Extinguish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntitySystem.Get<AtmosphereSystem>().HotspotExpose(tile.GridIndex, tile.GridIndices, 700f, 50f, true);
|
EntitySystem.Get<AtmosphereSystem>().HotspotExpose(Owner.Transform.Coordinates, 700f, 50f, true);
|
||||||
|
|
||||||
var physics = Owner.GetComponent<IPhysBody>();
|
var physics = Owner.GetComponent<IPhysBody>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#nullable disable warnings
|
|
||||||
using System;
|
using System;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Body.Respiratory;
|
using Content.Server.Body.Respiratory;
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ using Dependency = Robust.Shared.IoC.DependencyAttribute;
|
|||||||
namespace Content.Server.Atmos.Components
|
namespace Content.Server.Atmos.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is our SSAir equivalent.
|
/// Internal Atmos class. Use <see cref="AtmosphereSystem"/> to interact with atmos instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
[ComponentReference(typeof(IAtmosphereComponent))]
|
||||||
[RegisterComponent, Serializable]
|
[RegisterComponent, Serializable]
|
||||||
public class GridAtmosphereComponent : Component, IGridAtmosphereComponent, ISerializationHooks
|
public class GridAtmosphereComponent : Component, IAtmosphereComponent, ISerializationHooks
|
||||||
{
|
{
|
||||||
public override string Name => "GridAtmosphere";
|
public override string Name => "GridAtmosphere";
|
||||||
|
|
||||||
public virtual bool Simulated => true;
|
public virtual bool Simulated => true;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Content.Server.Atmos.Components
|
namespace Content.Server.Atmos.Components
|
||||||
{
|
{
|
||||||
public interface IGridAtmosphereComponent : IComponent
|
public interface IAtmosphereComponent : IComponent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this atmosphere is simulated or not.
|
/// Whether this atmosphere is simulated or not.
|
||||||
13
Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs
Normal file
13
Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IAtmosphereComponent))]
|
||||||
|
public class SpaceAtmosphereComponent : Component, IAtmosphereComponent
|
||||||
|
{
|
||||||
|
public override string Name => "SpaceAtmosphere";
|
||||||
|
|
||||||
|
public bool Simulated => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Components
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
|
||||||
public class SpaceGridAtmosphereComponent : UnsimulatedGridAtmosphereComponent
|
|
||||||
{
|
|
||||||
public override string Name => "SpaceGridAtmosphere";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,10 +8,9 @@ using Robust.Shared.Maths;
|
|||||||
namespace Content.Server.Atmos.Components
|
namespace Content.Server.Atmos.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
[ComponentReference(typeof(IAtmosphereComponent))]
|
||||||
[ComponentReference(typeof(GridAtmosphereComponent))]
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent
|
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent
|
||||||
{
|
{
|
||||||
public override string Name => "UnsimulatedGridAtmosphere";
|
public override string Name => "UnsimulatedGridAtmosphere";
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
/// <returns>All tile mixtures in a grid.</returns>
|
/// <returns>All tile mixtures in a grid.</returns>
|
||||||
public IEnumerable<GasMixture> GetAllTileMixtures(GridId grid, bool invalidate = false)
|
public IEnumerable<GasMixture> GetAllTileMixtures(GridId grid, bool invalidate = false)
|
||||||
{
|
{
|
||||||
|
// Return an array with a single space gas mixture for invalid grids.
|
||||||
|
if (!grid.IsValid())
|
||||||
|
return new []{ GasMixture.SpaceGas };
|
||||||
|
|
||||||
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
||||||
return Enumerable.Empty<GasMixture>();
|
return Enumerable.Empty<GasMixture>();
|
||||||
|
|
||||||
@@ -666,7 +670,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
public GasMixture? GetTileMixture(EntityCoordinates coordinates, bool invalidate = false)
|
public GasMixture? GetTileMixture(EntityCoordinates coordinates, bool invalidate = false)
|
||||||
{
|
{
|
||||||
return TryGetGridAndTile(coordinates, out var tuple)
|
return TryGetGridAndTile(coordinates, out var tuple)
|
||||||
? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : null;
|
? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : GasMixture.SpaceGas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -678,6 +682,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
/// <returns>The tile mixture, or null</returns>
|
/// <returns>The tile mixture, or null</returns>
|
||||||
public GasMixture? GetTileMixture(GridId grid, Vector2i tile, bool invalidate = false)
|
public GasMixture? GetTileMixture(GridId grid, Vector2i tile, bool invalidate = false)
|
||||||
{
|
{
|
||||||
|
// Always return space gas mixtures for invalid grids (grid 0)
|
||||||
|
if (!grid.IsValid())
|
||||||
|
return GasMixture.SpaceGas;
|
||||||
|
|
||||||
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -686,7 +694,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
return GetTileMixture(gridAtmosphere, tile, invalidate);
|
return GetTileMixture(gridAtmosphere, tile, invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceGridAtmosphereComponent? spaceAtmosphere))
|
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceAtmosphereComponent? _))
|
||||||
{
|
{
|
||||||
// Always return a new space gas mixture in this case.
|
// Always return a new space gas mixture in this case.
|
||||||
return GasMixture.SpaceGas;
|
return GasMixture.SpaceGas;
|
||||||
@@ -967,6 +975,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
/// <returns>All adjacent tile gas mixtures to the tile in question</returns>
|
/// <returns>All adjacent tile gas mixtures to the tile in question</returns>
|
||||||
public IEnumerable<GasMixture> GetAdjacentTileMixtures(GridId grid, Vector2i tile, bool includeBlocked = false, bool invalidate = false)
|
public IEnumerable<GasMixture> GetAdjacentTileMixtures(GridId grid, Vector2i tile, bool includeBlocked = false, bool invalidate = false)
|
||||||
{
|
{
|
||||||
|
// For invalid grids, return an array with a single space gas mixture in it.
|
||||||
|
if (!grid.IsValid())
|
||||||
|
return new []{ GasMixture.SpaceGas };
|
||||||
|
|
||||||
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
|
||||||
return Enumerable.Empty<GasMixture>();
|
return Enumerable.Empty<GasMixture>();
|
||||||
|
|
||||||
@@ -1374,7 +1386,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out GridAtmosphereComponent? gridAtmosphere)
|
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out GridAtmosphereComponent? gridAtmosphere)
|
||||||
&& gridAtmosphere.AtmosDevices.Contains(atmosDevice))
|
&& gridAtmosphere.AtmosDevices.Contains(atmosDevice))
|
||||||
{
|
{
|
||||||
atmosDevice.JoinedGrid = null;
|
atmosDevice.JoinedGrid = null;
|
||||||
gridAtmosphere.AtmosDevices.Remove(atmosDevice);
|
gridAtmosphere.AtmosDevices.Remove(atmosDevice);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
#nullable disable warnings
|
|
||||||
#nullable enable annotations
|
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Atmos.Reactions;
|
using Content.Server.Atmos.Reactions;
|
||||||
using Content.Server.Coordinates.Helpers;
|
using Content.Server.Coordinates.Helpers;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.Server.Atmos.EntitySystems
|
namespace Content.Server.Atmos.EntitySystems
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#nullable disable warnings
|
|
||||||
#nullable enable annotations
|
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
#nullable disable warnings
|
|
||||||
#nullable enable annotations
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Coordinates.Helpers;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.EntitySystems
|
namespace Content.Server.Atmos.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -20,7 +18,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
private readonly TileAtmosphereComparer _monstermosComparer = new();
|
private readonly TileAtmosphereComparer _monstermosComparer = new();
|
||||||
|
|
||||||
private readonly TileAtmosphere[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
|
private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
|
||||||
private readonly TileAtmosphere[] _equalizeGiverTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
private readonly TileAtmosphere[] _equalizeGiverTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
||||||
private readonly TileAtmosphere[] _equalizeTakerTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
private readonly TileAtmosphere[] _equalizeTakerTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
||||||
private readonly TileAtmosphere[] _equalizeQueue = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
private readonly TileAtmosphere[] _equalizeQueue = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
|
||||||
@@ -28,7 +26,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
|
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
|
||||||
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
|
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
|
||||||
|
|
||||||
public void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
|
private void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
|
||||||
{
|
{
|
||||||
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
|
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
|
||||||
return; // Already done.
|
return; // Already done.
|
||||||
@@ -65,11 +63,12 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
if (i > Atmospherics.MonstermosHardTileLimit) break;
|
if (i > Atmospherics.MonstermosHardTileLimit) break;
|
||||||
var exploring = _equalizeTiles[i];
|
var exploring = _equalizeTiles[i]!;
|
||||||
|
|
||||||
if (i < Atmospherics.MonstermosTileLimit)
|
if (i < Atmospherics.MonstermosTileLimit)
|
||||||
{
|
{
|
||||||
var tileMoles = exploring.Air.TotalMoles;
|
// Tiles in the _equalizeTiles array cannot have null air.
|
||||||
|
var tileMoles = exploring.Air!.TotalMoles;
|
||||||
exploring.MonstermosInfo.MoleDelta = tileMoles;
|
exploring.MonstermosInfo.MoleDelta = tileMoles;
|
||||||
totalMoles += tileMoles;
|
totalMoles += tileMoles;
|
||||||
}
|
}
|
||||||
@@ -106,7 +105,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (otherTile == null)
|
if (otherTile == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_equalizeTiles[i].MonstermosInfo.LastQueueCycle = 0;
|
otherTile.MonstermosInfo.LastQueueCycle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tileCount = Atmospherics.MonstermosTileLimit;
|
tileCount = Atmospherics.MonstermosTileLimit;
|
||||||
@@ -118,7 +117,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var otherTile = _equalizeTiles[i];
|
var otherTile = _equalizeTiles[i]!;
|
||||||
otherTile.MonstermosInfo.LastCycle = cycleNum;
|
otherTile.MonstermosInfo.LastCycle = cycleNum;
|
||||||
otherTile.MonstermosInfo.MoleDelta -= averageMoles;
|
otherTile.MonstermosInfo.MoleDelta -= averageMoles;
|
||||||
if (otherTile.MonstermosInfo.MoleDelta > 0)
|
if (otherTile.MonstermosInfo.MoleDelta > 0)
|
||||||
@@ -133,7 +132,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
var logN = MathF.Log2(tileCount);
|
var logN = MathF.Log2(tileCount);
|
||||||
|
|
||||||
// Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2)
|
// Optimization - try to spread gases using an O(n log n) algorithm that has a chance of not working first to avoid O(n^2)
|
||||||
if (giverTilesLength > logN && takerTilesLength > logN)
|
if (giverTilesLength > logN && takerTilesLength > logN)
|
||||||
{
|
{
|
||||||
// Even if it fails, it will speed up the next part.
|
// Even if it fails, it will speed up the next part.
|
||||||
@@ -141,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var otherTile = _equalizeTiles[i];
|
var otherTile = _equalizeTiles[i]!;
|
||||||
otherTile.MonstermosInfo.FastDone = true;
|
otherTile.MonstermosInfo.FastDone = true;
|
||||||
if (!(otherTile.MonstermosInfo.MoleDelta > 0)) continue;
|
if (!(otherTile.MonstermosInfo.MoleDelta > 0)) continue;
|
||||||
var eligibleDirections = AtmosDirection.Invalid;
|
var eligibleDirections = AtmosDirection.Invalid;
|
||||||
@@ -150,7 +149,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
var direction = (AtmosDirection) (1 << j);
|
var direction = (AtmosDirection) (1 << j);
|
||||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||||
var tile2 = otherTile.AdjacentTiles[j];
|
var tile2 = otherTile.AdjacentTiles[j]!;
|
||||||
|
|
||||||
// skip anything that isn't part of our current processing block.
|
// skip anything that isn't part of our current processing block.
|
||||||
if (tile2.MonstermosInfo.FastDone || tile2.MonstermosInfo.LastQueueCycle != queueCycle)
|
if (tile2.MonstermosInfo.FastDone || tile2.MonstermosInfo.LastQueueCycle != queueCycle)
|
||||||
@@ -171,7 +170,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
AdjustEqMovement(otherTile, direction, molesToMove);
|
AdjustEqMovement(otherTile, direction, molesToMove);
|
||||||
otherTile.MonstermosInfo.MoleDelta -= molesToMove;
|
otherTile.MonstermosInfo.MoleDelta -= molesToMove;
|
||||||
otherTile.AdjacentTiles[j].MonstermosInfo.MoleDelta += molesToMove;
|
otherTile.AdjacentTiles[j]!.MonstermosInfo.MoleDelta += molesToMove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +179,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var otherTile = _equalizeTiles[i];
|
var otherTile = _equalizeTiles[i]!;
|
||||||
if (otherTile.MonstermosInfo.MoleDelta > 0)
|
if (otherTile.MonstermosInfo.MoleDelta > 0)
|
||||||
{
|
{
|
||||||
_equalizeGiverTiles[giverTilesLength++] = otherTile;
|
_equalizeGiverTiles[giverTilesLength++] = otherTile;
|
||||||
@@ -252,7 +251,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (otherTile.MonstermosInfo.CurrentTransferAmount != 0 && otherTile.MonstermosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
|
if (otherTile.MonstermosInfo.CurrentTransferAmount != 0 && otherTile.MonstermosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
|
||||||
{
|
{
|
||||||
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
|
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
|
||||||
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]
|
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
|
||||||
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
|
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
|
||||||
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
|
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
|
||||||
}
|
}
|
||||||
@@ -319,7 +318,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
|
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
|
||||||
|
|
||||||
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]
|
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
|
||||||
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
|
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
|
||||||
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
|
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
|
||||||
}
|
}
|
||||||
@@ -328,19 +327,19 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var otherTile = _equalizeTiles[i];
|
var otherTile = _equalizeTiles[i]!;
|
||||||
FinalizeEq(gridAtmosphere, otherTile);
|
FinalizeEq(gridAtmosphere, otherTile);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var otherTile = _equalizeTiles[i];
|
var otherTile = _equalizeTiles[i]!;
|
||||||
for (var j = 0; j < Atmospherics.Directions; j++)
|
for (var j = 0; j < Atmospherics.Directions; j++)
|
||||||
{
|
{
|
||||||
var direction = (AtmosDirection) (1 << j);
|
var direction = (AtmosDirection) (1 << j);
|
||||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||||
var otherTile2 = otherTile.AdjacentTiles[j];
|
var otherTile2 = otherTile.AdjacentTiles[j]!;
|
||||||
if (otherTile2?.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue;
|
if (otherTile2.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue;
|
||||||
AddActiveTile(gridAtmosphere, otherTile2);
|
AddActiveTile(gridAtmosphere, otherTile2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -353,7 +352,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
|
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
|
private void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
|
||||||
{
|
{
|
||||||
// Check if explosive depressurization is enabled and if the tile is valid.
|
// Check if explosive depressurization is enabled and if the tile is valid.
|
||||||
if (!MonstermosDepressurization || tile.Air == null)
|
if (!MonstermosDepressurization || tile.Air == null)
|
||||||
@@ -376,7 +375,8 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
var otherTile = _depressurizeTiles[i];
|
var otherTile = _depressurizeTiles[i];
|
||||||
otherTile.MonstermosInfo.LastCycle = cycleNum;
|
otherTile.MonstermosInfo.LastCycle = cycleNum;
|
||||||
otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
|
otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
|
||||||
if (otherTile.Air.Immutable)
|
// Tiles in the _depressurizeTiles array cannot have null air.
|
||||||
|
if (otherTile.Air!.Immutable)
|
||||||
{
|
{
|
||||||
_depressurizeSpaceTiles[spaceTileCount++] = otherTile;
|
_depressurizeSpaceTiles[spaceTileCount++] = otherTile;
|
||||||
otherTile.PressureSpecificTarget = otherTile;
|
otherTile.PressureSpecificTarget = otherTile;
|
||||||
@@ -388,7 +388,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
var direction = (AtmosDirection) (1 << j);
|
var direction = (AtmosDirection) (1 << j);
|
||||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||||
var otherTile2 = otherTile.AdjacentTiles[j];
|
var otherTile2 = otherTile.AdjacentTiles[j];
|
||||||
if (otherTile2.Air == null) continue;
|
if (otherTile2?.Air == null) continue;
|
||||||
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
|
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
|
||||||
|
|
||||||
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2);
|
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2);
|
||||||
@@ -421,8 +421,8 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
for (var j = 0; j < Atmospherics.Directions; j++)
|
for (var j = 0; j < Atmospherics.Directions; j++)
|
||||||
{
|
{
|
||||||
var direction = (AtmosDirection) (1 << j);
|
var direction = (AtmosDirection) (1 << j);
|
||||||
// TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres.
|
// Tiles in _depressurizeProgressionOrder cannot have null air.
|
||||||
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air.Immutable) continue;
|
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air!.Immutable) continue;
|
||||||
var tile2 = otherTile.AdjacentTiles[j];
|
var tile2 = otherTile.AdjacentTiles[j];
|
||||||
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
|
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
|
||||||
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
||||||
@@ -509,7 +509,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
InvalidateVisuals(other.GridIndex, other.GridIndices);
|
InvalidateVisuals(other.GridIndex, other.GridIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
|
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
|
||||||
{
|
{
|
||||||
Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
|
Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
|
||||||
var hasTransferDirs = false;
|
var hasTransferDirs = false;
|
||||||
@@ -533,7 +533,8 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (otherTile?.Air == null) continue;
|
if (otherTile?.Air == null) continue;
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
if (tile.Air.TotalMoles < amount)
|
// Everything that calls this method already ensures that Air will not be null.
|
||||||
|
if (tile.Air!.TotalMoles < amount)
|
||||||
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections);
|
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections);
|
||||||
|
|
||||||
otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
|
otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
|
||||||
@@ -551,15 +552,19 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
var direction = (AtmosDirection) (1 << i);
|
var direction = (AtmosDirection) (1 << i);
|
||||||
var amount = transferDirs[i];
|
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))
|
if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction))
|
||||||
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]); // A bit of recursion if needed.
|
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!); // A bit of recursion if needed.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, float amount)
|
private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, float amount)
|
||||||
{
|
{
|
||||||
|
DebugTools.Assert(tile.AdjacentBits.HasFlag(direction));
|
||||||
|
DebugTools.Assert(tile.AdjacentTiles[direction.ToIndex()] != null);
|
||||||
tile.MonstermosInfo[direction] += amount;
|
tile.MonstermosInfo[direction] += amount;
|
||||||
tile.AdjacentTiles[direction.ToIndex()].MonstermosInfo[direction.GetOpposite()] -= amount;
|
// Every call to this method already ensures that the adjacent tile won't be null.
|
||||||
|
tile.AdjacentTiles[direction.ToIndex()]!.MonstermosInfo[direction.GetOpposite()] -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleDecompressionFloorRip(IMapGrid mapGrid, TileAtmosphere tile, float sum)
|
private void HandleDecompressionFloorRip(IMapGrid mapGrid, TileAtmosphere tile, float sum)
|
||||||
@@ -573,9 +578,9 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
PryTile(mapGrid, tile.GridIndices);
|
PryTile(mapGrid, tile.GridIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TileAtmosphereComparer : IComparer<TileAtmosphere>
|
private class TileAtmosphereComparer : IComparer<TileAtmosphere?>
|
||||||
{
|
{
|
||||||
public int Compare(TileAtmosphere a, TileAtmosphere b)
|
public int Compare(TileAtmosphere? a, TileAtmosphere? b)
|
||||||
{
|
{
|
||||||
if (a == null && b == null)
|
if (a == null && b == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
private readonly AtmosDeviceUpdateEvent _updateEvent = new();
|
||||||
private readonly Stopwatch _simulationStopwatch = new();
|
private readonly Stopwatch _simulationStopwatch = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -204,11 +205,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
atmosphere.CurrentRunAtmosDevices = new Queue<AtmosDeviceComponent>(atmosphere.AtmosDevices);
|
atmosphere.CurrentRunAtmosDevices = new Queue<AtmosDeviceComponent>(atmosphere.AtmosDevices);
|
||||||
|
|
||||||
var time = _gameTiming.CurTime;
|
var time = _gameTiming.CurTime;
|
||||||
var updateEvent = new AtmosDeviceUpdateEvent();
|
|
||||||
var number = 0;
|
var number = 0;
|
||||||
while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device))
|
while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device))
|
||||||
{
|
{
|
||||||
EntityManager.EventBus.RaiseLocalEvent(device.Owner.Uid, updateEvent, false);
|
RaiseLocalEvent(device.Owner.Uid, _updateEvent, false);
|
||||||
device.LastProcess = time;
|
device.LastProcess = time;
|
||||||
|
|
||||||
if (number++ < LagCheckIterations) continue;
|
if (number++ < LagCheckIterations) continue;
|
||||||
@@ -241,7 +241,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex];
|
var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex];
|
||||||
|
|
||||||
if (atmosphere.Paused || atmosphere.LifeStage >= ComponentLifeStage.Stopping)
|
if (atmosphere.Paused || !atmosphere.Simulated || atmosphere.LifeStage >= ComponentLifeStage.Stopping)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
atmosphere.Timer += frameTime;
|
atmosphere.Timer += frameTime;
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ using Robust.Shared.Map;
|
|||||||
|
|
||||||
namespace Content.Server.Atmos.EntitySystems
|
namespace Content.Server.Atmos.EntitySystems
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is our SSAir equivalent, if you need to interact with or query atmos in any way, go through this.
|
||||||
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public partial class AtmosphereSystem : SharedAtmosphereSystem
|
public partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||||
{
|
{
|
||||||
@@ -29,7 +32,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
// Map events.
|
// Map events.
|
||||||
_mapManager.MapCreated += OnMapCreated;
|
|
||||||
_mapManager.TileChanged += OnTileChanged;
|
_mapManager.TileChanged += OnTileChanged;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -39,7 +41,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Shutdown();
|
||||||
|
|
||||||
_mapManager.MapCreated -= OnMapCreated;
|
|
||||||
_mapManager.TileChanged -= OnTileChanged;
|
_mapManager.TileChanged -= OnTileChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +58,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
InvalidateTile(eventArgs.NewTile.GridIndex, eventArgs.NewTile.GridIndices);
|
InvalidateTile(eventArgs.NewTile.GridIndex, eventArgs.NewTile.GridIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapCreated(object? sender, MapEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Map == MapId.Nullspace)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var map = _mapManager.GetMapEntity(e.Map);
|
|
||||||
|
|
||||||
if (!map.HasComponent<IGridAtmosphereComponent>())
|
|
||||||
map.AddComponent<SpaceGridAtmosphereComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
@@ -81,7 +71,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
foreach (var exposed in EntityManager.ComponentManager.EntityQuery<AtmosExposedComponent>())
|
foreach (var exposed in EntityManager.ComponentManager.EntityQuery<AtmosExposedComponent>())
|
||||||
{
|
{
|
||||||
// TODO ATMOS: Kill this with fire.
|
// TODO ATMOS: Kill this with fire.
|
||||||
var tile = GetTileAtmosphereOrCreateSpace(exposed.Owner.Transform.Coordinates);
|
var tile = GetTileMixture(exposed.Owner.Transform.Coordinates);
|
||||||
if (tile == null) continue;
|
if (tile == null) continue;
|
||||||
exposed.Update(tile, _exposedTimer, this);
|
exposed.Update(tile, _exposedTimer, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,5 @@ namespace Content.Server.Atmos
|
|||||||
public interface IGasMixtureHolder
|
public interface IGasMixtureHolder
|
||||||
{
|
{
|
||||||
public GasMixture Air { get; set; }
|
public GasMixture Air { get; set; }
|
||||||
|
|
||||||
public virtual void AssumeAir(GasMixture giver)
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AtmosphereSystem>().Merge(Air, giver);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GasMixture RemoveAir(float amount)
|
|
||||||
{
|
|
||||||
return Air.Remove(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GasMixture RemoveAirVolume(float ratio)
|
|
||||||
{
|
|
||||||
return Air.RemoveRatio(ratio);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
|
|||||||
namespace Content.Server.Atmos.Piping.Components
|
namespace Content.Server.Atmos.Piping.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
|
/// Adds itself to a <see cref="IAtmosphereComponent"/> to be updated by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class AtmosDeviceComponent : Component
|
public class AtmosDeviceComponent : Component
|
||||||
@@ -22,6 +22,19 @@ namespace Content.Server.Atmos.Piping.Components
|
|||||||
[DataField("requireAnchored")]
|
[DataField("requireAnchored")]
|
||||||
public bool RequireAnchored { get; private set; } = true;
|
public bool RequireAnchored { get; private set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this device will join an entity system to process when not in a grid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
[DataField("joinSystem")]
|
||||||
|
public bool JoinSystem { get; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we have joined an entity system to process.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool JoinedSystem { get; set; } = false;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public TimeSpan LastProcess { get; set; } = TimeSpan.Zero;
|
public TimeSpan LastProcess { get; set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Atmos.Piping.Components;
|
using Content.Server.Atmos.Piping.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Piping.EntitySystems
|
namespace Content.Server.Atmos.Piping.EntitySystems
|
||||||
@@ -12,9 +12,14 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class AtmosDeviceSystem : EntitySystem
|
public class AtmosDeviceSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
|
|
||||||
|
private readonly AtmosDeviceUpdateEvent _updateEvent = new();
|
||||||
|
|
||||||
|
private float _timer = 0f;
|
||||||
|
private readonly HashSet<AtmosDeviceComponent> _joinedDevices = new();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -33,11 +38,24 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
|||||||
public void JoinAtmosphere(AtmosDeviceComponent component)
|
public void JoinAtmosphere(AtmosDeviceComponent component)
|
||||||
{
|
{
|
||||||
if (!CanJoinAtmosphere(component))
|
if (!CanJoinAtmosphere(component))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We try to add the device to a valid atmosphere.
|
// We try to add the device to a valid atmosphere, and if we can't, try to add it to the entity system.
|
||||||
if (!_atmosphereSystem.AddAtmosDevice(component))
|
if (!_atmosphereSystem.AddAtmosDevice(component))
|
||||||
return;
|
{
|
||||||
|
if (component.JoinSystem)
|
||||||
|
{
|
||||||
|
_joinedDevices.Add(component);
|
||||||
|
component.JoinedSystem = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
component.LastProcess = _gameTiming.CurTime;
|
component.LastProcess = _gameTiming.CurTime;
|
||||||
|
|
||||||
@@ -46,8 +64,19 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
|||||||
|
|
||||||
public void LeaveAtmosphere(AtmosDeviceComponent component)
|
public void LeaveAtmosphere(AtmosDeviceComponent component)
|
||||||
{
|
{
|
||||||
if (!_atmosphereSystem.RemoveAtmosDevice(component))
|
// Try to remove the component from an atmosphere, and if not
|
||||||
|
if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component))
|
||||||
|
{
|
||||||
|
// The grid might have been removed but not us... This usually shouldn't happen.
|
||||||
|
component.JoinedGrid = null;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.JoinedSystem)
|
||||||
|
{
|
||||||
|
_joinedDevices.Remove(component);
|
||||||
|
component.JoinedSystem = false;
|
||||||
|
}
|
||||||
|
|
||||||
component.LastProcess = TimeSpan.Zero;
|
component.LastProcess = TimeSpan.Zero;
|
||||||
RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceDisabledEvent(), false);
|
RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceDisabledEvent(), false);
|
||||||
@@ -85,5 +114,22 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
|||||||
{
|
{
|
||||||
RejoinAtmosphere(component);
|
RejoinAtmosphere(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
_timer += frameTime;
|
||||||
|
|
||||||
|
if (_timer < _atmosphereSystem.AtmosTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_timer -= _atmosphereSystem.AtmosTime;
|
||||||
|
|
||||||
|
var time = _gameTiming.CurTime;
|
||||||
|
foreach (var device in _joinedDevices)
|
||||||
|
{
|
||||||
|
RaiseLocalEvent(device.Owner.Uid, _updateEvent, false);
|
||||||
|
device.LastProcess = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ using Content.Server.Atmos.Piping.Trinary.Components;
|
|||||||
using Content.Server.NodeContainer;
|
using Content.Server.NodeContainer;
|
||||||
using Content.Server.NodeContainer.Nodes;
|
using Content.Server.NodeContainer.Nodes;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Atmos.Piping;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
@@ -24,33 +26,35 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
|||||||
|
|
||||||
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, AtmosDeviceUpdateEvent args)
|
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, AtmosDeviceUpdateEvent args)
|
||||||
{
|
{
|
||||||
if (!filter.Enabled)
|
var appearance = filter.Owner.GetComponentOrNull<AppearanceComponent>();
|
||||||
return;
|
|
||||||
|
|
||||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
if (!filter.Enabled
|
||||||
|
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||||
|
|| !ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device)
|
||||||
|
|| !nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode)
|
||||||
|
|| !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode)
|
||||||
|
|| !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode)
|
||||||
|
|| outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full.
|
||||||
|
{
|
||||||
|
appearance?.SetData(FilterVisuals.Enabled, false);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if (!ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode)
|
|
||||||
|| !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode)
|
|
||||||
|| !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure)
|
|
||||||
return; // No need to transfer if target is full.
|
|
||||||
|
|
||||||
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
|
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
|
||||||
var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume;
|
var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume;
|
||||||
|
|
||||||
if (transferRatio <= 0)
|
if (transferRatio <= 0)
|
||||||
|
{
|
||||||
|
appearance?.SetData(FilterVisuals.Enabled, false);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var removed = inletNode.Air.RemoveRatio(transferRatio);
|
var removed = inletNode.Air.RemoveRatio(transferRatio);
|
||||||
|
|
||||||
if (filter.FilteredGas.HasValue)
|
if (filter.FilteredGas.HasValue)
|
||||||
{
|
{
|
||||||
|
appearance?.SetData(FilterVisuals.Enabled, true);
|
||||||
|
|
||||||
var filteredOut = new GasMixture() {Temperature = removed.Temperature};
|
var filteredOut = new GasMixture() {Temperature = removed.Temperature};
|
||||||
|
|
||||||
filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value));
|
filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value));
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable disable warnings
|
using Content.Server.Atmos.EntitySystems;
|
||||||
#nullable enable annotations
|
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
@@ -10,6 +9,7 @@ namespace Content.Server.Atmos
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal Atmos class that stores data about the atmosphere in a grid.
|
/// Internal Atmos class that stores data about the atmosphere in a grid.
|
||||||
|
/// You shouldn't use this directly, use <see cref="AtmosphereSystem"/> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TileAtmosphere : IGasMixtureHolder
|
public class TileAtmosphere : IGasMixtureHolder
|
||||||
{
|
{
|
||||||
@@ -71,6 +71,12 @@ namespace Content.Server.Atmos
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public GasMixture? Air { get; set; }
|
public GasMixture? Air { get; set; }
|
||||||
|
|
||||||
|
GasMixture IGasMixtureHolder.Air
|
||||||
|
{
|
||||||
|
get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature };
|
||||||
|
set => Air = value;
|
||||||
|
}
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public float MaxFireTemperatureSustained { get; set; }
|
public float MaxFireTemperatureSustained { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Body.Circulatory;
|
using Content.Server.Body.Circulatory;
|
||||||
using Content.Shared.Body.Networks;
|
using Content.Shared.Body.Networks;
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -13,52 +12,8 @@ namespace Content.Server.Body.Behavior
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LiverBehavior : MechanismBehavior
|
public class LiverBehavior : MechanismBehavior
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
|
|
||||||
private float _accumulatedFrameTime;
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delay time that determines how often to metabolise blood contents (in seconds).
|
|
||||||
/// </summary>
|
|
||||||
private float _updateIntervalSeconds = 1.0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the liver is functional.
|
|
||||||
/// </summary>
|
|
||||||
//[ViewVariables] private bool _liverFailing = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Modifier for alcohol damage.
|
|
||||||
/// </summary>
|
|
||||||
//[DataField("alcoholLethality")]
|
|
||||||
//[ViewVariables] private float _alcoholLethality = 0.005f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Modifier for alcohol damage.
|
|
||||||
/// </summary>
|
|
||||||
//[DataField("alcoholExponent")]
|
|
||||||
//[ViewVariables] private float _alcoholExponent = 1.6f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toxin volume that can be purged without damage.
|
|
||||||
/// </summary>
|
|
||||||
//[DataField("toxinTolerance")]
|
|
||||||
//[ViewVariables] private float _toxinTolerance = 3f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toxin damage modifier.
|
|
||||||
/// </summary>
|
|
||||||
//[DataField("toxinLethality")]
|
|
||||||
//[ViewVariables] private float _toxinLethality = 0.01f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loops through each reagent in _internalSolution,
|
|
||||||
/// and calls <see cref="IMetabolizable.Metabolize"/> for each of them.
|
|
||||||
/// Also handles toxins and alcohol.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="frameTime">
|
|
||||||
/// The time since the last update in seconds.
|
|
||||||
/// </param>
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (Body == null)
|
if (Body == null)
|
||||||
@@ -68,51 +23,13 @@ namespace Content.Server.Body.Behavior
|
|||||||
|
|
||||||
_accumulatedFrameTime += frameTime;
|
_accumulatedFrameTime += frameTime;
|
||||||
|
|
||||||
// Update at most once every _updateIntervalSeconds
|
// Update at most once per second
|
||||||
if (_accumulatedFrameTime < _updateIntervalSeconds)
|
if (_accumulatedFrameTime < 1)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_accumulatedFrameTime -= _updateIntervalSeconds;
|
_accumulatedFrameTime -= 1;
|
||||||
|
|
||||||
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bloodstream.Solution.CurrentVolume <= ReagentUnit.Zero)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run metabolism for each reagent, remove metabolized reagents
|
|
||||||
// Using ToList here lets us edit reagents while iterating
|
|
||||||
foreach (var reagent in bloodstream.Solution.ReagentList.ToList())
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? prototype))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// How much reagent is available to metabolise?
|
|
||||||
// This needs to be passed to other functions that have metabolism rate information, such that they don't "overmetabolise" a reagent.
|
|
||||||
var availableReagent = bloodstream.Solution.Solution.GetReagentQuantity(reagent.ReagentId);
|
|
||||||
|
|
||||||
//TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality
|
|
||||||
//TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10
|
|
||||||
//TODO BODY Liver failure.
|
|
||||||
|
|
||||||
//TODO Make sure reagent prototypes actually have the toxin and boozepower vars set.
|
|
||||||
|
|
||||||
// Run metabolism code for each reagent
|
|
||||||
foreach (var metabolizable in prototype.Metabolism)
|
|
||||||
{
|
|
||||||
var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent);
|
|
||||||
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
|
|
||||||
availableReagent -= reagentDelta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Body.Respiratory;
|
||||||
using Content.Server.Chemistry.Components;
|
using Content.Server.Chemistry.Components;
|
||||||
using Content.Server.Metabolism;
|
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Body.Networks;
|
using Content.Shared.Body.Networks;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -72,7 +72,7 @@ namespace Content.Server.Body.Circulatory
|
|||||||
{
|
{
|
||||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||||
|
|
||||||
if (!Owner.TryGetComponent(out MetabolismComponent? metabolism))
|
if (!Owner.TryGetComponent(out RespiratorComponent? metabolism))
|
||||||
{
|
{
|
||||||
atmosphereSystem.Merge(to, Air);
|
atmosphereSystem.Merge(to, Air);
|
||||||
Air.Clear();
|
Air.Clear();
|
||||||
|
|||||||
57
Content.Server/Body/Metabolism/MetabolizerComponent.cs
Normal file
57
Content.Server/Body/Metabolism/MetabolizerComponent.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Metabolism
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles metabolizing various reagents with given effects.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class MetabolizerComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Metabolizer";
|
||||||
|
|
||||||
|
public float AccumulatedFrametime = 0.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How often to metabolize reagents, in seconds.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[DataField("updateFrequency")]
|
||||||
|
public float UpdateFrequency = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this metabolizer should attempt to metabolize chemicals in its parent bodies' bloodstream,
|
||||||
|
/// as opposed to a solution container on the metabolizing entity itself.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("takeFromBloodstream")]
|
||||||
|
public bool TakeFromBloodstream = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary mapping reagent string IDs to a list of effects & associated metabolism rate.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[DataField("metabolisms", required: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer<ReagentEffectsEntry, ReagentPrototype>))]
|
||||||
|
public Dictionary<string, ReagentEffectsEntry> Metabolisms = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataDefinition]
|
||||||
|
public class ReagentEffectsEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of reagent to metabolize, per metabolism cycle.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("metabolismRate")]
|
||||||
|
public ReagentUnit MetabolismRate = ReagentUnit.New(1.0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of effects to apply when these reagents are metabolized.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("effects", required: true)]
|
||||||
|
public ReagentEffect[] Effects = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Content.Server/Body/Metabolism/MetabolizerSystem.cs
Normal file
107
Content.Server/Body/Metabolism/MetabolizerSystem.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Body.Circulatory;
|
||||||
|
using Content.Server.Chemistry.Components;
|
||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Body.Mechanism;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Metabolism
|
||||||
|
{
|
||||||
|
// TODO mirror in the future working on mechanisms move updating here to BodySystem so it can be ordered?
|
||||||
|
public class MetabolizerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var metab in ComponentManager.EntityQuery<MetabolizerComponent>(false))
|
||||||
|
{
|
||||||
|
metab.AccumulatedFrametime += frameTime;
|
||||||
|
|
||||||
|
// Only update as frequently as it should
|
||||||
|
if (metab.AccumulatedFrametime >= metab.UpdateFrequency)
|
||||||
|
{
|
||||||
|
metab.AccumulatedFrametime = 0.0f;
|
||||||
|
TryMetabolize(metab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryMetabolize(MetabolizerComponent comp)
|
||||||
|
{
|
||||||
|
var owner = comp.Owner;
|
||||||
|
var reagentList = new List<Solution.ReagentQuantity>();
|
||||||
|
SolutionContainerComponent? solution = null;
|
||||||
|
SharedBodyComponent? body = null;
|
||||||
|
|
||||||
|
// if this field is passed we should try and take from the bloodstream over anything else
|
||||||
|
if (comp.TakeFromBloodstream && owner.TryGetComponent<SharedMechanismComponent>(out var mech))
|
||||||
|
{
|
||||||
|
body = mech.Body;
|
||||||
|
if (body != null)
|
||||||
|
{
|
||||||
|
if (body.Owner.TryGetComponent<BloodstreamComponent>(out var bloodstream)
|
||||||
|
&& bloodstream.Solution.CurrentVolume >= ReagentUnit.Zero)
|
||||||
|
{
|
||||||
|
solution = bloodstream.Solution;
|
||||||
|
reagentList = bloodstream.Solution.ReagentList.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (owner.TryGetComponent<SolutionContainerComponent>(out var sol))
|
||||||
|
{
|
||||||
|
// if we have no mechanism/body but a solution container instead,
|
||||||
|
// we'll just use that to metabolize from
|
||||||
|
solution = sol;
|
||||||
|
reagentList = sol.ReagentList.ToList();
|
||||||
|
}
|
||||||
|
if (solution == null || reagentList.Count == 0)
|
||||||
|
{
|
||||||
|
// We're all outta ideas on where to metabolize from
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run metabolism for each reagent, remove metabolized reagents
|
||||||
|
foreach (var reagent in reagentList)
|
||||||
|
{
|
||||||
|
if (!comp.Metabolisms.ContainsKey(reagent.ReagentId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var metabolism = comp.Metabolisms[reagent.ReagentId];
|
||||||
|
// Run metabolism code for each reagent
|
||||||
|
foreach (var effect in metabolism.Effects)
|
||||||
|
{
|
||||||
|
var ent = body != null ? body.Owner : owner;
|
||||||
|
var conditionsMet = true;
|
||||||
|
if (effect.Conditions != null)
|
||||||
|
{
|
||||||
|
// yes this is 3 nested for loops, but all of these lists are
|
||||||
|
// basically guaranteed to be small or empty
|
||||||
|
foreach (var condition in effect.Conditions)
|
||||||
|
{
|
||||||
|
if (!condition.Condition(ent, reagent))
|
||||||
|
{
|
||||||
|
conditionsMet = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditionsMet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we're part of a body, pass that entity to Metabolize
|
||||||
|
// Otherwise, just pass our owner entity, maybe we're a plant or something
|
||||||
|
effect.Metabolize(ent, reagent);
|
||||||
|
}
|
||||||
|
|
||||||
|
solution.TryRemoveReagent(reagent.ReagentId, metabolism.MetabolismRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ using Content.Shared.Atmos;
|
|||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Damage.Components;
|
using Content.Shared.Damage.Components;
|
||||||
using Content.Shared.Metabolism.Events;
|
|
||||||
using Content.Shared.MobState;
|
using Content.Shared.MobState;
|
||||||
using Content.Shared.Notification.Managers;
|
using Content.Shared.Notification.Managers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -21,14 +20,14 @@ using Robust.Shared.Localization;
|
|||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.Metabolism
|
namespace Content.Server.Body.Respiratory
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class MetabolismComponent : Component
|
public class RespiratorComponent : Component
|
||||||
{
|
{
|
||||||
[ComponentDependency] private readonly SharedBodyComponent? _body = default!;
|
[ComponentDependency] private readonly SharedBodyComponent? _body = default!;
|
||||||
|
|
||||||
public override string Name => "Metabolism";
|
public override string Name => "Respirator";
|
||||||
|
|
||||||
private float _accumulatedFrameTime;
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.Metabolism
|
namespace Content.Server.Body.Respiratory
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class MetabolismSystem : EntitySystem
|
public class RespiratorSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
foreach (var metabolism in ComponentManager.EntityQuery<MetabolismComponent>(true))
|
foreach (var respirator in ComponentManager.EntityQuery<RespiratorComponent>(false))
|
||||||
{
|
{
|
||||||
metabolism.Update(frameTime);
|
respirator.Update(frameTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
|
||||||
using Content.Shared.Chemistry;
|
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.Metabolism
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
|
|
||||||
/// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable.
|
|
||||||
/// </summary>
|
|
||||||
[DataDefinition]
|
|
||||||
public class DefaultDrink : DefaultMetabolizable
|
|
||||||
{
|
|
||||||
//How much thirst is satiated when 1u of the reagent is metabolized
|
|
||||||
[DataField("hydrationFactor")]
|
|
||||||
public float HydrationFactor { get; set; } = 30.0f;
|
|
||||||
|
|
||||||
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
|
|
||||||
public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
|
|
||||||
{
|
|
||||||
// use DefaultMetabolism to determine how much reagent we should metabolize
|
|
||||||
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
|
|
||||||
|
|
||||||
// If metabolizing entity has a ThirstComponent, hydrate them.
|
|
||||||
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
|
|
||||||
thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor);
|
|
||||||
|
|
||||||
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
|
|
||||||
return amountMetabolized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
|
||||||
using Content.Shared.Chemistry;
|
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.Metabolism
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Default metabolism for food reagents. Attempts to find a HungerComponent on the target,
|
|
||||||
/// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable.
|
|
||||||
/// </summary>
|
|
||||||
[DataDefinition]
|
|
||||||
public class DefaultFood : DefaultMetabolizable
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How much hunger is satiated when 1u of the reagent is metabolized
|
|
||||||
/// </summary>
|
|
||||||
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f;
|
|
||||||
|
|
||||||
|
|
||||||
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
|
|
||||||
public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
|
|
||||||
{
|
|
||||||
// use DefaultMetabolism to determine how much reagent we should metabolize
|
|
||||||
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
|
|
||||||
|
|
||||||
// If metabolizing entity has a HungerComponent, feed them.
|
|
||||||
if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
|
|
||||||
hunger.UpdateFood(amountMetabolized.Float() * NutritionFactor);
|
|
||||||
|
|
||||||
//Return amount of reagent to be removed. Reagent is removed regardless of HungerComponent presence
|
|
||||||
return amountMetabolized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using Content.Shared.Chemistry;
|
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.Damage.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.Metabolism
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
|
|
||||||
/// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable.
|
|
||||||
/// </summary>
|
|
||||||
[DataDefinition]
|
|
||||||
public class HealthChangeMetabolism : DefaultMetabolizable
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How much damage is changed when 1u of the reagent is metabolized.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("healthChange")]
|
|
||||||
public float HealthChange { get; set; } = 1.0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class of damage changed, Brute, Burn, Toxin, Airloss.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("damageClass")]
|
|
||||||
public DamageClass DamageType { get; set; } = DamageClass.Brute;
|
|
||||||
|
|
||||||
private float _accumulatedHealth;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove reagent at set rate, changes damage if a DamageableComponent can be found.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="solutionEntity"></param>
|
|
||||||
/// <param name="reagentId"></param>
|
|
||||||
/// <param name="tickTime"></param>
|
|
||||||
/// <param name="availableReagent">Reagent available to be metabolized.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
|
|
||||||
{
|
|
||||||
// use DefaultMetabolism to determine how much reagent we should metabolize
|
|
||||||
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
|
|
||||||
|
|
||||||
// how much does this much reagent heal for
|
|
||||||
var healthChangeAmount = HealthChange * amountMetabolized.Float();
|
|
||||||
|
|
||||||
if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
|
|
||||||
{
|
|
||||||
// Heal damage by healthChangeAmmount, rounding down to nearest integer
|
|
||||||
health.ChangeDamage(DamageType, (int) healthChangeAmount, true);
|
|
||||||
|
|
||||||
// Store decimal remainder of healthChangeAmmount in _accumulatedHealth
|
|
||||||
_accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount);
|
|
||||||
|
|
||||||
if (_accumulatedHealth >= 1)
|
|
||||||
{
|
|
||||||
health.ChangeDamage(DamageType, 1, true);
|
|
||||||
_accumulatedHealth -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if(_accumulatedHealth <= -1)
|
|
||||||
{
|
|
||||||
health.ChangeDamage(DamageType, -1, true);
|
|
||||||
_accumulatedHealth += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return amountMetabolized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.ReagentEffectConditions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for implementing reagent effects that require a certain amount of reagent before it should be applied.
|
||||||
|
/// For instance, overdoses.
|
||||||
|
/// </summary>
|
||||||
|
public class ReagentThreshold : ReagentEffectCondition
|
||||||
|
{
|
||||||
|
[DataField("min")]
|
||||||
|
public ReagentUnit Min = ReagentUnit.Zero;
|
||||||
|
|
||||||
|
[DataField("max")]
|
||||||
|
public ReagentUnit Max = ReagentUnit.MaxValue;
|
||||||
|
|
||||||
|
public override bool Condition(IEntity solutionEntity, Solution.ReagentQuantity reagent)
|
||||||
|
{
|
||||||
|
return reagent.Quantity >= Min && reagent.Quantity < Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Content.Server/Chemistry/ReagentEffects/HealthChange.cs
Normal file
56
Content.Server/Chemistry/ReagentEffects/HealthChange.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.ReagentEffects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
|
||||||
|
/// and to update its damage values.
|
||||||
|
/// </summary>
|
||||||
|
public class HealthChange : ReagentEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How much damage is changed when 1u of the reagent is metabolized.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("healthChange")]
|
||||||
|
public float AmountToChange { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class of damage changed, Brute, Burn, Toxin, Airloss.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("damageClass")]
|
||||||
|
public DamageClass DamageType { get; set; } = DamageClass.Brute;
|
||||||
|
|
||||||
|
private float _accumulatedHealth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes damage if a DamageableComponent can be found.
|
||||||
|
/// </summary>
|
||||||
|
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
|
||||||
|
{
|
||||||
|
if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
|
||||||
|
{
|
||||||
|
health.ChangeDamage(DamageType, (int)AmountToChange, true);
|
||||||
|
float decHealthChange = (float) (AmountToChange - (int) AmountToChange);
|
||||||
|
_accumulatedHealth += decHealthChange;
|
||||||
|
|
||||||
|
if (_accumulatedHealth >= 1)
|
||||||
|
{
|
||||||
|
health.ChangeDamage(DamageType, 1, true);
|
||||||
|
_accumulatedHealth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(_accumulatedHealth <= -1)
|
||||||
|
{
|
||||||
|
health.ChangeDamage(DamageType, -1, true);
|
||||||
|
_accumulatedHealth += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs
Normal file
69
Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Content.Shared.Movement.Components;
|
||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using System;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.ReagentEffects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target,
|
||||||
|
/// adding one if not there and to change the movespeed
|
||||||
|
/// </summary>
|
||||||
|
public class MovespeedModifier : ReagentEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How much the entities' walk speed is multiplied by.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("walkSpeedModifier")]
|
||||||
|
public float WalkSpeedModifier { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much the entities' run speed is multiplied by.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("sprintSpeedModifier")]
|
||||||
|
public float SprintSpeedModifier { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the modifier applies (in seconds) when metabolized.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("statusLifetime")]
|
||||||
|
public float StatusLifetime = 2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there.
|
||||||
|
/// </summary>
|
||||||
|
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
|
||||||
|
{
|
||||||
|
if (!solutionEntity.TryGetComponent(out MovementSpeedModifierComponent? movement)) return;
|
||||||
|
|
||||||
|
solutionEntity.EnsureComponent(out MovespeedModifierMetabolismComponent status);
|
||||||
|
|
||||||
|
// Only refresh movement if we need to.
|
||||||
|
var modified = !status.WalkSpeedModifier.Equals(WalkSpeedModifier) ||
|
||||||
|
!status.SprintSpeedModifier.Equals(SprintSpeedModifier);
|
||||||
|
|
||||||
|
status.WalkSpeedModifier = WalkSpeedModifier;
|
||||||
|
status.SprintSpeedModifier = SprintSpeedModifier;
|
||||||
|
|
||||||
|
IncreaseTimer(status, StatusLifetime * amount.Quantity.Float());
|
||||||
|
|
||||||
|
if (modified)
|
||||||
|
movement.RefreshMovementSpeedModifiers();
|
||||||
|
|
||||||
|
}
|
||||||
|
public void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time)
|
||||||
|
{
|
||||||
|
var gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||||
|
|
||||||
|
var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds);
|
||||||
|
|
||||||
|
status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time);
|
||||||
|
status.Dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs
Normal file
28
Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.Nutrition.Components;
|
||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.ReagentEffects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to find a HungerComponent on the target,
|
||||||
|
/// and to update it's hunger values.
|
||||||
|
/// </summary>
|
||||||
|
public class SatiateHunger : ReagentEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How much hunger is satiated when 1u of the reagent is metabolized
|
||||||
|
/// </summary>
|
||||||
|
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 3.0f;
|
||||||
|
|
||||||
|
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
|
||||||
|
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
|
||||||
|
{
|
||||||
|
if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
|
||||||
|
hunger.UpdateFood(NutritionFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs
Normal file
28
Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.Nutrition.Components;
|
||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.ReagentEffects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
|
||||||
|
/// and to update it's thirst values.
|
||||||
|
/// </summary>
|
||||||
|
public class SatiateThirst : ReagentEffect
|
||||||
|
{
|
||||||
|
/// How much thirst is satiated each metabolism tick. Not currently tied to
|
||||||
|
/// rate or anything.
|
||||||
|
[DataField("hydrationFactor")]
|
||||||
|
public float HydrationFactor { get; set; } = 3.0f;
|
||||||
|
|
||||||
|
/// Satiate thirst if a ThirstComponent can be found
|
||||||
|
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
|
||||||
|
{
|
||||||
|
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
|
||||||
|
thirst.UpdateThirst(HydrationFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,30 +25,47 @@ namespace Content.Server.Doors.Components
|
|||||||
/// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
|
/// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IDoorCheck))]
|
public class AirlockComponent : Component, IWires
|
||||||
public class AirlockComponent : Component, IWires, IDoorCheck
|
|
||||||
{
|
{
|
||||||
public override string Name => "Airlock";
|
public override string Name => "Airlock";
|
||||||
|
|
||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
private readonly ServerDoorComponent? _doorComponent = null;
|
public readonly ServerDoorComponent? DoorComponent = null;
|
||||||
|
|
||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
private readonly SharedAppearanceComponent? _appearanceComponent = null;
|
public readonly SharedAppearanceComponent? AppearanceComponent = null;
|
||||||
|
|
||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
private readonly ApcPowerReceiverComponent? _receiverComponent = null;
|
public readonly ApcPowerReceiverComponent? ReceiverComponent = null;
|
||||||
|
|
||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
private readonly WiresComponent? _wiresComponent = null;
|
public readonly WiresComponent? WiresComponent = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play when the bolts on the airlock go up.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("boltUpSound")]
|
||||||
|
public SoundSpecifier BoltUpSound = new SoundPathSpecifier("/Audio/Machines/boltsup.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play when the bolts on the airlock go down.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("boltDownSound")]
|
||||||
|
public SoundSpecifier BoltDownSound = new SoundPathSpecifier("/Audio/Machines/boltsdown.ogg");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duration for which power will be disabled after pulsing either power wire.
|
/// Duration for which power will be disabled after pulsing either power wire.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
|
[DataField("powerWiresTimeout")]
|
||||||
|
public float PowerWiresTimeout = 5.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the maintenance panel should be visible even if the airlock is opened.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("openPanelVisible")]
|
||||||
|
public bool OpenPanelVisible = false;
|
||||||
|
|
||||||
private CancellationTokenSource _powerWiresPulsedTimerCancel = new();
|
private CancellationTokenSource _powerWiresPulsedTimerCancel = new();
|
||||||
|
|
||||||
private bool _powerWiresPulsed;
|
private bool _powerWiresPulsed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -85,7 +102,7 @@ namespace Content.Server.Doors.Components
|
|||||||
private bool BoltLightsVisible
|
private bool BoltLightsVisible
|
||||||
{
|
{
|
||||||
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
|
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
|
||||||
&& _doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Closed;
|
&& DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Closed;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_boltLightsWirePulsed = value;
|
_boltLightsWirePulsed = value;
|
||||||
@@ -93,124 +110,53 @@ namespace Content.Server.Doors.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataField("setBoltsDownSound")] private SoundSpecifier _setBoltsDownSound = new SoundPathSpecifier("/Audio/Machines/boltsdown.ogg");
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("autoClose")]
|
||||||
[DataField("setBoltsUpSound")] private SoundSpecifier _setBoltsUpSound = new SoundPathSpecifier("/Audio/Machines/boltsup.ogg");
|
public bool AutoClose = true;
|
||||||
|
|
||||||
private static readonly TimeSpan AutoCloseDelayFast = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
private bool _autoClose = true;
|
[DataField("autoCloseDelayModifier")]
|
||||||
|
public float AutoCloseDelayModifier = 1.0f;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
private bool _normalCloseSpeed = true;
|
public bool Safety = true;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
private bool _safety = true;
|
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
if (_receiverComponent != null && _appearanceComponent != null)
|
if (ReceiverComponent != null && AppearanceComponent != null)
|
||||||
{
|
{
|
||||||
_appearanceComponent.SetData(DoorVisuals.Powered, _receiverComponent.Powered);
|
AppearanceComponent.SetData(DoorVisuals.Powered, ReceiverComponent.Powered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
public bool CanChangeState()
|
||||||
{
|
|
||||||
base.HandleMessage(message, component);
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
case PowerChangedMessage powerChanged:
|
|
||||||
PowerDeviceOnOnPowerStateChanged(powerChanged);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IDoorCheck.OnStateChange(SharedDoorComponent.DoorState doorState)
|
|
||||||
{
|
|
||||||
// Only show the maintenance panel if the airlock is closed
|
|
||||||
if (_wiresComponent != null)
|
|
||||||
{
|
|
||||||
_wiresComponent.IsPanelVisible = doorState != SharedDoorComponent.DoorState.Open;
|
|
||||||
}
|
|
||||||
// If the door is closed, we should look if the bolt was locked while closing
|
|
||||||
UpdateBoltLightStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDoorCheck.OpenCheck() => CanChangeState();
|
|
||||||
|
|
||||||
bool IDoorCheck.CloseCheck() => CanChangeState();
|
|
||||||
|
|
||||||
bool IDoorCheck.DenyCheck() => CanChangeState();
|
|
||||||
|
|
||||||
bool IDoorCheck.SafetyCheck() => _safety;
|
|
||||||
|
|
||||||
bool IDoorCheck.AutoCloseCheck() => _autoClose;
|
|
||||||
|
|
||||||
TimeSpan? IDoorCheck.GetCloseSpeed()
|
|
||||||
{
|
|
||||||
if (_normalCloseSpeed)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return AutoCloseDelayFast;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (_wiresComponent != null && _wiresComponent.IsPanelOpen &&
|
|
||||||
eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
|
||||||
{
|
|
||||||
_wiresComponent.OpenInterface(actor.PlayerSession);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDoorCheck.CanPryCheck(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (IsBolted())
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message "));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (IsPowered())
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanChangeState()
|
|
||||||
{
|
{
|
||||||
return IsPowered() && !IsBolted();
|
return IsPowered() && !IsBolted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsBolted()
|
public bool IsBolted()
|
||||||
{
|
{
|
||||||
return _boltsDown;
|
return _boltsDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPowered()
|
public bool IsPowered()
|
||||||
{
|
{
|
||||||
return _receiverComponent == null || _receiverComponent.Powered;
|
return ReceiverComponent == null || ReceiverComponent.Powered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBoltLightStatus()
|
public void UpdateBoltLightStatus()
|
||||||
{
|
{
|
||||||
if (_appearanceComponent != null)
|
if (AppearanceComponent != null)
|
||||||
{
|
{
|
||||||
_appearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
|
AppearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateWiresStatus()
|
public void UpdateWiresStatus()
|
||||||
{
|
{
|
||||||
if (_doorComponent == null)
|
if (DoorComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,9 +166,9 @@ namespace Content.Server.Doors.Components
|
|||||||
{
|
{
|
||||||
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
|
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
|
||||||
}
|
}
|
||||||
else if (_wiresComponent != null &&
|
else if (WiresComponent != null &&
|
||||||
_wiresComponent.IsWireCut(Wires.MainPower) &&
|
WiresComponent.IsWireCut(Wires.MainPower) &&
|
||||||
_wiresComponent.IsWireCut(Wires.BackupPower))
|
WiresComponent.IsWireCut(Wires.BackupPower))
|
||||||
{
|
{
|
||||||
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
|
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
|
||||||
}
|
}
|
||||||
@@ -232,63 +178,59 @@ namespace Content.Server.Doors.Components
|
|||||||
var boltLightsStatus = new StatusLightData(Color.Lime,
|
var boltLightsStatus = new StatusLightData(Color.Lime,
|
||||||
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL");
|
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL");
|
||||||
|
|
||||||
|
var ev = new DoorGetCloseTimeModifierEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
|
||||||
var timingStatus =
|
var timingStatus =
|
||||||
new StatusLightData(Color.Orange, !_autoClose ? StatusLightState.Off :
|
new StatusLightData(Color.Orange, !AutoClose ? StatusLightState.Off :
|
||||||
!_normalCloseSpeed ? StatusLightState.BlinkingSlow :
|
!MathHelper.CloseTo(ev.CloseTimeModifier, 1.0f) ? StatusLightState.BlinkingSlow :
|
||||||
StatusLightState.On,
|
StatusLightState.On,
|
||||||
"TIME");
|
"TIME");
|
||||||
|
|
||||||
var safetyStatus =
|
var safetyStatus =
|
||||||
new StatusLightData(Color.Red, _safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
|
new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
|
||||||
|
|
||||||
if (_wiresComponent == null)
|
if (WiresComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
|
WiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
|
WiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
|
WiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
|
WiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
|
WiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
|
||||||
_wiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
|
WiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
|
||||||
/*
|
|
||||||
_wires.SetStatus(6, powerLight);
|
|
||||||
_wires.SetStatus(7, powerLight);
|
|
||||||
_wires.SetStatus(8, powerLight);
|
|
||||||
_wires.SetStatus(9, powerLight);
|
|
||||||
_wires.SetStatus(10, powerLight);
|
|
||||||
_wires.SetStatus(11, powerLight);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePowerCutStatus()
|
private void UpdatePowerCutStatus()
|
||||||
{
|
{
|
||||||
if (_receiverComponent == null)
|
if (ReceiverComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PowerWiresPulsed)
|
if (PowerWiresPulsed)
|
||||||
{
|
{
|
||||||
_receiverComponent.PowerDisabled = true;
|
ReceiverComponent.PowerDisabled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wiresComponent == null)
|
if (WiresComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_receiverComponent.PowerDisabled =
|
ReceiverComponent.PowerDisabled =
|
||||||
_wiresComponent.IsWireCut(Wires.MainPower) ||
|
WiresComponent.IsWireCut(Wires.MainPower) ||
|
||||||
_wiresComponent.IsWireCut(Wires.BackupPower);
|
WiresComponent.IsWireCut(Wires.BackupPower);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e)
|
private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e)
|
||||||
{
|
{
|
||||||
if (_appearanceComponent != null)
|
if (AppearanceComponent != null)
|
||||||
{
|
{
|
||||||
_appearanceComponent.SetData(DoorVisuals.Powered, e.Powered);
|
AppearanceComponent.SetData(DoorVisuals.Powered, e.Powered);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoltLights also got out
|
// BoltLights also got out
|
||||||
@@ -347,19 +289,13 @@ namespace Content.Server.Doors.Components
|
|||||||
builder.CreateWire(Wires.BoltLight);
|
builder.CreateWire(Wires.BoltLight);
|
||||||
builder.CreateWire(Wires.Timing);
|
builder.CreateWire(Wires.Timing);
|
||||||
builder.CreateWire(Wires.Safety);
|
builder.CreateWire(Wires.Safety);
|
||||||
/*
|
|
||||||
builder.CreateWire(6);
|
|
||||||
builder.CreateWire(7);
|
|
||||||
builder.CreateWire(8);
|
|
||||||
builder.CreateWire(9);
|
|
||||||
builder.CreateWire(10);
|
|
||||||
builder.CreateWire(11);*/
|
|
||||||
UpdateWiresStatus();
|
UpdateWiresStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WiresUpdate(WiresUpdateEventArgs args)
|
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||||
{
|
{
|
||||||
if (_doorComponent == null)
|
if (DoorComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -373,7 +309,7 @@ namespace Content.Server.Doors.Components
|
|||||||
PowerWiresPulsed = true;
|
PowerWiresPulsed = true;
|
||||||
_powerWiresPulsedTimerCancel.Cancel();
|
_powerWiresPulsedTimerCancel.Cancel();
|
||||||
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
|
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
|
||||||
Owner.SpawnTimer(PowerWiresTimeout,
|
Owner.SpawnTimer(TimeSpan.FromSeconds(PowerWiresTimeout),
|
||||||
() => PowerWiresPulsed = false,
|
() => PowerWiresPulsed = false,
|
||||||
_powerWiresPulsedTimerCancel.Token);
|
_powerWiresPulsedTimerCancel.Token);
|
||||||
break;
|
break;
|
||||||
@@ -396,11 +332,11 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = !_boltLightsWirePulsed;
|
BoltLightsVisible = !_boltLightsWirePulsed;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
_normalCloseSpeed = !_normalCloseSpeed;
|
AutoCloseDelayModifier = 0.5f;
|
||||||
_doorComponent.RefreshAutoClose();
|
DoorComponent.RefreshAutoClose();
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
_safety = !_safety;
|
Safety = !Safety;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,11 +355,11 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = true;
|
BoltLightsVisible = true;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
_autoClose = true;
|
AutoClose = true;
|
||||||
_doorComponent.RefreshAutoClose();
|
DoorComponent.RefreshAutoClose();
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
_safety = true;
|
Safety = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,11 +375,11 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = false;
|
BoltLightsVisible = false;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
_autoClose = false;
|
AutoClose = false;
|
||||||
_doorComponent.RefreshAutoClose();
|
DoorComponent.RefreshAutoClose();
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
_safety = false;
|
Safety = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,14 +397,7 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
BoltsDown = newBolts;
|
BoltsDown = newBolts;
|
||||||
|
|
||||||
if (newBolts)
|
SoundSystem.Play(Filter.Broadcast(), newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Owner);
|
||||||
{
|
|
||||||
SoundSystem.Play(Filter.Broadcast(), _setBoltsDownSound.GetSound(), Owner);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SoundSystem.Play(Filter.Broadcast(), _setBoltsUpSound.GetSound(), Owner);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Doors;
|
using Content.Server.Doors;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
@@ -6,26 +7,34 @@ using Content.Shared.Interaction;
|
|||||||
using Content.Shared.Notification.Managers;
|
using Content.Shared.Notification.Managers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Components
|
namespace Content.Server.Doors.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, and not being openable on open-hand click.
|
/// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying,
|
||||||
|
/// and not being openable on open-hand click.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IDoorCheck))]
|
public class FirelockComponent : Component
|
||||||
public class FirelockComponent : Component, IDoorCheck
|
|
||||||
{
|
{
|
||||||
public override string Name => "Firelock";
|
public override string Name => "Firelock";
|
||||||
|
|
||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
private readonly ServerDoorComponent? _doorComponent = null;
|
public readonly ServerDoorComponent? DoorComponent = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pry time modifier to be used when the firelock is currently closed due to fire or pressure.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[DataField("lockedPryTimeModifier")]
|
||||||
|
public float LockedPryTimeModifier = 1.5f;
|
||||||
|
|
||||||
public bool EmergencyPressureStop()
|
public bool EmergencyPressureStop()
|
||||||
{
|
{
|
||||||
if (_doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Open && _doorComponent.CanCloseGeneric())
|
if (DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Open && DoorComponent.CanCloseGeneric())
|
||||||
{
|
{
|
||||||
_doorComponent.Close();
|
DoorComponent.Close();
|
||||||
if (Owner.TryGetComponent(out AirtightComponent? airtight))
|
if (Owner.TryGetComponent(out AirtightComponent? airtight))
|
||||||
{
|
{
|
||||||
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
|
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
|
||||||
@@ -35,41 +44,6 @@ namespace Content.Server.Atmos.Components
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IDoorCheck.OpenCheck()
|
|
||||||
{
|
|
||||||
return !IsHoldingFire() && !IsHoldingPressure();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDoorCheck.DenyCheck() => false;
|
|
||||||
|
|
||||||
float? IDoorCheck.GetPryTime()
|
|
||||||
{
|
|
||||||
if (IsHoldingFire() || IsHoldingPressure())
|
|
||||||
{
|
|
||||||
return 1.5f;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) => true;
|
|
||||||
|
|
||||||
void IDoorCheck.OnStartPry(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (_doorComponent == null || _doorComponent.State != SharedDoorComponent.DoorState.Closed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsHoldingPressure())
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-pressure-message"));
|
|
||||||
}
|
|
||||||
else if (IsHoldingFire())
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-fire-message"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsHoldingPressure(float threshold = 20)
|
public bool IsHoldingPressure(float threshold = 20)
|
||||||
{
|
{
|
||||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||||
@@ -38,9 +38,6 @@ namespace Content.Server.Doors.Components
|
|||||||
[ComponentReference(typeof(SharedDoorComponent))]
|
[ComponentReference(typeof(SharedDoorComponent))]
|
||||||
public class ServerDoorComponent : SharedDoorComponent, IActivate, IInteractUsing, IMapInit
|
public class ServerDoorComponent : SharedDoorComponent, IActivate, IInteractUsing, IMapInit
|
||||||
{
|
{
|
||||||
[ComponentDependency]
|
|
||||||
private readonly IDoorCheck? _doorCheck = null;
|
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
[DataField("board")]
|
[DataField("board")]
|
||||||
private string? _boardPrototype;
|
private string? _boardPrototype;
|
||||||
@@ -67,11 +64,8 @@ namespace Content.Server.Doors.Components
|
|||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_doorCheck != null)
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new DoorStateChangedEvent(State), false);
|
||||||
{
|
_autoCloseCancelTokenSource?.Cancel();
|
||||||
_doorCheck.OnStateChange(State);
|
|
||||||
RefreshAutoClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
@@ -109,7 +103,7 @@ namespace Content.Server.Doors.Components
|
|||||||
/// Handled in Startup().
|
/// Handled in Startup().
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("startOpen")]
|
[ViewVariables(VVAccess.ReadWrite)] [DataField("startOpen")]
|
||||||
private bool _startOpen;
|
private bool _startOpen = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the airlock is welded shut. Can be set by the prototype, although this will fail if the door isn't weldable.
|
/// Whether the airlock is welded shut. Can be set by the prototype, although this will fail if the door isn't weldable.
|
||||||
@@ -143,6 +137,41 @@ namespace Content.Server.Doors.Components
|
|||||||
[DataField("weldable")]
|
[DataField("weldable")]
|
||||||
private bool _weldable = true;
|
private bool _weldable = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play when the door opens.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("openSound")]
|
||||||
|
public SoundSpecifier? OpenSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play when the door closes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("closeSound")]
|
||||||
|
public SoundSpecifier? CloseSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play if the door is denied.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("denySound")]
|
||||||
|
public SoundSpecifier? DenySound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default time that the door should take to pry open.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("pryTime")]
|
||||||
|
public float PryTime = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum interval allowed between deny sounds in milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("denySoundMinimumInterval")]
|
||||||
|
public float DenySoundMinimumInterval = 250.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to stop people from spamming the deny sound.
|
||||||
|
/// </summary>
|
||||||
|
private TimeSpan LastDenySoundTime = TimeSpan.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the door can currently be welded.
|
/// Whether the door can currently be welded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -153,6 +182,7 @@ namespace Content.Server.Doors.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _beingWelded;
|
private bool _beingWelded;
|
||||||
|
|
||||||
|
|
||||||
//[ViewVariables(VVAccess.ReadWrite)]
|
//[ViewVariables(VVAccess.ReadWrite)]
|
||||||
//[DataField("canCrush")]
|
//[DataField("canCrush")]
|
||||||
//private bool _canCrush = true; // TODO implement door crushing
|
//private bool _canCrush = true; // TODO implement door crushing
|
||||||
@@ -191,7 +221,7 @@ namespace Content.Server.Doors.Components
|
|||||||
Logger.Warning("{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true.", Owner.Name);
|
Logger.Warning("{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true.", Owner.Name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QuickOpen();
|
QuickOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateDoorElectronicsBoard();
|
CreateDoorElectronicsBoard();
|
||||||
@@ -199,10 +229,10 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (_doorCheck != null && _doorCheck.BlockActivate(eventArgs))
|
DoorClickShouldActivateEvent ev = new DoorClickShouldActivateEvent(eventArgs);
|
||||||
{
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
if (ev.Handled)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (State == DoorState.Open)
|
if (State == DoorState.Open)
|
||||||
{
|
{
|
||||||
@@ -282,12 +312,10 @@ namespace Content.Server.Doors.Components
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(_doorCheck != null)
|
|
||||||
{
|
|
||||||
return _doorCheck.OpenCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
var ev = new BeforeDoorOpenedEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
return !ev.Cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -304,12 +332,19 @@ namespace Content.Server.Doors.Components
|
|||||||
_stateChangeCancelTokenSource?.Cancel();
|
_stateChangeCancelTokenSource?.Cancel();
|
||||||
_stateChangeCancelTokenSource = new();
|
_stateChangeCancelTokenSource = new();
|
||||||
|
|
||||||
|
if (OpenSound != null)
|
||||||
|
{
|
||||||
|
SoundSystem.Play(Filter.Pvs(Owner), OpenSound.GetSound(),
|
||||||
|
AudioParams.Default.WithVolume(-5));
|
||||||
|
}
|
||||||
|
|
||||||
Owner.SpawnTimer(OpenTimeOne, async () =>
|
Owner.SpawnTimer(OpenTimeOne, async () =>
|
||||||
{
|
{
|
||||||
OnPartialOpen();
|
OnPartialOpen();
|
||||||
await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token);
|
await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token);
|
||||||
|
|
||||||
State = DoorState.Open;
|
State = DoorState.Open;
|
||||||
|
RefreshAutoClose();
|
||||||
}, _stateChangeCancelTokenSource.Token);
|
}, _stateChangeCancelTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +358,7 @@ namespace Content.Server.Doors.Components
|
|||||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false));
|
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QuickOpen()
|
private void QuickOpen(bool refresh)
|
||||||
{
|
{
|
||||||
if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
|
if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
|
||||||
{
|
{
|
||||||
@@ -331,6 +366,8 @@ namespace Content.Server.Doors.Components
|
|||||||
}
|
}
|
||||||
OnPartialOpen();
|
OnPartialOpen();
|
||||||
State = DoorState.Open;
|
State = DoorState.Open;
|
||||||
|
if(refresh)
|
||||||
|
RefreshAutoClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -369,17 +406,19 @@ namespace Content.Server.Doors.Components
|
|||||||
/// <returns>Boolean describing whether this door can close.</returns>
|
/// <returns>Boolean describing whether this door can close.</returns>
|
||||||
public bool CanCloseGeneric()
|
public bool CanCloseGeneric()
|
||||||
{
|
{
|
||||||
if (_doorCheck != null && !_doorCheck.CloseCheck())
|
var ev = new BeforeDoorClosedEvent();
|
||||||
{
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
if (ev.Cancelled)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return !IsSafetyColliding();
|
return !IsSafetyColliding();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SafetyCheck()
|
private bool SafetyCheck()
|
||||||
{
|
{
|
||||||
return (_doorCheck != null && _doorCheck.SafetyCheck()) || _inhibitCrush;
|
var ev = new DoorSafetyEnabledEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
return ev.Safety || _inhibitCrush;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -415,6 +454,13 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
_stateChangeCancelTokenSource?.Cancel();
|
_stateChangeCancelTokenSource?.Cancel();
|
||||||
_stateChangeCancelTokenSource = new();
|
_stateChangeCancelTokenSource = new();
|
||||||
|
|
||||||
|
if (CloseSound != null)
|
||||||
|
{
|
||||||
|
SoundSystem.Play(Filter.Pvs(Owner), CloseSound.GetSound(),
|
||||||
|
AudioParams.Default.WithVolume(-10));
|
||||||
|
}
|
||||||
|
|
||||||
Owner.SpawnTimer(CloseTimeOne, async () =>
|
Owner.SpawnTimer(CloseTimeOne, async () =>
|
||||||
{
|
{
|
||||||
// if somebody walked into the door as it was closing, and we don't crush things
|
// if somebody walked into the door as it was closing, and we don't crush things
|
||||||
@@ -507,10 +553,10 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
public void Deny()
|
public void Deny()
|
||||||
{
|
{
|
||||||
if (_doorCheck != null && !_doorCheck.DenyCheck())
|
var ev = new BeforeDoorDeniedEvent();
|
||||||
{
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
if (ev.Cancelled)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (State == DoorState.Open || IsWeldedShut)
|
if (State == DoorState.Open || IsWeldedShut)
|
||||||
return;
|
return;
|
||||||
@@ -518,6 +564,25 @@ namespace Content.Server.Doors.Components
|
|||||||
_stateChangeCancelTokenSource?.Cancel();
|
_stateChangeCancelTokenSource?.Cancel();
|
||||||
_stateChangeCancelTokenSource = new();
|
_stateChangeCancelTokenSource = new();
|
||||||
SetAppearance(DoorVisualState.Deny);
|
SetAppearance(DoorVisualState.Deny);
|
||||||
|
|
||||||
|
if (DenySound != null)
|
||||||
|
{
|
||||||
|
if (LastDenySoundTime == TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
LastDenySoundTime = _gameTiming.CurTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var difference = _gameTiming.CurTime - LastDenySoundTime;
|
||||||
|
if (difference < TimeSpan.FromMilliseconds(DenySoundMinimumInterval))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastDenySoundTime = _gameTiming.CurTime;
|
||||||
|
SoundSystem.Play(Filter.Pvs(Owner), DenySound.GetSound(),
|
||||||
|
AudioParams.Default.WithVolume(-3));
|
||||||
|
}
|
||||||
|
|
||||||
Owner.SpawnTimer(DenyTime, () =>
|
Owner.SpawnTimer(DenyTime, () =>
|
||||||
{
|
{
|
||||||
SetAppearance(DoorVisualState.Closed);
|
SetAppearance(DoorVisualState.Closed);
|
||||||
@@ -525,19 +590,24 @@ namespace Content.Server.Doors.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops the current auto-close timer if there is one. Starts a new one if this is appropriate (i.e. entity has an IDoorCheck component that allows auto-closing).
|
/// Starts a new auto close timer if this is appropriate
|
||||||
|
/// (i.e. event raised is not cancelled).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RefreshAutoClose()
|
public void RefreshAutoClose()
|
||||||
{
|
{
|
||||||
_autoCloseCancelTokenSource?.Cancel();
|
if (State != DoorState.Open)
|
||||||
|
|
||||||
if (State != DoorState.Open || _doorCheck == null || !_doorCheck.AutoCloseCheck())
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
var autoev = new BeforeDoorAutoCloseEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, autoev, false);
|
||||||
|
if (autoev.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
_autoCloseCancelTokenSource = new();
|
_autoCloseCancelTokenSource = new();
|
||||||
|
|
||||||
var realCloseTime = _doorCheck.GetCloseSpeed() ?? AutoCloseDelay;
|
var ev = new DoorGetCloseTimeModifierEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
var realCloseTime = AutoCloseDelay * ev.CloseTimeModifier;
|
||||||
|
|
||||||
Owner.SpawnRepeatingTimer(realCloseTime, async () =>
|
Owner.SpawnRepeatingTimer(realCloseTime, async () =>
|
||||||
{
|
{
|
||||||
@@ -559,21 +629,18 @@ namespace Content.Server.Doors.Components
|
|||||||
// for prying doors
|
// for prying doors
|
||||||
if (tool.HasQuality(ToolQuality.Prying) && !IsWeldedShut)
|
if (tool.HasQuality(ToolQuality.Prying) && !IsWeldedShut)
|
||||||
{
|
{
|
||||||
var successfulPry = false;
|
var ev = new DoorGetPryTimeModifierEvent();
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false);
|
||||||
|
|
||||||
if (_doorCheck != null)
|
var canEv = new BeforeDoorPryEvent(eventArgs);
|
||||||
{
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, canEv, false);
|
||||||
_doorCheck.OnStartPry(eventArgs);
|
|
||||||
successfulPry = await tool.UseTool(eventArgs.User, Owner,
|
var successfulPry = await tool.UseTool(eventArgs.User, Owner,
|
||||||
_doorCheck.GetPryTime() ?? 0.5f, ToolQuality.Prying, () => _doorCheck.CanPryCheck(eventArgs));
|
ev.PryTimeModifier * PryTime, ToolQuality.Prying, () => !canEv.Cancelled);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
successfulPry = await tool.UseTool(eventArgs.User, Owner, 0.5f, ToolQuality.Prying);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successfulPry && !IsWeldedShut)
|
if (successfulPry && !IsWeldedShut)
|
||||||
{
|
{
|
||||||
|
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OnDoorPryEvent(eventArgs), false);
|
||||||
if (State == DoorState.Closed)
|
if (State == DoorState.Closed)
|
||||||
{
|
{
|
||||||
Open();
|
Open();
|
||||||
|
|||||||
141
Content.Server/Doors/DoorEvents.cs
Normal file
141
Content.Server/Doors/DoorEvents.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Doors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the door's State variable is changed to a new variable that it was not equal to before.
|
||||||
|
/// </summary>
|
||||||
|
public class DoorStateChangedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public SharedDoorComponent.DoorState State;
|
||||||
|
|
||||||
|
public DoorStateChangedEvent(SharedDoorComponent.DoorState state)
|
||||||
|
{
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the door is determining whether it is able to open.
|
||||||
|
/// Cancel to stop the door from being opened.
|
||||||
|
/// </summary>
|
||||||
|
public class BeforeDoorOpenedEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the door is successfully opened.
|
||||||
|
/// </summary>
|
||||||
|
public class OnDoorOpenedEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the door is determining whether it is able to close.
|
||||||
|
/// Cancel to stop the door from being closed.
|
||||||
|
/// </summary>
|
||||||
|
public class BeforeDoorClosedEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the door is successfully closed.
|
||||||
|
/// </summary>
|
||||||
|
public class OnDoorClosedEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the door is determining whether it is able to deny.
|
||||||
|
/// Cancel to stop the door from being able to deny.
|
||||||
|
/// </summary>
|
||||||
|
public class BeforeDoorDeniedEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when access to the door is denied.
|
||||||
|
/// </summary>
|
||||||
|
public class OnDoorDeniedEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to determine whether the door's safety is on.
|
||||||
|
/// Modify Safety to set the door's safety.
|
||||||
|
/// </summary>
|
||||||
|
public class DoorSafetyEnabledEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
public bool Safety = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to determine whether the door should automatically close.
|
||||||
|
/// Cancel to stop it from automatically closing.
|
||||||
|
/// </summary>
|
||||||
|
public class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to determine how long the door's pry time should be modified by.
|
||||||
|
/// Multiply PryTimeModifier by the desired amount.
|
||||||
|
/// </summary>
|
||||||
|
public class DoorGetPryTimeModifierEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public float PryTimeModifier = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to determine how long the door's close time should be modified by.
|
||||||
|
/// Multiply CloseTimeModifier by the desired amount.
|
||||||
|
/// </summary>
|
||||||
|
public class DoorGetCloseTimeModifierEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public float CloseTimeModifier = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to determine whether clicking the door should open/close it.
|
||||||
|
/// </summary>
|
||||||
|
public class DoorClickShouldActivateEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
public ActivateEventArgs Args;
|
||||||
|
|
||||||
|
public DoorClickShouldActivateEvent(ActivateEventArgs args)
|
||||||
|
{
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when an attempt to pry open the door is made.
|
||||||
|
/// Cancel to stop the door from being pried open.
|
||||||
|
/// </summary>
|
||||||
|
public class BeforeDoorPryEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
public InteractUsingEventArgs Args;
|
||||||
|
|
||||||
|
public BeforeDoorPryEvent(InteractUsingEventArgs args)
|
||||||
|
{
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a door is successfully pried open.
|
||||||
|
/// </summary>
|
||||||
|
public class OnDoorPryEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public InteractUsingEventArgs Args;
|
||||||
|
|
||||||
|
public OnDoorPryEvent(InteractUsingEventArgs args)
|
||||||
|
{
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.Doors;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
|
|
||||||
namespace Content.Server.Doors
|
|
||||||
{
|
|
||||||
public interface IDoorCheck
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door's State variable is changed to a new variable that it was not equal to before.
|
|
||||||
/// </summary>
|
|
||||||
void OnStateChange(SharedDoorComponent.DoorState doorState) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door is determining whether it is able to open.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the door should open, false if it should not.</returns>
|
|
||||||
bool OpenCheck() => true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door is determining whether it is able to close.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the door should close, false if it should not.</returns>
|
|
||||||
bool CloseCheck() => true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door is determining whether it is able to deny.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the door should deny, false if it should not.</returns>
|
|
||||||
bool DenyCheck() => true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the door's safety is on.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if safety is on, false if it is not.</returns>
|
|
||||||
bool SafetyCheck() => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the door should close automatically.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the door should close automatically, false if it should not.</returns>
|
|
||||||
bool AutoCloseCheck() => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an override for the amount of time to pry open the door, or null if there is no override.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Float if there is an override, null otherwise.</returns>
|
|
||||||
float? GetPryTime() => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an override for the amount of time before the door automatically closes, or null if there is no override.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>TimeSpan if there is an override, null otherwise.</returns>
|
|
||||||
TimeSpan? GetCloseSpeed() => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A check to determine whether or not a click on the door should interact with it with the intent to open/close.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the door's IActivate should not run, false otherwise.</returns>
|
|
||||||
bool BlockActivate(ActivateEventArgs eventArgs) => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when somebody begins to pry open the door.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventArgs">The eventArgs of the InteractUsing method that called this function.</param>
|
|
||||||
void OnStartPry(InteractUsingEventArgs eventArgs) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check representing whether or not the door can be pried open.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventArgs">The eventArgs of the InteractUsing method that called this function.</param>
|
|
||||||
/// <returns>True if the door can be pried open, false if it cannot.</returns>
|
|
||||||
bool CanPryCheck(InteractUsingEventArgs eventArgs) => true;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
111
Content.Server/Doors/Systems/AirlockSystem.cs
Normal file
111
Content.Server/Doors/Systems/AirlockSystem.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using Content.Server.Doors.Components;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Notification.Managers;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
|
namespace Content.Server.Doors.Systems
|
||||||
|
{
|
||||||
|
public class AirlockSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, DoorSafetyEnabledEvent>(OnDoorSafetyCheck);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorAutoCloseEvent>(OnDoorAutoCloseCheck);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, DoorGetCloseTimeModifierEvent>(OnDoorCloseTimeModifier);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, DoorClickShouldActivateEvent>(OnDoorClickShouldActivate);
|
||||||
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid uid, AirlockComponent component, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
if (component.AppearanceComponent != null)
|
||||||
|
{
|
||||||
|
component.AppearanceComponent.SetData(DoorVisuals.Powered, args.Powered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoltLights also got out
|
||||||
|
component.UpdateBoltLightStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
// Only show the maintenance panel if the airlock is closed
|
||||||
|
if (component.WiresComponent != null)
|
||||||
|
{
|
||||||
|
component.WiresComponent.IsPanelVisible =
|
||||||
|
component.OpenPanelVisible
|
||||||
|
|| args.State != SharedDoorComponent.DoorState.Open;
|
||||||
|
}
|
||||||
|
// If the door is closed, we should look if the bolt was locked while closing
|
||||||
|
component.UpdateBoltLightStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.CanChangeState())
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorClosed(EntityUid uid, AirlockComponent component, BeforeDoorClosedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.CanChangeState())
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.CanChangeState())
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorSafetyCheck(EntityUid uid, AirlockComponent component, DoorSafetyEnabledEvent args)
|
||||||
|
{
|
||||||
|
args.Safety = component.Safety;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorAutoCloseCheck(EntityUid uid, AirlockComponent component, BeforeDoorAutoCloseEvent args)
|
||||||
|
{
|
||||||
|
if (!component.AutoClose)
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorCloseTimeModifier(EntityUid uid, AirlockComponent component, DoorGetCloseTimeModifierEvent args)
|
||||||
|
{
|
||||||
|
args.CloseTimeModifier *= component.AutoCloseDelayModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorClickShouldActivate(EntityUid uid, AirlockComponent component, DoorClickShouldActivateEvent args)
|
||||||
|
{
|
||||||
|
if (component.WiresComponent != null && component.WiresComponent.IsPanelOpen &&
|
||||||
|
args.Args.User.TryGetComponent(out ActorComponent? actor))
|
||||||
|
{
|
||||||
|
component.WiresComponent.OpenInterface(actor.PlayerSession);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorPry(EntityUid uid, AirlockComponent component, BeforeDoorPryEvent args)
|
||||||
|
{
|
||||||
|
if (component.IsBolted())
|
||||||
|
{
|
||||||
|
component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message"));
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
if (component.IsPowered())
|
||||||
|
{
|
||||||
|
component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message"));
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Content.Server/Doors/Systems/FirelockSystem.cs
Normal file
69
Content.Server/Doors/Systems/FirelockSystem.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Content.Server.Doors.Components;
|
||||||
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Notification.Managers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
|
namespace Content.Server.Doors.Systems
|
||||||
|
{
|
||||||
|
public class FirelockSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||||
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
||||||
|
SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||||
|
SubscribeLocalEvent<FirelockComponent, DoorClickShouldActivateEvent>(OnDoorClickShouldActivate);
|
||||||
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorPryEvent>(OnBeforeDoorPry);
|
||||||
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
|
||||||
|
{
|
||||||
|
if (component.IsHoldingFire() || component.IsHoldingPressure())
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorDenied(EntityUid uid, FirelockComponent component, BeforeDoorDeniedEvent args)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args)
|
||||||
|
{
|
||||||
|
if (component.IsHoldingFire() || component.IsHoldingPressure())
|
||||||
|
args.PryTimeModifier *= component.LockedPryTimeModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorClickShouldActivate(EntityUid uid, FirelockComponent component, DoorClickShouldActivateEvent args)
|
||||||
|
{
|
||||||
|
// We're a firelock, you can't click to open it
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args)
|
||||||
|
{
|
||||||
|
if (component.DoorComponent == null || component.DoorComponent.State != SharedDoorComponent.DoorState.Closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.IsHoldingPressure())
|
||||||
|
{
|
||||||
|
component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-pressure-message"));
|
||||||
|
}
|
||||||
|
else if (component.IsHoldingFire())
|
||||||
|
{
|
||||||
|
component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-fire-message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args)
|
||||||
|
{
|
||||||
|
// Firelocks can't autoclose, they must be manually closed
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,6 +112,7 @@ namespace Content.Server.Explosion
|
|||||||
|
|
||||||
Timer.Spawn(delay, () =>
|
Timer.Spawn(delay, () =>
|
||||||
{
|
{
|
||||||
|
if (triggered.Deleted) return;
|
||||||
Trigger(triggered, user);
|
Trigger(triggered, user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,22 @@ namespace Content.Server.GameTicking
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public bool DisallowLateJoin { get; private set; } = false;
|
public bool DisallowLateJoin { get; private set; } = false;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public bool StationOffset { get; private set; } = false;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public float MaxStationOffset { get; private set; } = 0f;
|
||||||
|
|
||||||
private void InitializeCVars()
|
private void InitializeCVars()
|
||||||
{
|
{
|
||||||
_configurationManager.OnValueChanged(CCVars.GameLobbyEnabled, value => LobbyEnabled = value, true);
|
_configurationManager.OnValueChanged(CCVars.GameLobbyEnabled, value => LobbyEnabled = value, true);
|
||||||
_configurationManager.OnValueChanged(CCVars.GameDummyTicker, value => DummyTicker = value, true);
|
_configurationManager.OnValueChanged(CCVars.GameDummyTicker, value => DummyTicker = value, true);
|
||||||
_configurationManager.OnValueChanged(CCVars.GameMap, value => ChosenMap = value, true);
|
_configurationManager.OnValueChanged(CCVars.GameMap, value => ChosenMap = value, true);
|
||||||
_configurationManager.OnValueChanged(CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
|
_configurationManager.OnValueChanged(CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
|
||||||
_configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, invokeImmediately:true,
|
_configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins,
|
||||||
onValueChanged:value => { DisallowLateJoin = value; UpdateLateJoinStatus(); UpdateJobsAvailable(); });
|
value => { DisallowLateJoin = value; UpdateLateJoinStatus(); UpdateJobsAvailable(); }, true);
|
||||||
|
_configurationManager.OnValueChanged(CCVars.StationOffset, value => StationOffset = value, true);
|
||||||
|
_configurationManager.OnValueChanged(CCVars.MaxStationOffset, value => MaxStationOffset = value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Robust.Server.Player;
|
|||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
@@ -62,6 +63,14 @@ namespace Content.Server.GameTicking
|
|||||||
throw new InvalidOperationException($"No grid found for map {map}");
|
throw new InvalidOperationException($"No grid found for map {map}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StationOffset)
|
||||||
|
{
|
||||||
|
// Apply a random offset to the station grid entity.
|
||||||
|
var x = _robustRandom.NextFloat() * MaxStationOffset * 2 - MaxStationOffset;
|
||||||
|
var y = _robustRandom.NextFloat() * MaxStationOffset * 2 - MaxStationOffset;
|
||||||
|
EntityManager.GetEntity(grid.GridEntityId).Transform.LocalPosition = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
DefaultGridId = grid.Index;
|
DefaultGridId = grid.Index;
|
||||||
_spawnPoint = grid.ToCoordinates();
|
_spawnPoint = grid.ToCoordinates();
|
||||||
|
|
||||||
@@ -280,7 +289,7 @@ namespace Content.Server.GameTicking
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete all entities.
|
// Delete all entities.
|
||||||
foreach (var entity in _entityManager.GetEntities().ToList())
|
foreach (var entity in EntityManager.GetEntities().ToList())
|
||||||
{
|
{
|
||||||
// TODO: Maybe something less naive here?
|
// TODO: Maybe something less naive here?
|
||||||
// FIXME: Actually, definitely.
|
// FIXME: Actually, definitely.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Content.Server.Roles;
|
|||||||
using Content.Server.Spawners.Components;
|
using Content.Server.Spawners.Components;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -144,7 +145,8 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
var mob = SpawnObserverMob();
|
var mob = SpawnObserverMob();
|
||||||
mob.Name = name;
|
mob.Name = name;
|
||||||
mob.GetComponent<GhostComponent>().CanReturnToBody = false;
|
var ghost = mob.GetComponent<GhostComponent>();
|
||||||
|
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false);
|
||||||
data.Mind.TransferTo(mob);
|
data.Mind.TransferTo(mob);
|
||||||
|
|
||||||
_playersInLobby[player] = LobbyPlayerStatus.Observer;
|
_playersInLobby[player] = LobbyPlayerStatus.Observer;
|
||||||
@@ -155,7 +157,7 @@ namespace Content.Server.GameTicking
|
|||||||
private IEntity SpawnPlayerMob(Job job, HumanoidCharacterProfile? profile, bool lateJoin = true)
|
private IEntity SpawnPlayerMob(Job job, HumanoidCharacterProfile? profile, bool lateJoin = true)
|
||||||
{
|
{
|
||||||
var coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID);
|
var coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID);
|
||||||
var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates);
|
var entity = EntityManager.SpawnEntity(PlayerPrototypeName, coordinates);
|
||||||
|
|
||||||
if (job.StartingGear != null)
|
if (job.StartingGear != null)
|
||||||
{
|
{
|
||||||
@@ -175,7 +177,7 @@ namespace Content.Server.GameTicking
|
|||||||
private IEntity SpawnObserverMob()
|
private IEntity SpawnObserverMob()
|
||||||
{
|
{
|
||||||
var coordinates = GetObserverSpawnPoint();
|
var coordinates = GetObserverSpawnPoint();
|
||||||
return _entityManager.SpawnEntity(ObserverPrototypeName, coordinates);
|
return EntityManager.SpawnEntity(ObserverPrototypeName, coordinates);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -189,7 +191,7 @@ namespace Content.Server.GameTicking
|
|||||||
var equipmentStr = startingGear.GetGear(slot, profile);
|
var equipmentStr = startingGear.GetGear(slot, profile);
|
||||||
if (equipmentStr != string.Empty)
|
if (equipmentStr != string.Empty)
|
||||||
{
|
{
|
||||||
var equipmentEntity = _entityManager.SpawnEntity(equipmentStr, entity.Transform.Coordinates);
|
var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, entity.Transform.Coordinates);
|
||||||
inventory.Equip(slot, equipmentEntity.GetComponent<ItemComponent>());
|
inventory.Equip(slot, equipmentEntity.GetComponent<ItemComponent>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +202,7 @@ namespace Content.Server.GameTicking
|
|||||||
var inhand = startingGear.Inhand;
|
var inhand = startingGear.Inhand;
|
||||||
foreach (var (hand, prototype) in inhand)
|
foreach (var (hand, prototype) in inhand)
|
||||||
{
|
{
|
||||||
var inhandEntity = _entityManager.SpawnEntity(prototype, entity.Transform.Coordinates);
|
var inhandEntity = EntityManager.SpawnEntity(prototype, entity.Transform.Coordinates);
|
||||||
handsComponent.TryPickupEntity(hand, inhandEntity, checkActionBlocker: false);
|
handsComponent.TryPickupEntity(hand, inhandEntity, checkActionBlocker: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ namespace Content.Server.GameTicking
|
|||||||
UpdateRoundFlow(frameTime);
|
UpdateRoundFlow(frameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IMapLoader _mapLoader = default!;
|
[Dependency] private readonly IMapLoader _mapLoader = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Damage.Components;
|
using Content.Shared.Damage.Components;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.MobState;
|
using Content.Shared.MobState;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
@@ -75,7 +76,7 @@ namespace Content.Server.GameTicking.Presets
|
|||||||
ghost.Name = mind.CharacterName ?? string.Empty;
|
ghost.Name = mind.CharacterName ?? string.Empty;
|
||||||
|
|
||||||
var ghostComponent = ghost.GetComponent<GhostComponent>();
|
var ghostComponent = ghost.GetComponent<GhostComponent>();
|
||||||
ghostComponent.CanReturnToBody = canReturn;
|
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghostComponent, canReturn);
|
||||||
|
|
||||||
if (canReturn)
|
if (canReturn)
|
||||||
mind.Visit(ghost);
|
mind.Visit(ghost);
|
||||||
|
|||||||
@@ -90,19 +90,15 @@ namespace Content.Server.Hands.Components
|
|||||||
|
|
||||||
protected override void HandlePickupAnimation(IEntity entity)
|
protected override void HandlePickupAnimation(IEntity entity)
|
||||||
{
|
{
|
||||||
var pickupDirection = Owner.Transform.WorldPosition;
|
var initialPosition = EntityCoordinates.FromMap(Owner.Transform.Coordinates.GetParent(Owner.EntityManager), entity.Transform.MapPosition);
|
||||||
|
|
||||||
var outermostEntity = entity;
|
var finalPosition = Owner.Transform.Coordinates.Position;
|
||||||
while (outermostEntity.TryGetContainer(out var container)) //TODO: Use WorldPosition instead of this loop
|
|
||||||
outermostEntity = container.Owner;
|
|
||||||
|
|
||||||
var initialPosition = outermostEntity.Transform.Coordinates;
|
if (finalPosition.EqualsApprox(initialPosition.Position))
|
||||||
|
|
||||||
if (pickupDirection == initialPosition.ToMapPos(Owner.EntityManager))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Owner.EntityManager.EntityNetManager!.SendSystemNetworkMessage(
|
Owner.EntityManager.EntityNetManager!.SendSystemNetworkMessage(
|
||||||
new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition));
|
new PickupAnimationMessage(entity.Uid, finalPosition, initialPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Pull/Disarm
|
#region Pull/Disarm
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ using Content.Server.Hands.Components;
|
|||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.Kitchen.Components;
|
using Content.Server.Kitchen.Components;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.Stack;
|
||||||
using Content.Server.UserInterface;
|
using Content.Server.UserInterface;
|
||||||
|
using Content.Server.Kitchen.Events;
|
||||||
using Content.Shared.Chemistry.Solution;
|
using Content.Shared.Chemistry.Solution;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Kitchen.Components;
|
using Content.Shared.Kitchen.Components;
|
||||||
@@ -30,7 +32,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new ();
|
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -40,11 +42,17 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) => EnqueueUiUpdate(component));
|
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) => EnqueueUiUpdate(component));
|
||||||
SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand);
|
SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand);
|
||||||
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
|
SubscribeLocalEvent<StackComponent, JuiceableScalingEvent>(JuiceableScaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void JuiceableScaling(EntityUid uid, StackComponent component, JuiceableScalingEvent args)
|
||||||
|
{
|
||||||
|
args.Scalar *= component.Count; // multiply scalar by amount of items in stack
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args)
|
private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args)
|
||||||
{
|
{
|
||||||
if(args.Handled) return;
|
if (args.Handled) return;
|
||||||
|
|
||||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||||
{
|
{
|
||||||
@@ -58,7 +66,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
//First, check if user is trying to insert a beaker.
|
//First, check if user is trying to insert a beaker.
|
||||||
//No promise it will be a beaker right now, but whatever.
|
//No promise it will be a beaker right now, but whatever.
|
||||||
//Maybe this should whitelist "beaker" in the prototype id of heldEnt?
|
//Maybe this should whitelist "beaker" in the prototype id of heldEnt?
|
||||||
if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
|
if (heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
|
||||||
{
|
{
|
||||||
component.BeakerContainer.Insert(heldEnt);
|
component.BeakerContainer.Insert(heldEnt);
|
||||||
component.HeldBeaker = beaker;
|
component.HeldBeaker = beaker;
|
||||||
@@ -74,7 +82,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Next, see if the user is trying to insert something they want to be ground/juiced.
|
//Next, see if the user is trying to insert something they want to be ground/juiced.
|
||||||
if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice))
|
if (!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice))
|
||||||
{
|
{
|
||||||
//Entity did NOT pass the whitelist for grind/juice.
|
//Entity did NOT pass the whitelist for grind/juice.
|
||||||
//Wouldn't want the clown grinding up the Captain's ID card now would you?
|
//Wouldn't want the clown grinding up the Captain's ID card now would you?
|
||||||
@@ -113,7 +121,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
|
|
||||||
private void EnqueueUiUpdate(ReagentGrinderComponent component)
|
private void EnqueueUiUpdate(ReagentGrinderComponent component)
|
||||||
{
|
{
|
||||||
if(!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component);
|
if (!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args)
|
private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args)
|
||||||
@@ -138,12 +146,12 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component,
|
private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component,
|
||||||
ServerBoundUserInterfaceMessage message)
|
ServerBoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
if(component.Busy)
|
if (component.Busy)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(message.Message)
|
switch (message.Message)
|
||||||
{
|
{
|
||||||
case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
|
case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
|
||||||
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break;
|
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break;
|
||||||
@@ -158,7 +166,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
|
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
|
||||||
if(component.Chamber.ContainedEntities.Count > 0)
|
if (component.Chamber.ContainedEntities.Count > 0)
|
||||||
{
|
{
|
||||||
ClickSound(component);
|
ClickSound(component);
|
||||||
for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--)
|
for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--)
|
||||||
@@ -234,7 +242,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var beaker = component.BeakerContainer.ContainedEntity;
|
var beaker = component.BeakerContainer.ContainedEntity;
|
||||||
if(beaker is null)
|
if (beaker is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
component.BeakerContainer.Remove(beaker);
|
component.BeakerContainer.Remove(beaker);
|
||||||
@@ -258,7 +266,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program)
|
private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program)
|
||||||
{
|
{
|
||||||
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
|
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
|
||||||
if(!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null)
|
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -278,12 +286,14 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
{
|
{
|
||||||
if (!item.HasTag("Grindable")) continue;
|
if (!item.HasTag("Grindable")) continue;
|
||||||
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue;
|
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue;
|
||||||
if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume > component.HeldBeaker.MaxVolume) continue;
|
var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0
|
||||||
|
RaiseLocalEvent<JuiceableScalingEvent>(item.Uid, juiceEvent, false);
|
||||||
|
if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue;
|
||||||
|
solution.Solution.ScaleSolution(juiceEvent.Scalar);
|
||||||
component.HeldBeaker.TryAddSolution(solution.Solution);
|
component.HeldBeaker.TryAddSolution(solution.Solution);
|
||||||
solution.RemoveAllSolution();
|
solution.RemoveAllSolution();
|
||||||
item.Delete();
|
item.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
component.Busy = false;
|
component.Busy = false;
|
||||||
EnqueueUiUpdate(component);
|
EnqueueUiUpdate(component);
|
||||||
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
|
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
|
||||||
@@ -297,7 +307,13 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
foreach (var item in component.Chamber.ContainedEntities.ToList())
|
foreach (var item in component.Chamber.ContainedEntities.ToList())
|
||||||
{
|
{
|
||||||
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue;
|
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue;
|
||||||
if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > component.HeldBeaker.MaxVolume) continue;
|
var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0
|
||||||
|
if (item.HasComponent<StackComponent>())
|
||||||
|
{
|
||||||
|
RaiseLocalEvent<JuiceableScalingEvent>(item.Uid, juiceEvent);
|
||||||
|
}
|
||||||
|
if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue;
|
||||||
|
juiceMe.JuiceResultSolution.ScaleSolution(juiceEvent.Scalar);
|
||||||
component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
|
component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
|
||||||
item.Delete();
|
item.Delete();
|
||||||
}
|
}
|
||||||
|
|||||||
23
Content.Server/Kitchen/Events/JuiceableScalingEvent.cs
Normal file
23
Content.Server/Kitchen/Events/JuiceableScalingEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Kitchen.Events
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used in scaling amount of solution to extract in juicing
|
||||||
|
/// </summary>
|
||||||
|
public class JuiceableScalingEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
|
||||||
|
public JuiceableScalingEvent()
|
||||||
|
{
|
||||||
|
Scalar = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Scalar
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.MobState;
|
using Content.Shared.MobState;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -86,7 +87,7 @@ namespace Content.Server.Mind.Components
|
|||||||
{
|
{
|
||||||
if (visiting.TryGetComponent(out GhostComponent? ghost))
|
if (visiting.TryGetComponent(out GhostComponent? ghost))
|
||||||
{
|
{
|
||||||
ghost.CanReturnToBody = false;
|
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Mind!.TransferTo(visiting);
|
Mind!.TransferTo(visiting);
|
||||||
@@ -108,7 +109,7 @@ namespace Content.Server.Mind.Components
|
|||||||
|
|
||||||
var ghost = Owner.EntityManager.SpawnEntity("MobObserver", spawnPosition);
|
var ghost = Owner.EntityManager.SpawnEntity("MobObserver", spawnPosition);
|
||||||
var ghostComponent = ghost.GetComponent<GhostComponent>();
|
var ghostComponent = ghost.GetComponent<GhostComponent>();
|
||||||
ghostComponent.CanReturnToBody = false;
|
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghostComponent, false);
|
||||||
|
|
||||||
if (Mind != null)
|
if (Mind != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Directions;
|
using Content.Shared.Directions;
|
||||||
@@ -13,6 +14,7 @@ using Robust.Server.GameObjects;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
@@ -141,6 +143,20 @@ namespace Content.Server.Morgue.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<IEntity> DetermineCollidingEntities()
|
||||||
|
{
|
||||||
|
if (_tray == null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
||||||
|
foreach (var entity in entityLookup.GetEntitiesIntersecting(_tray))
|
||||||
|
{
|
||||||
|
yield return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Called every 10 seconds
|
//Called every 10 seconds
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ namespace Content.Server.Placeable
|
|||||||
}
|
}
|
||||||
handComponent.Drop(eventArgs.Using);
|
handComponent.Drop(eventArgs.Using);
|
||||||
if (_placeCentered)
|
if (_placeCentered)
|
||||||
eventArgs.Using.Transform.WorldPosition = eventArgs.Target.Transform.WorldPosition + _positionOffset;
|
eventArgs.Using.Transform.LocalPosition = eventArgs.Target.Transform.LocalPosition + _positionOffset;
|
||||||
else
|
else
|
||||||
eventArgs.Using.Transform.WorldPosition = eventArgs.ClickLocation.Position;
|
eventArgs.Using.Transform.Coordinates = eventArgs.ClickLocation;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ namespace Content.Server.Pointing.EntitySystems
|
|||||||
|
|
||||||
public bool TryPoint(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
public bool TryPoint(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||||
{
|
{
|
||||||
|
var mapCoords = coords.ToMap(EntityManager);
|
||||||
var player = (session as IPlayerSession)?.ContentData()?.Mind?.CurrentEntity;
|
var player = (session as IPlayerSession)?.ContentData()?.Mind?.CurrentEntity;
|
||||||
if (player == null)
|
if (player == null)
|
||||||
{
|
{
|
||||||
@@ -115,14 +116,14 @@ namespace Content.Server.Pointing.EntitySystems
|
|||||||
|
|
||||||
if (_actionBlockerSystem.CanChangeDirection(player))
|
if (_actionBlockerSystem.CanChangeDirection(player))
|
||||||
{
|
{
|
||||||
var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position;
|
var diff = mapCoords.Position - player.Transform.MapPosition.Position;
|
||||||
if (diff.LengthSquared > 0.01f)
|
if (diff.LengthSquared > 0.01f)
|
||||||
{
|
{
|
||||||
player.Transform.LocalRotation = new Angle(diff);
|
player.Transform.LocalRotation = new Angle(diff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var arrow = EntityManager.SpawnEntity("pointingarrow", coords);
|
var arrow = EntityManager.SpawnEntity("pointingarrow", mapCoords);
|
||||||
|
|
||||||
var layer = (int) VisibilityFlags.Normal;
|
var layer = (int) VisibilityFlags.Normal;
|
||||||
if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
|
if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
|
||||||
@@ -160,8 +161,14 @@ namespace Content.Server.Pointing.EntitySystems
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var tileRef = _mapManager.GetGrid(coords.GetGridId(EntityManager)).GetTileRef(coords);
|
TileRef? tileRef = null;
|
||||||
var tileDef = _tileDefinitionManager[tileRef.Tile.TypeId];
|
|
||||||
|
if (_mapManager.TryFindGridAt(mapCoords, out var grid))
|
||||||
|
{
|
||||||
|
tileRef = grid.GetTileRef(grid.WorldToTile(mapCoords.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tileDef = _tileDefinitionManager[tileRef?.Tile.TypeId ?? 0];
|
||||||
|
|
||||||
selfMessage = Loc.GetString("pointing-system-point-at-tile", ("tileName", tileDef.DisplayName));
|
selfMessage = Loc.GetString("pointing-system-point-at-tile", ("tileName", tileDef.DisplayName));
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Content.Server.PowerCell.Components
|
|||||||
{
|
{
|
||||||
if (inDetailsRange)
|
if (inDetailsRange)
|
||||||
{
|
{
|
||||||
message.AddMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{CurrentCharge / MaxCharge * 100}:F0")));
|
message.AddMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{CurrentCharge / MaxCharge * 100:F0}")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ namespace Content.Server.Storage.Components
|
|||||||
EmptyContents();
|
EmptyContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<IEntity> DetermineCollidingEntities()
|
protected virtual IEnumerable<IEntity> DetermineCollidingEntities()
|
||||||
{
|
{
|
||||||
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
||||||
return entityLookup.GetEntitiesIntersecting(Owner);
|
return entityLookup.GetEntitiesIntersecting(Owner);
|
||||||
|
|||||||
@@ -69,7 +69,9 @@ namespace Content.Server.Throwing
|
|||||||
EntitySystem.Get<InteractionSystem>().ThrownInteraction(user, entity);
|
EntitySystem.Get<InteractionSystem>().ThrownInteraction(user, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
physicsComponent.ApplyLinearImpulse(direction.Normalized * strength * physicsComponent.Mass);
|
var impulseVector = direction.Normalized * strength * physicsComponent.Mass;
|
||||||
|
physicsComponent.ApplyLinearImpulse(impulseVector);
|
||||||
|
|
||||||
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
|
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
|
||||||
var time = (direction / strength).Length;
|
var time = (direction / strength).Length;
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ namespace Content.Server.Throwing
|
|||||||
|
|
||||||
if (!msg.Cancelled)
|
if (!msg.Cancelled)
|
||||||
{
|
{
|
||||||
body.ApplyLinearImpulse(-direction * pushbackRatio);
|
body.ApplyLinearImpulse(-impulseVector * pushbackRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Content.Shared.Emoting;
|
|||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Inventory.Events;
|
using Content.Shared.Inventory.Events;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using Content.Shared.Metabolism.Events;
|
using Content.Shared.Body.Metabolism;
|
||||||
using Content.Shared.Movement;
|
using Content.Shared.Movement;
|
||||||
using Content.Shared.Speech;
|
using Content.Shared.Speech;
|
||||||
using Content.Shared.Throwing;
|
using Content.Shared.Throwing;
|
||||||
|
|||||||
@@ -4,31 +4,37 @@ using Robust.Shared.Serialization;
|
|||||||
namespace Content.Shared.Atmos.Piping
|
namespace Content.Shared.Atmos.Piping
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum OutletInjectorVisuals
|
public enum OutletInjectorVisuals : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum PassiveVentVisuals
|
public enum PassiveVentVisuals : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum VentScrubberVisuals
|
public enum VentScrubberVisuals : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum ThermoMachineVisuals
|
public enum ThermoMachineVisuals : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum PressurePumpVisuals
|
public enum PressurePumpVisuals : byte
|
||||||
|
{
|
||||||
|
Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum FilterVisuals : byte
|
||||||
{
|
{
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Shared.Metabolism.Events
|
namespace Content.Shared.Body.Metabolism
|
||||||
{
|
{
|
||||||
public class ShiverAttemptEvent : CancellableEntityEventArgs
|
public class ShiverAttemptEvent : CancellableEntityEventArgs
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Shared.Metabolism.Events
|
namespace Content.Shared.Body.Metabolism
|
||||||
{
|
{
|
||||||
public class SweatAttemptEvent : CancellableEntityEventArgs
|
public class SweatAttemptEvent : CancellableEntityEventArgs
|
||||||
{
|
{
|
||||||
@@ -55,6 +55,19 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<string>
|
public static readonly CVarDef<string>
|
||||||
GameMap = CVarDef.Create("game.map", "Maps/saltern.yml", CVar.SERVERONLY);
|
GameMap = CVarDef.Create("game.map", "Maps/saltern.yml", CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a random position offset will be applied to the station on roundstart.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<bool> StationOffset =
|
||||||
|
CVarDef.Create("game.station_offset", true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the default blueprint is loaded what is the maximum amount it can be offset from 0,0.
|
||||||
|
/// Does nothing without <see cref="StationOffset"/> as true.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<float> MaxStationOffset =
|
||||||
|
CVarDef.Create("game.maxstationoffset", 1000.0f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When enabled, guests will be assigned permanent UIDs and will have their preferences stored.
|
/// When enabled, guests will be assigned permanent UIDs and will have their preferences stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,47 +1,90 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
|
|
||||||
namespace Content.Shared.CharacterAppearance
|
namespace Content.Shared.CharacterAppearance
|
||||||
{
|
{
|
||||||
public static class HumanoidVisualLayersExtension
|
public static class HumanoidVisualLayersExtension
|
||||||
{
|
{
|
||||||
public static HumanoidVisualLayers? ToHumanoidLayer(this SharedBodyPartComponent part)
|
public static IEnumerable<HumanoidVisualLayers> ToHumanoidLayers(this SharedBodyPartComponent part)
|
||||||
{
|
{
|
||||||
return part.PartType switch
|
switch (part.PartType)
|
||||||
{
|
{
|
||||||
BodyPartType.Other => null,
|
case BodyPartType.Other:
|
||||||
BodyPartType.Torso => HumanoidVisualLayers.Chest,
|
yield break;
|
||||||
BodyPartType.Head => HumanoidVisualLayers.Head,
|
case BodyPartType.Torso:
|
||||||
BodyPartType.Arm => part.Symmetry switch
|
yield return HumanoidVisualLayers.Chest;
|
||||||
{
|
break;
|
||||||
BodyPartSymmetry.None => null,
|
case BodyPartType.Head:
|
||||||
BodyPartSymmetry.Left => HumanoidVisualLayers.LArm,
|
yield return HumanoidVisualLayers.Head;
|
||||||
BodyPartSymmetry.Right => HumanoidVisualLayers.RArm,
|
yield return HumanoidVisualLayers.Eyes;
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
yield return HumanoidVisualLayers.FacialHair;
|
||||||
},
|
yield return HumanoidVisualLayers.Hair;
|
||||||
BodyPartType.Hand => part.Symmetry switch
|
yield return HumanoidVisualLayers.StencilMask;
|
||||||
{
|
break;
|
||||||
BodyPartSymmetry.None => null,
|
case BodyPartType.Arm:
|
||||||
BodyPartSymmetry.Left => HumanoidVisualLayers.LHand,
|
switch (part.Symmetry)
|
||||||
BodyPartSymmetry.Right => HumanoidVisualLayers.RHand,
|
{
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
case BodyPartSymmetry.None:
|
||||||
},
|
yield break;
|
||||||
BodyPartType.Leg => part.Symmetry switch
|
case BodyPartSymmetry.Left:
|
||||||
{
|
yield return HumanoidVisualLayers.LArm;
|
||||||
BodyPartSymmetry.None => null,
|
break;
|
||||||
BodyPartSymmetry.Left => HumanoidVisualLayers.LLeg,
|
case BodyPartSymmetry.Right:
|
||||||
BodyPartSymmetry.Right => HumanoidVisualLayers.RLeg,
|
yield return HumanoidVisualLayers.RArm;
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
break;
|
||||||
},
|
default:
|
||||||
BodyPartType.Foot => part.Symmetry switch
|
yield break;
|
||||||
{
|
}
|
||||||
BodyPartSymmetry.None => null,
|
yield break;
|
||||||
BodyPartSymmetry.Left => HumanoidVisualLayers.LFoot,
|
case BodyPartType.Hand:
|
||||||
BodyPartSymmetry.Right => HumanoidVisualLayers.RFoot,
|
switch (part.Symmetry)
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
{
|
||||||
},
|
case BodyPartSymmetry.None:
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
yield break;
|
||||||
};
|
case BodyPartSymmetry.Left:
|
||||||
|
yield return HumanoidVisualLayers.LHand;
|
||||||
|
break;
|
||||||
|
case BodyPartSymmetry.Right:
|
||||||
|
yield return HumanoidVisualLayers.RHand;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
yield break;
|
||||||
|
case BodyPartType.Leg:
|
||||||
|
switch (part.Symmetry)
|
||||||
|
{
|
||||||
|
case BodyPartSymmetry.None:
|
||||||
|
yield break;
|
||||||
|
case BodyPartSymmetry.Left:
|
||||||
|
yield return HumanoidVisualLayers.LLeg;
|
||||||
|
break;
|
||||||
|
case BodyPartSymmetry.Right:
|
||||||
|
yield return HumanoidVisualLayers.RLeg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
yield break;
|
||||||
|
case BodyPartType.Foot:
|
||||||
|
switch (part.Symmetry)
|
||||||
|
{
|
||||||
|
case BodyPartSymmetry.None:
|
||||||
|
yield break;
|
||||||
|
case BodyPartSymmetry.Left:
|
||||||
|
yield return HumanoidVisualLayers.LFoot;
|
||||||
|
break;
|
||||||
|
case BodyPartSymmetry.Right:
|
||||||
|
yield return HumanoidVisualLayers.RFoot;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
yield break;
|
||||||
|
default:
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Shared.Movement.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Players;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using System;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chemistry.Components
|
||||||
|
{
|
||||||
|
//TODO: refactor movement modifier component because this is a pretty poor solution
|
||||||
|
[RegisterComponent]
|
||||||
|
[NetworkedComponent]
|
||||||
|
public sealed class MovespeedModifierMetabolismComponent : Component, IMoveSpeedModifier
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
public override string Name => "MovespeedModifierMetabolism";
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public float WalkSpeedModifier { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public float SprintSpeedModifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the current modifier is expected to end.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public TimeSpan ModifierTimer { get; set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
|
public override ComponentState GetComponentState(ICommonSession player)
|
||||||
|
{
|
||||||
|
return new MovespeedModifierMetabolismComponentState(WalkSpeedModifier, SprintSpeedModifier, ModifierTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class MovespeedModifierMetabolismComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public float WalkSpeedModifier { get; }
|
||||||
|
public float SprintSpeedModifier { get; }
|
||||||
|
public TimeSpan ModifierTimer { get; }
|
||||||
|
|
||||||
|
public MovespeedModifierMetabolismComponentState(float walkSpeedModifier, float sprintSpeedModifier, TimeSpan modifierTimer)
|
||||||
|
{
|
||||||
|
WalkSpeedModifier = walkSpeedModifier;
|
||||||
|
SprintSpeedModifier = sprintSpeedModifier;
|
||||||
|
ModifierTimer = modifierTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Movement.Components;
|
||||||
|
using static Content.Shared.Chemistry.Components.MovespeedModifierMetabolismComponent;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chemistry
|
||||||
|
{
|
||||||
|
public class MetabolismMovespeedModifierSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
private readonly List<MovespeedModifierMetabolismComponent> _components = new();
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MovespeedModifierMetabolismComponent, ComponentHandleState>(OnMovespeedHandleState);
|
||||||
|
SubscribeLocalEvent<MovespeedModifierMetabolismComponent, ComponentStartup>(AddComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMovespeedHandleState(EntityUid uid, MovespeedModifierMetabolismComponent component, ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not MovespeedModifierMetabolismComponentState cast)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ComponentManager.TryGetComponent<MovementSpeedModifierComponent>(uid, out var modifier) &&
|
||||||
|
(!component.WalkSpeedModifier.Equals(cast.WalkSpeedModifier) ||
|
||||||
|
!component.SprintSpeedModifier.Equals(cast.SprintSpeedModifier)))
|
||||||
|
{
|
||||||
|
modifier.RefreshMovementSpeedModifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
component.WalkSpeedModifier = cast.WalkSpeedModifier;
|
||||||
|
component.SprintSpeedModifier = cast.SprintSpeedModifier;
|
||||||
|
component.ModifierTimer = cast.ModifierTimer;
|
||||||
|
|
||||||
|
}
|
||||||
|
private void AddComponent(EntityUid uid, MovespeedModifierMetabolismComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
_components.Add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var currentTime = _gameTiming.CurTime;
|
||||||
|
|
||||||
|
for (var i = _components.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var component = _components[i];
|
||||||
|
|
||||||
|
if (component.Deleted)
|
||||||
|
{
|
||||||
|
_components.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.ModifierTimer > currentTime) continue;
|
||||||
|
|
||||||
|
_components.RemoveAt(i);
|
||||||
|
ComponentManager.RemoveComponent<MovespeedModifierMetabolismComponent>(component.Owner.Uid);
|
||||||
|
|
||||||
|
if (component.Owner.TryGetComponent(out MovementSpeedModifierComponent? modifier))
|
||||||
|
{
|
||||||
|
modifier.RefreshMovementSpeedModifiers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
|
|
||||||
namespace Content.Shared.Chemistry.Metabolizable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Default metabolization for reagents. Returns the amount of reagents metabolized without applying effects.
|
|
||||||
/// Metabolizes reagents at a constant rate, limited by how much is available. Other classes are derived from
|
|
||||||
/// this class, so that they do not need their own metabolization quantity calculation.
|
|
||||||
/// </summary>
|
|
||||||
[DataDefinition]
|
|
||||||
public class DefaultMetabolizable : IMetabolizable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Rate of metabolism in units / second
|
|
||||||
/// </summary>
|
|
||||||
[DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
|
|
||||||
|
|
||||||
public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
|
|
||||||
{
|
|
||||||
|
|
||||||
// How much reagent should we metabolize
|
|
||||||
// The default behaviour is to metabolize at a constant rate, independent of the quantity of reagents.
|
|
||||||
var amountMetabolized = MetabolismRate * tickTime;
|
|
||||||
|
|
||||||
// is that much reagent actually available?
|
|
||||||
if (availableReagent < amountMetabolized)
|
|
||||||
{
|
|
||||||
return availableReagent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return amountMetabolized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Shared.Chemistry.Metabolizable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Metabolism behavior for a reagent.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMetabolizable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Metabolize the attached reagent. Return the amount of reagent to be removed from the solution.
|
|
||||||
/// You shouldn't remove the reagent yourself to avoid invalidating the iterator of the metabolism
|
|
||||||
/// organ that is processing it's reagents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="solutionEntity">The entity containing the solution.</param>
|
|
||||||
/// <param name="reagentId">The reagent id</param>
|
|
||||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
|
||||||
/// <param name="availableReagent">Reagent available to be metabolized.</param>
|
|
||||||
/// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns>
|
|
||||||
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
Content.Shared/Chemistry/Reagent/ReagentEffect.cs
Normal file
23
Content.Shared/Chemistry/Reagent/ReagentEffect.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chemistry.Reagent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reagent effects describe behavior that occurs when a reagent is ingested and metabolized by some
|
||||||
|
/// organ. They only trigger when their conditions (<see cref="ReagentEffectCondition"/>
|
||||||
|
/// </summary>
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public abstract class ReagentEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of conditions required for the effect to activate. Not required.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("conditions")]
|
||||||
|
public ReagentEffectCondition[]? Conditions;
|
||||||
|
|
||||||
|
public abstract void Metabolize(IEntity solutionEntity, Solution.Solution.ReagentQuantity amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs
Normal file
13
Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Chemistry.Solution;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chemistry.Reagent
|
||||||
|
{
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public abstract class ReagentEffectCondition
|
||||||
|
{
|
||||||
|
public abstract bool Condition(IEntity solutionEntity, Solution.Solution.ReagentQuantity reagent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Shared.Botany;
|
using Content.Shared.Botany;
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
@@ -16,9 +15,6 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public class ReagentPrototype : IPrototype
|
public class ReagentPrototype : IPrototype
|
||||||
{
|
{
|
||||||
[DataField("metabolism", serverOnly: true)]
|
|
||||||
private readonly List<IMetabolizable> _metabolism = new() {new DefaultMetabolizable()};
|
|
||||||
|
|
||||||
[DataField("tileReactions", serverOnly: true)]
|
[DataField("tileReactions", serverOnly: true)]
|
||||||
private readonly List<ITileReaction> _tileReactions = new(0);
|
private readonly List<ITileReaction> _tileReactions = new(0);
|
||||||
|
|
||||||
@@ -60,7 +56,6 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
public string SpriteReplacementPath { get; } = string.Empty;
|
public string SpriteReplacementPath { get; } = string.Empty;
|
||||||
|
|
||||||
//List of metabolism effects this reagent has, should really only be used server-side.
|
//List of metabolism effects this reagent has, should really only be used server-side.
|
||||||
public IReadOnlyList<IMetabolizable> Metabolism => _metabolism;
|
|
||||||
public IReadOnlyList<ITileReaction> TileReactions => _tileReactions;
|
public IReadOnlyList<ITileReaction> TileReactions => _tileReactions;
|
||||||
public IReadOnlyList<IPlantMetabolizable> PlantMetabolism => _plantMetabolism;
|
public IReadOnlyList<IPlantMetabolizable> PlantMetabolism => _plantMetabolism;
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,27 @@ namespace Content.Shared.Chemistry.Solution
|
|||||||
TotalVolume += quantity;
|
TotalVolume += quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scales the amount of solution.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scale">The scalar to modify the solution by.</param>
|
||||||
|
public void ScaleSolution(float scale)
|
||||||
|
{
|
||||||
|
if (scale == 1) return;
|
||||||
|
var tempContents = new List<ReagentQuantity>(_contents);
|
||||||
|
foreach(ReagentQuantity current in tempContents)
|
||||||
|
{
|
||||||
|
if(scale > 1)
|
||||||
|
{
|
||||||
|
AddReagent(current.ReagentId, current.Quantity * scale - current.Quantity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveReagent(current.ReagentId, current.Quantity - current.Quantity * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the amount of a single reagent inside the solution.
|
/// Returns the amount of a single reagent inside the solution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace Content.Shared.Doors
|
|||||||
[ComponentDependency]
|
[ComponentDependency]
|
||||||
protected readonly IPhysBody? PhysicsComponent = null;
|
protected readonly IPhysBody? PhysicsComponent = null;
|
||||||
|
|
||||||
|
[Dependency]
|
||||||
|
protected readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private DoorState _state = DoorState.Closed;
|
private DoorState _state = DoorState.Closed;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ namespace Content.Shared.Ghost
|
|||||||
public override string Name => "Ghost";
|
public override string Name => "Ghost";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changed by <see cref="GhostChangeCanReturnToBodyEvent"/>
|
/// Changed by <see cref="SharedGhostSystem.SetCanReturnToBody"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// TODO MIRROR change this to use friend classes when thats merged
|
||||||
[DataField("canReturnToBody")]
|
[DataField("canReturnToBody")]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public bool CanReturnToBody { get; set; }
|
public bool CanReturnToBody { get; set; }
|
||||||
|
|||||||
@@ -10,36 +10,20 @@ namespace Content.Shared.Ghost
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<SharedGhostComponent, GhostChangeCanReturnToBodyEvent>(OnGhostChangeCanReturnToBody);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGhostChangeCanReturnToBody(EntityUid uid, SharedGhostComponent component, GhostChangeCanReturnToBodyEvent args)
|
public void SetCanReturnToBody(SharedGhostComponent component, bool canReturn)
|
||||||
{
|
{
|
||||||
if (component.CanReturnToBody == args.CanReturnToBody)
|
if (component.CanReturnToBody == canReturn)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
component.CanReturnToBody = args.CanReturnToBody;
|
component.CanReturnToBody = canReturn;
|
||||||
component.Dirty();
|
component.Dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised to change the value of <see cref="SharedGhostComponent.CanReturnToBody"/>
|
|
||||||
/// </summary>
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class GhostChangeCanReturnToBodyEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public GhostChangeCanReturnToBodyEvent(bool canReturnToBody)
|
|
||||||
{
|
|
||||||
CanReturnToBody = canReturnToBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanReturnToBody { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public class GhostWarpsRequestEvent : EntityEventArgs
|
public class GhostWarpsRequestEvent : EntityEventArgs
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -924,12 +924,12 @@ namespace Content.Shared.Hands.Components
|
|||||||
{
|
{
|
||||||
public EntityUid EntityUid { get; }
|
public EntityUid EntityUid { get; }
|
||||||
public EntityCoordinates InitialPosition { get; }
|
public EntityCoordinates InitialPosition { get; }
|
||||||
public Vector2 PickupDirection { get; }
|
public Vector2 FinalPosition { get; }
|
||||||
|
|
||||||
public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition)
|
public PickupAnimationMessage(EntityUid entityUid, Vector2 finalPosition, EntityCoordinates initialPosition)
|
||||||
{
|
{
|
||||||
EntityUid = entityUid;
|
EntityUid = entityUid;
|
||||||
PickupDirection = pickupDirection;
|
FinalPosition = finalPosition;
|
||||||
InitialPosition = initialPosition;
|
InitialPosition = initialPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user