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:
@@ -19,14 +19,9 @@ namespace Content.Client.AME.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new AMEWindow();
|
||||
_window = new AMEWindow(this);
|
||||
_window.OnClose += Close;
|
||||
_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>
|
||||
@@ -44,7 +39,7 @@ namespace Content.Client.AME.UI
|
||||
_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));
|
||||
}
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Content.Client/AME/UI/AMEWindow.xaml
Normal file
46
Content.Client/AME/UI/AMEWindow.xaml
Normal 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>
|
||||
74
Content.Client/AME/UI/AMEWindow.xaml.cs
Normal file
74
Content.Client/AME/UI/AMEWindow.xaml.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Content.Client/Access/UI/IdCardConsoleWindow.xaml
Normal file
30
Content.Client/Access/UI/IdCardConsoleWindow.xaml
Normal 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>
|
||||
121
Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
Normal file
121
Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Content.Client/Alerts/UI/AlertsUI.xaml
Normal file
10
Content.Client/Alerts/UI/AlertsUI.xaml
Normal 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>
|
||||
@@ -1,8 +1,9 @@
|
||||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.Chat.UI;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.Alerts.UI
|
||||
@@ -10,13 +11,20 @@ namespace Content.Client.Alerts.UI
|
||||
/// <summary>
|
||||
/// The status effects display on the right side of the screen.
|
||||
/// </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 GridContainer Grid { get; }
|
||||
|
||||
public GridContainer Grid => AlertContainer;
|
||||
|
||||
public AlertsUI()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End);
|
||||
LayoutContainer.SetAnchorTop(this, 0f);
|
||||
@@ -25,28 +33,11 @@ namespace Content.Client.Alerts.UI
|
||||
LayoutContainer.SetMarginBottom(this, -180);
|
||||
LayoutContainer.SetMarginTop(this, 250);
|
||||
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()
|
||||
{
|
||||
base.EnteredTree();
|
||||
var _chatManager = IoCManager.Resolve<IChatManager>();
|
||||
_chatManager.OnChatBoxResized += OnChatResized;
|
||||
OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom));
|
||||
}
|
||||
@@ -54,15 +45,12 @@ namespace Content.Client.Alerts.UI
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
var _chatManager = IoCManager.Resolve<IChatManager>();
|
||||
_chatManager.OnChatBoxResized -= OnChatResized;
|
||||
}
|
||||
|
||||
|
||||
private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs)
|
||||
{
|
||||
// resize us to fit just below the chatbox
|
||||
var _chatManager = IoCManager.Resolve<IChatManager>();
|
||||
if (_chatManager.CurrentChatBox != null)
|
||||
{
|
||||
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
|
||||
// on constraints, otherwise we would use anchors to lay it out
|
||||
base.Resized();
|
||||
Grid.MaxGridHeight = Height;
|
||||
AlertContainer.MaxGridHeight = Height;
|
||||
}
|
||||
|
||||
protected override void UIScaleChanged()
|
||||
{
|
||||
Grid.MaxGridHeight = Height;
|
||||
AlertContainer.MaxGridHeight = Height;
|
||||
base.UIScaleChanged();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
// Gas overlays
|
||||
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 =
|
||||
new();
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var overlay = _atmosphereSystem.GetOverlay(i);
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Content.Client.Audio
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
|
||||
|
||||
private SoundCollectionPrototype _ambientCollection = default!;
|
||||
|
||||
@@ -48,7 +49,7 @@ namespace Content.Client.Audio
|
||||
_client.PlayerJoinedServer += OnJoin;
|
||||
_client.PlayerLeaveServer += OnLeave;
|
||||
|
||||
Get<ClientGameTicker>().LobbyStatusUpdated += LobbySongReceived;
|
||||
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -60,7 +61,7 @@ namespace Content.Client.Audio
|
||||
_client.PlayerJoinedServer -= OnJoin;
|
||||
_client.PlayerLeaveServer -= OnLeave;
|
||||
|
||||
Get<ClientGameTicker>().LobbyStatusUpdated -= LobbySongReceived;
|
||||
_gameTicker.LobbyStatusUpdated -= LobbySongReceived;
|
||||
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
@@ -165,7 +166,7 @@ namespace Content.Client.Audio
|
||||
private void StartLobbyMusic()
|
||||
{
|
||||
EndLobbyMusic();
|
||||
var file = Get<ClientGameTicker>().LobbySong;
|
||||
var file = _gameTicker.LobbySong;
|
||||
if (file == null) // We have not received the lobby song yet.
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -279,34 +280,11 @@ namespace Content.Client.Chemistry.UI
|
||||
UpdatePanelInfo(castState);
|
||||
if (Contents.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(Contents, !castState.HasPower);
|
||||
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
|
||||
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>
|
||||
/// Update the container, buffer, and packaging panels.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Chemistry.Dispenser;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
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>
|
||||
/// Update the UI state when new state data is received from the server.
|
||||
/// </summary>
|
||||
@@ -209,7 +187,7 @@ namespace Content.Client.Chemistry.UI
|
||||
// Disable all buttons if not powered
|
||||
if (Contents.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(Contents, !castState.HasPower);
|
||||
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
|
||||
EjectButton.Disabled = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace Content.Client.DragDrop
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = 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
|
||||
// check logic each update)
|
||||
@@ -69,8 +71,6 @@ namespace Content.Client.DragDrop
|
||||
|
||||
private ShaderInstance? _dropTargetInRangeShader;
|
||||
private ShaderInstance? _dropTargetOutOfRangeShader;
|
||||
private SharedInteractionSystem _interactionSystem = default!;
|
||||
private InputSystem _inputSystem = default!;
|
||||
|
||||
private readonly List<ISpriteComponent> _highlightedSprites = new();
|
||||
|
||||
@@ -80,8 +80,6 @@ namespace Content.Client.DragDrop
|
||||
|
||||
_dropTargetInRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetInRange).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
|
||||
CommandBinds.Builder
|
||||
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false))
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Content.Client.Entry
|
||||
"CablePlacer",
|
||||
"Drink",
|
||||
"Food",
|
||||
"FoodContainer",
|
||||
"MagicMirror",
|
||||
"FloorTile",
|
||||
"ShuttleController",
|
||||
@@ -93,6 +92,7 @@ namespace Content.Client.Entry
|
||||
"ExaminableBattery",
|
||||
"PottedPlantHide",
|
||||
"SecureEntityStorage",
|
||||
"Lock",
|
||||
"PresetIdCard",
|
||||
"SolarControlConsole",
|
||||
"FlashOnTrigger",
|
||||
@@ -277,6 +277,7 @@ namespace Content.Client.Entry
|
||||
"Advertise",
|
||||
"PowerNetworkBattery",
|
||||
"BatteryCharger",
|
||||
"SpawnItemsOnUse"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
15
Content.Client/Kitchen/UI/GrinderMenu.xaml
Normal file
15
Content.Client/Kitchen/UI/GrinderMenu.xaml
Normal 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>
|
||||
131
Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
Normal file
131
Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Content.Client/Kitchen/UI/LabelledContentBox.xaml
Normal file
7
Content.Client/Kitchen/UI/LabelledContentBox.xaml
Normal 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>
|
||||
15
Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
Normal file
15
Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
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 Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Chemistry.Solution.Solution;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Kitchen.UI
|
||||
{
|
||||
@@ -21,8 +14,6 @@ namespace Content.Client.Kitchen.UI
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
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) { }
|
||||
|
||||
@@ -30,22 +21,9 @@ namespace Content.Client.Kitchen.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new GrinderMenu(this);
|
||||
_menu = new GrinderMenu(this, _entityManager, _prototypeManager);
|
||||
_menu.OpenCentered();
|
||||
_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)
|
||||
@@ -56,8 +34,6 @@ namespace Content.Client.Kitchen.UI
|
||||
return;
|
||||
}
|
||||
|
||||
_chamberVisualContents?.Clear();
|
||||
_beakerVisualContents?.Clear();
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
@@ -69,243 +45,19 @@ namespace Content.Client.Kitchen.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (_menu == null)
|
||||
{
|
||||
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);
|
||||
_menu?.UpdateState(cState);
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
base.ReceiveMessage(message);
|
||||
|
||||
if (_menu == null)
|
||||
{
|
||||
return;
|
||||
_menu?.HandleMessage(message);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
//Refresh chamber contents
|
||||
_chamberVisualContents.Clear();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
|
||||
public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
|
||||
public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
|
||||
public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
|
||||
public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
31
Content.Client/UserInterface/ButtonHelpers.cs
Normal file
31
Content.Client/UserInterface/ButtonHelpers.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ namespace Content.Client.Weapons.Melee
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EffectSystem _effectSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -67,7 +68,6 @@ namespace Content.Client.Weapons.Melee
|
||||
source.TryGetComponent(out ISpriteComponent? sourceSprite) &&
|
||||
sourceSprite.BaseRSI?.Path != null)
|
||||
{
|
||||
var sys = Get<EffectSystem>();
|
||||
var curTime = _gameTiming.CurTime;
|
||||
var effect = new EffectSystemMessage
|
||||
{
|
||||
@@ -81,7 +81,8 @@ namespace Content.Client.Weapons.Melee
|
||||
Born = curTime,
|
||||
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
|
||||
};
|
||||
sys.CreateEffect(effect);
|
||||
|
||||
_effectSystem.CreateEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,21 +24,12 @@ namespace Content.Client.Weapons.Ranged
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = 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 int _shotCounter;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
_inputSystem = Get<InputSystem>();
|
||||
_combatModeSystem = Get<CombatModeSystem>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,8 +37,7 @@ namespace Content.Server.AI.Pathfinding.Accessible
|
||||
*/
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private PathfindingSystem _pathfindingSystem = default!;
|
||||
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Queued region updates
|
||||
@@ -80,7 +79,6 @@ namespace Content.Server.AI.Pathfinding.Accessible
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_pathfindingSystem = Get<PathfindingSystem>();
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<PathfindingChunkUpdateMessage>(RecalculateNodeRegions);
|
||||
#if DEBUG
|
||||
|
||||
@@ -27,8 +27,7 @@ namespace Content.Server.AI.Steering
|
||||
// http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
|
||||
private PathfindingSystem _pathfindingSystem = default!;
|
||||
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we try to avoid non-blocking physics objects
|
||||
@@ -87,7 +86,6 @@ namespace Content.Server.AI.Steering
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_pathfindingSystem = Get<PathfindingSystem>();
|
||||
|
||||
for (var i = 0; i < AgentListCount; i++)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public class AirtightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
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())
|
||||
return;
|
||||
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
atmosphereSystem.UpdateAdjacent(gridId, pos);
|
||||
atmosphereSystem.InvalidateTile(gridId, pos);
|
||||
_atmosphereSystem.UpdateAdjacent(gridId, pos);
|
||||
_atmosphereSystem.InvalidateTile(gridId, pos);
|
||||
}
|
||||
|
||||
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Players allowed to see the atmos debug overlay.
|
||||
@@ -127,7 +128,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
AccumulatedFrameTime -= _updateCooldown;
|
||||
|
||||
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
|
||||
// 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++)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public bool MonstermosRipTiles { get; private set; }
|
||||
public bool GridImpulse { get; private set; }
|
||||
public bool Superconduction { get; private set; }
|
||||
public bool ExcitedGroups { get; private set; }
|
||||
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
|
||||
public float AtmosMaxProcessTime { 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.AtmosMaxProcessTime, value => AtmosMaxProcessTime = 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,13 +42,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
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)
|
||||
{
|
||||
return mixture.Temperature * GetHeatCapacity(mixture);
|
||||
@@ -79,7 +72,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
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 oldHeatCapacity = 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.
|
||||
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)
|
||||
{
|
||||
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.
|
||||
@@ -154,17 +147,17 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var moles = receiver.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)
|
||||
{
|
||||
var temperatureDelta = receiver.TemperatureArchived - sharer.TemperatureArchived;
|
||||
var temperatureDelta = receiver.Temperature - sharer.Temperature;
|
||||
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
|
||||
{
|
||||
var heatCapacity = GetHeatCapacityArchived(receiver);
|
||||
var sharerHeatCapacity = GetHeatCapacityArchived(sharer);
|
||||
var heatCapacity = GetHeatCapacity(receiver);
|
||||
var sharerHeatCapacity = GetHeatCapacity(sharer);
|
||||
|
||||
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)
|
||||
{
|
||||
var temperatureDelta = receiver.TemperatureArchived - sharerTemperature;
|
||||
var temperatureDelta = receiver.Temperature - sharerTemperature;
|
||||
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
|
||||
{
|
||||
var heatCapacity = GetHeatCapacityArchived(receiver);
|
||||
var heatCapacity = GetHeatCapacity(receiver);
|
||||
|
||||
if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity)
|
||||
{
|
||||
|
||||
@@ -23,13 +23,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public partial class AtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
private GasTileOverlaySystem _gasTileOverlaySystem = default!;
|
||||
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
|
||||
|
||||
private void InitializeGrid()
|
||||
{
|
||||
_gasTileOverlaySystem = Get<GasTileOverlaySystem>();
|
||||
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,10 +108,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
Volume = exposedVolume * 25f,
|
||||
Temperature = exposedTemperature,
|
||||
SkippedFirstProcess = tile.CurrentCycle > gridAtmosphere.UpdateCounter
|
||||
SkippedFirstProcess = tile.CurrentCycle > gridAtmosphere.UpdateCounter,
|
||||
Valid = true,
|
||||
State = 1
|
||||
};
|
||||
|
||||
tile.Hotspot.Start();
|
||||
|
||||
AddActiveTile(gridAtmosphere, tile);
|
||||
gridAtmosphere.HotspotTiles.Add(tile);
|
||||
@@ -139,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -16,9 +16,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
if (tile.ArchivedCycle < fireCount)
|
||||
Archive(tile, fireCount);
|
||||
|
||||
tile.CurrentCycle = fireCount;
|
||||
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(enemyTile?.Air == null) continue;
|
||||
if (fireCount <= enemyTile.CurrentCycle) continue;
|
||||
Archive(enemyTile, fireCount);
|
||||
|
||||
var shouldShareAir = false;
|
||||
|
||||
if (tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null)
|
||||
if (ExcitedGroups && tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null)
|
||||
{
|
||||
if (tile.ExcitedGroup != enemyTile.ExcitedGroup)
|
||||
{
|
||||
@@ -57,6 +53,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
AddActiveTile(gridAtmosphere, enemyTile);
|
||||
}
|
||||
|
||||
if (ExcitedGroups)
|
||||
{
|
||||
var excitedGroup = tile.ExcitedGroup;
|
||||
excitedGroup ??= enemyTile.ExcitedGroup;
|
||||
|
||||
@@ -71,6 +69,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if(enemyTile.ExcitedGroup == null)
|
||||
ExcitedGroupAddTile(excitedGroup, enemyTile);
|
||||
}
|
||||
|
||||
shouldShareAir = true;
|
||||
}
|
||||
@@ -106,17 +105,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (ConsiderSuperconductivity(gridAtmosphere, tile, true))
|
||||
remove = false;
|
||||
|
||||
if(tile.ExcitedGroup == null && remove)
|
||||
if(ExcitedGroups && tile.ExcitedGroup == null && remove)
|
||||
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)
|
||||
{
|
||||
if (tile.Air == null || tile.ExcitedGroup == null)
|
||||
|
||||
@@ -281,7 +281,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
|
||||
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;
|
||||
case AtmosphereProcessingState.ExcitedGroups:
|
||||
if (!ProcessExcitedGroups(atmosphere))
|
||||
|
||||
@@ -21,9 +21,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (adjacent == null || adjacent.ThermalConductivity == 0f)
|
||||
continue;
|
||||
|
||||
if(adjacent.ArchivedCycle < gridAtmosphere.UpdateCounter)
|
||||
Archive(adjacent, gridAtmosphere.UpdateCounter);
|
||||
|
||||
NeighborConductWithSource(gridAtmosphere, adjacent, tile);
|
||||
|
||||
ConsiderSuperconductivity(gridAtmosphere, adjacent);
|
||||
@@ -37,8 +34,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
if(tile.Air == null)
|
||||
{
|
||||
if(tile.ArchivedCycle < gridAtmosphere.UpdateCounter)
|
||||
Archive(tile, gridAtmosphere.UpdateCounter);
|
||||
return AtmosDirection.All;
|
||||
}
|
||||
|
||||
@@ -128,7 +123,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
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
|
||||
&& tile.HeatCapacity != 0f && other.HeatCapacity != 0f)
|
||||
{
|
||||
@@ -146,7 +141,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (tile.Temperature > Atmospherics.T0C)
|
||||
{
|
||||
// Hardcoded space temperature.
|
||||
var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB);
|
||||
var deltaTemperature = (tile.Temperature - Atmospherics.TCMB);
|
||||
if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider))
|
||||
{
|
||||
var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity *
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasTankSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
private const float TimerDelay = 0.5f;
|
||||
private float _timer = 0f;
|
||||
|
||||
@@ -19,11 +22,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (_timer < TimerDelay) return;
|
||||
_timer -= TimerDelay;
|
||||
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
foreach (var gasTank in EntityManager.ComponentManager.EntityQuery<GasTankComponent>())
|
||||
{
|
||||
atmosphereSystem.React(gasTank.Air, gasTank);
|
||||
_atmosphereSystem.React(gasTank.Air, gasTank);
|
||||
gasTank.CheckStatus();
|
||||
gasTank.UpdateUserInterface();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The tiles that have had their atmos data updated since last tick
|
||||
@@ -58,8 +59,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
private float _updateCooldown;
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
private int _thresholds;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -68,7 +67,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace Content.Server.Atmos
|
||||
[DataField("moles")] [ViewVariables]
|
||||
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
[DataField("molesArchived")] [ViewVariables]
|
||||
public float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
[DataField("temperature")] [ViewVariables]
|
||||
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]
|
||||
public float Volume { get; set; }
|
||||
|
||||
@@ -96,13 +90,6 @@ namespace Content.Server.Atmos
|
||||
Immutable = true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Archive()
|
||||
{
|
||||
Moles.AsSpan().CopyTo(MolesArchived.AsSpan());
|
||||
TemperatureArchived = Temperature;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public float GetMoles(int gasId)
|
||||
{
|
||||
@@ -253,7 +240,6 @@ namespace Content.Server.Atmos
|
||||
{
|
||||
// The arrays MUST have a specific length.
|
||||
Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
|
||||
Array.Resize(ref MolesArchived, Atmospherics.AdjustedNumberOfGases);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
@@ -268,12 +254,10 @@ namespace Content.Server.Atmos
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Moles.SequenceEqual(other.Moles)
|
||||
&& MolesArchived.SequenceEqual(other.MolesArchived)
|
||||
&& _temperature.Equals(other._temperature)
|
||||
&& ReactionResults.SequenceEqual(other.ReactionResults)
|
||||
&& Immutable == other.Immutable
|
||||
&& LastShare.Equals(other.LastShare)
|
||||
&& TemperatureArchived.Equals(other.TemperatureArchived)
|
||||
&& Volume.Equals(other.Volume);
|
||||
}
|
||||
|
||||
@@ -284,13 +268,10 @@ namespace Content.Server.Atmos
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var moles = Moles[i];
|
||||
var molesArchived = MolesArchived[i];
|
||||
hashCode.Add(moles);
|
||||
hashCode.Add(molesArchived);
|
||||
}
|
||||
|
||||
hashCode.Add(_temperature);
|
||||
hashCode.Add(TemperatureArchived);
|
||||
hashCode.Add(Immutable);
|
||||
hashCode.Add(LastShare);
|
||||
hashCode.Add(Volume);
|
||||
@@ -303,11 +284,9 @@ namespace Content.Server.Atmos
|
||||
var newMixture = new GasMixture()
|
||||
{
|
||||
Moles = (float[])Moles.Clone(),
|
||||
MolesArchived = (float[])MolesArchived.Clone(),
|
||||
_temperature = _temperature,
|
||||
Immutable = Immutable,
|
||||
LastShare = LastShare,
|
||||
TemperatureArchived = TemperatureArchived,
|
||||
Volume = Volume,
|
||||
};
|
||||
return newMixture;
|
||||
|
||||
@@ -24,11 +24,5 @@ namespace Content.Server.Atmos
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public byte State;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Valid = true;
|
||||
State = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,15 @@ using Content.Shared.Atmos.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasDualPortVentPumpSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -34,24 +37,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
|
||||
|
||||
if (!vent.Enabled)
|
||||
return;
|
||||
|
||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet)
|
||||
if (!vent.Enabled
|
||||
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet)
|
||||
|| !nodeContainer.TryGetNode(vent.OutletName, out PipeNode? outlet))
|
||||
{
|
||||
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
|
||||
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.
|
||||
if (environment == null)
|
||||
{
|
||||
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
|
||||
return;
|
||||
}
|
||||
|
||||
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 removed = inlet.Air.Remove(transferMoles);
|
||||
atmosphereSystem.Merge(environment, removed);
|
||||
_atmosphereSystem.Merge(environment, removed);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
Get<AtmosphereSystem>().Merge(outlet.Air, removed);
|
||||
_atmosphereSystem.Merge(outlet.Air, removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,22 +25,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var appearance = pump.Owner.GetComponentOrNull<AppearanceComponent>();
|
||||
appearance?.SetData(PressurePumpVisuals.Enabled, false);
|
||||
|
||||
if (!pump.Enabled)
|
||||
return;
|
||||
|
||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
|
||||
if (!pump.Enabled
|
||||
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
|
||||
|| !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet))
|
||||
{
|
||||
appearance?.SetData(PressurePumpVisuals.Enabled, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var outputStartingPressure = outlet.Air.Pressure;
|
||||
|
||||
if (MathHelper.CloseTo(pump.TargetPressure, outputStartingPressure))
|
||||
{
|
||||
appearance?.SetData(PressurePumpVisuals.Enabled, false);
|
||||
return; // No need to pump gas if target has been reached.
|
||||
}
|
||||
|
||||
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
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()
|
||||
{
|
||||
@@ -56,13 +57,12 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
// Some of the gas from the mixture leaks when 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)
|
||||
{
|
||||
var leaked = removed.RemoveRatio(pump.LeakRatio);
|
||||
atmosphereSystem.Merge(tile, leaked);
|
||||
_atmosphereSystem.Merge(tile, leaked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
public class AtmosDeviceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,7 +36,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
return;
|
||||
|
||||
// We try to add the device to a valid atmosphere.
|
||||
if (!Get<AtmosphereSystem>().AddAtmosDevice(component))
|
||||
if (!_atmosphereSystem.AddAtmosDevice(component))
|
||||
return;
|
||||
|
||||
component.LastProcess = _gameTiming.CurTime;
|
||||
@@ -45,7 +46,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
|
||||
public void LeaveAtmosphere(AtmosDeviceComponent component)
|
||||
{
|
||||
if (!Get<AtmosphereSystem>().RemoveAtmosDevice(component))
|
||||
if (!_atmosphereSystem.RemoveAtmosDevice(component))
|
||||
return;
|
||||
|
||||
component.LastProcess = TimeSpan.Zero;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Atmos;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
@@ -15,6 +16,8 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public class AtmosUnsafeUnanchorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BeforeUnanchoredEvent>(OnBeforeUnanchored);
|
||||
@@ -26,7 +29,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
|
||||
return;
|
||||
|
||||
if (Get<AtmosphereSystem>().GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment)
|
||||
if (_atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment)
|
||||
return;
|
||||
|
||||
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))
|
||||
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;
|
||||
|
||||
var lost = 0f;
|
||||
@@ -71,10 +72,10 @@ namespace Content.Server.Atmos.Piping.EntitySystems
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@ using Content.Server.Atmos.Piping.Other.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Other.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasMinerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -21,9 +24,7 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems
|
||||
|
||||
private void OnMinerUpdated(EntityUid uid, GasMinerComponent miner, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
if (!CheckMinerOperation(atmosphereSystem, miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f)
|
||||
if (!CheckMinerOperation(miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f)
|
||||
return;
|
||||
|
||||
// Time to mine some gas.
|
||||
@@ -31,15 +32,15 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems
|
||||
var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature };
|
||||
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.
|
||||
if (atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates))
|
||||
if (_atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates))
|
||||
{
|
||||
miner.Broken = true;
|
||||
return false;
|
||||
|
||||
@@ -19,6 +19,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
@@ -26,6 +27,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public class GasCanisterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -131,23 +134,21 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!nodeContainer.TryGetNode(canister.PortName, out PortablePipeNode? portNode))
|
||||
return;
|
||||
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
atmosphereSystem.React(canister.Air, portNode);
|
||||
_atmosphereSystem.React(canister.Air, portNode);
|
||||
|
||||
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||
{
|
||||
var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume);
|
||||
|
||||
atmosphereSystem.Merge(buffer, net.Air);
|
||||
atmosphereSystem.Merge(buffer, canister.Air);
|
||||
_atmosphereSystem.Merge(buffer, net.Air);
|
||||
_atmosphereSystem.Merge(buffer, canister.Air);
|
||||
|
||||
net.Air.Clear();
|
||||
atmosphereSystem.Merge(net.Air, buffer);
|
||||
_atmosphereSystem.Merge(net.Air, buffer);
|
||||
net.Air.Multiply(net.Air.Volume / buffer.Volume);
|
||||
|
||||
canister.Air.Clear();
|
||||
atmosphereSystem.Merge(canister.Air, buffer);
|
||||
_atmosphereSystem.Merge(canister.Air, buffer);
|
||||
canister.Air.Multiply(canister.Air.Volume / buffer.Volume);
|
||||
}
|
||||
|
||||
@@ -161,12 +162,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (container.ContainedEntities.Count > 0)
|
||||
{
|
||||
var gasTank = container.ContainedEntities[0].GetComponent<GasTankComponent>();
|
||||
atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure);
|
||||
_atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure);
|
||||
}
|
||||
else
|
||||
{
|
||||
var environment = atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true);
|
||||
atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure);
|
||||
var environment = _atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true);
|
||||
_atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,15 @@ using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasOutletInjectorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -32,8 +35,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!nodeContainer.TryGetNode(injector.InletName, out PipeNode? inlet))
|
||||
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)
|
||||
return;
|
||||
@@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
var removed = inlet.Air.Remove(transferMoles);
|
||||
|
||||
atmosphereSystem.Merge(environment, removed);
|
||||
_atmosphereSystem.Merge(environment, removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,15 @@ using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasPassiveVentSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -22,8 +25,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
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)
|
||||
return;
|
||||
@@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
var airTemperature = environment.Temperature > 0 ? environment.Temperature : inlet.Air.Temperature;
|
||||
var transferMoles = pressureDelta * environment.Volume / (airTemperature * Atmospherics.R);
|
||||
var removed = inlet.Air.Remove(transferMoles);
|
||||
atmosphereSystem.Merge(environment, removed);
|
||||
_atmosphereSystem.Merge(environment, removed);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -7,12 +7,15 @@ using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasThermoMachineSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -24,18 +27,16 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var appearance = thermoMachine.Owner.GetComponentOrNull<AppearanceComponent>();
|
||||
|
||||
if (!thermoMachine.Enabled
|
||||
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet))
|
||||
{
|
||||
appearance?.SetData(ThermoMachineVisuals.Enabled, false);
|
||||
|
||||
if (!thermoMachine.Enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet))
|
||||
return;
|
||||
|
||||
var airHeatCapacity = Get<AtmosphereSystem>().GetHeatCapacity(inlet.Air);
|
||||
var airHeatCapacity = _atmosphereSystem.GetHeatCapacity(inlet.Air);
|
||||
var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity;
|
||||
var oldTemperature = inlet.Air.Temperature;
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@ using Content.Shared.Atmos.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasVentPumpSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -33,23 +36,22 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vent.Enabled
|
||||
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe))
|
||||
{
|
||||
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
|
||||
|
||||
if (!vent.Enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe))
|
||||
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.
|
||||
if (environment == null)
|
||||
{
|
||||
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles));
|
||||
_atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles));
|
||||
}
|
||||
}
|
||||
else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0)
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Atmos.Piping.Unary.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
@@ -16,6 +17,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public class GasVentScrubberSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -34,28 +37,24 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scrubber.Enabled
|
||||
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet))
|
||||
{
|
||||
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off);
|
||||
|
||||
if (!scrubber.Enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
var environment = _atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true);
|
||||
|
||||
if (!nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet))
|
||||
return;
|
||||
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
var environment = atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true);
|
||||
|
||||
Scrub(atmosphereSystem, scrubber, appearance, environment, outlet);
|
||||
Scrub(_atmosphereSystem, scrubber, appearance, environment, outlet);
|
||||
|
||||
if (!scrubber.WideNet) return;
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Cannot scrub if tile is null or air-blocked.
|
||||
if (tile == null)
|
||||
return;
|
||||
|
||||
// Cannot scrub if pressure too high.
|
||||
if (outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere)
|
||||
if (tile == null
|
||||
|| outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
|
||||
{
|
||||
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing)
|
||||
{
|
||||
|
||||
@@ -13,18 +13,12 @@ namespace Content.Server.Atmos
|
||||
/// </summary>
|
||||
public class TileAtmosphere : IGasMixtureHolder
|
||||
{
|
||||
[ViewVariables]
|
||||
public int ArchivedCycle;
|
||||
|
||||
[ViewVariables]
|
||||
public int CurrentCycle;
|
||||
|
||||
[ViewVariables]
|
||||
public float Temperature { get; set; } = Atmospherics.T20C;
|
||||
|
||||
[ViewVariables]
|
||||
public float TemperatureArchived { get; set; } = Atmospherics.T20C;
|
||||
|
||||
[ViewVariables]
|
||||
public TileAtmosphere? PressureSpecificTarget { get; set; }
|
||||
|
||||
|
||||
@@ -6,12 +6,15 @@ using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Body.Surgery.Components
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SurgeryToolSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
|
||||
private readonly HashSet<SurgeryToolComponent> _openSurgeryUIs = new();
|
||||
|
||||
public override void Initialize()
|
||||
@@ -54,7 +57,7 @@ namespace Content.Server.Body.Surgery.Components
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Get<ActionBlockerSystem>().CanInteract(tool.PerformerCache) ||
|
||||
if (!_actionBlockerSystem.CanInteract(tool.PerformerCache) ||
|
||||
!tool.PerformerCache.InRangeUnobstructed(tool.BodyCache))
|
||||
{
|
||||
tool.CloseAllSurgeryUIs();
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace Content.Server.Construction
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = 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();
|
||||
|
||||
@@ -170,7 +172,7 @@ namespace Content.Server.Construction
|
||||
if (!materialStep.EntityValid(entity, out var stack))
|
||||
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)
|
||||
continue;
|
||||
@@ -226,8 +228,6 @@ namespace Content.Server.Construction
|
||||
return null;
|
||||
}
|
||||
|
||||
var doAfterSystem = Get<DoAfterSystem>();
|
||||
|
||||
var doAfterArgs = new DoAfterEventArgs(user, doAfterTime)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
@@ -237,7 +237,7 @@ namespace Content.Server.Construction
|
||||
NeedHand = false,
|
||||
};
|
||||
|
||||
if (await doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled)
|
||||
if (await _doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled)
|
||||
{
|
||||
FailCleanup();
|
||||
return null;
|
||||
|
||||
@@ -11,17 +11,7 @@ namespace Content.Server.Destructible
|
||||
public class DestructibleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] public readonly IRobustRandom Random = default!;
|
||||
|
||||
public AudioSystem AudioSystem { get; private set; } = default!;
|
||||
|
||||
public ActSystem ActSystem { get; private set; } = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
AudioSystem = Get<AudioSystem>();
|
||||
ActSystem = Get<ActSystem>();
|
||||
}
|
||||
[Dependency] public readonly AudioSystem AudioSystem = default!;
|
||||
[Dependency] public readonly ActSystem ActSystem = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Content.Server.Engineering.EntitySystems
|
||||
public class SpawnAfterInteractSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -41,7 +43,7 @@ namespace Content.Server.Engineering.EntitySystems
|
||||
if (!IsTileClear())
|
||||
return;
|
||||
|
||||
if (component.DoAfterTime > 0 && TryGet<DoAfterSystem>(out var doAfterSystem))
|
||||
if (component.DoAfterTime > 0)
|
||||
{
|
||||
var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
|
||||
{
|
||||
@@ -49,7 +51,7 @@ namespace Content.Server.Engineering.EntitySystems
|
||||
BreakOnStun = true,
|
||||
PostCheck = IsTileClear,
|
||||
};
|
||||
var result = await doAfterSystem.WaitDoAfter(doAfterArgs);
|
||||
var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
|
||||
|
||||
if (result != DoAfterStatus.Finished)
|
||||
return;
|
||||
@@ -59,7 +61,7 @@ namespace Content.Server.Engineering.EntitySystems
|
||||
return;
|
||||
|
||||
if (component.Owner.TryGetComponent<SharedStackComponent>(out var stackComp)
|
||||
&& component.RemoveOnInteract && !Get<StackSystem>().Use(uid, stackComp, 1))
|
||||
&& component.RemoveOnInteract && !_stackSystem.Use(uid, stackComp, 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -29,6 +30,9 @@ namespace Content.Server.Hands
|
||||
[UsedImplicitly]
|
||||
internal sealed class HandsSystem : SharedHandsSystem
|
||||
{
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -113,12 +117,12 @@ namespace Content.Server.Hands
|
||||
if (!hands.TryGetActiveHeldEntity(out var throwEnt))
|
||||
return false;
|
||||
|
||||
if (!Get<InteractionSystem>().TryThrowInteraction(hands.Owner, throwEnt))
|
||||
if (!_interactionSystem.TryThrowInteraction(hands.Owner, throwEnt))
|
||||
return false;
|
||||
|
||||
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)
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Buckle.Components;
|
||||
using Content.Server.CombatMode;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Pulling;
|
||||
@@ -46,6 +47,7 @@ namespace Content.Server.Interaction
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -175,9 +177,7 @@ namespace Content.Server.Interaction
|
||||
|
||||
private void InteractionActivate(IEntity user, IEntity used)
|
||||
{
|
||||
var actionBlocker = Get<ActionBlockerSystem>();
|
||||
|
||||
if (!actionBlocker.CanInteract(user) || ! actionBlocker.CanUse(user))
|
||||
if (!_actionBlockerSystem.CanInteract(user) || ! _actionBlockerSystem.CanUse(user))
|
||||
return;
|
||||
|
||||
// all activates should only fire when in range / unobstructed
|
||||
@@ -275,7 +275,7 @@ namespace Content.Server.Interaction
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
return;
|
||||
|
||||
if (!Get<ActionBlockerSystem>().CanInteract(user))
|
||||
if (!_actionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
// 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)
|
||||
return;
|
||||
var diffAngle = Angle.FromWorldVec(diff);
|
||||
if (Get<ActionBlockerSystem>().CanChangeDirection(user))
|
||||
if (_actionBlockerSystem.CanChangeDirection(user))
|
||||
{
|
||||
user.Transform.WorldRotation = diffAngle;
|
||||
}
|
||||
@@ -394,7 +394,7 @@ namespace Content.Server.Interaction
|
||||
/// </summary>
|
||||
public async Task InteractUsing(IEntity user, IEntity used, IEntity target, EntityCoordinates clickLocation)
|
||||
{
|
||||
if (!Get<ActionBlockerSystem>().CanInteract(user))
|
||||
if (!_actionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
@@ -424,7 +424,7 @@ namespace Content.Server.Interaction
|
||||
/// </summary>
|
||||
public void InteractHand(IEntity user, IEntity target)
|
||||
{
|
||||
if (!Get<ActionBlockerSystem>().CanInteract(user))
|
||||
if (!_actionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
// 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>
|
||||
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);
|
||||
}
|
||||
@@ -501,7 +501,7 @@ namespace Content.Server.Interaction
|
||||
/// </summary>
|
||||
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);
|
||||
return true;
|
||||
@@ -618,7 +618,7 @@ namespace Content.Server.Interaction
|
||||
/// </summary>
|
||||
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);
|
||||
return true;
|
||||
@@ -726,7 +726,7 @@ namespace Content.Server.Interaction
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
return;
|
||||
|
||||
if (!Get<ActionBlockerSystem>().CanAttack(user))
|
||||
if (!_actionBlockerSystem.CanAttack(user))
|
||||
return;
|
||||
|
||||
IEntity? targetEnt = null;
|
||||
|
||||
@@ -460,7 +460,7 @@ namespace Content.Server.Inventory.Components
|
||||
{
|
||||
if (!HasSlot(slot))
|
||||
{
|
||||
throw new InvalidOperationException($"Slow '{slot}' does not exist.");
|
||||
throw new InvalidOperationException($"Slot '{slot}' does not exist.");
|
||||
}
|
||||
|
||||
ForceUnequip(slot);
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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.Kitchen.Components;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -32,343 +17,30 @@ namespace Content.Server.Kitchen.Components
|
||||
/// it contained, juice an apple and get "apple juice".
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing
|
||||
public class ReagentGrinderComponent : SharedReagentGrinderComponent
|
||||
{
|
||||
private AudioSystem _audioSystem = default!;
|
||||
[ViewVariables] private ContainerSlot _beakerContainer = default!;
|
||||
[ViewVariables] public ContainerSlot BeakerContainer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Can be null since we won't always have a beaker in the grinder.
|
||||
/// </summary>
|
||||
[ViewVariables] private SolutionContainerComponent? _heldBeaker = default!;
|
||||
[ViewVariables] public SolutionContainerComponent? HeldBeaker = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the things that are going to be ground or juiced.
|
||||
/// </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>
|
||||
/// Is the machine actively doing something and can't be used right now?
|
||||
/// </summary>
|
||||
private bool _busy = false;
|
||||
public bool Busy;
|
||||
|
||||
//YAML serialization vars
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] private int _storageCap = 16;
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now.
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16;
|
||||
[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("grindSound")] private SoundSpecifier _grindSound = new SoundPathSpecifier("/Audio/Machines/blender.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
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
|
||||
{
|
||||
[UsedImplicitly]
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Content.Server.Light.Components
|
||||
|
||||
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
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Content.Server.Light.Components
|
||||
/// </summary>
|
||||
private bool TryActivate()
|
||||
{
|
||||
if (!Activated)
|
||||
if (!Activated && CurrentState == ExpendableLightState.BrandNew)
|
||||
{
|
||||
if (Owner.TryGetComponent<ItemComponent>(out var item))
|
||||
{
|
||||
|
||||
64
Content.Server/Lock/LockComponent.cs
Normal file
64
Content.Server/Lock/LockComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Content.Server/Lock/LockSystem.cs
Normal file
116
Content.Server/Lock/LockSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
|
||||
private static readonly TimeSpan PointDelay = TimeSpan.FromSeconds(0.5f);
|
||||
|
||||
@@ -112,7 +113,7 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EntitySystem.Get<ActionBlockerSystem>().CanChangeDirection(player))
|
||||
if (_actionBlockerSystem.CanChangeDirection(player))
|
||||
{
|
||||
var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position;
|
||||
if (diff.LengthSquared > 0.01f)
|
||||
|
||||
@@ -11,23 +11,25 @@ namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BatteryComponent, NetworkBatteryPreSync>(PreSync);
|
||||
SubscribeLocalEvent<BatteryComponent, NetworkBatteryPostSync>(PostSync);
|
||||
SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync);
|
||||
SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
|
||||
}
|
||||
|
||||
private void PreSync(EntityUid uid, BatteryComponent component, NetworkBatteryPreSync args)
|
||||
private void PreSync(NetworkBatteryPreSync ev)
|
||||
{
|
||||
var networkBattery = ComponentManager.GetComponent<PowerNetworkBatteryComponent>(uid);
|
||||
|
||||
networkBattery.NetworkBattery.Capacity = component.MaxCharge;
|
||||
networkBattery.NetworkBattery.CurrentStorage = component.CurrentCharge;
|
||||
foreach (var (bat, netBat) in ComponentManager.EntityQuery<BatteryComponent, PowerNetworkBatteryComponent>())
|
||||
{
|
||||
netBat.NetworkBattery.Capacity = bat.MaxCharge;
|
||||
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);
|
||||
|
||||
component.CurrentCharge = networkBattery.NetworkBattery.CurrentStorage;
|
||||
foreach (var (bat, netBat) in ComponentManager.EntityQuery<BatteryComponent, PowerNetworkBatteryComponent>())
|
||||
{
|
||||
bat.CurrentCharge = netBat.NetworkBattery.CurrentStorage;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -175,19 +175,13 @@ namespace Content.Server.Power.EntitySystems
|
||||
}
|
||||
|
||||
// Synchronize batteries
|
||||
foreach (var battery in ComponentManager.EntityQuery<PowerNetworkBatteryComponent>())
|
||||
{
|
||||
RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPreSync());
|
||||
}
|
||||
RaiseLocalEvent(new NetworkBatteryPreSync());
|
||||
|
||||
// Run power solver.
|
||||
_solver.Tick(frameTime, _powerState);
|
||||
|
||||
// Synchronize batteries, the other way around.
|
||||
foreach (var battery in ComponentManager.EntityQuery<PowerNetworkBatteryComponent>())
|
||||
{
|
||||
RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPostSync());
|
||||
}
|
||||
RaiseLocalEvent(new NetworkBatteryPostSync());
|
||||
|
||||
// Send events where necessary.
|
||||
{
|
||||
@@ -313,7 +307,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
/// Raised before power network simulation happens, to synchronize battery state from
|
||||
/// components like <see cref="BatteryComponent"/> into <see cref="PowerNetworkBatteryComponent"/>.
|
||||
/// </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
|
||||
/// <see cref="PowerNetworkBatteryComponent"/> to components like <see cref="BatteryComponent"/>.
|
||||
/// </summary>
|
||||
public sealed class NetworkBatteryPostSync : EntityEventArgs
|
||||
public struct NetworkBatteryPostSync
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -132,7 +131,8 @@ namespace Content.Server.Storage.Components
|
||||
private bool _beingWelded;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanWeldShut {
|
||||
public bool CanWeldShut
|
||||
{
|
||||
get => _canWeldShut;
|
||||
set
|
||||
{
|
||||
@@ -161,6 +161,13 @@ namespace Content.Server.Storage.Components
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -168,7 +175,7 @@ namespace Content.Server.Storage.Components
|
||||
{
|
||||
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 true;
|
||||
@@ -468,7 +475,8 @@ namespace Content.Server.Storage.Components
|
||||
|
||||
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;
|
||||
return;
|
||||
@@ -478,7 +486,7 @@ namespace Content.Server.Storage.Components
|
||||
{
|
||||
data.Visibility = VerbVisibility.Disabled;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Content.Server.Storage.Components
|
||||
if (_itemContainer.ContainedEntity == null)
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Notification;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -48,10 +49,16 @@ namespace Content.Server.Storage.Components
|
||||
|
||||
[DataField("occludesLight")]
|
||||
private bool _occludesLight = true;
|
||||
|
||||
[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")]
|
||||
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 int _storageUsed;
|
||||
[DataField("capacity")]
|
||||
@@ -124,6 +131,11 @@ namespace Content.Server.Storage.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_whitelist != null && !_whitelist.IsValid(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ namespace Content.Server.Storage.Components
|
||||
{
|
||||
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()
|
||||
{
|
||||
@@ -39,7 +39,6 @@ namespace Content.Server.Storage.Components
|
||||
var alreadySpawnedGroups = new List<string>();
|
||||
foreach (var storageItem in _contents)
|
||||
{
|
||||
if (string.IsNullOrEmpty(storageItem.PrototypeId)) continue;
|
||||
if (!string.IsNullOrEmpty(storageItem.GroupId) &&
|
||||
alreadySpawnedGroups.Contains(storageItem.GroupId)) continue;
|
||||
|
||||
@@ -58,50 +57,5 @@ namespace Content.Server.Storage.Components
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
Content.Server/Storage/EntitySpawnEntry.cs
Normal file
59
Content.Server/Storage/EntitySpawnEntry.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Storage
|
||||
namespace Content.Server.Storage.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ItemCounterSystem : SharedItemCounterSystem
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Storage
|
||||
namespace Content.Server.Storage.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class StorageSystem : EntitySystem
|
||||
@@ -142,6 +142,28 @@ namespace Content.Shared.Atmos
|
||||
{
|
||||
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 { }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Content.Shared.Atmos
|
||||
{
|
||||
|
||||
@@ -175,6 +175,12 @@ namespace Content.Shared.CCVar
|
||||
* 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 =
|
||||
CVarDef.Create("physics.tile_friction", 40.0f);
|
||||
|
||||
@@ -285,7 +291,6 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> AtmosGridImpulse =
|
||||
CVarDef.Create("atmos.grid_impulse", false, CVar.SERVERONLY);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether atmos superconduction is enabled.
|
||||
/// </summary>
|
||||
@@ -293,10 +298,17 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> Superconduction =
|
||||
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>
|
||||
/// 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
|
||||
/// things being thrown around very violently.
|
||||
/// Needs <see cref="ExcitedGroups"/> to be enabled to work.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ExcitedGroupsSpaceIsAllConsuming =
|
||||
CVarDef.Create("atmos.excited_groups_space_is_all_consuming", false, CVar.SERVERONLY);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Physics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -17,6 +18,8 @@ namespace Content.Shared.Interaction
|
||||
[UsedImplicitly]
|
||||
public class SharedInteractionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedBroadphaseSystem _sharedBroadphaseSystem = default!;
|
||||
|
||||
public const float InteractionRange = 2;
|
||||
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
||||
|
||||
@@ -46,7 +49,7 @@ namespace Content.Shared.Interaction
|
||||
|
||||
predicate ??= _ => false;
|
||||
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;
|
||||
return (rayResults[0].HitPos - origin.Position).Length;
|
||||
@@ -122,7 +125,7 @@ namespace Content.Shared.Interaction
|
||||
predicate ??= _ => false;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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]
|
||||
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -9,6 +11,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Movement
|
||||
{
|
||||
@@ -22,10 +25,14 @@ namespace Content.Shared.Movement
|
||||
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private bool _relativeMovement;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CCVars.RelativeMovement, value => _relativeMovement = value, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,12 +46,14 @@ namespace Content.Shared.Movement
|
||||
// Target velocity.
|
||||
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>
|
||||
@@ -53,7 +62,8 @@ namespace Content.Shared.Movement
|
||||
/// <param name="mover"></param>
|
||||
/// <param name="physicsComponent"></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
|
||||
if (!UseMobMovement(_broadPhaseSystem, physicsComponent, _mapManager))
|
||||
@@ -74,30 +84,37 @@ namespace Content.Shared.Movement
|
||||
|
||||
if (!touching)
|
||||
{
|
||||
transform.LocalRotation = physicsComponent.LinearVelocity.GetDir().ToAngle();
|
||||
transform.WorldRotation = physicsComponent.LinearVelocity.GetDir().ToAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Regular movement.
|
||||
// Target velocity.
|
||||
// This is relative to the map / grid we're on.
|
||||
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)
|
||||
{
|
||||
total *= mobMover.WeightlessStrength;
|
||||
worldTotal *= mobMover.WeightlessStrength;
|
||||
}
|
||||
|
||||
if (total != Vector2.Zero)
|
||||
if (worldTotal != Vector2.Zero)
|
||||
{
|
||||
// This should have its event run during island solver soooo
|
||||
transform.DeferUpdates = true;
|
||||
transform.LocalRotation = total.GetDir().ToAngle();
|
||||
transform.WorldRotation = worldTotal.GetDir().ToAngle();
|
||||
transform.DeferUpdates = false;
|
||||
HandleFootsteps(mover, mobMover);
|
||||
}
|
||||
|
||||
physicsComponent.LinearVelocity = total;
|
||||
physicsComponent.LinearVelocity = worldTotal;
|
||||
}
|
||||
|
||||
public static bool UseMobMovement(SharedBroadphaseSystem broadPhaseSystem, PhysicsComponent body, IMapManager mapManager)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.SubFloor
|
||||
{
|
||||
@@ -13,5 +15,12 @@ namespace Content.Shared.SubFloor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ namespace Content.Shared.SubFloor
|
||||
var transform = ComponentManager.GetComponent<ITransformComponent>(uid);
|
||||
|
||||
// We do this directly instead of calling UpdateEntity.
|
||||
if(_mapManager.TryGetGrid(transform.GridID, out var grid))
|
||||
UpdateTile(grid, grid.TileIndicesFor(transform.Coordinates));
|
||||
UpdateEntity(uid);
|
||||
}
|
||||
|
||||
private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e)
|
||||
@@ -113,6 +112,7 @@ namespace Content.Shared.SubFloor
|
||||
private void UpdateEntity(EntityUid uid)
|
||||
{
|
||||
var transform = ComponentManager.GetComponent<ITransformComponent>(uid);
|
||||
|
||||
if (!_mapManager.TryGetGrid(transform.GridID, out var grid))
|
||||
{
|
||||
// Not being on a grid counts as no subfloor, unhide this.
|
||||
@@ -134,6 +134,18 @@ namespace Content.Shared.SubFloor
|
||||
if (subFloorHideEvent.Handled)
|
||||
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
|
||||
if (ComponentManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent))
|
||||
{
|
||||
|
||||
@@ -1588,3 +1588,28 @@ Entries:
|
||||
- {message: Items with missing inhand sprites no longer show a giant ERROR, type: Fix}
|
||||
id: 282
|
||||
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'
|
||||
|
||||
@@ -13,7 +13,7 @@ chat-manager-sender-announcement-wrap-message = {$sender} Announcement:
|
||||
chat-manager-entity-say-wrap-message = {$entityName} says: "{"{0}"}"
|
||||
chat-manager-entity-me-wrap-message = {$entityName} {"{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-admin-dead-chat-wrap-message = {$adminChannelName}:({$userName}): {"{0}"}
|
||||
chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: {$playerName}: {"{0}"}
|
||||
|
||||
10
Resources/Locale/en-US/lock/lock-component.ftl
Normal file
10
Resources/Locale/en-US/lock/lock-component.ftl
Normal 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
|
||||
@@ -1,6 +0,0 @@
|
||||
secure-entity-storage-component-not-allowed-message = Access denied
|
||||
|
||||
## ToggleLockVerb
|
||||
|
||||
toggle-lock-verb-unlock = Unlock
|
||||
toggle-lock-verb-lock = Lock
|
||||
@@ -12318,15 +12318,6 @@ entities:
|
||||
uniqueMixes:
|
||||
- volume: 2500
|
||||
temperature: 293.15
|
||||
molesArchived:
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
moles:
|
||||
- 21.824879
|
||||
- 82.10312
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
# - type: entity
|
||||
# id: ClosetL3Filled
|
||||
# suffix: Filled, Generic
|
||||
# parent: ClosetL3
|
||||
- type: entity
|
||||
id: ClosetL3Filled
|
||||
suffix: Filled, Generic
|
||||
parent: ClosetL3
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: ClothingOuterBioGeneral
|
||||
prob: 1
|
||||
- id: ClothingHeadHatHoodBioGeneral
|
||||
prob: 1
|
||||
|
||||
- type: entity
|
||||
id: ClosetL3VirologyFilled
|
||||
suffix: Filled, Virology
|
||||
parent: ClosetL3Virology
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: ClothingOuterBioVirology
|
||||
prob: 1
|
||||
- id: ClothingHeadHatHoodBioVirology
|
||||
prob: 1
|
||||
|
||||
- type: entity
|
||||
id: ClosetL3SecurityFilled
|
||||
suffix: Filled, Security
|
||||
parent: ClosetL3Security
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: ClothingOuterBioSecurity
|
||||
prob: 1
|
||||
- id: ClothingHeadHatHoodBioSecurity
|
||||
prob: 1
|
||||
|
||||
- type: entity
|
||||
id: ClosetL3JanitorFilled
|
||||
suffix: Filled, Janitor
|
||||
parent: ClosetL3Janitor
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: ClothingOuterBioJanitor
|
||||
prob: 1
|
||||
- id: ClothingHeadHatHoodBioJanitor
|
||||
prob: 1
|
||||
|
||||
# - type: entity
|
||||
# id: ClosetL3ScienceFilled
|
||||
# suffix: Filled, Science
|
||||
# parent: ClosetL3Virology
|
||||
- type: entity
|
||||
id: ClosetL3ScienceFilled
|
||||
suffix: Filled, Science
|
||||
parent: ClosetL3Virology
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: ClothingOuterBioScientist
|
||||
prob: 1
|
||||
- id: ClothingHeadHatHoodBioScientist
|
||||
prob: 1
|
||||
|
||||
@@ -147,6 +147,10 @@
|
||||
prob: 1
|
||||
- id: ClothingHeadsetMedicalScience
|
||||
prob: 1
|
||||
- id: ClothingHeadHelmetHardsuitRd
|
||||
prob: 1
|
||||
- id: ClothingOuterHardsuitRd
|
||||
prob: 1
|
||||
- id: PlushieSlime
|
||||
prob: 0.1
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
prob: 1
|
||||
- id: ClothingOuterApronBotanist
|
||||
prob: 0.8
|
||||
- id: ClothingBeltPlant
|
||||
prob: 1
|
||||
- id: TowercapSeeds
|
||||
prob: 1
|
||||
- id: BananaSeeds
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/bomber.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/bomber.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -19,6 +21,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/detective.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/detective.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -30,6 +34,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -41,6 +47,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -52,6 +60,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/insp_coat.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/insp_coat.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -63,6 +73,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/jensencoat.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/jensencoat.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -74,6 +86,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -85,6 +99,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -96,6 +112,8 @@
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi
|
||||
- type: Storage
|
||||
capacity: 10
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
@@ -107,3 +125,5 @@
|
||||
sprite: Clothing/OuterClothing/Coats/pirate.rsi
|
||||
- type: Clothing
|
||||
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
Reference in New Issue
Block a user