Merge branch 'master' into 2021-07-16/windoors

# Conflicts:
#	Content.Server/Doors/Components/AirlockComponent.cs
This commit is contained in:
Kara D
2021-08-05 17:36:13 -07:00
299 changed files with 6186 additions and 3919 deletions

View File

@@ -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));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using Content.Client.Actions.Assignments;
using Content.Client.Actions.UI;
using Content.Client.Hands;
using Content.Client.Inventory;
using Content.Client.Items.UI;
using Content.Client.Items.Managers;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Robust.Client.GameObjects;
@@ -26,12 +25,13 @@ namespace Content.Client.Actions
public const byte Slots = 10;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[ComponentDependency] private readonly HandsComponent? _handsComponent = null;
[ComponentDependency] private readonly ClientInventoryComponent? _inventoryComponent = null;
private ActionsUI? _ui;
private readonly List<ItemSlotButton> _highlightingItemSlots = new();
private EntityUid _highlightedEntity;
/// <summary>
/// Current assignments for all hotbars / slots for this entity.
@@ -225,26 +225,8 @@ namespace Content.Client.Actions
{
StopHighlightingItemSlots();
// figure out if it's in hand or inventory and highlight it
foreach (var hand in _handsComponent!.Gui!.Hands)
{
if (hand.HeldItem != item || hand.HandButton == null) continue;
_highlightingItemSlots.Add(hand.HandButton);
hand.HandButton.Highlight(true);
return;
}
foreach (var (slot, slotItem) in _inventoryComponent!.AllSlots)
{
if (slotItem != item) continue;
foreach (var itemSlotButton in
_inventoryComponent.InterfaceController.GetItemSlotButtons(slot))
{
_highlightingItemSlots.Add(itemSlotButton);
itemSlotButton.Highlight(true);
}
return;
}
_highlightedEntity = item.Uid;
_itemSlotManager.HighlightEntity(item.Uid);
}
/// <summary>
@@ -252,11 +234,11 @@ namespace Content.Client.Actions
/// </summary>
public void StopHighlightingItemSlots()
{
foreach (var itemSlot in _highlightingItemSlots)
{
itemSlot.Highlight(false);
}
_highlightingItemSlots.Clear();
if (_highlightedEntity == default)
return;
_itemSlotManager.UnHighlightEntity(_highlightedEntity);
_highlightedEntity = default;
}
public void ToggleActionsMenu()

View File

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

View File

@@ -1,8 +1,9 @@
using Content.Client.Chat.Managers;
using Content.Client.Chat.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();
}
}

View File

@@ -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);

View File

@@ -9,22 +9,13 @@ namespace Content.Client.Atmos.Visualizers
[UsedImplicitly]
public abstract class EnabledAtmosDeviceVisualizer : AppearanceVisualizer
{
[DataField("disabledState")]
private string _disabledState = string.Empty;
[DataField("enabledState")]
private string _enabledState = string.Empty;
protected abstract object LayerMap { get; }
protected abstract Enum DataKey { get; }
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent? sprite))
return;
sprite.LayerMapSet(LayerMap, sprite.AddLayerState(_enabledState));
sprite.LayerSetVisible(LayerMap, false);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
@@ -32,8 +23,8 @@ namespace Content.Client.Atmos.Visualizers
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
return;
if(component.TryGetData(DataKey, out bool enabled))
sprite.LayerSetVisible(LayerMap, enabled);
if(component.TryGetData(DataKey, out bool enabled) && sprite.LayerMapTryGet(LayerMap, out var layer))
sprite.LayerSetState(layer, enabled ? _enabledState : _disabledState);
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
namespace Content.Client.Atmos.Visualizers
{
[UsedImplicitly]
public class GasFilterVisualizer : EnabledAtmosDeviceVisualizer
{
protected override object LayerMap => Layers.Enabled;
protected override Enum DataKey => FilterVisuals.Enabled;
enum Layers : byte
{
Enabled,
}
}
}

View File

@@ -46,7 +46,7 @@ namespace Content.Client.Atmos.Visualizers
}
}
private enum Layers
private enum Layers : byte
{
ConnectedToPort,
}

View File

@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
protected override object LayerMap => Layers.Enabled;
protected override Enum DataKey => OutletInjectorVisuals.Enabled;
enum Layers
enum Layers : byte
{
Enabled,
}

View File

@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
protected override object LayerMap => Layers.Enabled;
protected override Enum DataKey => PassiveVentVisuals.Enabled;
enum Layers
enum Layers : byte
{
Enabled,
}

View File

@@ -1,6 +1,7 @@
using System;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using Content.Shared.SubFloor;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -69,13 +70,16 @@ namespace Content.Client.Atmos.Visualizers
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualState state))
return;
if(!component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor))
subfloor = true;
foreach (Layer layerKey in Enum.GetValues(typeof(Layer)))
{
var dir = (PipeDirection) layerKey;
var layerVisible = state.ConnectedDirections.HasDirection(dir);
var layer = sprite.LayerMapGet(layerKey);
sprite.LayerSetVisible(layer, layerVisible);
sprite.LayerSetVisible(layer, layerVisible && subfloor);
sprite.LayerSetColor(layer, color);
}
}

View File

@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
protected override object LayerMap => Layers.Enabled;
protected override Enum DataKey => PressurePumpVisuals.Enabled;
enum Layers
enum Layers : byte
{
Enabled,
}

View File

@@ -44,7 +44,7 @@ namespace Content.Client.Atmos.Visualizers
}
}
public enum ScrubberVisualLayers
public enum ScrubberVisualLayers : byte
{
Scrubber,
}

View File

@@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers
protected override object LayerMap => Layers.Enabled;
protected override Enum DataKey => ThermoMachineVisuals.Enabled;
enum Layers
enum Layers : byte
{
Enabled,
}

View File

@@ -40,7 +40,7 @@ namespace Content.Client.Atmos.Visualizers
}
}
public enum VentVisualLayers
public enum VentVisualLayers : byte
{
Vent,
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -1,7 +1,7 @@
using Content.Shared.Clothing;
using Content.Shared.Clothing;
using Robust.Shared.GameObjects;
namespace Content.Client.Cloning
namespace Content.Client.Clothing
{
[RegisterComponent]
public sealed class MagbootsComponent : SharedMagbootsComponent

View File

@@ -17,15 +17,6 @@ namespace Content.Client.Doors
{
private const string AnimationKey = "airlock_animation";
[DataField("open_sound", required: true)]
private string _openSound = default!;
[DataField("close_sound", required: true)]
private string _closeSound = default!;
[DataField("deny_sound", required: true)]
private string _denySound = default!;
[DataField("animation_time")]
private float _delay = 0.8f;
@@ -51,14 +42,6 @@ namespace Content.Client.Doors
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
var sound = new AnimationTrackPlaySound();
CloseAnimation.AnimationTracks.Add(sound);
if (_closeSound != null)
{
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_closeSound, 0));
}
}
OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(_delay)};
@@ -80,11 +63,6 @@ namespace Content.Client.Doors
var sound = new AnimationTrackPlaySound();
OpenAnimation.AnimationTracks.Add(sound);
if (_openSound != null)
{
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_openSound, 0));
}
}
DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(0.3f)};
@@ -96,11 +74,6 @@ namespace Content.Client.Doors
var sound = new AnimationTrackPlaySound();
DenyAnimation.AnimationTracks.Add(sound);
if (_denySound != null)
{
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_denySound, 0, () => AudioHelpers.WithVariation(0.05f)));
}
}
}

View File

@@ -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))

View File

@@ -81,7 +81,6 @@ namespace Content.Client.Entry
factory.RegisterClass<SharedCargoConsoleComponent>();
factory.RegisterClass<SharedReagentDispenserComponent>();
factory.RegisterClass<SharedChemMasterComponent>();
factory.RegisterClass<SharedMicrowaveComponent>();
factory.RegisterClass<SharedGravityGeneratorComponent>();
factory.RegisterClass<SharedAMEControllerComponent>();

View File

@@ -92,6 +92,7 @@ namespace Content.Client.Entry
"ExaminableBattery",
"PottedPlantHide",
"SecureEntityStorage",
"Lock",
"PresetIdCard",
"SolarControlConsole",
"FlashOnTrigger",
@@ -153,7 +154,8 @@ namespace Content.Client.Entry
"GasPassiveGate",
"GasValve",
"GasThermoMachine",
"Metabolism",
"Respirator",
"Metabolizer",
"AiFactionTag",
"PressureProtection",
"AMEPart",

View File

@@ -0,0 +1,3 @@
<Control xmlns="https://spacestation14.io">
<Label StyleClasses="ItemStatus" Text="Pulling" />
</Control>

View File

@@ -0,0 +1,13 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Hands
{
public sealed class HandVirtualPullItemStatus : Control
{
public HandVirtualPullItemStatus()
{
RobustXamlLoader.Load(this);
}
}
}

View File

@@ -1,15 +1,8 @@
using System.Collections.Generic;
using Content.Client.Animations;
using Content.Client.HUD;
using Content.Shared.Hands.Components;
using Content.Shared.Item;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Client.Hands
{
@@ -18,16 +11,7 @@ namespace Content.Client.Hands
[ComponentReference(typeof(SharedHandsComponent))]
public class HandsComponent : SharedHandsComponent
{
[Dependency] private readonly IGameHud _gameHud = default!;
[ViewVariables]
public HandsGui? Gui { get; private set; }
protected override void OnRemove()
{
ClearGui();
base.OnRemove();
}
public HandsGui? Gui { get; set; }
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
@@ -38,94 +22,23 @@ namespace Content.Client.Hands
foreach (var handState in state.Hands)
{
var newHand = new Hand(handState.Name, handState.Enabled, handState.Location);
var newHand = new Hand(handState.Name, handState.Location);
Hands.Add(newHand);
}
ActiveHand = state.ActiveHand;
UpdateHandContainers();
UpdateHandVisualizer();
UpdateHandsGuiState();
}
public void SettupGui()
{
if (Gui == null)
{
Gui = new HandsGui();
_gameHud.HandsContainer.AddChild(Gui);
Gui.HandClick += args => OnHandClick(args.HandClicked);
Gui.HandActivate += args => OnActivateInHand(args.HandUsed);
UpdateHandsGuiState();
}
}
public void ClearGui()
{
Gui?.Dispose();
Gui = null;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
{
case PickupAnimationMessage msg:
RunPickupAnimation(msg);
break;
}
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this });
}
public override void HandsModified()
{
base.HandsModified();
UpdateHandContainers();
UpdateHandVisualizer();
UpdateHandsGuiState();
}
private void OnHandClick(string handClicked)
{
if (!TryGetHand(handClicked, out var pressedHand))
return;
if (!TryGetActiveHand(out var activeHand))
return;
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = activeHand.HeldEntity;
if (pressedHand == activeHand && activeEntity != null)
{
SendNetworkMessage(new UseInHandMsg()); //use item in hand
return;
}
if (pressedHand != activeHand && pressedEntity == null)
{
SendNetworkMessage(new ClientChangedHandMsg(pressedHand.Name)); //swap hand
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity != null)
{
SendNetworkMessage(new ClientAttackByInHandMsg(pressedHand.Name)); //use active item on held item
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity == null)
{
SendNetworkMessage(new MoveItemFromHandMsg(pressedHand.Name)); //move item in hand to active hand
return;
}
}
private void OnActivateInHand(string handActivated)
{
SendNetworkMessage(new ActivateInHandMsg(handActivated));
base.HandsModified();
}
public void UpdateHandContainers()
@@ -149,27 +62,10 @@ namespace Content.Client.Hands
appearance.SetData(HandsVisuals.VisualState, GetHandsVisualState());
}
public void UpdateHandsGuiState()
{
Gui?.SetState(GetHandsGuiState());
}
private HandsGuiState GetHandsGuiState()
{
var handStates = new List<GuiHand>();
foreach (var hand in ReadOnlyHands)
{
var handState = new GuiHand(hand.Name, hand.Location, hand.HeldEntity, hand.Enabled);
handStates.Add(handState);
}
return new HandsGuiState(handStates, ActiveHand);
}
private HandsVisualState GetHandsVisualState()
{
var hands = new List<HandVisualState>();
foreach (var hand in ReadOnlyHands)
foreach (var hand in Hands)
{
if (hand.HeldEntity == null)
continue;
@@ -182,16 +78,5 @@ namespace Content.Client.Hands
}
return new(hands);
}
private void RunPickupAnimation(PickupAnimationMessage msg)
{
if (!Owner.EntityManager.TryGetEntity(msg.EntityUid, out var entity))
return;
if (!IoCManager.Resolve<IGameTiming>().IsFirstTimePredicted)
return;
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection);
}
}
}

View File

@@ -0,0 +1,6 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical">
<Control Name="StatusContainer" />
<BoxContainer Name="HandsContainer" Orientation="Horizontal" HorizontalAlignment="Center" />
</BoxContainer>
</Control>

View File

@@ -1,87 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.HUD;
using Content.Client.Items.Managers;
using Content.Client.Items.UI;
using Content.Client.Resources;
using Content.Shared;
using Content.Shared.CCVar;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Hands
{
public class HandsGui : Control
[GenerateTypedNameReferences]
public sealed partial class HandsGui : Control
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
private readonly HandsSystem _handsSystem;
private readonly HandsComponent _handsComponent;
private Texture StorageTexture => _gameHud.GetHudTexture("back.png");
private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png");
private ItemStatusPanel StatusPanel { get; }
private BoxContainer HandsContainer { get; }
[ViewVariables]
public IReadOnlyList<GuiHand> Hands => _hands;
private List<GuiHand> _hands = new();
[ViewVariables] private GuiHand[] _hands = Array.Empty<GuiHand>();
private string? ActiveHand { get; set; }
public Action<HandClickEventArgs>? HandClick; //TODO: Move to Eventbus
public Action<HandActivateEventArgs>? HandActivate; //TODO: Move to Eventbus
public HandsGui()
public HandsGui(HandsComponent hands, HandsSystem handsSystem)
{
IoCManager.InjectDependencies(this);
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
_handsComponent = hands;
_handsSystem = handsSystem;
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 0,
HorizontalAlignment = HAlignment.Center,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
(StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle)),
(HandsContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center
}),
}
},
}
});
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle);
StatusContainer.AddChild(StatusPanel);
StatusPanel.SetPositionFirst();
}
public void SetState(HandsGuiState state)
protected override void EnteredTree()
{
base.EnteredTree();
_handsSystem.GuiStateUpdated += HandsSystemOnGuiStateUpdated;
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
HandsSystemOnGuiStateUpdated();
}
protected override void ExitedTree()
{
base.ExitedTree();
_handsSystem.GuiStateUpdated -= HandsSystemOnGuiStateUpdated;
_configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme);
}
private void HandsSystemOnGuiStateUpdated()
{
var state = _handsSystem.GetGuiState();
ActiveHand = state.ActiveHand;
_hands = state.GuiHands;
Array.Sort(_hands, HandOrderComparer.Instance);
UpdateGui();
}
@@ -97,12 +94,15 @@ namespace Content.Client.Hands
var handName = hand.Name;
newButton.OnPressed += args => OnHandPressed(args, handName);
newButton.OnStoragePressed += args => OnStoragePressed(handName);
newButton.Blocked.Visible = !hand.Enabled;
newButton.OnStoragePressed += _ => OnStoragePressed(handName);
_itemSlotManager.SetItemSlot(newButton, hand.HeldItem);
// Show blocked overlay if hand is pulling.
newButton.Blocked.Visible =
hand.HeldItem != null && hand.HeldItem.HasComponent<HandVirtualPullComponent>();
}
if (TryGetActiveHand(out var activeHand))
{
activeHand.HandButton.SetActiveHand(true);
@@ -114,7 +114,7 @@ namespace Content.Client.Hands
{
if (args.Function == EngineKeyFunctions.UIClick)
{
HandClick?.Invoke(new HandClickEventArgs(handName));
_handsSystem.UIHandClick(_handsComponent, handName);
}
else if (TryGetHand(handName, out var hand))
{
@@ -124,7 +124,7 @@ namespace Content.Client.Hands
private void OnStoragePressed(string handName)
{
HandActivate?.Invoke(new HandActivateEventArgs(handName));
_handsSystem.UIHandActivate(handName);
}
private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand)
@@ -145,6 +145,7 @@ namespace Content.Client.Hands
if (hand.Name == handName)
foundHand = hand;
}
return foundHand != null;
}
@@ -153,7 +154,9 @@ namespace Content.Client.Hands
base.FrameUpdate(args);
foreach (var hand in _hands)
{
_itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem);
}
}
private HandButton MakeHandButton(HandLocation buttonLocation)
@@ -173,23 +176,31 @@ namespace Content.Client.Hands
UpdateGui();
}
public class HandClickEventArgs
private sealed class HandOrderComparer : IComparer<GuiHand>
{
public string HandClicked { get; }
public static readonly HandOrderComparer Instance = new();
public HandClickEventArgs(string handClicked)
public int Compare(GuiHand? x, GuiHand? y)
{
HandClicked = handClicked;
}
}
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
public class HandActivateEventArgs
{
public string HandUsed { get; }
var orderX = Map(x.HandLocation);
var orderY = Map(y.HandLocation);
public HandActivateEventArgs(string handUsed)
{
HandUsed = handUsed;
return orderX.CompareTo(orderY);
static int Map(HandLocation loc)
{
return loc switch
{
HandLocation.Left => 3,
HandLocation.Middle => 2,
HandLocation.Right => 1,
_ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null)
};
}
}
}
}
@@ -203,7 +214,7 @@ namespace Content.Client.Hands
/// The set of hands to be displayed.
/// </summary>
[ViewVariables]
public List<GuiHand> GuiHands { get; } = new();
public GuiHand[] GuiHands { get; }
/// <summary>
/// The name of the currently active hand.
@@ -211,7 +222,7 @@ namespace Content.Client.Hands
[ViewVariables]
public string? ActiveHand { get; }
public HandsGuiState(List<GuiHand> guiHands, string? activeHand = null)
public HandsGuiState(GuiHand[] guiHands, string? activeHand = null)
{
GuiHands = guiHands;
ActiveHand = activeHand;
@@ -247,18 +258,11 @@ namespace Content.Client.Hands
[ViewVariables]
public HandButton HandButton { get; set; } = default!;
/// <summary>
/// If this hand can be used by the player.
/// </summary>
[ViewVariables]
public bool Enabled { get; }
public GuiHand(string name, HandLocation handLocation, IEntity? heldItem, bool enabled)
public GuiHand(string name, HandLocation handLocation, IEntity? heldItem)
{
Name = name;
HandLocation = handLocation;
HeldItem = heldItem;
Enabled = enabled;
}
}
}

View File

@@ -1,80 +0,0 @@
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Players;
namespace Content.Client.Hands
{
internal sealed class HandsSystem : SharedHandsSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HandsComponent, PlayerAttachedEvent>((_, component, _) => component.SettupGui());
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>((_, component, _) => component.ClearGui());
CommandBinds.Builder
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed))
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
.Register<HandsSystem>();
}
public override void Shutdown()
{
CommandBinds.Unregister<HandsSystem>();
base.Shutdown();
}
private void SwapHandsPressed(ICommonSession? session)
{
if (session == null)
return;
var player = session.AttachedEntity;
if (player == null)
return;
if (!player.TryGetComponent(out SharedHandsComponent? hands))
return;
if (!hands.TryGetSwapHandsResult(out var nextHand))
return;
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(nextHand));
}
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (session == null)
return false;
var player = session.AttachedEntity;
if (player == null)
return false;
if (!player.TryGetComponent(out SharedHandsComponent? hands))
return false;
var activeHand = hands.ActiveHand;
if (activeHand == null)
return false;
EntityManager.RaisePredictiveEvent(new RequestDropHeldEntityEvent(activeHand, coords));
return true;
}
protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args)
{
component.HandsModified();
}
}
}

View File

@@ -0,0 +1,18 @@
using Content.Client.Items;
using Content.Shared.Hands.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Client.Hands
{
[UsedImplicitly]
public sealed class HandVirtualPullSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<HandVirtualPullComponent>(_ => new HandVirtualPullItemStatus());
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Linq;
using Content.Client.Animations;
using Content.Client.HUD;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Client.Hands
{
[UsedImplicitly]
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? GuiStateUpdated;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HandsComponent, PlayerAttachedEvent>(HandlePlayerAttached);
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached);
SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove);
SubscribeLocalEvent<HandsModifiedMessage>(HandleHandsModified);
SubscribeNetworkEvent<PickupAnimationMessage>(HandlePickupAnimation);
}
public override void Shutdown()
{
CommandBinds.Unregister<HandsSystem>();
base.Shutdown();
}
private void HandleHandsModified(HandsModifiedMessage ev)
{
if (ev.Hands.Owner == _playerManager.LocalPlayer?.ControlledEntity)
GuiStateUpdated?.Invoke();
}
protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args)
{
if (uid == _playerManager.LocalPlayer?.ControlledEntity?.Uid)
GuiStateUpdated?.Invoke();
}
private void HandlePickupAnimation(PickupAnimationMessage msg)
{
if (!EntityManager.TryGetEntity(msg.EntityUid, out var entity))
return;
if (!_gameTiming.IsFirstTimePredicted)
return;
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection);
}
public HandsGuiState GetGuiState()
{
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null || !player.TryGetComponent(out HandsComponent? hands))
return new HandsGuiState(Array.Empty<GuiHand>());
var states = hands.Hands
.Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity))
.ToArray();
return new HandsGuiState(states, hands.ActiveHand);
}
public void UIHandClick(HandsComponent hands, string handName)
{
if (!hands.TryGetHand(handName, out var pressedHand))
return;
if (!hands.TryGetActiveHand(out var activeHand))
return;
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = activeHand.HeldEntity;
if (pressedHand == activeHand && activeEntity != null)
{
// use item in hand
// it will always be attack_self() in my heart.
RaiseNetworkEvent(new UseInHandMsg());
return;
}
if (pressedHand != activeHand && pressedEntity == null)
{
// change active hand
RaiseNetworkEvent(new RequestSetHandEvent(handName));
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity != null)
{
// use active item on held item
RaiseNetworkEvent(new ClientInteractUsingInHandMsg(pressedHand.Name));
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity == null)
{
// use active item on held item
RaiseNetworkEvent(new MoveItemFromHandMsg(pressedHand.Name));
}
}
public void UIHandActivate(string handName)
{
RaiseNetworkEvent (new ActivateInHandMsg(handName));
}
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args)
{
component.Gui = new HandsGui(component, this);
_gameHud.HandsContainer.AddChild(component.Gui);
}
private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
{
ClearGui(component);
}
private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
{
ClearGui(component);
}
private static void ClearGui(HandsComponent comp)
{
comp.Gui?.Orphan();
comp.Gui = null;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
namespace Content.Client.Items
{
public sealed class ItemStatusCollectMessage : EntityEventArgs
{
public List<Control> Controls = new();
}
public static class ItemStatusRegisterExt
{
/// <summary>
/// Register an item status control for a component.
/// </summary>
/// <param name="subs">The <see cref="EntitySystem.Subs"/> handle from within entity system initialize.</param>
/// <param name="createControl">A delegate to create the actual control.</param>
/// <typeparam name="TComp">The type of component for which this control should be made.</typeparam>
public static void ItemStatus<TComp>(
this EntitySystem.Subscriptions subs,
Func<EntityUid, Control> createControl)
where TComp : IComponent
{
subs.SubscribeLocalEvent<TComp, ItemStatusCollectMessage>((uid, _, args) =>
{
args.Controls.Add(createControl(uid));
});
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Client.Items.UI;
using System;
using Content.Client.Items.UI;
using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
@@ -10,5 +11,21 @@ namespace Content.Client.Items.Managers
void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity);
bool SetItemSlot(ItemSlotButton button, IEntity? entity);
void HoverInSlot(ItemSlotButton button, IEntity? entity, bool fits);
event Action<EntitySlotHighlightedEventArgs>? EntityHighlightedUpdated;
bool IsHighlighted(EntityUid uid);
/// <summary>
/// Highlight all slot controls that contain the specified entity.
/// </summary>
/// <param name="uid">The UID of the entity to highlight.</param>
/// <seealso cref="UnHighlightEntity"/>
void HighlightEntity(EntityUid uid);
/// <summary>
/// Remove highlighting for the specified entity.
/// </summary>
/// <param name="uid">The UID of the entity to unhighlight.</param>
/// <seealso cref="HighlightEntity"/>
void UnHighlightEntity(EntityUid uid);
}
}

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using Content.Client.Examine;
using Content.Client.Items.UI;
using Content.Client.Storage;
using Content.Client.Verbs;
using Content.Shared.Cooldown;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -28,6 +31,11 @@ namespace Content.Client.Items.Managers
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
private readonly HashSet<EntityUid> _highlightEntities = new();
public event Action<EntitySlotHighlightedEventArgs>? EntityHighlightedUpdated;
public bool SetItemSlot(ItemSlotButton button, IEntity? entity)
{
@@ -38,13 +46,26 @@ namespace Content.Client.Items.Managers
}
else
{
if (!entity.TryGetComponent(out ISpriteComponent? sprite))
ISpriteComponent? sprite;
if (entity.TryGetComponent(out HandVirtualPullComponent? virtPull)
&& _componentManager.TryGetComponent(virtPull.PulledEntity, out ISpriteComponent pulledSprite))
{
sprite = pulledSprite;
}
else if (!entity.TryGetComponent(out sprite))
{
return false;
}
button.ClearHover();
button.SpriteView.Sprite = sprite;
button.StorageButton.Visible = entity.HasComponent<ClientStorageComponent>();
}
button.Entity = entity?.Uid ?? default;
// im lazy
button.UpdateSlotHighlighted();
return true;
}
@@ -145,5 +166,38 @@ namespace Content.Client.Items.Managers
button.HoverSpriteView.Sprite = hoverSprite;
}
public bool IsHighlighted(EntityUid uid)
{
return _highlightEntities.Contains(uid);
}
public void HighlightEntity(EntityUid uid)
{
if (!_highlightEntities.Add(uid))
return;
EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, true));
}
public void UnHighlightEntity(EntityUid uid)
{
if (!_highlightEntities.Remove(uid))
return;
EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, false));
}
}
public readonly struct EntitySlotHighlightedEventArgs
{
public EntitySlotHighlightedEventArgs(EntityUid entity, bool newHighlighted)
{
Entity = entity;
NewHighlighted = newHighlighted;
}
public EntityUid Entity { get; }
public bool NewHighlighted { get; }
}
}

View File

@@ -1,18 +1,24 @@
using System;
using Content.Client.Cooldown;
using Content.Client.Items.Managers;
using Content.Client.Stylesheets;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Items.UI
{
public class ItemSlotButton : Control
public class ItemSlotButton : Control, IEntityEventSubscriber
{
private const string HighlightShader = "SelectionOutlineInrange";
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
public EntityUid Entity { get; set; }
public TextureRect Button { get; }
public SpriteView SpriteView { get; }
public SpriteView HoverSpriteView { get; }
@@ -32,6 +38,8 @@ namespace Content.Client.Items.UI
public ItemSlotButton(Texture texture, Texture storageTexture, string textureName)
{
IoCManager.InjectDependencies(this);
MinSize = (64, 64);
TextureName = textureName;
@@ -101,6 +109,31 @@ namespace Content.Client.Items.UI
});
}
protected override void EnteredTree()
{
base.EnteredTree();
_itemSlotManager.EntityHighlightedUpdated += HandleEntitySlotHighlighted;
UpdateSlotHighlighted();
}
protected override void ExitedTree()
{
base.ExitedTree();
_itemSlotManager.EntityHighlightedUpdated -= HandleEntitySlotHighlighted;
}
private void HandleEntitySlotHighlighted(EntitySlotHighlightedEventArgs entitySlotHighlightedEventArgs)
{
UpdateSlotHighlighted();
}
public void UpdateSlotHighlighted()
{
Highlight(_itemSlotManager.IsHighlighted(Entity));
}
public void ClearHover()
{
if (EntityHover)

View File

@@ -8,7 +8,9 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Content.Client.IoC.StaticIoC;
@@ -18,6 +20,8 @@ namespace Content.Client.Items.UI
{
public class ItemStatusPanel : Control
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables]
private readonly List<(IItemStatus, Control)> _activeStatusComponents = new();
@@ -33,6 +37,8 @@ namespace Content.Client.Items.UI
public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign)
{
IoCManager.InjectDependencies(this);
var panel = new StyleBoxTexture
{
Texture = texture
@@ -117,6 +123,13 @@ namespace Content.Client.Items.UI
return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateItemName();
}
public void Update(IEntity? entity)
{
if (entity == null)
@@ -131,12 +144,29 @@ namespace Content.Client.Items.UI
{
_entity = entity;
BuildNewEntityStatus();
_itemNameLabel.Text = entity.Name;
UpdateItemName();
}
_panel.Visible = true;
}
private void UpdateItemName()
{
if (_entity == null)
return;
if (_entity.TryGetComponent(out HandVirtualPullComponent? virtualPull)
&& _entityManager.TryGetEntity(virtualPull.PulledEntity, out var pulledEnt))
{
_itemNameLabel.Text = pulledEnt.Name;
}
else
{
_itemNameLabel.Text = _entity.Name;
}
}
private void ClearOldStatus()
{
_statusContents.RemoveAllChildren();
@@ -162,6 +192,14 @@ namespace Content.Client.Items.UI
_activeStatusComponents.Add((statusComponent, control));
}
var collectMsg = new ItemStatusCollectMessage();
_entity.EntityManager.EventBus.RaiseLocalEvent(_entity.Uid, collectMsg);
foreach (var control in collectMsg.Controls)
{
_statusContents.AddChild(control);
}
}
}
}

View File

@@ -2,7 +2,7 @@ using Content.Shared.DragDrop;
using Content.Shared.Kitchen.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.Kitchen
namespace Content.Client.Kitchen.Components
{
[RegisterComponent]
internal sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent

View File

@@ -0,0 +1,17 @@
using Content.Shared.Kitchen.Components;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Kitchen.Components
{
[RegisterComponent]
public class MicrowaveComponent : SharedMicrowaveComponent
{
public IPlayingAudioStream? PlayingStream { get; set; }
[DataField("loopingSound")]
public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg");
}
}

View File

@@ -0,0 +1,31 @@
using System;
using Content.Client.Kitchen.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Content.Client.Kitchen.EntitySystems
{
public class MicrowaveSystem : EntitySystem
{
public void StartSoundLoop(MicrowaveComponent microwave)
{
StopSoundLoop(microwave);
microwave.PlayingStream = SoundSystem.Play(Filter.Local(), microwave.LoopingSound.GetSound(), microwave.Owner,
AudioParams.Default.WithAttenuation(1).WithMaxDistance(5).WithLoop(true));
}
public void StopSoundLoop(MicrowaveComponent microwave)
{
try
{
microwave.PlayingStream?.Stop();
}
catch (Exception _)
{
// TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING.
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,10 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Kitchen.Components;
using 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;
}
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;
}
_menu?.HandleMessage(message);
}
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));
}
}

View File

@@ -1,10 +1,10 @@
using Content.Client.Sound;
using Content.Client.Kitchen.Components;
using Content.Client.Kitchen.EntitySystems;
using Content.Shared.Kitchen.Components;
using Content.Shared.Power;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
namespace Content.Client.Kitchen.Visualizers
@@ -17,35 +17,33 @@ namespace Content.Client.Kitchen.Visualizers
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
var loopingSoundComponent = component.Owner.GetComponentOrNull<LoopingSoundComponent>();
var microwaveComponent = component.Owner.GetComponentOrNull<MicrowaveComponent>();
if (!component.TryGetData(PowerDeviceVisuals.VisualState, out MicrowaveVisualState state))
{
state = MicrowaveVisualState.Idle;
}
// The only reason we get the entity system so late is so that tests don't fail... Amazing, huh?
switch (state)
{
case MicrowaveVisualState.Broken:
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mwb");
loopingSoundComponent?.StopAllSounds();
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break;
case MicrowaveVisualState.Idle:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit");
loopingSoundComponent?.StopAllSounds();
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break;
case MicrowaveVisualState.Cooking:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit");
var audioParams = AudioParams.Default;
audioParams.Loop = true;
var scheduledSound = new ScheduledSound();
scheduledSound.Filename = "/Audio/Machines/microwave_loop.ogg";
scheduledSound.AudioParams = audioParams;
loopingSoundComponent?.StopAllSounds();
loopingSoundComponent?.AddScheduledSound(scheduledSound);
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StartSoundLoop(microwaveComponent);
break;
default:

View File

@@ -1,4 +1,5 @@
using Content.Shared.Light.Component;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
namespace Content.Client.Light.Components
@@ -9,6 +10,6 @@ namespace Content.Client.Light.Components
[RegisterComponent]
public class ExpendableLightComponent : SharedExpendableLightComponent
{
public IPlayingAudioStream? PlayingStream { get; set; }
}
}

View File

@@ -1,7 +1,10 @@
using Content.Client.Light.Components;
using System;
using Content.Client.Light.Components;
using Content.Shared.Light.Component;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Client.Light.Visualizers
{
@@ -17,7 +20,7 @@ namespace Content.Client.Light.Visualizers
return;
}
if (component.TryGetData(ExpendableLightVisuals.State, out string lightBehaviourID))
if (component.TryGetData(ExpendableLightVisuals.Behavior, out string lightBehaviourID))
{
if (component.Owner.TryGetComponent<LightBehaviourComponent>(out var lightBehaviour))
{
@@ -33,6 +36,35 @@ namespace Content.Client.Light.Visualizers
}
}
}
void TryStopStream(IPlayingAudioStream? stream)
{
try
{
stream?.Stop();
}
catch (Exception _)
{
// TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING.
}
}
if (component.TryGetData(ExpendableLightVisuals.State, out ExpendableLightState state)
&& component.Owner.TryGetComponent<ExpendableLightComponent>(out var expendableLight))
{
switch (state)
{
case ExpendableLightState.Lit:
TryStopStream(expendableLight.PlayingStream);
expendableLight.PlayingStream = SoundSystem.Play(Filter.Local(), expendableLight.LoopedSound,
expendableLight.Owner, SharedExpendableLightComponent.LoopedSoundParams.WithLoop(true));
break;
case ExpendableLightState.Dead:
TryStopStream(expendableLight.PlayingStream);
break;
}
}
}
}
}

View File

@@ -19,7 +19,18 @@ namespace Content.Client.Physics.Controllers
!player.TryGetComponent(out IMoverComponent? mover) ||
!player.TryGetComponent(out PhysicsComponent? body)) return;
body.Predict = true; // TODO: equal prediction instead of true?
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
// (i.e. only see what the server has sent us).
// The exception to this is joints.
body.Predict = true;
// We set joints to predicted given these can affect how our mob moves.
// I would only recommend disabling this if you make pulling not use joints anymore (someday maybe?)
foreach (var joint in body.Joints)
{
joint.BodyA.Predict = true;
joint.BodyB.Predict = true;
}
// Server-side should just be handled on its own so we'll just do this shizznit
if (player.TryGetComponent(out IMobMoverComponent? mobMover))

View File

@@ -1,105 +0,0 @@
using System.Collections.Generic;
using Content.Shared.Physics;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Sound
{
[RegisterComponent]
public class LoopingSoundComponent : SharedLoopingSoundComponent
{
[Dependency] private readonly IRobustRandom _random = default!;
private readonly Dictionary<ScheduledSound, IPlayingAudioStream> _audioStreams = new();
[DataField("schedules", true)]
private List<ScheduledSound> _scheduledSounds
{
set => value.ForEach(AddScheduledSound);
get => new();
}
public override void StopAllSounds()
{
foreach (var kvp in _audioStreams)
{
kvp.Key.Play = false;
kvp.Value.Stop();
}
_audioStreams.Clear();
}
public override void StopScheduledSound(string filename)
{
foreach (var kvp in _audioStreams)
{
if (kvp.Key.Filename != filename) continue;
kvp.Key.Play = false;
kvp.Value.Stop();
_audioStreams.Remove(kvp.Key);
}
}
public override void AddScheduledSound(ScheduledSound schedule)
{
Play(schedule);
}
public void Play(ScheduledSound schedule)
{
if (!schedule.Play) return;
Owner.SpawnTimer((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
{
if (!schedule.Play) return; // We make sure this hasn't changed.
if (!_audioStreams.ContainsKey(schedule))
{
_audioStreams.Add(schedule, SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!);
}
else
{
_audioStreams[schedule] = SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!;
}
if (schedule.Times == 0) return;
if (schedule.Times > 0) schedule.Times--;
Play(schedule);
});
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case ScheduledSoundMessage msg:
AddScheduledSound(msg.Schedule);
break;
case StopSoundScheduleMessage msg:
StopScheduledSound(msg.Filename);
break;
case StopAllSoundsMessage _:
StopAllSounds();
break;
}
}
protected override void Initialize()
{
base.Initialize();
SoundSystem.OcclusionCollisionMask = (int) CollisionGroup.Impassable;
}
}
}

View File

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

View File

@@ -0,0 +1,40 @@
using Content.Shared.SubFloor;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
namespace Content.Client.SubFloor
{
[UsedImplicitly]
public class SubFloorShowLayerVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out SpriteComponent? sprite))
return;
if (component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor))
{
sprite.Visible = true;
// Due to the way this visualizer works, you might want to specify it before any other
// visualizer that hides/shows layers depending on certain conditions, such as PipeConnectorVisualizer.
foreach (var layer in sprite.AllLayers)
{
layer.Visible = subfloor;
}
if (sprite.LayerMapTryGet(Layers.FirstLayer, out var firstLayer))
{
sprite.LayerSetVisible(firstLayer, true);
}
}
}
public enum Layers : byte
{
FirstLayer,
}
}
}

View File

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

View File

@@ -18,6 +18,7 @@ namespace Content.Client.Weapons.Melee
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly 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);
}
}

View File

@@ -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);

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Content.Server.Atmos;
using Content.Server.Body.Behavior;
using Content.Server.Body.Circulatory;
using Content.Server.Metabolism;
using Content.Server.Body.Respiratory;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using NUnit.Framework;
@@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Body
template: HumanoidTemplate
preset: HumanPreset
centerSlot: torso
- type: Metabolism
- type: Respirator
metabolismHeat: 5000
radiatedHeat: 400
implicitHeatRegulation: 5000
@@ -148,7 +148,7 @@ namespace Content.IntegrationTests.Tests.Body
MapId mapId;
IMapGrid grid = null;
MetabolismComponent metabolism = null;
RespiratorComponent respirator = null;
IEntity human = null;
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
@@ -169,8 +169,8 @@ namespace Content.IntegrationTests.Tests.Body
Assert.True(human.TryGetComponent(out SharedBodyComponent body));
Assert.True(body.HasMechanismBehavior<LungBehavior>());
Assert.True(human.TryGetComponent(out metabolism));
Assert.False(metabolism.Suffocating);
Assert.True(human.TryGetComponent(out respirator));
Assert.False(respirator.Suffocating);
});
var increment = 10;
@@ -178,7 +178,7 @@ namespace Content.IntegrationTests.Tests.Body
for (var tick = 0; tick < 600; tick += increment)
{
await server.WaitRunTicks(increment);
Assert.False(metabolism.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
Assert.False(respirator.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
}
await server.WaitIdleAsync();

View File

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

View File

@@ -1,11 +1,14 @@
using System;
using System.Threading.Tasks;
using Content.Client.Clickable;
using Content.Server.GameTicking;
using NUnit.Framework;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Content.IntegrationTests.Tests
{
@@ -57,12 +60,19 @@ namespace Content.IntegrationTests.Tests
[TestCase("ClickTestRotatingCornerInvisibleNoRot", 0.25f, 0.25f, DirSouthEastJustShy, 1, ExpectedResult = true)]
public async Task<bool> Test(string prototype, float clickPosX, float clickPosY, double angle, float scale)
{
Vector2? worldPos = null;
EntityUid entity = default;
var clientEntManager = _client.ResolveDependency<IEntityManager>();
var serverEntManager = _server.ResolveDependency<IEntityManager>();
var mapManager = _server.ResolveDependency<IMapManager>();
var gameTicker = _server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
await _server.WaitPost(() =>
{
var entMgr = IoCManager.Resolve<IEntityManager>();
var ent = entMgr.SpawnEntity(prototype, new MapCoordinates(0, 0, new MapId(1)));
var gridEnt = mapManager.GetGrid(gameTicker.DefaultGridId).GridEntityId;
worldPos = serverEntManager.GetEntity(gridEnt).Transform.WorldPosition;
var ent = serverEntManager.SpawnEntity(prototype, new EntityCoordinates(gridEnt, 0f, 0f));
ent.Transform.LocalRotation = angle;
ent.GetComponent<SpriteComponent>().Scale = (scale, scale);
entity = ent.Uid;
@@ -75,17 +85,15 @@ namespace Content.IntegrationTests.Tests
await _client.WaitPost(() =>
{
var entMgr = IoCManager.Resolve<IEntityManager>();
var ent = entMgr.GetEntity(entity);
var ent = clientEntManager.GetEntity(entity);
var clickable = ent.GetComponent<ClickableComponent>();
hit = clickable.CheckClick((clickPosX, clickPosY), out _, out _);
hit = clickable.CheckClick((clickPosX, clickPosY) + worldPos!.Value, out _, out _);
});
await _server.WaitPost(() =>
{
var entMgr = IoCManager.Resolve<IEntityManager>();
entMgr.DeleteEntity(entity);
serverEntManager.DeleteEntity(entity);
});
return hit;

View File

@@ -52,7 +52,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
- type: PointLight
enabled: false
radius: 3
- type: LoopingSound
- type: Appearance
visuals:
- type: FlashLightVisualizer

View File

@@ -5,6 +5,7 @@ using Content.Shared.Spawning;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
@@ -44,7 +45,11 @@ namespace Content.IntegrationTests.Tests.Utility
await server.WaitAssertion(() =>
{
var mapId = new MapId(1);
var grid = sMapManager.GetGrid(new GridId(1));
grid.SetTile(new Vector2i(0, 0), new Tile(1));
var gridEnt = sEntityManager.GetEntity(grid.GridEntityId);
var gridPos = gridEnt.Transform.WorldPosition;
var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0);
// Nothing blocking it, only entity is the grid
@@ -52,8 +57,7 @@ namespace Content.IntegrationTests.Tests.Utility
Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity));
Assert.NotNull(entity);
var mapId = new MapId(1);
var mapCoordinates = new MapCoordinates(0, 0, mapId);
var mapCoordinates = new MapCoordinates(gridPos.X, gridPos.Y, mapId);
// Nothing blocking it, only entity is the grid
Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable));

View File

@@ -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

View File

@@ -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++)
{

View File

@@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands
return;
}
if (grid.HasComponent<IGridAtmosphereComponent>())
if (grid.HasComponent<IAtmosphereComponent>())
{
shell.WriteLine("Grid already has an atmosphere.");
return;

View File

@@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands
return;
}
if (grid.HasComponent<IGridAtmosphereComponent>())
if (grid.HasComponent<IAtmosphereComponent>())
{
shell.WriteLine("Grid already has an atmosphere.");
return;

View File

@@ -23,23 +23,20 @@ namespace Content.Server.Atmos.Components
[ViewVariables]
[ComponentDependency] private readonly FlammableComponent? _flammableComponent = null;
public void Update(TileAtmosphere tile, float frameDelta, AtmosphereSystem atmosphereSystem)
public void Update(GasMixture air, float frameDelta, AtmosphereSystem atmosphereSystem)
{
if (_temperatureComponent != null)
{
if (tile.Air != null)
{
var temperatureDelta = tile.Air.Temperature - _temperatureComponent.CurrentTemperature;
var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(tile.Air);
var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity));
_temperatureComponent.ReceiveHeat(heat);
}
var temperatureDelta = air.Temperature - _temperatureComponent.CurrentTemperature;
var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(air);
var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity));
_temperatureComponent.ReceiveHeat(heat);
_temperatureComponent.Update();
}
_barotraumaComponent?.Update(tile.Air?.Pressure ?? 0);
_barotraumaComponent?.Update(air.Pressure);
_flammableComponent?.Update(tile);
_flammableComponent?.Update(air);
}
}
}

View File

@@ -63,7 +63,7 @@ namespace Content.Server.Atmos.Components
UpdateAppearance();
}
public void Update(TileAtmosphere tile)
public void Update(GasMixture air)
{
// Slowly dry ourselves off if wet.
if (FireStacks < 0)
@@ -104,13 +104,13 @@ namespace Content.Server.Atmos.Components
}
// If we're in an oxygenless environment, put the fire out.
if (tile.Air?.GetMoles(Gas.Oxygen) < 1f)
if (air.GetMoles(Gas.Oxygen) < 1f)
{
Extinguish();
return;
}
EntitySystem.Get<AtmosphereSystem>().HotspotExpose(tile.GridIndex, tile.GridIndices, 700f, 50f, true);
EntitySystem.Get<AtmosphereSystem>().HotspotExpose(Owner.Transform.Coordinates, 700f, 50f, true);
var physics = Owner.GetComponent<IPhysBody>();

View File

@@ -1,4 +1,3 @@
#nullable disable warnings
using System;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Respiratory;

View File

@@ -14,13 +14,14 @@ using Dependency = Robust.Shared.IoC.DependencyAttribute;
namespace Content.Server.Atmos.Components
{
/// <summary>
/// This is our SSAir equivalent.
/// Internal Atmos class. Use <see cref="AtmosphereSystem"/> to interact with atmos instead.
/// </summary>
[ComponentReference(typeof(IGridAtmosphereComponent))]
[ComponentReference(typeof(IAtmosphereComponent))]
[RegisterComponent, Serializable]
public class GridAtmosphereComponent : Component, IGridAtmosphereComponent, ISerializationHooks
public class GridAtmosphereComponent : Component, IAtmosphereComponent, ISerializationHooks
{
public override string Name => "GridAtmosphere";
public virtual bool Simulated => true;
[ViewVariables]

View File

@@ -2,7 +2,7 @@
namespace Content.Server.Atmos.Components
{
public interface IGridAtmosphereComponent : IComponent
public interface IAtmosphereComponent : IComponent
{
/// <summary>
/// Whether this atmosphere is simulated or not.

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IAtmosphereComponent))]
public class SpaceAtmosphereComponent : Component, IAtmosphereComponent
{
public override string Name => "SpaceAtmosphere";
public bool Simulated => false;
}
}

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IGridAtmosphereComponent))]
public class SpaceGridAtmosphereComponent : UnsimulatedGridAtmosphereComponent
{
public override string Name => "SpaceGridAtmosphere";
}
}

View File

@@ -8,10 +8,9 @@ using Robust.Shared.Maths;
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IGridAtmosphereComponent))]
[ComponentReference(typeof(GridAtmosphereComponent))]
[ComponentReference(typeof(IAtmosphereComponent))]
[Serializable]
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent
{
public override string Name => "UnsimulatedGridAtmosphere";

View File

@@ -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)

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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);
}
@@ -120,6 +117,10 @@ namespace Content.Server.Atmos.EntitySystems
/// <returns>All tile mixtures in a grid.</returns>
public IEnumerable<GasMixture> GetAllTileMixtures(GridId grid, bool invalidate = false)
{
// Return an array with a single space gas mixture for invalid grids.
if (!grid.IsValid())
return new []{ GasMixture.SpaceGas };
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
return Enumerable.Empty<GasMixture>();
@@ -669,7 +670,7 @@ namespace Content.Server.Atmos.EntitySystems
public GasMixture? GetTileMixture(EntityCoordinates coordinates, bool invalidate = false)
{
return TryGetGridAndTile(coordinates, out var tuple)
? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : null;
? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : GasMixture.SpaceGas;
}
/// <summary>
@@ -681,6 +682,10 @@ namespace Content.Server.Atmos.EntitySystems
/// <returns>The tile mixture, or null</returns>
public GasMixture? GetTileMixture(GridId grid, Vector2i tile, bool invalidate = false)
{
// Always return space gas mixtures for invalid grids (grid 0)
if (!grid.IsValid())
return GasMixture.SpaceGas;
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
return null;
@@ -689,7 +694,7 @@ namespace Content.Server.Atmos.EntitySystems
return GetTileMixture(gridAtmosphere, tile, invalidate);
}
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceGridAtmosphereComponent? spaceAtmosphere))
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceAtmosphereComponent? _))
{
// Always return a new space gas mixture in this case.
return GasMixture.SpaceGas;
@@ -970,6 +975,10 @@ namespace Content.Server.Atmos.EntitySystems
/// <returns>All adjacent tile gas mixtures to the tile in question</returns>
public IEnumerable<GasMixture> GetAdjacentTileMixtures(GridId grid, Vector2i tile, bool includeBlocked = false, bool invalidate = false)
{
// For invalid grids, return an array with a single space gas mixture in it.
if (!grid.IsValid())
return new []{ GasMixture.SpaceGas };
if (!_mapManager.TryGetGrid(grid, out var mapGrid))
return Enumerable.Empty<GasMixture>();
@@ -1377,7 +1386,7 @@ namespace Content.Server.Atmos.EntitySystems
return false;
if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out GridAtmosphereComponent? gridAtmosphere)
&& gridAtmosphere.AtmosDevices.Contains(atmosDevice))
&& gridAtmosphere.AtmosDevices.Contains(atmosDevice))
{
atmosDevice.JoinedGrid = null;
gridAtmosphere.AtmosDevices.Remove(atmosDevice);

View File

@@ -1,11 +1,8 @@
#nullable disable warnings
#nullable enable annotations
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Reactions;
using Content.Server.Coordinates.Helpers;
using Content.Shared.Atmos;
using Content.Shared.Maps;
using Robust.Shared.Map;
namespace Content.Server.Atmos.EntitySystems
{
@@ -140,7 +137,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())
{

View File

@@ -1,5 +1,3 @@
#nullable disable warnings
#nullable enable annotations
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
@@ -16,9 +14,6 @@ namespace Content.Server.Atmos.EntitySystems
return;
}
if (tile.ArchivedCycle < fireCount)
Archive(tile, fireCount);
tile.CurrentCycle = fireCount;
var adjacentTileLength = 0;
@@ -38,11 +33,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,21 +51,24 @@ namespace Content.Server.Atmos.EntitySystems
AddActiveTile(gridAtmosphere, enemyTile);
}
var excitedGroup = tile.ExcitedGroup;
excitedGroup ??= enemyTile.ExcitedGroup;
if (excitedGroup == null)
if (ExcitedGroups)
{
excitedGroup = new ExcitedGroup();
gridAtmosphere.ExcitedGroups.Add(excitedGroup);
var excitedGroup = tile.ExcitedGroup;
excitedGroup ??= enemyTile.ExcitedGroup;
if (excitedGroup == null)
{
excitedGroup = new ExcitedGroup();
gridAtmosphere.ExcitedGroups.Add(excitedGroup);
}
if (tile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, tile);
if(enemyTile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, enemyTile);
}
if (tile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, tile);
if(enemyTile.ExcitedGroup == null)
ExcitedGroupAddTile(excitedGroup, enemyTile);
shouldShareAir = true;
}
@@ -106,17 +103,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)

View File

@@ -1,16 +1,14 @@
#nullable disable warnings
#nullable enable annotations
using System;
using System.Buffers;
using System.Collections.Generic;
using Content.Server.Atmos.Components;
using Content.Server.Coordinates.Helpers;
using Content.Server.Doors.Components;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems
{
@@ -20,7 +18,7 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphereComparer _monstermosComparer = new();
private readonly TileAtmosphere[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _equalizeGiverTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
private readonly TileAtmosphere[] _equalizeTakerTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
private readonly TileAtmosphere[] _equalizeQueue = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
@@ -28,7 +26,7 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
public void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
private void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
{
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
return; // Already done.
@@ -65,11 +63,12 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
if (i > Atmospherics.MonstermosHardTileLimit) break;
var exploring = _equalizeTiles[i];
var exploring = _equalizeTiles[i]!;
if (i < Atmospherics.MonstermosTileLimit)
{
var tileMoles = exploring.Air.TotalMoles;
// Tiles in the _equalizeTiles array cannot have null air.
var tileMoles = exploring.Air!.TotalMoles;
exploring.MonstermosInfo.MoleDelta = tileMoles;
totalMoles += tileMoles;
}
@@ -106,7 +105,7 @@ namespace Content.Server.Atmos.EntitySystems
if (otherTile == null)
continue;
_equalizeTiles[i].MonstermosInfo.LastQueueCycle = 0;
otherTile.MonstermosInfo.LastQueueCycle = 0;
}
tileCount = Atmospherics.MonstermosTileLimit;
@@ -118,7 +117,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i];
var otherTile = _equalizeTiles[i]!;
otherTile.MonstermosInfo.LastCycle = cycleNum;
otherTile.MonstermosInfo.MoleDelta -= averageMoles;
if (otherTile.MonstermosInfo.MoleDelta > 0)
@@ -133,7 +132,7 @@ namespace Content.Server.Atmos.EntitySystems
var logN = MathF.Log2(tileCount);
// Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2)
// Optimization - try to spread gases using an O(n log n) algorithm that has a chance of not working first to avoid O(n^2)
if (giverTilesLength > logN && takerTilesLength > logN)
{
// Even if it fails, it will speed up the next part.
@@ -141,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i];
var otherTile = _equalizeTiles[i]!;
otherTile.MonstermosInfo.FastDone = true;
if (!(otherTile.MonstermosInfo.MoleDelta > 0)) continue;
var eligibleDirections = AtmosDirection.Invalid;
@@ -150,7 +149,7 @@ namespace Content.Server.Atmos.EntitySystems
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var tile2 = otherTile.AdjacentTiles[j];
var tile2 = otherTile.AdjacentTiles[j]!;
// skip anything that isn't part of our current processing block.
if (tile2.MonstermosInfo.FastDone || tile2.MonstermosInfo.LastQueueCycle != queueCycle)
@@ -171,7 +170,7 @@ namespace Content.Server.Atmos.EntitySystems
AdjustEqMovement(otherTile, direction, molesToMove);
otherTile.MonstermosInfo.MoleDelta -= molesToMove;
otherTile.AdjacentTiles[j].MonstermosInfo.MoleDelta += molesToMove;
otherTile.AdjacentTiles[j]!.MonstermosInfo.MoleDelta += molesToMove;
}
}
@@ -180,7 +179,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i];
var otherTile = _equalizeTiles[i]!;
if (otherTile.MonstermosInfo.MoleDelta > 0)
{
_equalizeGiverTiles[giverTilesLength++] = otherTile;
@@ -252,7 +251,7 @@ namespace Content.Server.Atmos.EntitySystems
if (otherTile.MonstermosInfo.CurrentTransferAmount != 0 && otherTile.MonstermosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
{
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
}
@@ -319,7 +318,7 @@ namespace Content.Server.Atmos.EntitySystems
AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]
otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
otherTile.MonstermosInfo.CurrentTransferAmount = 0;
}
@@ -328,19 +327,19 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i];
var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile);
}
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i];
var otherTile = _equalizeTiles[i]!;
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j];
if (otherTile2?.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue;
var otherTile2 = otherTile.AdjacentTiles[j]!;
if (otherTile2.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue;
AddActiveTile(gridAtmosphere, otherTile2);
break;
}
@@ -353,7 +352,7 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
}
public void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
private void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum)
{
// Check if explosive depressurization is enabled and if the tile is valid.
if (!MonstermosDepressurization || tile.Air == null)
@@ -376,7 +375,8 @@ namespace Content.Server.Atmos.EntitySystems
var otherTile = _depressurizeTiles[i];
otherTile.MonstermosInfo.LastCycle = cycleNum;
otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
if (otherTile.Air.Immutable)
// Tiles in the _depressurizeTiles array cannot have null air.
if (otherTile.Air!.Immutable)
{
_depressurizeSpaceTiles[spaceTileCount++] = otherTile;
otherTile.PressureSpecificTarget = otherTile;
@@ -388,7 +388,7 @@ namespace Content.Server.Atmos.EntitySystems
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j];
if (otherTile2.Air == null) continue;
if (otherTile2?.Air == null) continue;
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2);
@@ -421,8 +421,8 @@ namespace Content.Server.Atmos.EntitySystems
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
// TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres.
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air.Immutable) continue;
// Tiles in _depressurizeProgressionOrder cannot have null air.
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air!.Immutable) continue;
var tile2 = otherTile.AdjacentTiles[j];
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -509,7 +509,7 @@ namespace Content.Server.Atmos.EntitySystems
InvalidateVisuals(other.GridIndex, other.GridIndices);
}
public void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
{
Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
var hasTransferDirs = false;
@@ -533,7 +533,8 @@ namespace Content.Server.Atmos.EntitySystems
if (otherTile?.Air == null) continue;
if (amount > 0)
{
if (tile.Air.TotalMoles < amount)
// Everything that calls this method already ensures that Air will not be null.
if (tile.Air!.TotalMoles < amount)
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections);
otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
@@ -551,15 +552,19 @@ namespace Content.Server.Atmos.EntitySystems
{
var direction = (AtmosDirection) (1 << i);
var amount = transferDirs[i];
// Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air.
if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction))
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]); // A bit of recursion if needed.
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!); // A bit of recursion if needed.
}
}
private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, float amount)
{
DebugTools.Assert(tile.AdjacentBits.HasFlag(direction));
DebugTools.Assert(tile.AdjacentTiles[direction.ToIndex()] != null);
tile.MonstermosInfo[direction] += amount;
tile.AdjacentTiles[direction.ToIndex()].MonstermosInfo[direction.GetOpposite()] -= amount;
// Every call to this method already ensures that the adjacent tile won't be null.
tile.AdjacentTiles[direction.ToIndex()]!.MonstermosInfo[direction.GetOpposite()] -= amount;
}
private void HandleDecompressionFloorRip(IMapGrid mapGrid, TileAtmosphere tile, float sum)
@@ -573,9 +578,9 @@ namespace Content.Server.Atmos.EntitySystems
PryTile(mapGrid, tile.GridIndices);
}
private class TileAtmosphereComparer : IComparer<TileAtmosphere>
private class TileAtmosphereComparer : IComparer<TileAtmosphere?>
{
public int Compare(TileAtmosphere a, TileAtmosphere b)
public int Compare(TileAtmosphere? a, TileAtmosphere? b)
{
if (a == null && b == null)
return 0;

View File

@@ -14,6 +14,7 @@ namespace Content.Server.Atmos.EntitySystems
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
private readonly AtmosDeviceUpdateEvent _updateEvent = new();
private readonly Stopwatch _simulationStopwatch = new();
/// <summary>
@@ -204,11 +205,10 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.CurrentRunAtmosDevices = new Queue<AtmosDeviceComponent>(atmosphere.AtmosDevices);
var time = _gameTiming.CurTime;
var updateEvent = new AtmosDeviceUpdateEvent();
var number = 0;
while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device))
{
EntityManager.EventBus.RaiseLocalEvent(device.Owner.Uid, updateEvent, false);
RaiseLocalEvent(device.Owner.Uid, _updateEvent, false);
device.LastProcess = time;
if (number++ < LagCheckIterations) continue;
@@ -241,7 +241,7 @@ namespace Content.Server.Atmos.EntitySystems
{
var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex];
if (atmosphere.Paused || atmosphere.LifeStage >= ComponentLifeStage.Stopping)
if (atmosphere.Paused || !atmosphere.Simulated || atmosphere.LifeStage >= ComponentLifeStage.Stopping)
continue;
atmosphere.Timer += frameTime;
@@ -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))

View File

@@ -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 *

View File

@@ -8,6 +8,9 @@ using Robust.Shared.Map;
namespace Content.Server.Atmos.EntitySystems
{
/// <summary>
/// This is our SSAir equivalent, if you need to interact with or query atmos in any way, go through this.
/// </summary>
[UsedImplicitly]
public partial class AtmosphereSystem : SharedAtmosphereSystem
{
@@ -29,7 +32,6 @@ namespace Content.Server.Atmos.EntitySystems
#region Events
// Map events.
_mapManager.MapCreated += OnMapCreated;
_mapManager.TileChanged += OnTileChanged;
#endregion
@@ -39,7 +41,6 @@ namespace Content.Server.Atmos.EntitySystems
{
base.Shutdown();
_mapManager.MapCreated -= OnMapCreated;
_mapManager.TileChanged -= OnTileChanged;
}
@@ -57,17 +58,6 @@ namespace Content.Server.Atmos.EntitySystems
InvalidateTile(eventArgs.NewTile.GridIndex, eventArgs.NewTile.GridIndices);
}
private void OnMapCreated(object? sender, MapEventArgs e)
{
if (e.Map == MapId.Nullspace)
return;
var map = _mapManager.GetMapEntity(e.Map);
if (!map.HasComponent<IGridAtmosphereComponent>())
map.AddComponent<SpaceGridAtmosphereComponent>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -81,7 +71,7 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var exposed in EntityManager.ComponentManager.EntityQuery<AtmosExposedComponent>())
{
// TODO ATMOS: Kill this with fire.
var tile = GetTileAtmosphereOrCreateSpace(exposed.Owner.Transform.Coordinates);
var tile = GetTileMixture(exposed.Owner.Transform.Coordinates);
if (tile == null) continue;
exposed.Update(tile, _exposedTimer, this);
}

View File

@@ -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();
}

View File

@@ -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>();

View File

@@ -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;

View File

@@ -6,20 +6,5 @@ namespace Content.Server.Atmos
public interface IGasMixtureHolder
{
public GasMixture Air { get; set; }
public virtual void AssumeAir(GasMixture giver)
{
EntitySystem.Get<AtmosphereSystem>().Merge(Air, giver);
}
public GasMixture RemoveAir(float amount)
{
return Air.Remove(amount);
}
public GasMixture RemoveAirVolume(float ratio)
{
return Air.RemoveRatio(ratio);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Components
{
/// <summary>
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
/// Adds itself to a <see cref="IAtmosphereComponent"/> to be updated by.
/// </summary>
[RegisterComponent]
public class AtmosDeviceComponent : Component
@@ -22,6 +22,19 @@ namespace Content.Server.Atmos.Piping.Components
[DataField("requireAnchored")]
public bool RequireAnchored { get; private set; } = true;
/// <summary>
/// Whether this device will join an entity system to process when not in a grid.
/// </summary>
[ViewVariables]
[DataField("joinSystem")]
public bool JoinSystem { get; } = false;
/// <summary>
/// Whether we have joined an entity system to process.
/// </summary>
[ViewVariables]
public bool JoinedSystem { get; set; } = false;
[ViewVariables]
public TimeSpan LastProcess { get; set; } = TimeSpan.Zero;

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
namespace Content.Server.Atmos.Piping.EntitySystems
@@ -12,7 +12,13 @@ namespace Content.Server.Atmos.Piping.EntitySystems
[UsedImplicitly]
public class AtmosDeviceSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
private readonly AtmosDeviceUpdateEvent _updateEvent = new();
private float _timer = 0f;
private readonly HashSet<AtmosDeviceComponent> _joinedDevices = new();
public override void Initialize()
{
@@ -32,11 +38,24 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public void JoinAtmosphere(AtmosDeviceComponent component)
{
if (!CanJoinAtmosphere(component))
{
return;
}
// We try to add the device to a valid atmosphere, and if we can't, try to add it to the entity system.
if (!_atmosphereSystem.AddAtmosDevice(component))
{
if (component.JoinSystem)
{
_joinedDevices.Add(component);
component.JoinedSystem = true;
}
else
{
return;
}
}
// We try to add the device to a valid atmosphere.
if (!Get<AtmosphereSystem>().AddAtmosDevice(component))
return;
component.LastProcess = _gameTiming.CurTime;
@@ -45,8 +64,19 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public void LeaveAtmosphere(AtmosDeviceComponent component)
{
if (!Get<AtmosphereSystem>().RemoveAtmosDevice(component))
// Try to remove the component from an atmosphere, and if not
if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component))
{
// The grid might have been removed but not us... This usually shouldn't happen.
component.JoinedGrid = null;
return;
}
if (component.JoinedSystem)
{
_joinedDevices.Remove(component);
component.JoinedSystem = false;
}
component.LastProcess = TimeSpan.Zero;
RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceDisabledEvent(), false);
@@ -84,5 +114,22 @@ namespace Content.Server.Atmos.Piping.EntitySystems
{
RejoinAtmosphere(component);
}
public override void Update(float frameTime)
{
_timer += frameTime;
if (_timer < _atmosphereSystem.AtmosTime)
return;
_timer -= _atmosphereSystem.AtmosTime;
var time = _gameTiming.CurTime;
foreach (var device in _joinedDevices)
{
RaiseLocalEvent(device.Owner.Uid, _updateEvent, false);
device.LastProcess = time;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -3,7 +3,9 @@ using Content.Server.Atmos.Piping.Trinary.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
@@ -24,33 +26,35 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, AtmosDeviceUpdateEvent args)
{
if (!filter.Enabled)
return;
var appearance = filter.Owner.GetComponentOrNull<AppearanceComponent>();
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
if (!filter.Enabled
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device)
|| !nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode)
|| !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode)
|| !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode)
|| outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full.
{
appearance?.SetData(FilterVisuals.Enabled, false);
return;
if (!ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device))
return;
if (!nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode)
|| !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode)
|| !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode))
return;
if (outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure)
return; // No need to transfer if target is full.
}
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume;
if (transferRatio <= 0)
{
appearance?.SetData(FilterVisuals.Enabled, false);
return;
}
var removed = inletNode.Air.RemoveRatio(transferRatio);
if (filter.FilteredGas.HasValue)
{
appearance?.SetData(FilterVisuals.Enabled, true);
var filteredOut = new GasMixture() {Temperature = removed.Temperature};
filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value));

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