Merge branch 'master' into replace-sounds-with-sound-specifier

# Conflicts:
#	Content.Server/Kitchen/Components/ReagentGrinderComponent.cs
#	Content.Server/Storage/Components/SecureEntityStorageComponent.cs
This commit is contained in:
Galactic Chimp
2021-07-30 20:26:22 +02:00
117 changed files with 1807 additions and 1851 deletions

View File

@@ -19,14 +19,9 @@ namespace Content.Client.AME.UI
{ {
base.Open(); base.Open();
_window = new AMEWindow(); _window = new AMEWindow(this);
_window.OnClose += Close; _window.OnClose += Close;
_window.OpenCentered(); _window.OpenCentered();
_window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject);
_window.ToggleInjection.OnPressed += _ => ButtonPressed(UiButton.ToggleInjection);
_window.IncreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.IncreaseFuel);
_window.DecreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.DecreaseFuel);
} }
/// <summary> /// <summary>
@@ -44,7 +39,7 @@ namespace Content.Client.AME.UI
_window?.UpdateState(castState); //Update window state _window?.UpdateState(castState); //Update window state
} }
private void ButtonPressed(UiButton button, int dispenseIndex = -1) public void ButtonPressed(UiButton button, int dispenseIndex = -1)
{ {
SendMessage(new UiButtonPressedMessage(button)); SendMessage(new UiButtonPressedMessage(button));
} }

View File

@@ -1,173 +0,0 @@
using Content.Client.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using static Content.Shared.AME.SharedAMEControllerComponent;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.AME.UI
{
public class AMEWindow : SS14Window
{
public Label InjectionStatus { get; set; }
public Button EjectButton { get; set; }
public Button ToggleInjection { get; set; }
public Button IncreaseFuelButton { get; set; }
public Button DecreaseFuelButton { get; set; }
public ProgressBar? FuelMeter { get; set; }
public Label FuelAmount { get; set; }
public Label InjectionAmount { get; set; }
public Label CoreCount { get; set; }
public AMEWindow()
{
IoCManager.InjectDependencies(this);
Title = Loc.GetString("ame-window-title");
MinSize = SetSize = (250, 250);
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = Loc.GetString("ame-window-engine-status-label") + " "},
(InjectionStatus = new Label {Text = Loc.GetString("ame-window-engine-injection-status-not-injecting-label")})
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(ToggleInjection = new Button {Text = Loc.GetString("ame-window-toggle-injection-button"), StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = Loc.GetString("ame-window-fuel-status-label") + " "},
(FuelAmount = new Label {Text = Loc.GetString("ame-window-fuel-not-inserted-text")})
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(EjectButton = new Button {Text = Loc.GetString("ame-window-eject-button"), StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = Loc.GetString("ame-window-injection-amount-label") + " "},
(InjectionAmount = new Label {Text = "0"})
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(IncreaseFuelButton = new Button {Text = Loc.GetString("ame-window-increase-fuel-button"), StyleClasses = {StyleBase.ButtonOpenRight}}),
(DecreaseFuelButton = new Button {Text = Loc.GetString("ame-window-decrease-fuel-button"), StyleClasses = {StyleBase.ButtonOpenLeft}}),
}
},
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label { Text = Loc.GetString("ame-window-core-count-label") + " "},
(CoreCount = new Label { Text = "0"}),
}
}
}
});
}
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
private void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary>
/// Update the UI state when new state data is received from the server.
/// </summary>
/// <param name="state">State data sent by the server.</param>
public void UpdateState(BoundUserInterfaceState state)
{
var castState = (AMEControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered
if (Contents.Children != null)
{
SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
}
if (!castState.HasFuelJar)
{
EjectButton.Disabled = true;
ToggleInjection.Disabled = true;
FuelAmount.Text = Loc.GetString("ame-window-fuel-not-inserted-text");
}
else
{
EjectButton.Disabled = false;
ToggleInjection.Disabled = false;
FuelAmount.Text = $"{castState.FuelAmount}";
}
if (!castState.IsMaster)
{
ToggleInjection.Disabled = true;
}
if (!castState.Injecting)
{
InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-not-injecting-label") + " ";
}
else
{
InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-injecting-label") + " ";
}
CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}";
}
}
}

View File

@@ -0,0 +1,46 @@
<SS14Window xmlns="https://spacestation14.io"
Title="{Loc 'ame-window-title'}"
MinSize="250 250">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ame-window-engine-status-label'}" />
<Label Text=" " />
<Label Name="InjectionStatus" Text="{Loc 'ame-window-engine-injection-status-not-injecting-label'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="ToggleInjection"
Text="{Loc 'ame-window-toggle-injection-button'}"
StyleClasses="OpenBoth"
Disabled="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ame-window-fuel-status-label'}" />
<Label Text=" " />
<Label Name="FuelAmount" Text="{Loc 'ame-window-fuel-not-inserted-text'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="EjectButton"
Text="{Loc 'ame-window-eject-button'}"
StyleClasses="OpenBoth"
Disabled="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ame-window-injection-amount-label'}" />
<Label Text=" " />
<Label Name="InjectionAmount" Text="0" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="IncreaseFuelButton"
Text="{Loc 'ame-window-increase-fuel-button'}"
StyleClasses="OpenRight" />
<Button Name="DecreaseFuelButton"
Text="{Loc 'ame-window-decrease-fuel-button'}"
StyleClasses="OpenLeft" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'ame-window-core-count-label'}" />
<Label Text=" " />
<Label Name="CoreCount" Text="0" />
</BoxContainer>
</BoxContainer>
</SS14Window>

View File

@@ -0,0 +1,74 @@
using Content.Client.UserInterface;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using static Content.Shared.AME.SharedAMEControllerComponent;
namespace Content.Client.AME.UI
{
[GenerateTypedNameReferences]
public partial class AMEWindow : SS14Window
{
public AMEWindow(AMEControllerBoundUserInterface ui)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
EjectButton.OnPressed += _ => ui.ButtonPressed(UiButton.Eject);
ToggleInjection.OnPressed += _ => ui.ButtonPressed(UiButton.ToggleInjection);
IncreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.IncreaseFuel);
DecreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.DecreaseFuel);
}
/// <summary>
/// Update the UI state when new state data is received from the server.
/// </summary>
/// <param name="state">State data sent by the server.</param>
public void UpdateState(BoundUserInterfaceState state)
{
var castState = (AMEControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered
if (Contents.Children != null)
{
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
}
if (!castState.HasFuelJar)
{
EjectButton.Disabled = true;
ToggleInjection.Disabled = true;
FuelAmount.Text = Loc.GetString("ame-window-fuel-not-inserted-text");
}
else
{
EjectButton.Disabled = false;
ToggleInjection.Disabled = false;
FuelAmount.Text = $"{castState.FuelAmount}";
}
if (!castState.IsMaster)
{
ToggleInjection.Disabled = true;
}
if (!castState.Injecting)
{
InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-not-injecting-label") + " ";
}
else
{
InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-injecting-label") + " ";
}
CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}";
}
}
}

View File

@@ -1,208 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Access;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.SharedIdCardConsoleComponent;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Access.UI
{
public class IdCardConsoleWindow : SS14Window
{
private readonly Button _privilegedIdButton;
private readonly Button _targetIdButton;
private readonly Label _privilegedIdLabel;
private readonly Label _targetIdLabel;
private readonly Label _fullNameLabel;
private readonly LineEdit _fullNameLineEdit;
private readonly Label _jobTitleLabel;
private readonly LineEdit _jobTitleLineEdit;
private readonly Button _fullNameSaveButton;
private readonly Button _jobTitleSaveButton;
private readonly IdCardConsoleBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
private string? _lastFullName;
private string? _lastJobTitle;
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager)
{
MinSize = SetSize = (650, 290);
_owner = owner;
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
vBox.AddChild(new GridContainer
{
Columns = 3,
Children =
{
new Label {Text = Loc.GetString("id-card-console-window-privileged-id")},
(_privilegedIdButton = new Button()),
(_privilegedIdLabel = new Label()),
new Label {Text = Loc.GetString("id-card-console-window-target-id")},
(_targetIdButton = new Button()),
(_targetIdLabel = new Label())
}
});
_privilegedIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.PrivilegedId);
_targetIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.TargetId);
// Separator
vBox.AddChild(new Control {MinSize = (0, 8)});
// Name and job title line edits.
vBox.AddChild(new GridContainer
{
Columns = 3,
HSeparationOverride = 4,
Children =
{
// Name
(_fullNameLabel = new Label
{
Text = Loc.GetString("id-card-console-window-full-name-label")
}),
(_fullNameLineEdit = new LineEdit
{
HorizontalExpand = true,
}),
(_fullNameSaveButton = new Button
{
Text = Loc.GetString("id-card-console-window-save-button"),
Disabled = true
}),
// Title
(_jobTitleLabel = new Label
{
Text = Loc.GetString("id-card-console-window-job-title-label")
}),
(_jobTitleLineEdit = new LineEdit
{
HorizontalExpand = true
}),
(_jobTitleSaveButton = new Button
{
Text = Loc.GetString("id-card-console-window-save-button"),
Disabled = true
}),
},
});
_fullNameLineEdit.OnTextEntered += _ => SubmitData();
_fullNameLineEdit.OnTextChanged += _ =>
{
_fullNameSaveButton.Disabled = _fullNameSaveButton.Text == _lastFullName;
};
_fullNameSaveButton.OnPressed += _ => SubmitData();
_jobTitleLineEdit.OnTextEntered += _ => SubmitData();
_jobTitleLineEdit.OnTextChanged += _ =>
{
_jobTitleSaveButton.Disabled = _jobTitleLineEdit.Text == _lastJobTitle;
};
_jobTitleSaveButton.OnPressed += _ => SubmitData();
// Separator
vBox.AddChild(new Control {MinSize = (0, 8)});
{
var grid = new GridContainer
{
Columns = 5,
HorizontalAlignment = HAlignment.Center
};
vBox.AddChild(grid);
foreach (var accessLevel in prototypeManager.EnumeratePrototypes<AccessLevelPrototype>())
{
var newButton = new Button
{
Text = accessLevel.Name,
ToggleMode = true,
};
grid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => SubmitData();
}
}
Contents.AddChild(vBox);
}
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
{
_privilegedIdButton.Text = state.IsPrivilegedIdPresent
? Loc.GetString("id-card-console-window-eject-button")
: Loc.GetString("id-card-console-window-insert-button");
_privilegedIdLabel.Text = state.PrivilegedIdName;
_targetIdButton.Text = state.IsTargetIdPresent
? Loc.GetString("id-card-console-window-eject-button")
: Loc.GetString("id-card-console-window-insert-button");
_targetIdLabel.Text = state.TargetIdName;
var interfaceEnabled =
state.IsPrivilegedIdPresent && state.IsPrivilegedIdAuthorized && state.IsTargetIdPresent;
var fullNameDirty = _lastFullName != null && _fullNameLineEdit.Text != state.TargetIdFullName;
var jobTitleDirty = _lastJobTitle != null && _jobTitleLineEdit.Text != state.TargetIdJobTitle;
_fullNameLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
_fullNameLineEdit.Editable = interfaceEnabled;
if (!fullNameDirty)
{
_fullNameLineEdit.Text = state.TargetIdFullName ?? string.Empty;
}
_fullNameSaveButton.Disabled = !interfaceEnabled || !fullNameDirty;
_jobTitleLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
_jobTitleLineEdit.Editable = interfaceEnabled;
if (!jobTitleDirty)
{
_jobTitleLineEdit.Text = state.TargetIdJobTitle ?? string.Empty;
}
_jobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
foreach (var (accessName, button) in _accessButtons)
{
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
}
}
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
}
private void SubmitData()
{
_owner.SubmitData(
_fullNameLineEdit.Text,
_jobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
}

View File

@@ -0,0 +1,30 @@
<SS14Window xmlns="https://spacestation14.io"
MinSize="650 290">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="3">
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
<Button Name="PrivilegedIdButton" />
<Label Name="PrivilegedIdLabel" />
<Label Text="{Loc 'id-card-console-window-target-id'}" />
<Button Name="TargetIdButton" />
<Label Name="TargetIdLabel" />
</GridContainer>
<Control MinSize="0 8" />
<GridContainer Columns="3" HSeparationOverride="4">
<Label Name="FullNameLabel" Text="{Loc 'id-card-console-window-full-name-label'}" />
<LineEdit Name="FullNameLineEdit" HorizontalExpand="True" />
<Button Name="FullNameSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
<Label Name="JobTitleLabel" Text="{Loc 'id-card-console-window-job-title-label'}" />
<LineEdit Name="JobTitleLineEdit" HorizontalExpand="True" />
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
</GridContainer>
<Control MinSize="0 8" />
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
<!-- Access level buttons are added here by the C# code -->
</GridContainer>
</BoxContainer>
</SS14Window>

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Access;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.SharedIdCardConsoleComponent;
namespace Content.Client.Access.UI
{
[GenerateTypedNameReferences]
public partial class IdCardConsoleWindow : SS14Window
{
private readonly IdCardConsoleBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
private string? _lastFullName;
private string? _lastJobTitle;
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager)
{
RobustXamlLoader.Load(this);
_owner = owner;
PrivilegedIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.PrivilegedId);
TargetIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.TargetId);
FullNameLineEdit.OnTextEntered += _ => SubmitData();
FullNameLineEdit.OnTextChanged += _ =>
{
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
};
FullNameSaveButton.OnPressed += _ => SubmitData();
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
JobTitleLineEdit.OnTextChanged += _ =>
{
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
};
JobTitleSaveButton.OnPressed += _ => SubmitData();
foreach (var accessLevel in prototypeManager.EnumeratePrototypes<AccessLevelPrototype>())
{
var newButton = new Button
{
Text = accessLevel.Name,
ToggleMode = true,
};
AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => SubmitData();
}
}
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
{
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
? Loc.GetString("id-card-console-window-eject-button")
: Loc.GetString("id-card-console-window-insert-button");
PrivilegedIdLabel.Text = state.PrivilegedIdName;
TargetIdButton.Text = state.IsTargetIdPresent
? Loc.GetString("id-card-console-window-eject-button")
: Loc.GetString("id-card-console-window-insert-button");
TargetIdLabel.Text = state.TargetIdName;
var interfaceEnabled =
state.IsPrivilegedIdPresent && state.IsPrivilegedIdAuthorized && state.IsTargetIdPresent;
var fullNameDirty = _lastFullName != null && FullNameLineEdit.Text != state.TargetIdFullName;
var jobTitleDirty = _lastJobTitle != null && JobTitleLineEdit.Text != state.TargetIdJobTitle;
FullNameLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
FullNameLineEdit.Editable = interfaceEnabled;
if (!fullNameDirty)
{
FullNameLineEdit.Text = state.TargetIdFullName ?? string.Empty;
}
FullNameSaveButton.Disabled = !interfaceEnabled || !fullNameDirty;
JobTitleLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
JobTitleLineEdit.Editable = interfaceEnabled;
if (!jobTitleDirty)
{
JobTitleLineEdit.Text = state.TargetIdJobTitle ?? string.Empty;
}
JobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
foreach (var (accessName, button) in _accessButtons)
{
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
}
}
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
}
private void SubmitData()
{
_owner.SubmitData(
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
}

View File

@@ -0,0 +1,10 @@
<Control xmlns="https://spacestation14.io"
MinSize="64 64">
<PanelContainer StyleClasses="TransparentBorderedWindowPanel"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<GridContainer Name="AlertContainer" MaxGridHeight="64" ExpandBackwards="True">
</GridContainer>
</PanelContainer>
</Control>

View File

@@ -1,8 +1,9 @@
using Content.Client.Chat.Managers; using Content.Client.Chat.Managers;
using Content.Client.Chat.UI; using Content.Client.Chat.UI;
using Content.Client.Stylesheets; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Client.Alerts.UI namespace Content.Client.Alerts.UI
@@ -10,13 +11,20 @@ namespace Content.Client.Alerts.UI
/// <summary> /// <summary>
/// The status effects display on the right side of the screen. /// The status effects display on the right side of the screen.
/// </summary> /// </summary>
public sealed class AlertsUI : Control [GenerateTypedNameReferences]
public sealed partial class AlertsUI : Control
{ {
[Dependency] private readonly IChatManager _chatManager = default!;
public const float ChatSeparation = 38f; public const float ChatSeparation = 38f;
public GridContainer Grid { get; }
public GridContainer Grid => AlertContainer;
public AlertsUI() public AlertsUI()
{ {
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetAnchorTop(this, 0f); LayoutContainer.SetAnchorTop(this, 0f);
@@ -25,28 +33,11 @@ namespace Content.Client.Alerts.UI
LayoutContainer.SetMarginBottom(this, -180); LayoutContainer.SetMarginBottom(this, -180);
LayoutContainer.SetMarginTop(this, 250); LayoutContainer.SetMarginTop(this, 250);
LayoutContainer.SetMarginRight(this, -10); LayoutContainer.SetMarginRight(this, -10);
var panelContainer = new PanelContainer
{
StyleClasses = {StyleNano.StyleClassTransparentBorderedWindowPanel},
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Top
};
AddChild(panelContainer);
Grid = new GridContainer
{
MaxGridHeight = 64,
ExpandBackwards = true
};
panelContainer.AddChild(Grid);
MinSize = (64, 64);
} }
protected override void EnteredTree() protected override void EnteredTree()
{ {
base.EnteredTree(); base.EnteredTree();
var _chatManager = IoCManager.Resolve<IChatManager>();
_chatManager.OnChatBoxResized += OnChatResized; _chatManager.OnChatBoxResized += OnChatResized;
OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom)); OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom));
} }
@@ -54,15 +45,12 @@ namespace Content.Client.Alerts.UI
protected override void ExitedTree() protected override void ExitedTree()
{ {
base.ExitedTree(); base.ExitedTree();
var _chatManager = IoCManager.Resolve<IChatManager>();
_chatManager.OnChatBoxResized -= OnChatResized; _chatManager.OnChatBoxResized -= OnChatResized;
} }
private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs) private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs)
{ {
// resize us to fit just below the chatbox // resize us to fit just below the chatbox
var _chatManager = IoCManager.Resolve<IChatManager>();
if (_chatManager.CurrentChatBox != null) if (_chatManager.CurrentChatBox != null)
{ {
LayoutContainer.SetMarginTop(this, chatResizedEventArgs.NewBottom + ChatSeparation); LayoutContainer.SetMarginTop(this, chatResizedEventArgs.NewBottom + ChatSeparation);
@@ -81,12 +69,12 @@ namespace Content.Client.Alerts.UI
// this is here because there isn't currently a good way to allow the grid to adjust its height based // this is here because there isn't currently a good way to allow the grid to adjust its height based
// on constraints, otherwise we would use anchors to lay it out // on constraints, otherwise we would use anchors to lay it out
base.Resized(); base.Resized();
Grid.MaxGridHeight = Height; AlertContainer.MaxGridHeight = Height;
} }
protected override void UIScaleChanged() protected override void UIScaleChanged()
{ {
Grid.MaxGridHeight = Height; AlertContainer.MaxGridHeight = Height;
base.UIScaleChanged(); base.UIScaleChanged();
} }
} }

View File

@@ -19,6 +19,7 @@ namespace Content.Client.Atmos.EntitySystems
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
// Gas overlays // Gas overlays
private readonly float[] _timer = new float[Atmospherics.TotalNumberOfGases]; private readonly float[] _timer = new float[Atmospherics.TotalNumberOfGases];
@@ -38,16 +39,12 @@ namespace Content.Client.Atmos.EntitySystems
private readonly Dictionary<GridId, Dictionary<Vector2i, GasOverlayChunk>> _tileData = private readonly Dictionary<GridId, Dictionary<Vector2i, GasOverlayChunk>> _tileData =
new(); new();
private AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage); SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
_mapManager.OnGridRemoved += OnGridRemoved; _mapManager.OnGridRemoved += OnGridRemoved;
_atmosphereSystem = Get<AtmosphereSystem>();
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{ {
var overlay = _atmosphereSystem.GetOverlay(i); var overlay = _atmosphereSystem.GetOverlay(i);

View File

@@ -25,6 +25,7 @@ namespace Content.Client.Audio
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
private SoundCollectionPrototype _ambientCollection = default!; private SoundCollectionPrototype _ambientCollection = default!;
@@ -48,7 +49,7 @@ namespace Content.Client.Audio
_client.PlayerJoinedServer += OnJoin; _client.PlayerJoinedServer += OnJoin;
_client.PlayerLeaveServer += OnLeave; _client.PlayerLeaveServer += OnLeave;
Get<ClientGameTicker>().LobbyStatusUpdated += LobbySongReceived; _gameTicker.LobbyStatusUpdated += LobbySongReceived;
} }
public override void Shutdown() public override void Shutdown()
@@ -60,7 +61,7 @@ namespace Content.Client.Audio
_client.PlayerJoinedServer -= OnJoin; _client.PlayerJoinedServer -= OnJoin;
_client.PlayerLeaveServer -= OnLeave; _client.PlayerLeaveServer -= OnLeave;
Get<ClientGameTicker>().LobbyStatusUpdated -= LobbySongReceived; _gameTicker.LobbyStatusUpdated -= LobbySongReceived;
EndAmbience(); EndAmbience();
EndLobbyMusic(); EndLobbyMusic();
@@ -165,7 +166,7 @@ namespace Content.Client.Audio
private void StartLobbyMusic() private void StartLobbyMusic()
{ {
EndLobbyMusic(); EndLobbyMusic();
var file = Get<ClientGameTicker>().LobbySong; var file = _gameTicker.LobbySong;
if (file == null) // We have not received the lobby song yet. if (file == null) // We have not received the lobby song yet.
{ {
return; return;

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -279,34 +280,11 @@ namespace Content.Client.Chemistry.UI
UpdatePanelInfo(castState); UpdatePanelInfo(castState);
if (Contents.Children != null) if (Contents.Children != null)
{ {
SetButtonDisabledRecursive(Contents, !castState.HasPower); ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = !castState.HasBeaker; EjectButton.Disabled = !castState.HasBeaker;
} }
} }
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
private void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary> /// <summary>
/// Update the container, buffer, and packaging panels. /// Update the container, buffer, and packaging panels.
/// </summary> /// </summary>

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface;
using Content.Shared.Chemistry.Dispenser; using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -173,29 +174,6 @@ namespace Content.Client.Chemistry.UI
} }
} }
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
private void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary> /// <summary>
/// Update the UI state when new state data is received from the server. /// Update the UI state when new state data is received from the server.
/// </summary> /// </summary>
@@ -209,7 +187,7 @@ namespace Content.Client.Chemistry.UI
// Disable all buttons if not powered // Disable all buttons if not powered
if (Contents.Children != null) if (Contents.Children != null)
{ {
SetButtonDisabledRecursive(Contents, !castState.HasPower); ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false; EjectButton.Disabled = false;
} }

View File

@@ -36,6 +36,8 @@ namespace Content.Client.DragDrop
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
// how often to recheck possible targets (prevents calling expensive // how often to recheck possible targets (prevents calling expensive
// check logic each update) // check logic each update)
@@ -69,8 +71,6 @@ namespace Content.Client.DragDrop
private ShaderInstance? _dropTargetInRangeShader; private ShaderInstance? _dropTargetInRangeShader;
private ShaderInstance? _dropTargetOutOfRangeShader; private ShaderInstance? _dropTargetOutOfRangeShader;
private SharedInteractionSystem _interactionSystem = default!;
private InputSystem _inputSystem = default!;
private readonly List<ISpriteComponent> _highlightedSprites = new(); private readonly List<ISpriteComponent> _highlightedSprites = new();
@@ -80,8 +80,6 @@ namespace Content.Client.DragDrop
_dropTargetInRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetInRange).Instance(); _dropTargetInRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetInRange).Instance();
_dropTargetOutOfRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetOutOfRange).Instance(); _dropTargetOutOfRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetOutOfRange).Instance();
_interactionSystem = Get<SharedInteractionSystem>();
_inputSystem = Get<InputSystem>();
// needs to fire on mouseup and mousedown so we can detect a drag / drop // needs to fire on mouseup and mousedown so we can detect a drag / drop
CommandBinds.Builder CommandBinds.Builder
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false)) .Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false))

View File

@@ -57,7 +57,6 @@ namespace Content.Client.Entry
"CablePlacer", "CablePlacer",
"Drink", "Drink",
"Food", "Food",
"FoodContainer",
"MagicMirror", "MagicMirror",
"FloorTile", "FloorTile",
"ShuttleController", "ShuttleController",
@@ -93,6 +92,7 @@ namespace Content.Client.Entry
"ExaminableBattery", "ExaminableBattery",
"PottedPlantHide", "PottedPlantHide",
"SecureEntityStorage", "SecureEntityStorage",
"Lock",
"PresetIdCard", "PresetIdCard",
"SolarControlConsole", "SolarControlConsole",
"FlashOnTrigger", "FlashOnTrigger",
@@ -277,6 +277,7 @@ namespace Content.Client.Entry
"Advertise", "Advertise",
"PowerNetworkBattery", "PowerNetworkBattery",
"BatteryCharger", "BatteryCharger",
"SpawnItemsOnUse"
}; };
} }
} }

View File

@@ -0,0 +1,15 @@
<SS14Window xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Kitchen.UI"
Title="{Loc grinder-menu-title}" MinSize="512 256" SetSize="512 256">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 64"/>
<Control MinSize="0 16"/>
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 64"/>
</BoxContainer>
<Control MinSize="16 0"/>
<ui:LabelledContentBox Name="ChamberContentBox" LabelText="{Loc grinder-menu-chamber-content-box-label}" ButtonText="{Loc grinder-menu-chamber-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
<Control MinSize="8 0"/>
<ui:LabelledContentBox Name="BeakerContentBox" LabelText="{Loc grinder-menu-beaker-content-box-label}" ButtonText="{Loc grinder-menu-beaker-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
</BoxContainer>
</SS14Window>

View File

@@ -0,0 +1,131 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Kitchen.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Content.Client.Kitchen.UI
{
[GenerateTypedNameReferences]
public partial class GrinderMenu : SS14Window
{
private readonly IEntityManager _entityManager;
private readonly IPrototypeManager _prototypeManager ;
private readonly ReagentGrinderBoundUserInterface _owner;
private readonly Dictionary<int, EntityUid> _chamberVisualContents = new();
public GrinderMenu(ReagentGrinderBoundUserInterface owner, IEntityManager entityManager, IPrototypeManager prototypeManager)
{
RobustXamlLoader.Load(this);
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_owner = owner;
GrindButton.OnPressed += owner.StartGrinding;
JuiceButton.OnPressed += owner.StartJuicing;
ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
BeakerContentBox.EjectButton.OnPressed += owner.EjectBeaker;
ChamberContentBox.BoxContents.OnItemSelected += OnChamberBoxContentsItemSelected;
BeakerContentBox.BoxContents.SelectMode = ItemList.ItemListSelectMode.None;
}
private void OnChamberBoxContentsItemSelected(ItemList.ItemListSelectedEventArgs args)
{
_owner.EjectChamberContent(_chamberVisualContents[args.ItemIndex]);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_chamberVisualContents.Clear();
GrindButton.OnPressed -= _owner.StartGrinding;
JuiceButton.OnPressed -= _owner.StartJuicing;
ChamberContentBox.EjectButton.OnPressed -= _owner.EjectAll;
BeakerContentBox.EjectButton.OnPressed -= _owner.EjectBeaker;
ChamberContentBox.BoxContents.OnItemSelected -= OnChamberBoxContentsItemSelected;
}
public void UpdateState(ReagentGrinderInterfaceState state)
{
BeakerContentBox.EjectButton.Disabled = !state.HasBeakerIn;
ChamberContentBox.EjectButton.Disabled = state.ChamberContents.Length <= 0;
GrindButton.Disabled = !state.CanGrind || !state.Powered;
JuiceButton.Disabled = !state.CanJuice || !state.Powered;
RefreshContentsDisplay(state.ReagentQuantities, state.ChamberContents, state.HasBeakerIn);
}
public void HandleMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
GrindButton.Disabled = true;
GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
JuiceButton.Disabled = true;
JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
BeakerContentBox.EjectButton.Disabled = true;
ChamberContentBox.EjectButton.Disabled = true;
break;
case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage doneMessage:
GrindButton.Disabled = false;
JuiceButton.Disabled = false;
GrindButton.Modulate = Color.White;
JuiceButton.Modulate = Color.White;
BeakerContentBox.EjectButton.Disabled = false;
ChamberContentBox.EjectButton.Disabled = false;
break;
}
}
private void RefreshContentsDisplay(IList<Solution.ReagentQuantity>? reagents, IReadOnlyList<EntityUid> containedSolids, bool isBeakerAttached)
{
//Refresh chamber contents
_chamberVisualContents.Clear();
ChamberContentBox.BoxContents.Clear();
foreach (var uid in containedSolids)
{
if (!_entityManager.TryGetEntity(uid, out var entity))
{
return;
}
var texture = entity.GetComponent<SpriteComponent>().Icon?.Default;
var solidItem = ChamberContentBox.BoxContents.AddItem(entity.Name, texture);
var solidIndex = ChamberContentBox.BoxContents.IndexOf(solidItem);
_chamberVisualContents.Add(solidIndex, uid);
}
//Refresh beaker contents
BeakerContentBox.BoxContents.Clear();
//if no beaker is attached use this guard to prevent hitting a null reference.
if (!isBeakerAttached || reagents == null)
{
return;
}
//Looks like we have a beaker attached.
if (reagents.Count <= 0)
{
BeakerContentBox.BoxContents.AddItem(Loc.GetString("grinder-menu-beaker-content-box-is-empty"));
}
else
{
foreach (var reagent in reagents)
{
var reagentName = _prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto) ? Loc.GetString($"{reagent.Quantity} {proto.Name}") : "???";
BeakerContentBox.BoxContents.AddItem(reagentName);
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
<customControls:BoxContainer Orientation="Vertical" xmlns:customControls="https://spacestation14.io">
<customControls:SplitContainer Orientation="Horizontal">
<customControls:Label Name="Label" Align="Center"/>
<customControls:Button Name="Button" TextAlign="Center"/>
</customControls:SplitContainer>
<customControls:ItemList Name="ItemList" VerticalExpand="True" HorizontalExpand="True" SelectMode="Button" SizeFlagsStretchRatio="2" MinSize="100 128"/>
</customControls:BoxContainer>

View File

@@ -0,0 +1,15 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Kitchen.UI
{
[GenerateTypedNameReferences]
public partial class LabelledContentBox : BoxContainer
{
public string? LabelText { get => Label.Text; set => Label.Text = value; }
public string? ButtonText { get => Button.Text; set => Button.Text = value; }
public ItemList BoxContents => ItemList;
public Button EjectButton => Button;
}
}

View File

@@ -1,17 +1,10 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Chemistry.Solution.Solution; using static Content.Shared.Chemistry.Solution.Solution;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Kitchen.UI namespace Content.Client.Kitchen.UI
{ {
@@ -21,8 +14,6 @@ namespace Content.Client.Kitchen.UI
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private GrinderMenu? _menu; private GrinderMenu? _menu;
private readonly Dictionary<int, EntityUid> _chamberVisualContents = new();
private readonly Dictionary<int, ReagentQuantity> _beakerVisualContents = new();
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { } public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
@@ -30,22 +21,9 @@ namespace Content.Client.Kitchen.UI
{ {
base.Open(); base.Open();
_menu = new GrinderMenu(this); _menu = new GrinderMenu(this, _entityManager, _prototypeManager);
_menu.OpenCentered(); _menu.OpenCentered();
_menu.OnClose += Close; _menu.OnClose += Close;
_menu.GrindButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
_menu.JuiceButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
_menu.ChamberContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
_menu.BeakerContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
_menu.ChamberContentBox.BoxContents.OnItemSelected += args =>
{
SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(_chamberVisualContents[args.ItemIndex]));
};
_menu.BeakerContentBox.BoxContents.OnItemSelected += args =>
{
SendMessage(new SharedReagentGrinderComponent.ReagentGrinderVaporizeReagentIndexedMessage(_beakerVisualContents[args.ItemIndex]));
};
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -56,8 +34,6 @@ namespace Content.Client.Kitchen.UI
return; return;
} }
_chamberVisualContents?.Clear();
_beakerVisualContents?.Clear();
_menu?.Dispose(); _menu?.Dispose();
} }
@@ -69,243 +45,19 @@ namespace Content.Client.Kitchen.UI
return; return;
} }
if (_menu == null) _menu?.UpdateState(cState);
{
return;
}
_menu.BeakerContentBox.EjectButton.Disabled = !cState.HasBeakerIn;
_menu.ChamberContentBox.EjectButton.Disabled = cState.ChamberContents.Length <= 0;
_menu.GrindButton.Disabled = !cState.CanGrind || !cState.Powered;
_menu.JuiceButton.Disabled = !cState.CanJuice || !cState.Powered;
RefreshContentsDisplay(cState.ReagentQuantities, cState.ChamberContents, cState.HasBeakerIn);
} }
protected override void ReceiveMessage(BoundUserInterfaceMessage message) protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{ {
base.ReceiveMessage(message); base.ReceiveMessage(message);
_menu?.HandleMessage(message);
if (_menu == null)
{
return;
}
switch (message)
{
case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
_menu.GrindButton.Disabled = true;
_menu.GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
_menu.JuiceButton.Disabled = true;
_menu.JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
_menu.BeakerContentBox.EjectButton.Disabled = true;
_menu.ChamberContentBox.EjectButton.Disabled = true;
break;
case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage doneMessage:
_menu.GrindButton.Disabled = false;
_menu.JuiceButton.Disabled = false;
_menu.GrindButton.Modulate = Color.White;
_menu.JuiceButton.Modulate = Color.White;
_menu.BeakerContentBox.EjectButton.Disabled = false;
_menu.ChamberContentBox.EjectButton.Disabled = false;
break;
}
} }
private void RefreshContentsDisplay(IList<ReagentQuantity>? reagents, IReadOnlyList<EntityUid> containedSolids, bool isBeakerAttached) public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
{ public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
//Refresh chamber contents public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
_chamberVisualContents.Clear(); public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid));
if (_menu == null)
{
return;
}
_menu.ChamberContentBox.BoxContents.Clear();
foreach (var uid in containedSolids)
{
if (!_entityManager.TryGetEntity(uid, out var entity))
{
return;
}
var texture = entity.GetComponent<SpriteComponent>().Icon?.Default;
var solidItem = _menu.ChamberContentBox.BoxContents.AddItem(entity.Name, texture);
var solidIndex = _menu.ChamberContentBox.BoxContents.IndexOf(solidItem);
_chamberVisualContents.Add(solidIndex, uid);
}
//Refresh beaker contents
_beakerVisualContents.Clear();
_menu.BeakerContentBox.BoxContents.Clear();
//if no beaker is attached use this guard to prevent hitting a null reference.
if (!isBeakerAttached || reagents == null)
{
return;
}
//Looks like we have a beaker attached.
if (reagents.Count <= 0)
{
_menu.BeakerContentBox.BoxContents.AddItem(Loc.GetString("grinder-menu-beaker-content-box-is-empty"));
}
else
{
for (var i = 0; i < reagents.Count; i++)
{
var goodIndex = _prototypeManager.TryIndex(reagents[i].ReagentId, out ReagentPrototype? proto);
var reagentName = goodIndex ? Loc.GetString($"{reagents[i].Quantity} {proto!.Name}") : "???";
var reagentAdded = _menu.BeakerContentBox.BoxContents.AddItem(reagentName);
var reagentIndex = _menu.BeakerContentBox.BoxContents.IndexOf(reagentAdded);
_beakerVisualContents.Add(reagentIndex, reagents[i]);
}
}
}
public class GrinderMenu : SS14Window
{
/*The contents of the chamber and beaker will both be vertical scroll rectangles.
* Will have a vsplit to split the g/j buttons from the contents menu.
* |--------------------------------\
* | | Chamber [E] Beaker [E] |
* | [G] | | | | | |
* | | | | | | |
* | | | | | | |
* | [J] | |-----| |-----| |
* | | |
* \---------------------------------/
*
*/
private ReagentGrinderBoundUserInterface Owner { get; set; }
//We'll need 4 buttons, grind, juice, eject beaker, eject the chamber contents.
//The other 2 are referenced in the Open function.
public Button GrindButton { get; }
public Button JuiceButton { get; }
public LabelledContentBox ChamberContentBox { get; }
public LabelledContentBox BeakerContentBox { get; }
public sealed class LabelledContentBox : VBoxContainer
{
public string? LabelText { get; set; }
public ItemList BoxContents { get; set; }
public Button EjectButton { get; set; }
private Label _label;
public LabelledContentBox(string labelText, string buttonText)
{
_label = new Label
{
Text = labelText,
Align = Label.AlignMode.Center,
};
EjectButton = new Button
{
Text = buttonText,
TextAlign = Label.AlignMode.Center,
};
var vSplit = new HSplitContainer
{
Children =
{
_label,
EjectButton
}
};
AddChild(vSplit);
BoxContents = new ItemList
{
VerticalExpand = true,
HorizontalExpand = true,
SelectMode = ItemList.ItemListSelectMode.Button,
SizeFlagsStretchRatio = 2,
MinSize = (100, 128)
};
AddChild(BoxContents);
}
}
public GrinderMenu(ReagentGrinderBoundUserInterface owner)
{
SetSize = MinSize = (512, 256);
Owner = owner;
Title = Loc.GetString("grinder-menu-title");
var hSplit = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
var vBoxGrindJuiceButtonPanel = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalAlignment = VAlignment.Center
};
GrindButton = new Button
{
Text = Loc.GetString("grinder-menu-grind-button"),
TextAlign = Label.AlignMode.Center,
MinSize = (64, 64)
};
JuiceButton = new Button
{
Text = Loc.GetString("grinder-menu-juice-button"),
TextAlign = Label.AlignMode.Center,
MinSize = (64, 64)
};
vBoxGrindJuiceButtonPanel.AddChild(GrindButton);
//inner button padding
vBoxGrindJuiceButtonPanel.AddChild(new Control
{
MinSize = (0, 16),
});
vBoxGrindJuiceButtonPanel.AddChild(JuiceButton);
ChamberContentBox = new LabelledContentBox(Loc.GetString("grinder-menu-chamber-content-box-label"), Loc.GetString("grinder-menu-chamber-content-box-button"))
{
//Modulate = Color.Red,
VerticalExpand = true,
HorizontalExpand = true,
SizeFlagsStretchRatio = 2,
};
BeakerContentBox = new LabelledContentBox(Loc.GetString("grinder-menu-beaker-content-box-label"), Loc.GetString("grinder-menu-beaker-content-box-button"))
{
//Modulate = Color.Blue,
VerticalExpand = true,
HorizontalExpand = true,
SizeFlagsStretchRatio = 2,
};
hSplit.AddChild(vBoxGrindJuiceButtonPanel);
//Padding between the g/j buttons panel and the itemlist boxes panel.
hSplit.AddChild(new Control
{
MinSize = (16, 0),
});
hSplit.AddChild(ChamberContentBox);
//Padding between the two itemlists.
hSplit.AddChild(new Control
{
MinSize = (8, 0),
});
hSplit.AddChild(BeakerContentBox);
Contents.AddChild(hSplit);
}
}
} }
} }

View File

@@ -1,53 +0,0 @@
using System;
using Content.Shared.Nutrition.Components;
using Content.Shared.Rounding;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Nutrition.Visualizers
{
[UsedImplicitly]
public sealed class FoodContainerVisualizer : AppearanceVisualizer
{
[DataField("base_state", required: true)]
private string? _baseState;
[DataField("steps", required: true)]
private int _steps;
[DataField("mode")]
private FoodContainerVisualMode _mode = FoodContainerVisualMode.Rounded;
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData<int>(FoodContainerVisuals.Current, out var current))
{
return;
}
if (!component.TryGetData<int>(FoodContainerVisuals.Capacity, out var capacity))
{
return;
}
int step;
switch (_mode)
{
case FoodContainerVisualMode.Discrete:
step = Math.Min(_steps - 1, current);
break;
case FoodContainerVisualMode.Rounded:
step = ContentHelpers.RoundToLevels(current, capacity, _steps);
break;
default:
throw new NullReferenceException();
}
sprite.LayerSetState(0, $"{_baseState}-{step}");
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Storage; using Content.Shared.Storage;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;

View File

@@ -0,0 +1,31 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface
{
public static class ButtonHelpers
{
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
public static void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.ChildCount > 0)
{
SetButtonDisabledRecursive(child, val);
}
}
}
}
}

View File

@@ -18,6 +18,7 @@ namespace Content.Client.Weapons.Melee
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly EffectSystem _effectSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -67,7 +68,6 @@ namespace Content.Client.Weapons.Melee
source.TryGetComponent(out ISpriteComponent? sourceSprite) && source.TryGetComponent(out ISpriteComponent? sourceSprite) &&
sourceSprite.BaseRSI?.Path != null) sourceSprite.BaseRSI?.Path != null)
{ {
var sys = Get<EffectSystem>();
var curTime = _gameTiming.CurTime; var curTime = _gameTiming.CurTime;
var effect = new EffectSystemMessage var effect = new EffectSystemMessage
{ {
@@ -81,7 +81,8 @@ namespace Content.Client.Weapons.Melee
Born = curTime, Born = curTime,
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)), DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
}; };
sys.CreateEffect(effect);
_effectSystem.CreateEffect(effect);
} }
} }

View File

@@ -24,21 +24,12 @@ namespace Content.Client.Weapons.Ranged
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly CombatModeSystem _combatModeSystem = default!;
private InputSystem _inputSystem = default!;
private CombatModeSystem _combatModeSystem = default!;
private bool _blocked; private bool _blocked;
private int _shotCounter; private int _shotCounter;
public override void Initialize()
{
base.Initialize();
IoCManager.InjectDependencies(this);
_inputSystem = Get<InputSystem>();
_combatModeSystem = Get<CombatModeSystem>();
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.Fluids.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.Chemistry
{
[TestFixture]
[TestOf(typeof(ReactionPrototype))]
public class TryAllReactionsTest : ContentIntegrationTest
{
[Test]
public async Task TryAllTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
foreach (var reactionPrototype in prototypeManager.EnumeratePrototypes<ReactionPrototype>())
{
//since i have no clue how to isolate each loop assert-wise im just gonna throw this one in for good measure
Console.WriteLine($"Testing {reactionPrototype.ID}");
IEntity beaker;
SolutionContainerComponent component = null;
server.Assert(() =>
{
mapManager.CreateNewMapEntity(MapId.Nullspace);
beaker = entityManager.SpawnEntity("BluespaceBeaker", MapCoordinates.Nullspace);
Assert.That(beaker.TryGetComponent(out component));
foreach (var (id, reactant) in reactionPrototype.Reactants)
{
Assert.That(component.TryAddReagent(id, reactant.Amount, out var quantity));
Assert.That(reactant.Amount, Is.EqualTo(quantity));
}
});
await server.WaitIdleAsync();
server.Assert(() =>
{
//you just got linq'd fool
//(i'm sorry)
var foundProductsMap = reactionPrototype.Products
.Concat(reactionPrototype.Reactants.Where(x => x.Value.Catalyst).ToDictionary(x => x.Key, x => x.Value.Amount))
.ToDictionary(x => x, x => false);
foreach (var reagent in component.Solution.Contents)
{
Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.ReagentId && x.Key.Value == reagent.Quantity, out var foundProduct));
foundProductsMap[foundProduct.Value.Key] = true;
}
Assert.That(foundProductsMap.All(x => x.Value));
});
}
}
}
}

View File

@@ -37,8 +37,7 @@ namespace Content.Server.AI.Pathfinding.Accessible
*/ */
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
private PathfindingSystem _pathfindingSystem = default!;
/// <summary> /// <summary>
/// Queued region updates /// Queued region updates
@@ -80,7 +79,6 @@ namespace Content.Server.AI.Pathfinding.Accessible
public override void Initialize() public override void Initialize()
{ {
_pathfindingSystem = Get<PathfindingSystem>();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset); SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<PathfindingChunkUpdateMessage>(RecalculateNodeRegions); SubscribeLocalEvent<PathfindingChunkUpdateMessage>(RecalculateNodeRegions);
#if DEBUG #if DEBUG

View File

@@ -27,8 +27,7 @@ namespace Content.Server.AI.Steering
// http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview // http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPauseManager _pauseManager = default!; [Dependency] private readonly IPauseManager _pauseManager = default!;
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
private PathfindingSystem _pathfindingSystem = default!;
/// <summary> /// <summary>
/// Whether we try to avoid non-blocking physics objects /// Whether we try to avoid non-blocking physics objects
@@ -87,7 +86,6 @@ namespace Content.Server.AI.Steering
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_pathfindingSystem = Get<PathfindingSystem>();
for (var i = 0; i < AgentListCount; i++) for (var i = 0; i < AgentListCount; i++)
{ {

View File

@@ -1,30 +0,0 @@
using System;
using Robust.Shared.Maths;
namespace Content.Server.Atmos
{
public static class EntityNetworkUtils
{
public static Vector2i CardinalToIntVec(this Direction dir)
{
switch (dir)
{
case Direction.North:
return new Vector2i(0, 1);
case Direction.East:
return new Vector2i(1, 0);
case Direction.South:
return new Vector2i(0, -1);
case Direction.West:
return new Vector2i(-1, 0);
default:
throw new ArgumentException($"Direction dir {dir} is not a cardinal direction", nameof(dir));
}
}
public static Vector2i Offset(this Vector2i pos, Direction dir)
{
return pos + (Vector2i) dir.CardinalToIntVec();
}
}
}

View File

@@ -12,6 +12,7 @@ namespace Content.Server.Atmos.EntitySystems
public class AirtightSystem : EntitySystem public class AirtightSystem : EntitySystem
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -42,7 +43,7 @@ namespace Content.Server.Atmos.EntitySystems
if (airtight.FixVacuum) if (airtight.FixVacuum)
{ {
Get<AtmosphereSystem>().FixVacuum(airtight.LastPosition.Item1, airtight.LastPosition.Item2); _atmosphereSystem.FixVacuum(airtight.LastPosition.Item1, airtight.LastPosition.Item2);
} }
} }
@@ -93,9 +94,8 @@ namespace Content.Server.Atmos.EntitySystems
if (!gridId.IsValid()) if (!gridId.IsValid())
return; return;
var atmosphereSystem = Get<AtmosphereSystem>(); _atmosphereSystem.UpdateAdjacent(gridId, pos);
atmosphereSystem.UpdateAdjacent(gridId, pos); _atmosphereSystem.InvalidateTile(gridId, pos);
atmosphereSystem.InvalidateTile(gridId, pos);
} }
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)

View File

@@ -21,6 +21,7 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
/// <summary> /// <summary>
/// Players allowed to see the atmos debug overlay. /// Players allowed to see the atmos debug overlay.
@@ -127,7 +128,6 @@ namespace Content.Server.Atmos.EntitySystems
AccumulatedFrameTime -= _updateCooldown; AccumulatedFrameTime -= _updateCooldown;
var currentTick = _gameTiming.CurTick; var currentTick = _gameTiming.CurTick;
var atmosphereSystem = Get<AtmosphereSystem>();
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
@@ -157,7 +157,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var x = 0; x < LocalViewRange; x++) for (var x = 0; x < LocalViewRange; x++)
{ {
var Vector2i = new Vector2i(baseTile.X + x, baseTile.Y + y); var Vector2i = new Vector2i(baseTile.X + x, baseTile.Y + y);
debugOverlayContent[index++] = ConvertTileToData(atmosphereSystem.GetTileAtmosphereOrCreateSpace(grid, gam, Vector2i)); debugOverlayContent[index++] = ConvertTileToData(_atmosphereSystem.GetTileAtmosphereOrCreateSpace(grid, gam, Vector2i));
} }
} }

View File

@@ -15,6 +15,7 @@ namespace Content.Server.Atmos.EntitySystems
public bool MonstermosRipTiles { get; private set; } public bool MonstermosRipTiles { get; private set; }
public bool GridImpulse { get; private set; } public bool GridImpulse { get; private set; }
public bool Superconduction { get; private set; } public bool Superconduction { get; private set; }
public bool ExcitedGroups { get; private set; }
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; } public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
public float AtmosMaxProcessTime { get; private set; } public float AtmosMaxProcessTime { get; private set; }
public float AtmosTickRate { get; private set; } public float AtmosTickRate { get; private set; }
@@ -31,6 +32,7 @@ namespace Content.Server.Atmos.EntitySystems
_cfg.OnValueChanged(CCVars.Superconduction, value => Superconduction = value, true); _cfg.OnValueChanged(CCVars.Superconduction, value => Superconduction = value, true);
_cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true); _cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true);
_cfg.OnValueChanged(CCVars.AtmosTickRate, value => AtmosTickRate = value, true); _cfg.OnValueChanged(CCVars.AtmosTickRate, value => AtmosTickRate = value, true);
_cfg.OnValueChanged(CCVars.ExcitedGroups, value => ExcitedGroups = value, true);
_cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true); _cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true);
} }
} }

View File

@@ -42,13 +42,6 @@ namespace Content.Server.Atmos.EntitySystems
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
} }
public float GetHeatCapacityArchived(GasMixture mixture)
{
Span<float> tmp = stackalloc float[mixture.Moles.Length];
NumericsHelpers.Multiply(mixture.MolesArchived, GasSpecificHeats, tmp);
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
}
public float GetThermalEnergy(GasMixture mixture) public float GetThermalEnergy(GasMixture mixture)
{ {
return mixture.Temperature * GetHeatCapacity(mixture); return mixture.Temperature * GetHeatCapacity(mixture);
@@ -79,7 +72,7 @@ namespace Content.Server.Atmos.EntitySystems
public float Share(GasMixture receiver, GasMixture sharer, int atmosAdjacentTurfs) public float Share(GasMixture receiver, GasMixture sharer, int atmosAdjacentTurfs)
{ {
var temperatureDelta = receiver.TemperatureArchived - sharer.TemperatureArchived; var temperatureDelta = receiver.Temperature - sharer.Temperature;
var absTemperatureDelta = Math.Abs(temperatureDelta); var absTemperatureDelta = Math.Abs(temperatureDelta);
var oldHeatCapacity = 0f; var oldHeatCapacity = 0f;
var oldSharerHeatCapacity = 0f; var oldSharerHeatCapacity = 0f;
@@ -130,12 +123,12 @@ namespace Content.Server.Atmos.EntitySystems
// Transfer of thermal energy (via changed heat capacity) between self and sharer. // Transfer of thermal energy (via changed heat capacity) between self and sharer.
if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity) if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity)
{ {
receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * receiver.TemperatureArchived) + (heatCapacitySharerToThis * sharer.TemperatureArchived)) / newHeatCapacity; receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * receiver.Temperature) + (heatCapacitySharerToThis * sharer.Temperature)) / newHeatCapacity;
} }
if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity) if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity)
{ {
sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * sharer.TemperatureArchived) + (heatCapacityToSharer*receiver.TemperatureArchived)) / newSharerHeatCapacity; sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * sharer.Temperature) + (heatCapacityToSharer*receiver.Temperature)) / newSharerHeatCapacity;
} }
// Thermal energy of the system (self and sharer) is unchanged. // Thermal energy of the system (self and sharer) is unchanged.
@@ -154,17 +147,17 @@ namespace Content.Server.Atmos.EntitySystems
var moles = receiver.TotalMoles; var moles = receiver.TotalMoles;
var theirMoles = sharer.TotalMoles; var theirMoles = sharer.TotalMoles;
return (receiver.TemperatureArchived * (moles + movedMoles)) - (sharer.TemperatureArchived * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; return (receiver.Temperature * (moles + movedMoles)) - (sharer.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume;
} }
public float TemperatureShare(GasMixture receiver, GasMixture sharer, float conductionCoefficient) public float TemperatureShare(GasMixture receiver, GasMixture sharer, float conductionCoefficient)
{ {
var temperatureDelta = receiver.TemperatureArchived - sharer.TemperatureArchived; var temperatureDelta = receiver.Temperature - sharer.Temperature;
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
{ {
var heatCapacity = GetHeatCapacityArchived(receiver); var heatCapacity = GetHeatCapacity(receiver);
var sharerHeatCapacity = GetHeatCapacityArchived(sharer); var sharerHeatCapacity = GetHeatCapacity(sharer);
if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity) if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity)
{ {
@@ -183,10 +176,10 @@ namespace Content.Server.Atmos.EntitySystems
public float TemperatureShare(GasMixture receiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity) public float TemperatureShare(GasMixture receiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity)
{ {
var temperatureDelta = receiver.TemperatureArchived - sharerTemperature; var temperatureDelta = receiver.Temperature - sharerTemperature;
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
{ {
var heatCapacity = GetHeatCapacityArchived(receiver); var heatCapacity = GetHeatCapacity(receiver);
if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity) if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity)
{ {

View File

@@ -23,13 +23,10 @@ namespace Content.Server.Atmos.EntitySystems
public partial class AtmosphereSystem public partial class AtmosphereSystem
{ {
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
private GasTileOverlaySystem _gasTileOverlaySystem = default!;
private void InitializeGrid() private void InitializeGrid()
{ {
_gasTileOverlaySystem = Get<GasTileOverlaySystem>();
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit); SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
} }

View File

@@ -108,10 +108,11 @@ namespace Content.Server.Atmos.EntitySystems
{ {
Volume = exposedVolume * 25f, Volume = exposedVolume * 25f,
Temperature = exposedTemperature, Temperature = exposedTemperature,
SkippedFirstProcess = tile.CurrentCycle > gridAtmosphere.UpdateCounter SkippedFirstProcess = tile.CurrentCycle > gridAtmosphere.UpdateCounter,
Valid = true,
State = 1
}; };
tile.Hotspot.Start();
AddActiveTile(gridAtmosphere, tile); AddActiveTile(gridAtmosphere, tile);
gridAtmosphere.HotspotTiles.Add(tile); gridAtmosphere.HotspotTiles.Add(tile);
@@ -139,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems
Merge(tile.Air, affected); Merge(tile.Air, affected);
} }
var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex); var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex, _mapManager);
foreach (var entity in tileRef.GetEntitiesInTileFast()) foreach (var entity in tileRef.GetEntitiesInTileFast())
{ {

View File

@@ -16,9 +16,6 @@ namespace Content.Server.Atmos.EntitySystems
return; return;
} }
if (tile.ArchivedCycle < fireCount)
Archive(tile, fireCount);
tile.CurrentCycle = fireCount; tile.CurrentCycle = fireCount;
var adjacentTileLength = 0; var adjacentTileLength = 0;
@@ -38,11 +35,10 @@ namespace Content.Server.Atmos.EntitySystems
// If the tile is null or has no air, we don't do anything for it. // If the tile is null or has no air, we don't do anything for it.
if(enemyTile?.Air == null) continue; if(enemyTile?.Air == null) continue;
if (fireCount <= enemyTile.CurrentCycle) continue; if (fireCount <= enemyTile.CurrentCycle) continue;
Archive(enemyTile, fireCount);
var shouldShareAir = false; var shouldShareAir = false;
if (tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null) if (ExcitedGroups && tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null)
{ {
if (tile.ExcitedGroup != enemyTile.ExcitedGroup) if (tile.ExcitedGroup != enemyTile.ExcitedGroup)
{ {
@@ -57,21 +53,24 @@ namespace Content.Server.Atmos.EntitySystems
AddActiveTile(gridAtmosphere, enemyTile); AddActiveTile(gridAtmosphere, enemyTile);
} }
var excitedGroup = tile.ExcitedGroup; if (ExcitedGroups)
excitedGroup ??= enemyTile.ExcitedGroup;
if (excitedGroup == null)
{ {
excitedGroup = new ExcitedGroup(); var excitedGroup = tile.ExcitedGroup;
gridAtmosphere.ExcitedGroups.Add(excitedGroup); excitedGroup ??= enemyTile.ExcitedGroup;
if (excitedGroup == null)
{
excitedGroup = new ExcitedGroup();
gridAtmosphere.ExcitedGroups.Add(excitedGroup);
}
if (tile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, tile);
if(enemyTile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, enemyTile);
} }
if (tile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, tile);
if(enemyTile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, enemyTile);
shouldShareAir = true; shouldShareAir = true;
} }
@@ -106,17 +105,10 @@ namespace Content.Server.Atmos.EntitySystems
if (ConsiderSuperconductivity(gridAtmosphere, tile, true)) if (ConsiderSuperconductivity(gridAtmosphere, tile, true))
remove = false; remove = false;
if(tile.ExcitedGroup == null && remove) if(ExcitedGroups && tile.ExcitedGroup == null && remove)
RemoveActiveTile(gridAtmosphere, tile); RemoveActiveTile(gridAtmosphere, tile);
} }
private void Archive(TileAtmosphere tile, int fireCount)
{
tile.Air?.Archive();
tile.ArchivedCycle = fireCount;
tile.TemperatureArchived = tile.Temperature;
}
private void LastShareCheck(TileAtmosphere tile) private void LastShareCheck(TileAtmosphere tile)
{ {
if (tile.Air == null || tile.ExcitedGroup == null) if (tile.Air == null || tile.ExcitedGroup == null)

View File

@@ -281,7 +281,8 @@ namespace Content.Server.Atmos.EntitySystems
} }
atmosphere.ProcessingPaused = false; atmosphere.ProcessingPaused = false;
atmosphere.State = AtmosphereProcessingState.ExcitedGroups; // Next state depends on whether excited groups are enabled or not.
atmosphere.State = ExcitedGroups ? AtmosphereProcessingState.ExcitedGroups : AtmosphereProcessingState.HighPressureDelta;
continue; continue;
case AtmosphereProcessingState.ExcitedGroups: case AtmosphereProcessingState.ExcitedGroups:
if (!ProcessExcitedGroups(atmosphere)) if (!ProcessExcitedGroups(atmosphere))

View File

@@ -21,9 +21,6 @@ namespace Content.Server.Atmos.EntitySystems
if (adjacent == null || adjacent.ThermalConductivity == 0f) if (adjacent == null || adjacent.ThermalConductivity == 0f)
continue; continue;
if(adjacent.ArchivedCycle < gridAtmosphere.UpdateCounter)
Archive(adjacent, gridAtmosphere.UpdateCounter);
NeighborConductWithSource(gridAtmosphere, adjacent, tile); NeighborConductWithSource(gridAtmosphere, adjacent, tile);
ConsiderSuperconductivity(gridAtmosphere, adjacent); ConsiderSuperconductivity(gridAtmosphere, adjacent);
@@ -37,8 +34,6 @@ namespace Content.Server.Atmos.EntitySystems
{ {
if(tile.Air == null) if(tile.Air == null)
{ {
if(tile.ArchivedCycle < gridAtmosphere.UpdateCounter)
Archive(tile, gridAtmosphere.UpdateCounter);
return AtmosDirection.All; return AtmosDirection.All;
} }
@@ -128,7 +123,7 @@ namespace Content.Server.Atmos.EntitySystems
private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient) private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient)
{ {
var deltaTemperature = (tile.TemperatureArchived - other.TemperatureArchived); var deltaTemperature = (tile.Temperature - other.Temperature);
if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider
&& tile.HeatCapacity != 0f && other.HeatCapacity != 0f) && tile.HeatCapacity != 0f && other.HeatCapacity != 0f)
{ {
@@ -146,7 +141,7 @@ namespace Content.Server.Atmos.EntitySystems
if (tile.Temperature > Atmospherics.T0C) if (tile.Temperature > Atmospherics.T0C)
{ {
// Hardcoded space temperature. // Hardcoded space temperature.
var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB); var deltaTemperature = (tile.Temperature - Atmospherics.TCMB);
if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider)) if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider))
{ {
var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity * var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity *

View File

@@ -1,12 +1,15 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasTankSystem : EntitySystem public class GasTankSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
private const float TimerDelay = 0.5f; private const float TimerDelay = 0.5f;
private float _timer = 0f; private float _timer = 0f;
@@ -19,11 +22,9 @@ namespace Content.Server.Atmos.EntitySystems
if (_timer < TimerDelay) return; if (_timer < TimerDelay) return;
_timer -= TimerDelay; _timer -= TimerDelay;
var atmosphereSystem = Get<AtmosphereSystem>();
foreach (var gasTank in EntityManager.ComponentManager.EntityQuery<GasTankComponent>()) foreach (var gasTank in EntityManager.ComponentManager.EntityQuery<GasTankComponent>())
{ {
atmosphereSystem.React(gasTank.Air, gasTank); _atmosphereSystem.React(gasTank.Air, gasTank);
gasTank.CheckStatus(); gasTank.CheckStatus();
gasTank.UpdateUserInterface(); gasTank.UpdateUserInterface();
} }

View File

@@ -29,6 +29,7 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
/// <summary> /// <summary>
/// The tiles that have had their atmos data updated since last tick /// The tiles that have had their atmos data updated since last tick
@@ -58,8 +59,6 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
private float _updateCooldown; private float _updateCooldown;
private AtmosphereSystem _atmosphereSystem = default!;
private int _thresholds; private int _thresholds;
public override void Initialize() public override void Initialize()
@@ -68,7 +67,6 @@ namespace Content.Server.Atmos.EntitySystems
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset); SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
_atmosphereSystem = Get<AtmosphereSystem>();
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_mapManager.OnGridRemoved += OnGridRemoved; _mapManager.OnGridRemoved += OnGridRemoved;
var configManager = IoCManager.Resolve<IConfigurationManager>(); var configManager = IoCManager.Resolve<IConfigurationManager>();

View File

@@ -26,9 +26,6 @@ namespace Content.Server.Atmos
[DataField("moles")] [ViewVariables] [DataField("moles")] [ViewVariables]
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
[DataField("molesArchived")] [ViewVariables]
public float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
[DataField("temperature")] [ViewVariables] [DataField("temperature")] [ViewVariables]
private float _temperature = Atmospherics.TCMB; private float _temperature = Atmospherics.TCMB;
@@ -73,9 +70,6 @@ namespace Content.Server.Atmos
} }
} }
[DataField("temperatureArchived")] [ViewVariables]
public float TemperatureArchived { get; private set; }
[DataField("volume")] [ViewVariables] [DataField("volume")] [ViewVariables]
public float Volume { get; set; } public float Volume { get; set; }
@@ -96,13 +90,6 @@ namespace Content.Server.Atmos
Immutable = true; Immutable = true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Archive()
{
Moles.AsSpan().CopyTo(MolesArchived.AsSpan());
TemperatureArchived = Temperature;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public float GetMoles(int gasId) public float GetMoles(int gasId)
{ {
@@ -253,7 +240,6 @@ namespace Content.Server.Atmos
{ {
// The arrays MUST have a specific length. // The arrays MUST have a specific length.
Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases); Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
Array.Resize(ref MolesArchived, Atmospherics.AdjustedNumberOfGases);
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@@ -268,12 +254,10 @@ namespace Content.Server.Atmos
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;
return Moles.SequenceEqual(other.Moles) return Moles.SequenceEqual(other.Moles)
&& MolesArchived.SequenceEqual(other.MolesArchived)
&& _temperature.Equals(other._temperature) && _temperature.Equals(other._temperature)
&& ReactionResults.SequenceEqual(other.ReactionResults) && ReactionResults.SequenceEqual(other.ReactionResults)
&& Immutable == other.Immutable && Immutable == other.Immutable
&& LastShare.Equals(other.LastShare) && LastShare.Equals(other.LastShare)
&& TemperatureArchived.Equals(other.TemperatureArchived)
&& Volume.Equals(other.Volume); && Volume.Equals(other.Volume);
} }
@@ -284,13 +268,10 @@ namespace Content.Server.Atmos
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{ {
var moles = Moles[i]; var moles = Moles[i];
var molesArchived = MolesArchived[i];
hashCode.Add(moles); hashCode.Add(moles);
hashCode.Add(molesArchived);
} }
hashCode.Add(_temperature); hashCode.Add(_temperature);
hashCode.Add(TemperatureArchived);
hashCode.Add(Immutable); hashCode.Add(Immutable);
hashCode.Add(LastShare); hashCode.Add(LastShare);
hashCode.Add(Volume); hashCode.Add(Volume);
@@ -303,11 +284,9 @@ namespace Content.Server.Atmos
var newMixture = new GasMixture() var newMixture = new GasMixture()
{ {
Moles = (float[])Moles.Clone(), Moles = (float[])Moles.Clone(),
MolesArchived = (float[])MolesArchived.Clone(),
_temperature = _temperature, _temperature = _temperature,
Immutable = Immutable, Immutable = Immutable,
LastShare = LastShare, LastShare = LastShare,
TemperatureArchived = TemperatureArchived,
Volume = Volume, Volume = Volume,
}; };
return newMixture; return newMixture;

View File

@@ -24,11 +24,5 @@ namespace Content.Server.Atmos
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public byte State; public byte State;
public void Start()
{
Valid = true;
State = 1;
}
} }
} }

View File

@@ -10,12 +10,15 @@ using Content.Shared.Atmos.Visuals;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasDualPortVentPumpSystem : EntitySystem public class GasDualPortVentPumpSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -34,24 +37,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
return; return;
} }
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); if (!vent.Enabled
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
if (!vent.Enabled) || !nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet)
|| !nodeContainer.TryGetNode(vent.OutletName, out PipeNode? outlet)) || !nodeContainer.TryGetNode(vent.OutletName, out PipeNode? outlet))
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
return; return;
}
var atmosphereSystem = Get<AtmosphereSystem>(); var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
// We're in an air-blocked tile... Do nothing. // We're in an air-blocked tile... Do nothing.
if (environment == null) if (environment == null)
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
return; return;
}
if (vent.PumpDirection == VentPumpDirection.Releasing) if (vent.PumpDirection == VentPumpDirection.Releasing)
{ {
@@ -68,7 +70,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{ {
var transferMoles = pressureDelta * environment.Volume / inlet.Air.Temperature * Atmospherics.R; var transferMoles = pressureDelta * environment.Volume / inlet.Air.Temperature * Atmospherics.R;
var removed = inlet.Air.Remove(transferMoles); var removed = inlet.Air.Remove(transferMoles);
atmosphereSystem.Merge(environment, removed); _atmosphereSystem.Merge(environment, removed);
} }
} }
else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0f) else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0f)
@@ -89,7 +91,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{ {
var removed = environment.Remove(molesDelta); var removed = environment.Remove(molesDelta);
Get<AtmosphereSystem>().Merge(outlet.Air, removed); _atmosphereSystem.Merge(outlet.Air, removed);
} }
} }
} }

View File

@@ -25,22 +25,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, AtmosDeviceUpdateEvent args) private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, AtmosDeviceUpdateEvent args)
{ {
var appearance = pump.Owner.GetComponentOrNull<AppearanceComponent>(); var appearance = pump.Owner.GetComponentOrNull<AppearanceComponent>();
appearance?.SetData(PressurePumpVisuals.Enabled, false);
if (!pump.Enabled) if (!pump.Enabled
return; || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
|| !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet)) || !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet))
{
appearance?.SetData(PressurePumpVisuals.Enabled, false);
return; return;
}
var outputStartingPressure = outlet.Air.Pressure; var outputStartingPressure = outlet.Air.Pressure;
if (MathHelper.CloseTo(pump.TargetPressure, outputStartingPressure)) if (MathHelper.CloseTo(pump.TargetPressure, outputStartingPressure))
{
appearance?.SetData(PressurePumpVisuals.Enabled, false);
return; // No need to pump gas if target has been reached. return; // No need to pump gas if target has been reached.
}
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0) if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{ {

View File

@@ -13,7 +13,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public class GasVolumePumpSystem : EntitySystem public class GasVolumePumpSystem : EntitySystem
{ {
[Dependency] private IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -56,13 +57,12 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
// Some of the gas from the mixture leaks when overclocked. // Some of the gas from the mixture leaks when overclocked.
if (pump.Overclocked) if (pump.Overclocked)
{ {
var atmosphereSystem = Get<AtmosphereSystem>(); var tile = _atmosphereSystem.GetTileMixture(pump.Owner.Transform.Coordinates, true);
var tile = atmosphereSystem.GetTileMixture(pump.Owner.Transform.Coordinates, true);
if (tile != null) if (tile != null)
{ {
var leaked = removed.RemoveRatio(pump.LeakRatio); var leaked = removed.RemoveRatio(pump.LeakRatio);
atmosphereSystem.Merge(tile, leaked); _atmosphereSystem.Merge(tile, leaked);
} }
} }

View File

@@ -13,6 +13,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public class AtmosDeviceSystem : EntitySystem public class AtmosDeviceSystem : EntitySystem
{ {
[Dependency] private IGameTiming _gameTiming = default!; [Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -35,7 +36,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
return; return;
// We try to add the device to a valid atmosphere. // We try to add the device to a valid atmosphere.
if (!Get<AtmosphereSystem>().AddAtmosDevice(component)) if (!_atmosphereSystem.AddAtmosDevice(component))
return; return;
component.LastProcess = _gameTiming.CurTime; component.LastProcess = _gameTiming.CurTime;
@@ -45,7 +46,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public void LeaveAtmosphere(AtmosDeviceComponent component) public void LeaveAtmosphere(AtmosDeviceComponent component)
{ {
if (!Get<AtmosphereSystem>().RemoveAtmosDevice(component)) if (!_atmosphereSystem.RemoveAtmosDevice(component))
return; return;
component.LastProcess = TimeSpan.Zero; component.LastProcess = TimeSpan.Zero;

View File

@@ -8,6 +8,7 @@ using Content.Shared.Atmos;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
namespace Content.Server.Atmos.Piping.EntitySystems namespace Content.Server.Atmos.Piping.EntitySystems
@@ -15,6 +16,8 @@ namespace Content.Server.Atmos.Piping.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public class AtmosUnsafeUnanchorSystem : EntitySystem public class AtmosUnsafeUnanchorSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BeforeUnanchoredEvent>(OnBeforeUnanchored); SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BeforeUnanchoredEvent>(OnBeforeUnanchored);
@@ -26,7 +29,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes)) if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
return; return;
if (Get<AtmosphereSystem>().GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment) if (_atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment)
return; return;
foreach (var node in nodes.Nodes.Values) foreach (var node in nodes.Nodes.Values)
@@ -47,9 +50,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes)) if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
return; return;
var atmosphereSystem = Get<AtmosphereSystem>(); if (_atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates, true) is not {} environment)
if (atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates, true) is not {} environment)
environment = GasMixture.SpaceGas; environment = GasMixture.SpaceGas;
var lost = 0f; var lost = 0f;
@@ -71,10 +72,10 @@ namespace Content.Server.Atmos.Piping.EntitySystems
{ {
if (node is not PipeNode pipe) continue; if (node is not PipeNode pipe) continue;
atmosphereSystem.Merge(buffer, pipe.Air.Remove(sharedLoss)); _atmosphereSystem.Merge(buffer, pipe.Air.Remove(sharedLoss));
} }
atmosphereSystem.Merge(environment, buffer); _atmosphereSystem.Merge(environment, buffer);
} }
} }
} }

View File

@@ -6,12 +6,15 @@ using Content.Server.Atmos.Piping.Other.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Other.EntitySystems namespace Content.Server.Atmos.Piping.Other.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasMinerSystem : EntitySystem public class GasMinerSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -21,9 +24,7 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems
private void OnMinerUpdated(EntityUid uid, GasMinerComponent miner, AtmosDeviceUpdateEvent args) private void OnMinerUpdated(EntityUid uid, GasMinerComponent miner, AtmosDeviceUpdateEvent args)
{ {
var atmosphereSystem = Get<AtmosphereSystem>(); if (!CheckMinerOperation(miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f)
if (!CheckMinerOperation(atmosphereSystem, miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f)
return; return;
// Time to mine some gas. // Time to mine some gas.
@@ -31,15 +32,15 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems
var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature }; var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature };
merger.SetMoles(miner.SpawnGas.Value, miner.SpawnAmount); merger.SetMoles(miner.SpawnGas.Value, miner.SpawnAmount);
atmosphereSystem.Merge(environment, merger); _atmosphereSystem.Merge(environment, merger);
} }
private bool CheckMinerOperation(AtmosphereSystem atmosphereSystem, GasMinerComponent miner, [NotNullWhen(true)] out GasMixture? environment) private bool CheckMinerOperation(GasMinerComponent miner, [NotNullWhen(true)] out GasMixture? environment)
{ {
environment = atmosphereSystem.GetTileMixture(miner.Owner.Transform.Coordinates, true); environment = _atmosphereSystem.GetTileMixture(miner.Owner.Transform.Coordinates, true);
// Space. // Space.
if (atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates)) if (_atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates))
{ {
miner.Broken = true; miner.Broken = true;
return false; return false;

View File

@@ -19,6 +19,7 @@ using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths; using Robust.Shared.Maths;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
@@ -26,6 +27,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public class GasCanisterSystem : EntitySystem public class GasCanisterSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -131,23 +134,21 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (!nodeContainer.TryGetNode(canister.PortName, out PortablePipeNode? portNode)) if (!nodeContainer.TryGetNode(canister.PortName, out PortablePipeNode? portNode))
return; return;
var atmosphereSystem = Get<AtmosphereSystem>(); _atmosphereSystem.React(canister.Air, portNode);
atmosphereSystem.React(canister.Air, portNode);
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net) if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
{ {
var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume); var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume);
atmosphereSystem.Merge(buffer, net.Air); _atmosphereSystem.Merge(buffer, net.Air);
atmosphereSystem.Merge(buffer, canister.Air); _atmosphereSystem.Merge(buffer, canister.Air);
net.Air.Clear(); net.Air.Clear();
atmosphereSystem.Merge(net.Air, buffer); _atmosphereSystem.Merge(net.Air, buffer);
net.Air.Multiply(net.Air.Volume / buffer.Volume); net.Air.Multiply(net.Air.Volume / buffer.Volume);
canister.Air.Clear(); canister.Air.Clear();
atmosphereSystem.Merge(canister.Air, buffer); _atmosphereSystem.Merge(canister.Air, buffer);
canister.Air.Multiply(canister.Air.Volume / buffer.Volume); canister.Air.Multiply(canister.Air.Volume / buffer.Volume);
} }
@@ -161,12 +162,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (container.ContainedEntities.Count > 0) if (container.ContainedEntities.Count > 0)
{ {
var gasTank = container.ContainedEntities[0].GetComponent<GasTankComponent>(); var gasTank = container.ContainedEntities[0].GetComponent<GasTankComponent>();
atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure); _atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure);
} }
else else
{ {
var environment = atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true); var environment = _atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true);
atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure); _atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure);
} }
} }

View File

@@ -6,12 +6,15 @@ using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasOutletInjectorSystem : EntitySystem public class GasOutletInjectorSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -32,8 +35,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (!nodeContainer.TryGetNode(injector.InletName, out PipeNode? inlet)) if (!nodeContainer.TryGetNode(injector.InletName, out PipeNode? inlet))
return; return;
var atmosphereSystem = Get<AtmosphereSystem>(); var environment = _atmosphereSystem.GetTileMixture(injector.Owner.Transform.Coordinates, true);
var environment = atmosphereSystem.GetTileMixture(injector.Owner.Transform.Coordinates, true);
if (environment == null) if (environment == null)
return; return;
@@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
var removed = inlet.Air.Remove(transferMoles); var removed = inlet.Air.Remove(transferMoles);
atmosphereSystem.Merge(environment, removed); _atmosphereSystem.Merge(environment, removed);
} }
} }
} }

View File

@@ -7,12 +7,15 @@ using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasPassiveVentSystem : EntitySystem public class GasPassiveVentSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -22,8 +25,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
private void OnPassiveVentUpdated(EntityUid uid, GasPassiveVentComponent vent, AtmosDeviceUpdateEvent args) private void OnPassiveVentUpdated(EntityUid uid, GasPassiveVentComponent vent, AtmosDeviceUpdateEvent args)
{ {
var atmosphereSystem = Get<AtmosphereSystem>(); var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
if (environment == null) if (environment == null)
return; return;
@@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
var airTemperature = environment.Temperature > 0 ? environment.Temperature : inlet.Air.Temperature; var airTemperature = environment.Temperature > 0 ? environment.Temperature : inlet.Air.Temperature;
var transferMoles = pressureDelta * environment.Volume / (airTemperature * Atmospherics.R); var transferMoles = pressureDelta * environment.Volume / (airTemperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles); var removed = inlet.Air.Remove(transferMoles);
atmosphereSystem.Merge(environment, removed); _atmosphereSystem.Merge(environment, removed);
} }
else else
{ {

View File

@@ -7,12 +7,15 @@ using Content.Shared.Atmos.Piping;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasThermoMachineSystem : EntitySystem public class GasThermoMachineSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -24,18 +27,16 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args) private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args)
{ {
var appearance = thermoMachine.Owner.GetComponentOrNull<AppearanceComponent>(); var appearance = thermoMachine.Owner.GetComponentOrNull<AppearanceComponent>();
appearance?.SetData(ThermoMachineVisuals.Enabled, false);
if (!thermoMachine.Enabled) if (!thermoMachine.Enabled
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet))
{
appearance?.SetData(ThermoMachineVisuals.Enabled, false);
return; return;
}
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) var airHeatCapacity = _atmosphereSystem.GetHeatCapacity(inlet.Air);
return;
if (!nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet))
return;
var airHeatCapacity = Get<AtmosphereSystem>().GetHeatCapacity(inlet.Air);
var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity; var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity;
var oldTemperature = inlet.Air.Temperature; var oldTemperature = inlet.Air.Temperature;

View File

@@ -9,12 +9,15 @@ using Content.Shared.Atmos.Visuals;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class GasVentPumpSystem : EntitySystem public class GasVentPumpSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -33,23 +36,22 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
return; return;
} }
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); if (!vent.Enabled
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
if (!vent.Enabled) || !nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe))
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
return; return;
}
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
return;
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe))
return;
var atmosphereSystem = Get<AtmosphereSystem>();
var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true);
// We're in an air-blocked tile... Do nothing. // We're in an air-blocked tile... Do nothing.
if (environment == null) if (environment == null)
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
return; return;
}
if (vent.PumpDirection == VentPumpDirection.Releasing) if (vent.PumpDirection == VentPumpDirection.Releasing)
{ {
@@ -66,7 +68,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{ {
var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R); var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R);
atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles)); _atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles));
} }
} }
else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0) else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0)

View File

@@ -9,6 +9,7 @@ using Content.Shared.Atmos.Piping.Unary.Visuals;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths; using Robust.Shared.Maths;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems namespace Content.Server.Atmos.Piping.Unary.EntitySystems
@@ -16,6 +17,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public class GasVentScrubberSystem : EntitySystem public class GasVentScrubberSystem : EntitySystem
{ {
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -34,28 +37,24 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
return; return;
} }
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off); if (!scrubber.Enabled
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
if (!scrubber.Enabled) || !nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet))
{
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off);
return; return;
}
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) var environment = _atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true);
return;
if (!nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet)) Scrub(_atmosphereSystem, scrubber, appearance, environment, outlet);
return;
var atmosphereSystem = Get<AtmosphereSystem>();
var environment = atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true);
Scrub(atmosphereSystem, scrubber, appearance, environment, outlet);
if (!scrubber.WideNet) return; if (!scrubber.WideNet) return;
// Scrub adjacent tiles too. // Scrub adjacent tiles too.
foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(scrubber.Owner.Transform.Coordinates, false, true)) foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(scrubber.Owner.Transform.Coordinates, false, true))
{ {
Scrub(atmosphereSystem, scrubber, null, adjacent, outlet); Scrub(_atmosphereSystem, scrubber, null, adjacent, outlet);
} }
} }
@@ -70,12 +69,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
private void Scrub(AtmosphereSystem atmosphereSystem, GasVentScrubberComponent scrubber, AppearanceComponent? appearance, GasMixture? tile, PipeNode outlet) private void Scrub(AtmosphereSystem atmosphereSystem, GasVentScrubberComponent scrubber, AppearanceComponent? appearance, GasMixture? tile, PipeNode outlet)
{ {
// Cannot scrub if tile is null or air-blocked. // Cannot scrub if tile is null or air-blocked.
if (tile == null) if (tile == null
return; || outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
{
// Cannot scrub if pressure too high. appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off);
if (outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere)
return; return;
}
if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing) if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing)
{ {

View File

@@ -13,18 +13,12 @@ namespace Content.Server.Atmos
/// </summary> /// </summary>
public class TileAtmosphere : IGasMixtureHolder public class TileAtmosphere : IGasMixtureHolder
{ {
[ViewVariables]
public int ArchivedCycle;
[ViewVariables] [ViewVariables]
public int CurrentCycle; public int CurrentCycle;
[ViewVariables] [ViewVariables]
public float Temperature { get; set; } = Atmospherics.T20C; public float Temperature { get; set; } = Atmospherics.T20C;
[ViewVariables]
public float TemperatureArchived { get; set; } = Atmospherics.T20C;
[ViewVariables] [ViewVariables]
public TileAtmosphere? PressureSpecificTarget { get; set; } public TileAtmosphere? PressureSpecificTarget { get; set; }

View File

@@ -6,12 +6,15 @@ using Content.Shared.Interaction.Events;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Body.Surgery.Components namespace Content.Server.Body.Surgery.Components
{ {
[UsedImplicitly] [UsedImplicitly]
public class SurgeryToolSystem : EntitySystem public class SurgeryToolSystem : EntitySystem
{ {
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
private readonly HashSet<SurgeryToolComponent> _openSurgeryUIs = new(); private readonly HashSet<SurgeryToolComponent> _openSurgeryUIs = new();
public override void Initialize() public override void Initialize()
@@ -54,7 +57,7 @@ namespace Content.Server.Body.Surgery.Components
continue; continue;
} }
if (!Get<ActionBlockerSystem>().CanInteract(tool.PerformerCache) || if (!_actionBlockerSystem.CanInteract(tool.PerformerCache) ||
!tool.PerformerCache.InRangeUnobstructed(tool.BodyCache)) !tool.PerformerCache.InRangeUnobstructed(tool.BodyCache))
{ {
tool.CloseAllSurgeryUIs(); tool.CloseAllSurgeryUIs();

View File

@@ -39,6 +39,8 @@ namespace Content.Server.Construction
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
private readonly Dictionary<ICommonSession, HashSet<int>> _beingBuilt = new(); private readonly Dictionary<ICommonSession, HashSet<int>> _beingBuilt = new();
@@ -170,7 +172,7 @@ namespace Content.Server.Construction
if (!materialStep.EntityValid(entity, out var stack)) if (!materialStep.EntityValid(entity, out var stack))
continue; continue;
var splitStack = Get<StackSystem>().Split(entity.Uid, stack, materialStep.Amount, user.ToCoordinates()); var splitStack = _stackSystem.Split(entity.Uid, stack, materialStep.Amount, user.ToCoordinates());
if (splitStack == null) if (splitStack == null)
continue; continue;
@@ -226,8 +228,6 @@ namespace Content.Server.Construction
return null; return null;
} }
var doAfterSystem = Get<DoAfterSystem>();
var doAfterArgs = new DoAfterEventArgs(user, doAfterTime) var doAfterArgs = new DoAfterEventArgs(user, doAfterTime)
{ {
BreakOnDamage = true, BreakOnDamage = true,
@@ -237,7 +237,7 @@ namespace Content.Server.Construction
NeedHand = false, NeedHand = false,
}; };
if (await doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled) if (await _doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled)
{ {
FailCleanup(); FailCleanup();
return null; return null;

View File

@@ -11,17 +11,7 @@ namespace Content.Server.Destructible
public class DestructibleSystem : EntitySystem public class DestructibleSystem : EntitySystem
{ {
[Dependency] public readonly IRobustRandom Random = default!; [Dependency] public readonly IRobustRandom Random = default!;
[Dependency] public readonly AudioSystem AudioSystem = default!;
public AudioSystem AudioSystem { get; private set; } = default!; [Dependency] public readonly ActSystem ActSystem = default!;
public ActSystem ActSystem { get; private set; } = default!;
public override void Initialize()
{
base.Initialize();
AudioSystem = Get<AudioSystem>();
ActSystem = Get<ActSystem>();
}
} }
} }

View File

@@ -16,6 +16,8 @@ namespace Content.Server.Engineering.EntitySystems
public class SpawnAfterInteractSystem : EntitySystem public class SpawnAfterInteractSystem : EntitySystem
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -41,7 +43,7 @@ namespace Content.Server.Engineering.EntitySystems
if (!IsTileClear()) if (!IsTileClear())
return; return;
if (component.DoAfterTime > 0 && TryGet<DoAfterSystem>(out var doAfterSystem)) if (component.DoAfterTime > 0)
{ {
var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime) var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
{ {
@@ -49,7 +51,7 @@ namespace Content.Server.Engineering.EntitySystems
BreakOnStun = true, BreakOnStun = true,
PostCheck = IsTileClear, PostCheck = IsTileClear,
}; };
var result = await doAfterSystem.WaitDoAfter(doAfterArgs); var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished) if (result != DoAfterStatus.Finished)
return; return;
@@ -59,7 +61,7 @@ namespace Content.Server.Engineering.EntitySystems
return; return;
if (component.Owner.TryGetComponent<SharedStackComponent>(out var stackComp) if (component.Owner.TryGetComponent<SharedStackComponent>(out var stackComp)
&& component.RemoveOnInteract && !Get<StackSystem>().Use(uid, stackComp, 1)) && component.RemoveOnInteract && !_stackSystem.Use(uid, stackComp, 1))
{ {
return; return;
} }

View File

@@ -18,6 +18,7 @@ using Robust.Server.Player;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
@@ -29,6 +30,9 @@ namespace Content.Server.Hands
[UsedImplicitly] [UsedImplicitly]
internal sealed class HandsSystem : SharedHandsSystem internal sealed class HandsSystem : SharedHandsSystem
{ {
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -113,12 +117,12 @@ namespace Content.Server.Hands
if (!hands.TryGetActiveHeldEntity(out var throwEnt)) if (!hands.TryGetActiveHeldEntity(out var throwEnt))
return false; return false;
if (!Get<InteractionSystem>().TryThrowInteraction(hands.Owner, throwEnt)) if (!_interactionSystem.TryThrowInteraction(hands.Owner, throwEnt))
return false; return false;
if (throwEnt.TryGetComponent(out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) if (throwEnt.TryGetComponent(out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
{ {
var splitStack = Get<StackSystem>().Split(throwEnt.Uid, stack, 1, playerEnt.Transform.Coordinates); var splitStack = _stackSystem.Split(throwEnt.Uid, stack, 1, playerEnt.Transform.Coordinates);
if (splitStack == null) if (splitStack == null)
return false; return false;

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Buckle.Components; using Content.Server.Buckle.Components;
using Content.Server.CombatMode; using Content.Server.CombatMode;
using Content.Server.DoAfter;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.Pulling; using Content.Server.Pulling;
@@ -46,6 +47,7 @@ namespace Content.Server.Interaction
{ {
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -175,9 +177,7 @@ namespace Content.Server.Interaction
private void InteractionActivate(IEntity user, IEntity used) private void InteractionActivate(IEntity user, IEntity used)
{ {
var actionBlocker = Get<ActionBlockerSystem>(); if (!_actionBlockerSystem.CanInteract(user) || ! _actionBlockerSystem.CanUse(user))
if (!actionBlocker.CanInteract(user) || ! actionBlocker.CanUse(user))
return; return;
// all activates should only fire when in range / unobstructed // all activates should only fire when in range / unobstructed
@@ -275,7 +275,7 @@ namespace Content.Server.Interaction
if (!ValidateInteractAndFace(user, coordinates)) if (!ValidateInteractAndFace(user, coordinates))
return; return;
if (!Get<ActionBlockerSystem>().CanInteract(user)) if (!_actionBlockerSystem.CanInteract(user))
return; return;
// Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null // Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
@@ -344,7 +344,7 @@ namespace Content.Server.Interaction
if (diff.LengthSquared <= 0.01f) if (diff.LengthSquared <= 0.01f)
return; return;
var diffAngle = Angle.FromWorldVec(diff); var diffAngle = Angle.FromWorldVec(diff);
if (Get<ActionBlockerSystem>().CanChangeDirection(user)) if (_actionBlockerSystem.CanChangeDirection(user))
{ {
user.Transform.WorldRotation = diffAngle; user.Transform.WorldRotation = diffAngle;
} }
@@ -394,7 +394,7 @@ namespace Content.Server.Interaction
/// </summary> /// </summary>
public async Task InteractUsing(IEntity user, IEntity used, IEntity target, EntityCoordinates clickLocation) public async Task InteractUsing(IEntity user, IEntity used, IEntity target, EntityCoordinates clickLocation)
{ {
if (!Get<ActionBlockerSystem>().CanInteract(user)) if (!_actionBlockerSystem.CanInteract(user))
return; return;
// all interactions should only happen when in range / unobstructed, so no range check is needed // all interactions should only happen when in range / unobstructed, so no range check is needed
@@ -424,7 +424,7 @@ namespace Content.Server.Interaction
/// </summary> /// </summary>
public void InteractHand(IEntity user, IEntity target) public void InteractHand(IEntity user, IEntity target)
{ {
if (!Get<ActionBlockerSystem>().CanInteract(user)) if (!_actionBlockerSystem.CanInteract(user))
return; return;
// all interactions should only happen when in range / unobstructed, so no range check is needed // all interactions should only happen when in range / unobstructed, so no range check is needed
@@ -457,7 +457,7 @@ namespace Content.Server.Interaction
/// <param name="used"></param> /// <param name="used"></param>
public void TryUseInteraction(IEntity user, IEntity used) public void TryUseInteraction(IEntity user, IEntity used)
{ {
if (user != null && used != null && Get<ActionBlockerSystem>().CanUse(user)) if (user != null && used != null && _actionBlockerSystem.CanUse(user))
{ {
UseInteraction(user, used); UseInteraction(user, used);
} }
@@ -501,7 +501,7 @@ namespace Content.Server.Interaction
/// </summary> /// </summary>
public bool TryThrowInteraction(IEntity user, IEntity item) public bool TryThrowInteraction(IEntity user, IEntity item)
{ {
if (user == null || item == null || !Get<ActionBlockerSystem>().CanThrow(user)) return false; if (user == null || item == null || !_actionBlockerSystem.CanThrow(user)) return false;
ThrownInteraction(user, item); ThrownInteraction(user, item);
return true; return true;
@@ -618,7 +618,7 @@ namespace Content.Server.Interaction
/// </summary> /// </summary>
public bool TryDroppedInteraction(IEntity user, IEntity item, bool intentional) public bool TryDroppedInteraction(IEntity user, IEntity item, bool intentional)
{ {
if (user == null || item == null || !Get<ActionBlockerSystem>().CanDrop(user)) return false; if (user == null || item == null || !_actionBlockerSystem.CanDrop(user)) return false;
DroppedInteraction(user, item, intentional); DroppedInteraction(user, item, intentional);
return true; return true;
@@ -726,7 +726,7 @@ namespace Content.Server.Interaction
if (!ValidateInteractAndFace(user, coordinates)) if (!ValidateInteractAndFace(user, coordinates))
return; return;
if (!Get<ActionBlockerSystem>().CanAttack(user)) if (!_actionBlockerSystem.CanAttack(user))
return; return;
IEntity? targetEnt = null; IEntity? targetEnt = null;

View File

@@ -460,7 +460,7 @@ namespace Content.Server.Inventory.Components
{ {
if (!HasSlot(slot)) if (!HasSlot(slot))
{ {
throw new InvalidOperationException($"Slow '{slot}' does not exist."); throw new InvalidOperationException($"Slot '{slot}' does not exist.");
} }
ForceUnequip(slot); ForceUnequip(slot);

View File

@@ -1,24 +1,9 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -32,343 +17,30 @@ namespace Content.Server.Kitchen.Components
/// it contained, juice an apple and get "apple juice". /// it contained, juice an apple and get "apple juice".
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))] public class ReagentGrinderComponent : SharedReagentGrinderComponent
public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing
{ {
private AudioSystem _audioSystem = default!; [ViewVariables] public ContainerSlot BeakerContainer = default!;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
/// <summary> /// <summary>
/// Can be null since we won't always have a beaker in the grinder. /// Can be null since we won't always have a beaker in the grinder.
/// </summary> /// </summary>
[ViewVariables] private SolutionContainerComponent? _heldBeaker = default!; [ViewVariables] public SolutionContainerComponent? HeldBeaker = default!;
/// <summary> /// <summary>
/// Contains the things that are going to be ground or juiced. /// Contains the things that are going to be ground or juiced.
/// </summary> /// </summary>
[ViewVariables] private Container _chamber = default!; [ViewVariables] public Container Chamber = default!;
[ViewVariables] private bool ChamberEmpty => _chamber.ContainedEntities.Count <= 0;
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentGrinderUiKey.Key);
private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
/// <summary>
/// Should the BoundUI be told to update?
/// </summary>
private bool _uiDirty = true;
/// <summary> /// <summary>
/// Is the machine actively doing something and can't be used right now? /// Is the machine actively doing something and can't be used right now?
/// </summary> /// </summary>
private bool _busy = false; public bool Busy;
//YAML serialization vars //YAML serialization vars
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] private int _storageCap = 16; [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16;
[ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now. [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public int WorkTime = 3500; //3.5 seconds, completely arbitrary for now.
[DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); [DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
[DataField("grindSound")] private SoundSpecifier _grindSound = new SoundPathSpecifier("/Audio/Machines/blender.ogg"); [DataField("grindSound")] private SoundSpecifier _grindSound = new SoundPathSpecifier("/Audio/Machines/blender.ogg");
[DataField("juiceSound")] private SoundSpecifier _juiceSound = new SoundPathSpecifier("/Audio/Machines/juicer.ogg"); [DataField("juiceSound")] private SoundSpecifier _juiceSound = new SoundPathSpecifier("/Audio/Machines/juicer.ogg");
protected override void Initialize()
{
base.Initialize();
//A slot for the beaker where the grounds/juices will go.
_beakerContainer =
ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer");
//A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
_chamber =
ContainerHelpers.EnsureContainer<Container>(Owner, $"{Name}-entityContainerContainer");
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
_audioSystem = EntitySystem.Get<AudioSystem>();
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
OnPowerStateChanged(powerChanged);
break;
}
}
protected override void OnRemove()
{
base.OnRemove();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage -= UserInterfaceOnReceiveMessage;
}
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if(_busy)
{
return;
}
switch(message.Message)
{
case ReagentGrinderGrindStartMessage msg:
if (!Powered) break;
ClickSound();
DoWork(message.Session.AttachedEntity!, GrinderProgram.Grind);
break;
case ReagentGrinderJuiceStartMessage msg:
if (!Powered) break;
ClickSound();
DoWork(message.Session.AttachedEntity!, GrinderProgram.Juice);
break;
case ReagentGrinderEjectChamberAllMessage msg:
if(!ChamberEmpty)
{
ClickSound();
for (var i = _chamber.ContainedEntities.Count - 1; i >= 0; i--)
{
EjectSolid(_chamber.ContainedEntities.ElementAt(i).Uid);
}
}
break;
case ReagentGrinderEjectChamberContentMessage msg:
if (!ChamberEmpty)
{
EjectSolid(msg.EntityID);
ClickSound();
_uiDirty = true;
}
break;
case ReagentGrinderEjectBeakerMessage msg:
ClickSound();
EjectBeaker(message.Session.AttachedEntity);
//EjectBeaker will dirty the UI for us, we don't have to do it explicitly here.
break;
}
}
private void OnPowerStateChanged(PowerChangedMessage e)
{
_uiDirty = true;
}
private void ClickSound()
{
if(_clickSound.TryGetSound(out var sound))
SoundSystem.Play(Filter.Pvs(Owner), sound, Owner, AudioParams.Default.WithVolume(-2f));
}
private void SetAppearance()
{
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(ReagentGrinderVisualState.BeakerAttached, HasBeaker);
}
}
public void OnUpdate()
{
if(_uiDirty)
{
UpdateInterface();
_uiDirty = false;
}
}
// This doesn't check for UI dirtiness so handle that when calling this.
private void UpdateInterface()
{
bool canJuice = false;
bool canGrind = false;
if (HasBeaker)
{
foreach (var entity in _chamber.ContainedEntities)
{
if (!canJuice && entity.HasComponent<JuiceableComponent>()) canJuice = true;
if (!canGrind && entity.HasTag("Grindable")) canGrind = true;
if (canJuice && canGrind) break;
}
}
UserInterface?.SetState(new ReagentGrinderInterfaceState
(
_busy,
HasBeaker,
Powered,
canJuice,
canGrind,
_chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
//Remember the beaker can be null!
_heldBeaker?.Solution.Contents.ToArray()
));
_uiDirty = false;
}
private void EjectSolid(EntityUid entityID)
{
if (_busy)
return;
if (Owner.EntityManager.TryGetEntity(entityID, out var entity))
{
_chamber.Remove(entity);
//Give the ejected entity a tiny bit of offset so each one is apparent in case of a big stack,
//but (hopefully) not enough to clip it through a solid (wall).
entity.RandomOffset(0.4f);
}
_uiDirty = true;
}
/// <summary>
/// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top
/// of the grinder.
/// </summary>
private void EjectBeaker(IEntity? user)
{
if (!HasBeaker || _heldBeaker == null || _busy)
return;
var beaker = _beakerContainer.ContainedEntity;
if(beaker is null)
return;
_beakerContainer.Remove(beaker);
if (user == null || !user.TryGetComponent<HandsComponent>(out var hands) || !_heldBeaker.Owner.TryGetComponent<ItemComponent>(out var item))
return;
hands.PutInHandOrDrop(item);
_heldBeaker = null;
_uiDirty = true;
SetAppearance();
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
{
return;
}
_uiDirty = true;
UserInterface?.Toggle(actor.PlayerSession);
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands))
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands"));
return true;
}
IEntity heldEnt = eventArgs.Using;
//First, check if user is trying to insert a beaker.
//No promise it will be a beaker right now, but whatever.
//Maybe this should whitelist "beaker" in the prototype id of heldEnt?
if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
{
_beakerContainer.Insert(heldEnt);
_heldBeaker = beaker;
_uiDirty = true;
//We are done, return. Insert the beaker and exit!
SetAppearance();
ClickSound();
return true;
}
//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))
{
//Entity did NOT pass the whitelist for grind/juice.
//Wouldn't want the clown grinding up the Captain's ID card now would you?
//Why am I asking you? You're biased.
return false;
}
//Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
//Maybe I should have done that for the microwave too?
if (_chamber.ContainedEntities.Count >= _storageCap)
{
return false;
}
if (!_chamber.Insert(heldEnt))
return false;
_uiDirty = true;
return true;
}
/// <summary>
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
/// </summary>
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
private async void DoWork(IEntity user, GrinderProgram program)
{
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
if(!Powered || _busy || ChamberEmpty || !HasBeaker || _heldBeaker == null)
{
return;
}
_busy = true;
UserInterface?.SendMessage(new ReagentGrinderWorkStartedMessage(program));
switch (program)
{
case GrinderProgram.Grind:
if(_grindSound.TryGetSound(out var grindSound))
SoundSystem.Play(Filter.Pvs(Owner), grindSound, Owner, AudioParams.Default);
//Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in.
Owner.SpawnTimer(_workTime, (Action) (() =>
{
foreach (var item in _chamber.ContainedEntities.ToList())
{
if (!item.HasTag("Grindable")) continue;
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue;
if (_heldBeaker.CurrentVolume + solution.CurrentVolume > _heldBeaker.MaxVolume) continue;
_heldBeaker.TryAddSolution(solution.Solution);
solution.RemoveAllSolution();
item.Delete();
}
_busy = false;
_uiDirty = true;
UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
}));
break;
case GrinderProgram.Juice:
if(_juiceSound.TryGetSound(out var juiceSound))
SoundSystem.Play(Filter.Pvs(Owner), juiceSound, Owner, AudioParams.Default);
Owner.SpawnTimer(_workTime, (Action) (() =>
{
foreach (var item in _chamber.ContainedEntities.ToList())
{
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue;
if (_heldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > _heldBeaker.MaxVolume) continue;
_heldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
item.Delete();
}
UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
_busy = false;
_uiDirty = true;
}));
break;
}
}
} }
} }

View File

@@ -1,19 +1,314 @@
using Content.Server.Kitchen.Components; using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Server.Kitchen.Components;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components;
using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers;
using Content.Shared.Tag;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Kitchen.EntitySystems namespace Content.Server.Kitchen.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
internal sealed class ReagentGrinderSystem : EntitySystem internal sealed class ReagentGrinderSystem : EntitySystem
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new ();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReagentGrinderComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) => EnqueueUiUpdate(component));
SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
}
private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args)
{
if(args.Handled) return;
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
component.Owner.PopupMessage(args.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands"));
args.Handled = true;
return;
}
IEntity heldEnt = args.Used;
//First, check if user is trying to insert a beaker.
//No promise it will be a beaker right now, but whatever.
//Maybe this should whitelist "beaker" in the prototype id of heldEnt?
if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
{
component.BeakerContainer.Insert(heldEnt);
component.HeldBeaker = beaker;
EnqueueUiUpdate(component);
//We are done, return. Insert the beaker and exit!
if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null);
}
ClickSound(component);
args.Handled = true;
return;
}
//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))
{
//Entity did NOT pass the whitelist for grind/juice.
//Wouldn't want the clown grinding up the Captain's ID card now would you?
//Why am I asking you? You're biased.
return;
}
//Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
//Maybe I should have done that for the microwave too?
if (component.Chamber.ContainedEntities.Count >= component.StorageCap)
{
return;
}
if (!component.Chamber.Insert(heldEnt))
{
return;
}
EnqueueUiUpdate(component);
args.Handled = true;
}
private void OnInteractHand(EntityUid uid, ReagentGrinderComponent component, InteractHandEvent args)
{
if (args.Handled) return;
if (!args.User.TryGetComponent(out ActorComponent? actor))
{
return;
}
EnqueueUiUpdate(component);
component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.Toggle(actor.PlayerSession);
args.Handled = true;
}
private void EnqueueUiUpdate(ReagentGrinderComponent component)
{
if(!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component);
}
private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args)
{
EnqueueUiUpdate(component);
//A slot for the beaker where the grounds/juices will go.
component.BeakerContainer =
ContainerHelpers.EnsureContainer<ContainerSlot>(component.Owner, $"{component.Name}-reagentContainerContainer");
//A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
component.Chamber =
ContainerHelpers.EnsureContainer<Container>(component.Owner, $"{component.Name}-entityContainerContainer");
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
if (bui != null)
{
bui.OnReceiveMessage += msg => OnUIMessageReceived(uid, component, msg);
}
}
private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component,
ServerBoundUserInterfaceMessage message)
{
if(component.Busy)
{
return;
}
switch(message.Message)
{
case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break;
ClickSound(component);
DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Grind);
break;
case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg:
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver2) || !receiver2.Powered) break;
ClickSound(component);
DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Juice);
break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
if(component.Chamber.ContainedEntities.Count > 0)
{
ClickSound(component);
for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--)
{
var entity = component.Chamber.ContainedEntities[i];
component.Chamber.Remove(entity);
entity.RandomOffset(0.4f);
}
EnqueueUiUpdate(component);
}
break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg:
if (component.Chamber.ContainedEntities.TryFirstOrDefault(x => x.Uid == msg.EntityID, out var ent))
{
component.Chamber.Remove(ent);
ent.RandomOffset(0.4f);
EnqueueUiUpdate(component);
ClickSound(component);
}
break;
case SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage msg:
ClickSound(component);
EjectBeaker(component, message.Session.AttachedEntity);
EnqueueUiUpdate(component);
break;
}
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
foreach (var comp in ComponentManager.EntityQuery<ReagentGrinderComponent>(true))
while (_uiUpdateQueue.TryDequeue(out var comp))
{ {
comp.OnUpdate(); bool canJuice = false;
bool canGrind = false;
if (comp.BeakerContainer.ContainedEntity != null)
{
foreach (var entity in comp.Chamber.ContainedEntities)
{
if (!canJuice && entity.HasComponent<JuiceableComponent>()) canJuice = true;
if (!canGrind && entity.HasTag("Grindable")) canGrind = true;
if (canJuice && canGrind) break;
}
}
comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(new ReagentGrinderInterfaceState
(
comp.Busy,
comp.BeakerContainer.ContainedEntity != null,
comp.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && receiver.Powered,
canJuice,
canGrind,
comp.Chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
//Remember the beaker can be null!
comp.HeldBeaker?.Solution.Contents.ToArray()
));
} }
} }
/// <summary>
/// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top
/// of the grinder.
/// </summary>
private void EjectBeaker(ReagentGrinderComponent component, IEntity? user)
{
if (component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null || component.Busy)
return;
var beaker = component.BeakerContainer.ContainedEntity;
if(beaker is null)
return;
component.BeakerContainer.Remove(beaker);
if (user == null || !user.TryGetComponent<HandsComponent>(out var hands) || !component.HeldBeaker.Owner.TryGetComponent<ItemComponent>(out var item))
return;
hands.PutInHandOrDrop(item);
component.HeldBeaker = null;
EnqueueUiUpdate(component);
if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null);
}
}
/// <summary>
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
/// </summary>
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
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?
if(!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null)
{
return;
}
component.Busy = true;
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program));
switch (program)
{
case SharedReagentGrinderComponent.GrinderProgram.Grind:
SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/blender.ogg", component.Owner, AudioParams.Default);
//Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in.
component.Owner.SpawnTimer(component.WorkTime, (Action) (() =>
{
foreach (var item in component.Chamber.ContainedEntities.ToList())
{
if (!item.HasTag("Grindable")) continue;
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue;
if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume > component.HeldBeaker.MaxVolume) continue;
component.HeldBeaker.TryAddSolution(solution.Solution);
solution.RemoveAllSolution();
item.Delete();
}
component.Busy = false;
EnqueueUiUpdate(component);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
}));
break;
case SharedReagentGrinderComponent.GrinderProgram.Juice:
SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/juicer.ogg", component.Owner, AudioParams.Default);
component.Owner.SpawnTimer(component.WorkTime, (Action) (() =>
{
foreach (var item in component.Chamber.ContainedEntities.ToList())
{
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue;
if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > component.HeldBeaker.MaxVolume) continue;
component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
item.Delete();
}
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
component.Busy = false;
EnqueueUiUpdate(component);
}));
break;
}
}
private void ClickSound(ReagentGrinderComponent component)
{
SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/machine_switch.ogg", component.Owner, AudioParams.Default.WithVolume(-2f));
}
} }
} }

View File

@@ -142,7 +142,7 @@ namespace Content.Server.Light.Components
void IExamine.Examine(FormattedMessage message, bool inDetailsRange) void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{ {
message.AddMarkup(Loc.GetString("emergency-light-component-on-examine",("batteryStateText", BatteryStateText[State]))); message.AddMarkup(Loc.GetString("emergency-light-component-on-examine",("batteryStateText", Loc.GetString(BatteryStateText[State]))));
} }
public enum EmergencyLightState public enum EmergencyLightState

View File

@@ -56,7 +56,7 @@ namespace Content.Server.Light.Components
/// </summary> /// </summary>
private bool TryActivate() private bool TryActivate()
{ {
if (!Activated) if (!Activated && CurrentState == ExpendableLightState.BrandNew)
{ {
if (Owner.TryGetComponent<ItemComponent>(out var item)) if (Owner.TryGetComponent<ItemComponent>(out var item))
{ {

View File

@@ -0,0 +1,64 @@
using Content.Server.Lock;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Sound;
using Content.Shared.Verbs;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Storage.Components
{
/// <summary>
/// Allows locking/unlocking, with access determined by AccessReader
/// </summary>
[RegisterComponent]
public class LockComponent : Component
{
public override string Name => "Lock";
[ViewVariables(VVAccess.ReadWrite)] [DataField("locked")] public bool Locked { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)] [DataField("unlockingSound")] public SoundSpecifier? UnlockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg");
[ViewVariables(VVAccess.ReadWrite)] [DataField("lockingSound")] public SoundSpecifier? LockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg");
[Verb]
private sealed class ToggleLockVerb : Verb<LockComponent>
{
protected override void GetData(IEntity user, LockComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) ||
component.Owner.TryGetComponent(out EntityStorageComponent? entityStorageComponent) && entityStorageComponent.Open)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock");
}
protected override void Activate(IEntity user, LockComponent component)
{
// Do checks
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) ||
!user.InRangeUnobstructed(component))
{
return;
}
// Call relevant entity system
var lockSystem = user.EntityManager.EntitySysManager.GetEntitySystem<LockSystem>();
var eventData = new ActivateInWorldEvent(user, component.Owner);
if (component.Locked)
{
lockSystem.DoUnlock(component, eventData);
}
else
{
lockSystem.DoLock(component, eventData);
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
using Content.Server.Access.Components;
using Content.Server.Storage.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Notification.Managers;
using Content.Shared.Storage;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Player;
namespace Content.Server.Lock
{
/// <summary>
/// Handles (un)locking and examining of Lock components
/// </summary>
[UsedImplicitly]
public class LockSystem : EntitySystem
{
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LockComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<LockComponent, ActivateInWorldEvent>(OnActivated);
SubscribeLocalEvent<LockComponent, ExaminedEvent>(OnExamined);
}
private void OnStartup(EntityUid eUI, LockComponent lockComp, ComponentStartup args)
{
if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(StorageVisuals.CanLock, true);
}
}
private void OnActivated(EntityUid eUI, LockComponent lockComp, ActivateInWorldEvent args)
{
// Only attempt an unlock by default on Activate
if (lockComp.Locked)
{
DoUnlock(lockComp, args);
}
}
private void OnExamined(EntityUid eUI, LockComponent lockComp, ExaminedEvent args)
{
args.Message.AddText("\n");
args.Message.AddText(Loc.GetString(lockComp.Locked
? "lock-comp-on-examined-is-locked"
: "lock-comp-on-examined-is-unlocked",
("entityName", lockComp.Owner.Name)));
}
public void DoLock(LockComponent lockComp, ActivateInWorldEvent args)
{
if (!HasUserAccess(lockComp, args.User))
{
return;
}
lockComp.Owner.PopupMessage(args.User, Loc.GetString("lock-comp-do-lock-success", ("entityName",lockComp.Owner.Name)));
lockComp.Locked = true;
if(lockComp.LockSound != null)
{
SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.LockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5));
}
if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearanceComp))
{
appearanceComp.SetData(StorageVisuals.Locked, true);
}
args.Handled = true;
}
public void DoUnlock(LockComponent lockComp, ActivateInWorldEvent args )
{
if (!HasUserAccess(lockComp, args.User))
{
return;
}
lockComp.Owner.PopupMessage(args.User, Loc.GetString("lock-comp-do-unlock-success", ("entityName", lockComp.Owner.Name)));
lockComp.Locked = false;
if(lockComp.UnlockSound != null)
{
SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.UnlockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5));
}
if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearanceComp))
{
appearanceComp.SetData(StorageVisuals.Locked, false);
}
// To stop EntityStorageComponent from opening right after the container gets unlocked
args.Handled = true;
}
private static bool HasUserAccess(LockComponent lockComp, IEntity user)
{
if (lockComp.Owner.TryGetComponent(out AccessReader? reader))
{
if (!reader.IsAllowed(user))
{
lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-has-user-access-fail"));
return false;
}
}
return true;
}
}
}

View File

@@ -1,103 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Nutrition.Components
{
/// <summary>
/// This container acts as a master object for things like Pizza, which holds slices.
/// </summary>
/// TODO: Perhaps implement putting food back (pizza boxes) but that really isn't mandatory.
/// This doesn't even need to have an actual Container for right now.
[RegisterComponent]
public sealed class FoodContainer : SharedFoodContainerComponent, IUse
{
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "FoodContainer";
private AppearanceComponent? _appearance;
[DataField("prototypes")]
private Dictionary<string, int>? _prototypes = default;
[DataField("capacity")]
private uint _capacity = 5;
public int Capacity => (int)_capacity;
[ViewVariables]
public int Count => _count;
private int _count = 0;
protected override void Initialize()
{
base.Initialize();
Owner.TryGetComponent(out _appearance);
_count = Capacity;
UpdateAppearance();
}
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
{
return false;
}
var itemToSpawn = Owner.EntityManager.SpawnEntity(GetRandomPrototype(), Owner.Transform.Coordinates);
handsComponent.PutInHandOrDrop(itemToSpawn.GetComponent<ItemComponent>());
_count--;
if (_count < 1)
{
Owner.Delete();
return false;
}
return true;
}
private string? GetRandomPrototype()
{
var defaultProto = _prototypes?.Keys.FirstOrDefault();
if (defaultProto == null)
{
return null;
}
DebugTools.AssertNotNull(_prototypes);
if (_prototypes!.Count == 1)
{
return defaultProto;
}
var probResult = _random.Next(0, 100);
var total = 0;
foreach (var item in _prototypes)
{
total += item.Value;
if (probResult < total)
{
return item.Key;
}
}
return defaultProto;
}
private void UpdateAppearance()
{
_appearance?.SetData(FoodContainerVisuals.Current, Count);
}
}
}

View File

@@ -31,6 +31,7 @@ namespace Content.Server.Pointing.EntitySystems
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
private static readonly TimeSpan PointDelay = TimeSpan.FromSeconds(0.5f); private static readonly TimeSpan PointDelay = TimeSpan.FromSeconds(0.5f);
@@ -112,7 +113,7 @@ namespace Content.Server.Pointing.EntitySystems
return false; return false;
} }
if (EntitySystem.Get<ActionBlockerSystem>().CanChangeDirection(player)) if (_actionBlockerSystem.CanChangeDirection(player))
{ {
var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position; var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position;
if (diff.LengthSquared > 0.01f) if (diff.LengthSquared > 0.01f)

View File

@@ -11,23 +11,25 @@ namespace Content.Server.Power.EntitySystems
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<BatteryComponent, NetworkBatteryPreSync>(PreSync); SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync);
SubscribeLocalEvent<BatteryComponent, NetworkBatteryPostSync>(PostSync); SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
} }
private void PreSync(EntityUid uid, BatteryComponent component, NetworkBatteryPreSync args) private void PreSync(NetworkBatteryPreSync ev)
{ {
var networkBattery = ComponentManager.GetComponent<PowerNetworkBatteryComponent>(uid); foreach (var (bat, netBat) in ComponentManager.EntityQuery<BatteryComponent, PowerNetworkBatteryComponent>())
{
networkBattery.NetworkBattery.Capacity = component.MaxCharge; netBat.NetworkBattery.Capacity = bat.MaxCharge;
networkBattery.NetworkBattery.CurrentStorage = component.CurrentCharge; netBat.NetworkBattery.CurrentStorage = bat.CurrentCharge;
}
} }
private void PostSync(EntityUid uid, BatteryComponent component, NetworkBatteryPostSync args) private void PostSync(NetworkBatteryPostSync ev)
{ {
var networkBattery = ComponentManager.GetComponent<PowerNetworkBatteryComponent>(uid); foreach (var (bat, netBat) in ComponentManager.EntityQuery<BatteryComponent, PowerNetworkBatteryComponent>())
{
component.CurrentCharge = networkBattery.NetworkBattery.CurrentStorage; bat.CurrentCharge = netBat.NetworkBattery.CurrentStorage;
}
} }
public override void Update(float frameTime) public override void Update(float frameTime)

View File

@@ -175,19 +175,13 @@ namespace Content.Server.Power.EntitySystems
} }
// Synchronize batteries // Synchronize batteries
foreach (var battery in ComponentManager.EntityQuery<PowerNetworkBatteryComponent>()) RaiseLocalEvent(new NetworkBatteryPreSync());
{
RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPreSync());
}
// Run power solver. // Run power solver.
_solver.Tick(frameTime, _powerState); _solver.Tick(frameTime, _powerState);
// Synchronize batteries, the other way around. // Synchronize batteries, the other way around.
foreach (var battery in ComponentManager.EntityQuery<PowerNetworkBatteryComponent>()) RaiseLocalEvent(new NetworkBatteryPostSync());
{
RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPostSync());
}
// Send events where necessary. // Send events where necessary.
{ {
@@ -313,7 +307,7 @@ namespace Content.Server.Power.EntitySystems
/// Raised before power network simulation happens, to synchronize battery state from /// Raised before power network simulation happens, to synchronize battery state from
/// components like <see cref="BatteryComponent"/> into <see cref="PowerNetworkBatteryComponent"/>. /// components like <see cref="BatteryComponent"/> into <see cref="PowerNetworkBatteryComponent"/>.
/// </summary> /// </summary>
public sealed class NetworkBatteryPreSync : EntityEventArgs public struct NetworkBatteryPreSync
{ {
} }
@@ -321,7 +315,7 @@ namespace Content.Server.Power.EntitySystems
/// Raised after power network simulation happens, to synchronize battery charge changes from /// Raised after power network simulation happens, to synchronize battery charge changes from
/// <see cref="PowerNetworkBatteryComponent"/> to components like <see cref="BatteryComponent"/>. /// <see cref="PowerNetworkBatteryComponent"/> to components like <see cref="BatteryComponent"/>.
/// </summary> /// </summary>
public sealed class NetworkBatteryPostSync : EntityEventArgs public struct NetworkBatteryPostSync
{ {
} }

View File

@@ -25,7 +25,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -132,7 +131,8 @@ namespace Content.Server.Storage.Components
private bool _beingWelded; private bool _beingWelded;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public bool CanWeldShut { public bool CanWeldShut
{
get => _canWeldShut; get => _canWeldShut;
set set
{ {
@@ -161,6 +161,13 @@ namespace Content.Server.Storage.Components
public virtual void Activate(ActivateEventArgs eventArgs) public virtual void Activate(ActivateEventArgs eventArgs)
{ {
// HACK until EntityStorageComponent gets refactored to the new ECS system
if (Owner.TryGetComponent<LockComponent>(out var @lock) && @lock.Locked)
{
// Do nothing, LockSystem is responsible for handling this case
return;
}
ToggleOpen(eventArgs.User); ToggleOpen(eventArgs.User);
} }
@@ -168,7 +175,7 @@ namespace Content.Server.Storage.Components
{ {
if (IsWeldedShut) if (IsWeldedShut)
{ {
if(!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message"));
return false; return false;
} }
return true; return true;
@@ -468,7 +475,8 @@ namespace Content.Server.Storage.Components
protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data)
{ {
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user)) if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) ||
component.Owner.TryGetComponent(out LockComponent? lockComponent) && lockComponent.Locked) // HACK extra check, until EntityStorage gets refactored
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;
@@ -478,7 +486,7 @@ namespace Content.Server.Storage.Components
{ {
data.Visibility = VerbVisibility.Disabled; data.Visibility = VerbVisibility.Disabled;
var verb = Loc.GetString(component.Open ? "open-toggle-verb-close" : "open-toggle-verb-open"); var verb = Loc.GetString(component.Open ? "open-toggle-verb-close" : "open-toggle-verb-open");
data.Text = Loc.GetString("open-toggle-verb-welded-shut-message",("verb", verb)); data.Text = Loc.GetString("open-toggle-verb-welded-shut-message", ("verb", verb));
return; return;
} }

View File

@@ -81,7 +81,7 @@ namespace Content.Server.Storage.Components
if (_itemContainer.ContainedEntity == null) if (_itemContainer.ContainedEntity == null)
return false; return false;
Owner.PopupMessage(user, Loc.GetString("There was something inside {0}!", ("stash", SecretPartName))); Owner.PopupMessage(user, Loc.GetString("comp-secret-stash-action-get-item-found-something", ("stash", SecretPartName)));
if (user.TryGetComponent(out HandsComponent? hands)) if (user.TryGetComponent(out HandsComponent? hands))
{ {

View File

@@ -1,153 +0,0 @@
using Content.Server.Access.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.Notification.Managers;
using Content.Shared.Sound;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Storage.Components
{
[RegisterComponent]
[ComponentReference(typeof(EntityStorageComponent))]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IStorageComponent))]
public class SecureEntityStorageComponent : EntityStorageComponent
{
public override string Name => "SecureEntityStorage";
[DataField("locked")]
private bool _locked = true;
[DataField("unlockSound")] private SoundSpecifier _unlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg");
[DataField("lockSound")] private SoundSpecifier _lockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg");
[ViewVariables(VVAccess.ReadWrite)]
public bool Locked
{
get => _locked;
set
{
_locked = value;
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(StorageVisuals.Locked, _locked);
}
}
}
protected override void Startup()
{
base.Startup();
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(StorageVisuals.CanLock, true);
}
}
public override void Activate(ActivateEventArgs eventArgs)
{
if (Locked)
{
DoToggleLock(eventArgs.User);
return;
}
base.Activate(eventArgs);
}
public override bool CanOpen(IEntity user, bool silent = false)
{
if (Locked)
{
Owner.PopupMessage(user, "It's locked!");
return false;
}
return base.CanOpen(user, silent);
}
protected override void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data)
{
if (Locked)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
base.OpenVerbGetData(user, component, data);
}
private void DoToggleLock(IEntity user)
{
if (Locked)
{
DoUnlock(user);
}
else
{
DoLock(user);
}
}
private void DoUnlock(IEntity user)
{
if (!CheckAccess(user)) return;
Locked = false;
if(_unlockSound.TryGetSound(out var unlockSound))
SoundSystem.Play(Filter.Pvs(Owner), unlockSound, Owner, AudioParams.Default.WithVolume(-5));
}
private void DoLock(IEntity user)
{
if (!CheckAccess(user)) return;
Locked = true;
if(_lockSound.TryGetSound(out var lockSound))
SoundSystem.Play(Filter.Pvs(Owner), lockSound, Owner, AudioParams.Default.WithVolume(-5));
}
private bool CheckAccess(IEntity user)
{
if (Owner.TryGetComponent(out AccessReader? reader))
{
if (!reader.IsAllowed(user))
{
Owner.PopupMessage(user, Loc.GetString("secure-entity-storage-component-not-allowed-message"));
return false;
}
}
return true;
}
[Verb]
private sealed class ToggleLockVerb : Verb<SecureEntityStorageComponent>
{
protected override void GetData(IEntity user, SecureEntityStorageComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) || component.Open)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock");
}
protected override void Activate(IEntity user, SecureEntityStorageComponent component)
{
component.DoToggleLock(user);
}
}
}
}

View File

@@ -16,6 +16,7 @@ using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -48,10 +49,16 @@ namespace Content.Server.Storage.Components
[DataField("occludesLight")] [DataField("occludesLight")]
private bool _occludesLight = true; private bool _occludesLight = true;
[DataField("quickInsert")] [DataField("quickInsert")]
private bool _quickInsert; //Can insert storables by "attacking" them with the storage entity private bool _quickInsert = false; // Can insert storables by "attacking" them with the storage entity
[DataField("areaInsert")] [DataField("areaInsert")]
private bool _areaInsert; //"Attacking" with the storage entity causes it to insert all nearby storables after a delay private bool _areaInsert = false; // "Attacking" with the storage entity causes it to insert all nearby storables after a delay
[DataField("whitelist")]
private EntityWhitelist? _whitelist = null;
private bool _storageInitialCalculated; private bool _storageInitialCalculated;
private int _storageUsed; private int _storageUsed;
[DataField("capacity")] [DataField("capacity")]
@@ -124,6 +131,11 @@ namespace Content.Server.Storage.Components
return false; return false;
} }
if (_whitelist != null && !_whitelist.IsValid(entity))
{
return false;
}
return true; return true;
} }

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using Content.Shared.Sound;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Storage.Components
{
/// <summary>
/// Spawns items when used in hand.
/// </summary>
[RegisterComponent]
public class SpawnItemsOnUseComponent : Component
{
public override string Name => "SpawnItemsOnUse";
/// <summary>
/// The list of entities to spawn, with amounts and orGroups.
/// </summary>
/// <returns></returns>
[DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new List<EntitySpawnEntry>();
/// <summary>
/// A sound to play when the items are spawned. For example, gift boxes being unwrapped.
/// </summary>
[DataField("sound")]
public SoundSpecifier? Sound = null;
/// <summary>
/// How many uses before the item should delete itself.
/// </summary>
/// <returns></returns>
[DataField("uses")]
public int Uses = 1;
}
}

View File

@@ -17,9 +17,9 @@ namespace Content.Server.Storage.Components
{ {
public override string Name => "StorageFill"; public override string Name => "StorageFill";
[DataField("contents")] private List<StorageFillEntry> _contents = new(); [DataField("contents")] private List<EntitySpawnEntry> _contents = new();
public IReadOnlyList<StorageFillEntry> Contents => _contents; public IReadOnlyList<EntitySpawnEntry> Contents => _contents;
void IMapInit.MapInit() void IMapInit.MapInit()
{ {
@@ -39,7 +39,6 @@ namespace Content.Server.Storage.Components
var alreadySpawnedGroups = new List<string>(); var alreadySpawnedGroups = new List<string>();
foreach (var storageItem in _contents) foreach (var storageItem in _contents)
{ {
if (string.IsNullOrEmpty(storageItem.PrototypeId)) continue;
if (!string.IsNullOrEmpty(storageItem.GroupId) && if (!string.IsNullOrEmpty(storageItem.GroupId) &&
alreadySpawnedGroups.Contains(storageItem.GroupId)) continue; alreadySpawnedGroups.Contains(storageItem.GroupId)) continue;
@@ -58,50 +57,5 @@ namespace Content.Server.Storage.Components
if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId); if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId);
} }
} }
[Serializable]
[DataDefinition]
public struct StorageFillEntry : IPopulateDefaultValues
{
[DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? PrototypeId;
[DataField("prob")] public float SpawnProbability;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("orGroup")] public string GroupId;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
///
/// <example>
/// <para>To define an orGroup in a StorageFill component you
/// need to add it to the entities you want to choose between and
/// add a prob field. In this example there is a 50% chance the storage
/// spawns with Y or Z.
///
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("amount")] public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
} }
} }

View File

@@ -0,0 +1,59 @@
using System;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Storage
{
/// <summary>
/// Dictates a list of items that can be spawned.
/// </summary>
[Serializable]
[DataDefinition]
public struct EntitySpawnEntry : IPopulateDefaultValues
{
[DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PrototypeId;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("prob")]
public float SpawnProbability;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
///
/// <example>
/// <para>To define an orGroup in a StorageFill component you
/// need to add it to the entities you want to choose between and
/// add a prob field. In this example there is a 50% chance the storage
/// spawns with Y or Z.
///
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("orGroup")]
public string? GroupId;
[DataField("amount")]
public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
}

View File

@@ -5,7 +5,7 @@ using JetBrains.Annotations;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Server.Storage namespace Content.Server.Storage.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public class ItemCounterSystem : SharedItemCounterSystem public class ItemCounterSystem : SharedItemCounterSystem

View File

@@ -0,0 +1,68 @@
using System.Collections.Generic;
using Content.Server.Storage.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Storage.EntitySystems
{
public class SpawnItemsOnUseSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpawnItemsOnUseComponent, UseInHandEvent>(OnUseInHand);
}
private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
var owner = EntityManager.GetEntity(uid);
var alreadySpawnedGroups = new List<string>();
IEntity? entityToPlaceInHands = null;
foreach (var storageItem in component.Items)
{
if (!string.IsNullOrEmpty(storageItem.GroupId) &&
alreadySpawnedGroups.Contains(storageItem.GroupId)) continue;
if (storageItem.SpawnProbability != 1f &&
!_random.Prob(storageItem.SpawnProbability))
{
continue;
}
for (var i = 0; i < storageItem.Amount; i++)
{
entityToPlaceInHands = EntityManager.SpawnEntity(storageItem.PrototypeId, args.User.Transform.Coordinates);
}
if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId);
}
if (component.Sound != null)
SoundSystem.Play(Filter.Pvs(owner), component.Sound.GetSound());
component.Uses--;
if (component.Uses == 0)
{
args.Handled = true;
owner.Delete();
}
if (entityToPlaceInHands != null
&& args.User.TryGetComponent<SharedHandsComponent>(out var hands))
{
hands.TryPutInAnyHand(entityToPlaceInHands);
}
}
}
}

View File

@@ -6,7 +6,7 @@ using Robust.Server.Player;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Server.Storage namespace Content.Server.Storage.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
internal sealed class StorageSystem : EntitySystem internal sealed class StorageSystem : EntitySystem
@@ -54,7 +54,7 @@ namespace Content.Server.Storage
{ {
storageComp.HandleEntityMaybeInserted(message); storageComp.HandleEntityMaybeInserted(message);
} }
if (oldParentEntity.TryGetComponent<StorageCounterComponent>(out var newStorageComp)) if (oldParentEntity.TryGetComponent<StorageCounterComponent>(out var newStorageComp))
{ {
newStorageComp.ContainerUpdateAppearance(message.Container); newStorageComp.ContainerUpdateAppearance(message.Container);

View File

@@ -142,6 +142,28 @@ namespace Content.Shared.Atmos
{ {
return (direction & other) == other; return (direction & other) == other;
} }
public static Vector2i CardinalToIntVec(this Direction dir)
{
switch (dir)
{
case Direction.North:
return new Vector2i(0, 1);
case Direction.East:
return new Vector2i(1, 0);
case Direction.South:
return new Vector2i(0, -1);
case Direction.West:
return new Vector2i(-1, 0);
default:
throw new ArgumentException($"Direction dir {dir} is not a cardinal direction", nameof(dir));
}
}
public static Vector2i Offset(this Vector2i pos, Direction dir)
{
return pos + dir.CardinalToIntVec();
}
} }
public sealed class AtmosDirectionFlags { } public sealed class AtmosDirectionFlags { }

View File

@@ -1,6 +1,7 @@
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using System; using System;
// ReSharper disable InconsistentNaming
namespace Content.Shared.Atmos namespace Content.Shared.Atmos
{ {

View File

@@ -175,6 +175,12 @@ namespace Content.Shared.CCVar
* Physics * Physics
*/ */
/// <summary>
/// When a mob is walking should its X / Y movement be relative to its parent (true) or the map (false).
/// </summary>
public static readonly CVarDef<bool> RelativeMovement =
CVarDef.Create("physics.relative_movement", true, CVar.ARCHIVE | CVar.REPLICATED);
public static readonly CVarDef<float> TileFrictionModifier = public static readonly CVarDef<float> TileFrictionModifier =
CVarDef.Create("physics.tile_friction", 40.0f); CVarDef.Create("physics.tile_friction", 40.0f);
@@ -285,7 +291,6 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> AtmosGridImpulse = public static readonly CVarDef<bool> AtmosGridImpulse =
CVarDef.Create("atmos.grid_impulse", false, CVar.SERVERONLY); CVarDef.Create("atmos.grid_impulse", false, CVar.SERVERONLY);
/// <summary> /// <summary>
/// Whether atmos superconduction is enabled. /// Whether atmos superconduction is enabled.
/// </summary> /// </summary>
@@ -293,10 +298,17 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> Superconduction = public static readonly CVarDef<bool> Superconduction =
CVarDef.Create("atmos.superconduction", false, CVar.SERVERONLY); CVarDef.Create("atmos.superconduction", false, CVar.SERVERONLY);
/// <summary>
/// Whether excited groups will be processed and created.
/// </summary>
public static readonly CVarDef<bool> ExcitedGroups =
CVarDef.Create("atmos.excited_groups", true, CVar.SERVERONLY);
/// <summary> /// <summary>
/// Whether all tiles in an excited group will clear themselves once being exposed to space. /// Whether all tiles in an excited group will clear themselves once being exposed to space.
/// Similar to <see cref="MonstermosDepressurization"/>, without none of the tile ripping or /// Similar to <see cref="MonstermosDepressurization"/>, without none of the tile ripping or
/// things being thrown around very violently. /// things being thrown around very violently.
/// Needs <see cref="ExcitedGroups"/> to be enabled to work.
/// </summary> /// </summary>
public static readonly CVarDef<bool> ExcitedGroupsSpaceIsAllConsuming = public static readonly CVarDef<bool> ExcitedGroupsSpaceIsAllConsuming =
CVarDef.Create("atmos.excited_groups_space_is_all_consuming", false, CVar.SERVERONLY); CVarDef.Create("atmos.excited_groups_space_is_all_consuming", false, CVar.SERVERONLY);

View File

@@ -4,6 +4,7 @@ using Content.Shared.Notification.Managers;
using Content.Shared.Physics; using Content.Shared.Physics;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics; using Robust.Shared.Physics;
@@ -17,6 +18,8 @@ namespace Content.Shared.Interaction
[UsedImplicitly] [UsedImplicitly]
public class SharedInteractionSystem : EntitySystem public class SharedInteractionSystem : EntitySystem
{ {
[Dependency] private readonly SharedBroadphaseSystem _sharedBroadphaseSystem = default!;
public const float InteractionRange = 2; public const float InteractionRange = 2;
public const float InteractionRangeSquared = InteractionRange * InteractionRange; public const float InteractionRangeSquared = InteractionRange * InteractionRange;
@@ -46,7 +49,7 @@ namespace Content.Shared.Interaction
predicate ??= _ => false; predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask); var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask);
var rayResults = Get<SharedBroadphaseSystem>().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
if (rayResults.Count == 0) return dir.Length; if (rayResults.Count == 0) return dir.Length;
return (rayResults[0].HitPos - origin.Position).Length; return (rayResults[0].HitPos - origin.Position).Length;
@@ -122,7 +125,7 @@ namespace Content.Shared.Interaction
predicate ??= _ => false; predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask); var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask);
var rayResults = Get<SharedBroadphaseSystem>().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
if (rayResults.Count == 0) return true; if (rayResults.Count == 0) return true;

View File

@@ -51,16 +51,6 @@ namespace Content.Shared.Kitchen.Components
} }
} }
[Serializable, NetSerializable]
public class ReagentGrinderVaporizeReagentIndexedMessage : BoundUserInterfaceMessage
{
public Solution.ReagentQuantity ReagentQuantity;
public ReagentGrinderVaporizeReagentIndexedMessage(Solution.ReagentQuantity reagentQuantity)
{
ReagentQuantity = reagentQuantity;
}
}
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
{ {

View File

@@ -1,7 +1,9 @@
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.CCVar;
using Content.Shared.MobState; using Content.Shared.MobState;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Pulling.Components; using Content.Shared.Pulling.Components;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -9,6 +11,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase; using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Controllers; using Robust.Shared.Physics.Controllers;
using Robust.Shared.Utility;
namespace Content.Shared.Movement namespace Content.Shared.Movement
{ {
@@ -22,10 +25,14 @@ namespace Content.Shared.Movement
private SharedBroadphaseSystem _broadPhaseSystem = default!; private SharedBroadphaseSystem _broadPhaseSystem = default!;
private bool _relativeMovement;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>(); _broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CCVars.RelativeMovement, value => _relativeMovement = value, true);
} }
/// <summary> /// <summary>
@@ -39,12 +46,14 @@ namespace Content.Shared.Movement
// Target velocity. // Target velocity.
var total = (walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed); var total = (walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed);
if (total != Vector2.Zero) var worldTotal = _relativeMovement ? new Angle(mover.Owner.Transform.Parent!.WorldRotation.Theta).RotateVec(total) : total;
if (worldTotal != Vector2.Zero)
{ {
mover.Owner.Transform.LocalRotation = total.GetDir().ToAngle(); mover.Owner.Transform.WorldRotation = worldTotal.GetDir().ToAngle();
} }
physicsComponent.LinearVelocity = total; physicsComponent.LinearVelocity = worldTotal;
} }
/// <summary> /// <summary>
@@ -53,7 +62,8 @@ namespace Content.Shared.Movement
/// <param name="mover"></param> /// <param name="mover"></param>
/// <param name="physicsComponent"></param> /// <param name="physicsComponent"></param>
/// <param name="mobMover"></param> /// <param name="mobMover"></param>
protected void HandleMobMovement(IMoverComponent mover, PhysicsComponent physicsComponent, IMobMoverComponent mobMover) protected void HandleMobMovement(IMoverComponent mover, PhysicsComponent physicsComponent,
IMobMoverComponent mobMover)
{ {
// TODO: Look at https://gameworksdocs.nvidia.com/PhysX/4.1/documentation/physxguide/Manual/CharacterControllers.html?highlight=controller as it has some adviceo n kinematic controllersx // TODO: Look at https://gameworksdocs.nvidia.com/PhysX/4.1/documentation/physxguide/Manual/CharacterControllers.html?highlight=controller as it has some adviceo n kinematic controllersx
if (!UseMobMovement(_broadPhaseSystem, physicsComponent, _mapManager)) if (!UseMobMovement(_broadPhaseSystem, physicsComponent, _mapManager))
@@ -74,30 +84,37 @@ namespace Content.Shared.Movement
if (!touching) if (!touching)
{ {
transform.LocalRotation = physicsComponent.LinearVelocity.GetDir().ToAngle(); transform.WorldRotation = physicsComponent.LinearVelocity.GetDir().ToAngle();
return; return;
} }
} }
// Regular movement. // Regular movement.
// Target velocity. // Target velocity.
// This is relative to the map / grid we're on.
var total = (walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed); var total = (walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed);
var worldTotal = _relativeMovement ?
new Angle(transform.Parent!.WorldRotation.Theta).RotateVec(total) :
total;
DebugTools.Assert(MathHelper.CloseTo(total.Length, worldTotal.Length));
if (weightless) if (weightless)
{ {
total *= mobMover.WeightlessStrength; worldTotal *= mobMover.WeightlessStrength;
} }
if (total != Vector2.Zero) if (worldTotal != Vector2.Zero)
{ {
// This should have its event run during island solver soooo // This should have its event run during island solver soooo
transform.DeferUpdates = true; transform.DeferUpdates = true;
transform.LocalRotation = total.GetDir().ToAngle(); transform.WorldRotation = worldTotal.GetDir().ToAngle();
transform.DeferUpdates = false; transform.DeferUpdates = false;
HandleFootsteps(mover, mobMover); HandleFootsteps(mover, mobMover);
} }
physicsComponent.LinearVelocity = total; physicsComponent.LinearVelocity = worldTotal;
} }
public static bool UseMobMovement(SharedBroadphaseSystem broadPhaseSystem, PhysicsComponent body, IMapManager mapManager) public static bool UseMobMovement(SharedBroadphaseSystem broadPhaseSystem, PhysicsComponent body, IMapManager mapManager)

View File

@@ -1,28 +0,0 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.Nutrition.Components
{
public abstract class SharedFoodContainerComponent : Component
{
}
[NetSerializable, Serializable]
public enum FoodContainerVisualMode
{
/// <summary>
/// Discrete: 50 eggs in a carton -> down to 25, will show 12/12 until it gets below max
/// Rounded: 50 eggs in a carton -> down to 25, will round it to 6 of 12
/// </summary>
Discrete,
Rounded,
}
[NetSerializable, Serializable]
public enum FoodContainerVisuals
{
Capacity,
Current,
}
}

View File

@@ -1,4 +1,6 @@
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.SubFloor namespace Content.Shared.SubFloor
{ {
@@ -13,5 +15,12 @@ namespace Content.Shared.SubFloor
{ {
/// <inheritdoc /> /// <inheritdoc />
public override string Name => "SubFloorHide"; public override string Name => "SubFloorHide";
/// <summary>
/// This entity needs to be anchored to be hid in the subfloor.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("requireAnchored")]
public bool RequireAnchored { get; set; } = true;
} }
} }

View File

@@ -68,8 +68,7 @@ namespace Content.Shared.SubFloor
var transform = ComponentManager.GetComponent<ITransformComponent>(uid); var transform = ComponentManager.GetComponent<ITransformComponent>(uid);
// We do this directly instead of calling UpdateEntity. // We do this directly instead of calling UpdateEntity.
if(_mapManager.TryGetGrid(transform.GridID, out var grid)) UpdateEntity(uid);
UpdateTile(grid, grid.TileIndicesFor(transform.Coordinates));
} }
private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e) private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e)
@@ -113,6 +112,7 @@ namespace Content.Shared.SubFloor
private void UpdateEntity(EntityUid uid) private void UpdateEntity(EntityUid uid)
{ {
var transform = ComponentManager.GetComponent<ITransformComponent>(uid); var transform = ComponentManager.GetComponent<ITransformComponent>(uid);
if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) if (!_mapManager.TryGetGrid(transform.GridID, out var grid))
{ {
// Not being on a grid counts as no subfloor, unhide this. // Not being on a grid counts as no subfloor, unhide this.
@@ -134,6 +134,18 @@ namespace Content.Shared.SubFloor
if (subFloorHideEvent.Handled) if (subFloorHideEvent.Handled)
return; return;
// This might look weird, but basically we only need to query the SubFloorHide and Transform components
// if we are gonna hide the entity and we require it to be anchored to be hidden. Because getting components
// is "expensive", we have a slow path where we query them, and a fast path where we don't.
if (!subFloor
&& ComponentManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent) &&
subFloorHideComponent.RequireAnchored
&& ComponentManager.TryGetComponent(uid, out ITransformComponent? transformComponent))
{
// If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it.
subFloor = !transformComponent.Anchored;
}
// Show sprite // Show sprite
if (ComponentManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent)) if (ComponentManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent))
{ {

View File

@@ -1588,3 +1588,28 @@ Entries:
- {message: Items with missing inhand sprites no longer show a giant ERROR, type: Fix} - {message: Items with missing inhand sprites no longer show a giant ERROR, type: Fix}
id: 282 id: 282
time: '2021-07-25T10:02:48.0000000+00:00' time: '2021-07-25T10:02:48.0000000+00:00'
- author: Seth
changes:
- {message: Added Makeshift Handcuffs into the construction list, type: Add}
id: 283
time: '2021-07-25T15:30:42.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Shuttle-relative movement for when we get shuttle rotation., type: Add}
id: 284
time: '2021-07-25T15:48:22.0000000+00:00'
- author: Seth
changes:
- {message: Added Storage Room to Coats, type: Add}
id: 285
time: '2021-07-26T05:36:27.0000000+00:00'
- author: Seth
changes:
- {message: Added a hardsuit into research directors locker, type: Add}
id: 286
time: '2021-07-26T22:14:10.0000000+00:00'
- author: Seth
changes:
- {message: Added bio suits into bio lockers, type: Add}
id: 287
time: '2021-07-28T17:36:06.0000000+00:00'

View File

@@ -13,7 +13,7 @@ chat-manager-sender-announcement-wrap-message = {$sender} Announcement:
chat-manager-entity-say-wrap-message = {$entityName} says: "{"{0}"}" chat-manager-entity-say-wrap-message = {$entityName} says: "{"{0}"}"
chat-manager-entity-me-wrap-message = {$entityName} {"{0}"} chat-manager-entity-me-wrap-message = {$entityName} {"{0}"}
chat-manager-send-ooc-wrap-message = OOC: {$playerName}: {"{0}"} chat-manager-send-ooc-wrap-message = OOC: {$playerName}: {"{0}"}
chat-manager-send-ooc-patron-wrap-message = OOC: [color={$patronColor}]{$playerName}[/color]: {"{0}"}: chat-manager-send-ooc-patron-wrap-message = OOC: [color={$patronColor}]{$playerName}[/color]: {"{0}"}
chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: {$playerName}: {"{0}"} chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: {$playerName}: {"{0}"}
chat-manager-send-admin-dead-chat-wrap-message = {$adminChannelName}:({$userName}): {"{0}"} chat-manager-send-admin-dead-chat-wrap-message = {$adminChannelName}:({$userName}): {"{0}"}
chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: {$playerName}: {"{0}"} chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: {$playerName}: {"{0}"}
@@ -21,4 +21,4 @@ chat-manager-send-admin-announcement-wrap-message = {$adminChannelName}: {"{0}"}
chat-manager-send-hook-ooc-wrap-message = OOC: (D){$senderName}: {"{0}"} chat-manager-send-hook-ooc-wrap-message = OOC: (D){$senderName}: {"{0}"}
chat-manager-dead-channel-name = DEAD chat-manager-dead-channel-name = DEAD
chat-manager-admin-channel-name = ADMIN chat-manager-admin-channel-name = ADMIN

View File

@@ -0,0 +1,10 @@
lock-comp-on-examined-is-locked = The {$entityName} seems to be locked.
lock-comp-on-examined-is-unlocked = The {$entityName} seems to be unlocked.
lock-comp-do-lock-success = You lock the {$entityName}.
lock-comp-do-unlock-success = You unlock the {$entityName}.
lock-comp-has-user-access-fail = Access denied
## ToggleLockVerb
toggle-lock-verb-unlock = Unlock
toggle-lock-verb-lock = Lock

View File

@@ -1,6 +0,0 @@
secure-entity-storage-component-not-allowed-message = Access denied
## ToggleLockVerb
toggle-lock-verb-unlock = Unlock
toggle-lock-verb-lock = Lock

View File

@@ -12318,15 +12318,6 @@ entities:
uniqueMixes: uniqueMixes:
- volume: 2500 - volume: 2500
temperature: 293.15 temperature: 293.15
molesArchived:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
moles: moles:
- 21.824879 - 21.824879
- 82.10312 - 82.10312

View File

@@ -1,24 +1,59 @@
# - type: entity - type: entity
# id: ClosetL3Filled id: ClosetL3Filled
# suffix: Filled, Generic suffix: Filled, Generic
# parent: ClosetL3 parent: ClosetL3
components:
- type: StorageFill
contents:
- id: ClothingOuterBioGeneral
prob: 1
- id: ClothingHeadHatHoodBioGeneral
prob: 1
- type: entity - type: entity
id: ClosetL3VirologyFilled id: ClosetL3VirologyFilled
suffix: Filled, Virology suffix: Filled, Virology
parent: ClosetL3Virology parent: ClosetL3Virology
components:
- type: StorageFill
contents:
- id: ClothingOuterBioVirology
prob: 1
- id: ClothingHeadHatHoodBioVirology
prob: 1
- type: entity - type: entity
id: ClosetL3SecurityFilled id: ClosetL3SecurityFilled
suffix: Filled, Security suffix: Filled, Security
parent: ClosetL3Security parent: ClosetL3Security
components:
- type: StorageFill
contents:
- id: ClothingOuterBioSecurity
prob: 1
- id: ClothingHeadHatHoodBioSecurity
prob: 1
- type: entity - type: entity
id: ClosetL3JanitorFilled id: ClosetL3JanitorFilled
suffix: Filled, Janitor suffix: Filled, Janitor
parent: ClosetL3Janitor parent: ClosetL3Janitor
components:
- type: StorageFill
contents:
- id: ClothingOuterBioJanitor
prob: 1
- id: ClothingHeadHatHoodBioJanitor
prob: 1
# - type: entity - type: entity
# id: ClosetL3ScienceFilled id: ClosetL3ScienceFilled
# suffix: Filled, Science suffix: Filled, Science
# parent: ClosetL3Virology parent: ClosetL3Virology
components:
- type: StorageFill
contents:
- id: ClothingOuterBioScientist
prob: 1
- id: ClothingHeadHatHoodBioScientist
prob: 1

View File

@@ -147,6 +147,10 @@
prob: 1 prob: 1
- id: ClothingHeadsetMedicalScience - id: ClothingHeadsetMedicalScience
prob: 1 prob: 1
- id: ClothingHeadHelmetHardsuitRd
prob: 1
- id: ClothingOuterHardsuitRd
prob: 1
- id: PlushieSlime - id: PlushieSlime
prob: 0.1 prob: 0.1

View File

@@ -72,6 +72,8 @@
prob: 1 prob: 1
- id: ClothingOuterApronBotanist - id: ClothingOuterApronBotanist
prob: 0.8 prob: 0.8
- id: ClothingBeltPlant
prob: 1
- id: TowercapSeeds - id: TowercapSeeds
prob: 1 prob: 1
- id: BananaSeeds - id: BananaSeeds

View File

@@ -8,6 +8,8 @@
sprite: Clothing/OuterClothing/Coats/bomber.rsi sprite: Clothing/OuterClothing/Coats/bomber.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/bomber.rsi sprite: Clothing/OuterClothing/Coats/bomber.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -19,6 +21,8 @@
sprite: Clothing/OuterClothing/Coats/detective.rsi sprite: Clothing/OuterClothing/Coats/detective.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/detective.rsi sprite: Clothing/OuterClothing/Coats/detective.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -30,6 +34,8 @@
sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -41,6 +47,8 @@
sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -52,6 +60,8 @@
sprite: Clothing/OuterClothing/Coats/insp_coat.rsi sprite: Clothing/OuterClothing/Coats/insp_coat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/insp_coat.rsi sprite: Clothing/OuterClothing/Coats/insp_coat.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -63,6 +73,8 @@
sprite: Clothing/OuterClothing/Coats/jensencoat.rsi sprite: Clothing/OuterClothing/Coats/jensencoat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/jensencoat.rsi sprite: Clothing/OuterClothing/Coats/jensencoat.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -74,6 +86,8 @@
sprite: Clothing/OuterClothing/Coats/labcoat.rsi sprite: Clothing/OuterClothing/Coats/labcoat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/labcoat.rsi sprite: Clothing/OuterClothing/Coats/labcoat.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -85,6 +99,8 @@
sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -96,6 +112,8 @@
sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi
- type: Storage
capacity: 10
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -107,3 +125,5 @@
sprite: Clothing/OuterClothing/Coats/pirate.rsi sprite: Clothing/OuterClothing/Coats/pirate.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Coats/pirate.rsi sprite: Clothing/OuterClothing/Coats/pirate.rsi
- type: Storage
capacity: 10

Some files were not shown because too many files have changed in this diff Show More