mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-06-09 15:06:34 +02:00
Merge remote-tracking branch 'refs/remotes/upstream/master' into master-syndie
# Conflicts: # .github/CODEOWNERS # Content.Client/Options/UI/Tabs/AudioTab.xaml # Content.Client/Options/UI/Tabs/AudioTab.xaml.cs # Content.Server/Connection/ConnectionManager.cs # Content.Shared/Preferences/Loadouts/RoleLoadout.cs # Resources/Prototypes/Loadouts/role_loadouts.yml # Resources/Prototypes/SoundCollections/NukeMusic.yml # Resources/Textures/Clothing/Belt/securitywebbing.rsi/equipped-BELT.png # Resources/Textures/Clothing/Belt/securitywebbing.rsi/icon.png # Resources/Textures/Clothing/Belt/securitywebbing.rsi/inhand-left.png # Resources/Textures/Clothing/Belt/securitywebbing.rsi/inhand-right.png # Resources/Textures/Clothing/Belt/securitywebbing.rsi/meta.json # Resources/Textures/Objects/Devices/nuke.rsi/nuclearbomb_gay.png
This commit is contained in:
@@ -9,5 +9,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
if: join(github.event.issue.labels) == ''
|
||||
with:
|
||||
labels: "Status: Untriaged"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<LineEdit Name="FilterLineEdit"
|
||||
MinSize="100 0"
|
||||
HorizontalExpand="True"
|
||||
PlaceHolder="{Loc Filter}"/>
|
||||
PlaceHolder="{Loc player-list-filter}"/>
|
||||
<PanelContainer Name="BackgroundPanel"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Reason}" MinWidth="100" />
|
||||
<Label Text="{Loc admin-player-actions-reason}" MinWidth="100" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="ReasonLine" MinWidth="100" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc Teleport}" MinSize="425 230">
|
||||
Title="{Loc admin-ui-teleport}" MinSize="425 230">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<cc:PlayerListControl Name="PlayerList" />
|
||||
<Button Name="SubmitButton" Text="{Loc Teleport}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-teleport}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Load Blueprint}">
|
||||
xmlns="https://spacestation14.io" Title="{Loc admin-ui-blueprint-load}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Map}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-blueprint-map}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="MapOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Path}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-blueprint-path}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<LineEdit Name="MapPath" MinSize="200 0" HorizontalExpand="True" Text="/Maps/" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc X}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-blueprint-x}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="XCoordinate" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Y}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-blueprint-y}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="YCoordinate" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Rotation}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-blueprint-rotation}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="RotationSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc Load Blueprint}" />
|
||||
<Button Name="TeleportButton" Text="{Loc Teleport to}" />
|
||||
<Button Name="ResetButton" Text="{Loc Reset to default}"></Button>
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-blueprint-load}" />
|
||||
<Button Name="TeleportButton" Text="{Loc admin-ui-blueprint-teleport}" />
|
||||
<Button Name="ResetButton" Text="{Loc admin-ui-blueprint-reset}"></Button>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Add Atmos}">
|
||||
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Grid}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc Add Atmos}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-add}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
|
||||
while (query.MoveNext(out var uid, out var grid))
|
||||
{
|
||||
_data.Add((uid, grid));
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
|
||||
}
|
||||
|
||||
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Add Gas}">
|
||||
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add-gas}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Grid}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc TileX}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-tile-x}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc TileY}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-tile-y}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Gas}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-gas}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Amount}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-gas-amount}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc Add Gas}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-add-gas}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
|
||||
_gridData.Add(entManager.GetNetEntity(uid));
|
||||
var player = playerManager.LocalEntity;
|
||||
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString("admin-ui-atmos-grid-current") : "")}");
|
||||
}
|
||||
|
||||
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
Margin="4"
|
||||
MinSize="50 50">
|
||||
<GridContainer Columns="4">
|
||||
<cc:UICommandButton Text="{Loc Add Atmos}" Command="addatmos" WindowType="{x:Type at:AddAtmosWindow}" />
|
||||
<cc:UICommandButton Text="{Loc Add Gas}" Command="addgas" WindowType="{x:Type at:AddGasWindow}" />
|
||||
<cc:UICommandButton Text="{Loc Fill Gas}" Command="fillgas" WindowType="{x:Type at:FillGasWindow}" />
|
||||
<cc:UICommandButton Text="{Loc Set Temperature}" Command="settemp"
|
||||
<cc:UICommandButton Text="{Loc admin-ui-atmos-add}" Command="addatmos" WindowType="{x:Type at:AddAtmosWindow}" />
|
||||
<cc:UICommandButton Text="{Loc admin-ui-atmos-add-gas}" Command="addgas" WindowType="{x:Type at:AddGasWindow}" />
|
||||
<cc:UICommandButton Text="{Loc admin-ui-atmos-fill-gas}" Command="fillgas" WindowType="{x:Type at:FillGasWindow}" />
|
||||
<cc:UICommandButton Text="{Loc admin-ui-atmos-set-temperature}" Command="settemp"
|
||||
WindowType="{x:Type at:SetTemperatureWindow}" />
|
||||
</GridContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Fill Gas}">
|
||||
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-fill-gas}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Grid}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Gas}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-gas}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Amount}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-gas-amount}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc Fill Gas}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-fill-gas}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
|
||||
{
|
||||
var player = playerManager.LocalEntity;
|
||||
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
|
||||
_gridData.Add(entManager.GetNetEntity(uid));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Set Temperature}">
|
||||
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-set-temperature}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Grid}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc TileX}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-tile-x}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc TileY}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-tile-y}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Temperature}" MinSize="100 0" />
|
||||
<Label Text="{Loc admin-ui-atmos-temperature}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<SpinBox Name="TemperatureSpin" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc Set Temperature}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-set-temperature}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
|
||||
{
|
||||
var player = playerManager.LocalEntity;
|
||||
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
|
||||
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
|
||||
_data.Add(entManager.GetNetEntity(uid));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
|
||||
Text="{Loc Object type:}" />
|
||||
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
|
||||
Text="{Loc object-tab-object-type}" />
|
||||
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc object-tab-object-search}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
|
||||
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed partial class ObjectsTab : Control
|
||||
foreach (var type in Enum.GetValues(typeof(ObjectsTabSelection)))
|
||||
{
|
||||
_selections.Add((ObjectsTabSelection)type!);
|
||||
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
|
||||
ObjectTypeOptions.AddItem(Loc.GetString($"object-tab-object-type-{((Enum.GetName((ObjectsTabSelection)type))!.ToString().ToLower())}"));
|
||||
}
|
||||
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="PlayerCount" HorizontalExpand="True" Text="{Loc Player Count}" />
|
||||
<Label Name="PlayerCount" HorizontalExpand="True" Text="{Loc player-tab-player-count}" />
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
|
||||
PlaceHolder="{Loc player-tab-filter-line-edit-placeholder}" />
|
||||
<Button Name="ShowDisconnectedButton" HorizontalExpand="True"
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
_players = players;
|
||||
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
|
||||
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
|
||||
|
||||
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
MinSize="50 50">
|
||||
<GridContainer
|
||||
Columns="3">
|
||||
<cc:CommandButton Command="startround" Text="{Loc Start Round}" />
|
||||
<cc:CommandButton Command="endround" Text="{Loc End Round}" />
|
||||
<cc:CommandButton Command="restartround" Text="{Loc Restart Round}" />
|
||||
<cc:CommandButton Command="startround" Text="{Loc administration-ui-round-tab-start-round}" />
|
||||
<cc:CommandButton Command="endround" Text="{Loc administration-ui-round-tab-end-round}" />
|
||||
<cc:CommandButton Command="restartround" Text="{Loc administration-ui-round-tab-restart-round}" />
|
||||
<cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
|
||||
</GridContainer>
|
||||
</Control>
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace Content.Client.Examine
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
SubscribeLocalEvent<GetVerbsEvent<ExamineVerb>>(AddExamineVerb);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NameLabel" MinWidth="400" />
|
||||
<OptionButton Name="Button" Access="Public" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,21 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Options.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Standard UI control used for drop-downs in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="OptionsTabControlRow.AddOptionDropDown{T}"/>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OptionDropDown : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// The text describing what this drop-down controls.
|
||||
/// </summary>
|
||||
public string? Title
|
||||
{
|
||||
get => NameLabel.Text;
|
||||
set => NameLabel.Text = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NameLabel" MinWidth="400" />
|
||||
<Slider Name="Slider" Access="Public" HorizontalExpand="True" />
|
||||
<Label Name="ValueLabel" Access="Public" Margin="8 0 4 0" MinWidth="48" Align="Right" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Options.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Standard UI control used for sliders in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="OptionsTabControlRow.AddOptionSlider"/>
|
||||
/// <seealso cref="OptionsTabControlRow.AddOptionPercentSlider"/>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OptionSlider : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// The text describing what this slider controls.
|
||||
/// </summary>
|
||||
public string? Title
|
||||
{
|
||||
get => NameLabel.Text;
|
||||
set => NameLabel.Text = value;
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@
|
||||
<tabs:GraphicsTab Name="GraphicsTab" />
|
||||
<tabs:KeyRebindTab Name="KeyRebindTab" />
|
||||
<tabs:AudioTab Name="AudioTab" />
|
||||
<tabs:AccessibilityTab Name="AccessibilityTab" />
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Content.Client.Options.UI.Tabs;
|
||||
|
||||
|
||||
namespace Content.Client.Options.UI
|
||||
{
|
||||
@@ -19,13 +16,17 @@ namespace Content.Client.Options.UI
|
||||
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
|
||||
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
|
||||
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
|
||||
|
||||
UpdateTabs();
|
||||
}
|
||||
|
||||
public void UpdateTabs()
|
||||
{
|
||||
GraphicsTab.UpdateProperties();
|
||||
GraphicsTab.Control.ReloadValues();
|
||||
MiscTab.Control.ReloadValues();
|
||||
AccessibilityTab.Control.ReloadValues();
|
||||
AudioTab.Control.ReloadValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<controls:StripeBack HasBottomEdge="False">
|
||||
<BoxContainer Orientation="Horizontal" Align="End" Margin="2">
|
||||
<Button Name="DefaultButton"
|
||||
Text="{Loc 'ui-options-default'}"
|
||||
TextAlign="Center"
|
||||
Margin="8 0" />
|
||||
|
||||
<Button Name="ResetButton"
|
||||
Text="{Loc 'ui-options-reset-all'}"
|
||||
StyleClasses="Caution" />
|
||||
<Button Name="ApplyButton"
|
||||
Text="{Loc 'ui-options-apply'}"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
</Control>
|
||||
@@ -0,0 +1,684 @@
|
||||
using System.Linq;
|
||||
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.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Options.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Control used on all tabs of the in-game options menu,
|
||||
/// contains the "save" and "reset" buttons and controls the entire logic.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Basic operation is simple: options tabs put this control at the bottom of the tab,
|
||||
/// they bind UI controls to it with calls such as <see cref="AddOptionCheckBox"/>,
|
||||
/// then they call <see cref="Initialize"/>. The rest is all handled by the control.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Individual options are implementations of <see cref="BaseOption"/>. See the type for details.
|
||||
/// Common implementations for building on top of CVars are already exist,
|
||||
/// but tabs can define their own if they need to.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Generally, options are added via helper methods such as <see cref="AddOptionCheckBox"/>,
|
||||
/// however it is totally possible to directly instantiate the backing types
|
||||
/// and add them via <see cref="AddOption{T}"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The options system is general purpose enough that <see cref="OptionsTabControlRow"/> does not, itself,
|
||||
/// know what a CVar is. It does automatically save CVars to config when save is pressed, but otherwise CVar interaction
|
||||
/// is handled by <see cref="BaseOption"/> implementations.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Behaviorally, the row has 3 control buttons: save, reset changed, and reset to default.
|
||||
/// "Save" writes the configuration changes and saves the configuration.
|
||||
/// "Reset changed" discards changes made in the menu and re-loads the saved settings.
|
||||
/// "Reset to default" resets the settings on the menu to be the default, out-of-the-box values.
|
||||
/// Note that "Reset to default" does not save immediately, the user must still press save manually.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The disabled state of the 3 buttons is updated dynamically based on the values of the options.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OptionsTabControlRow : Control
|
||||
{
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private ValueList<BaseOption> _options;
|
||||
|
||||
public OptionsTabControlRow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
ResetButton.StyleClasses.Add(StyleBase.ButtonOpenRight);
|
||||
ApplyButton.OnPressed += ApplyButtonPressed;
|
||||
ResetButton.OnPressed += ResetButtonPressed;
|
||||
DefaultButton.OnPressed += DefaultButtonPressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new option to be tracked by the control.
|
||||
/// </summary>
|
||||
/// <param name="option">The option object that manages this object's logic</param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of option being passed in. Necessary to allow the return type to match the parameter type
|
||||
/// for easy chaining.
|
||||
/// </typeparam>
|
||||
/// <returns>The same <paramref name="option"/> as passed in, for easy chaining.</returns>
|
||||
public T AddOption<T>(T option) where T : BaseOption
|
||||
{
|
||||
_options.Add(option);
|
||||
return option;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a checkbox option backed by a simple boolean CVar.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar represented by the checkbox.</param>
|
||||
/// <param name="checkBox">The UI control for the option.</param>
|
||||
/// <param name="invert">
|
||||
/// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
|
||||
/// </param>
|
||||
/// <returns>The option instance backing the added option.</returns>
|
||||
/// <seealso cref="OptionCheckboxCVar"/>
|
||||
public OptionCheckboxCVar AddOptionCheckBox(CVarDef<bool> cVar, CheckBox checkBox, bool invert = false)
|
||||
{
|
||||
return AddOption(new OptionCheckboxCVar(this, _cfg, cVar, checkBox, invert));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a slider option, displayed in percent, backed by a simple float CVar.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar represented by the slider.</param>
|
||||
/// <param name="slider">The UI control for the option.</param>
|
||||
/// <param name="min">The minimum value the slider should allow. The default value represents "0%"</param>
|
||||
/// <param name="max">The maximum value the slider should allow. The default value represents "100%"</param>
|
||||
/// <param name="scale">
|
||||
/// Scale with which to multiply slider values when mapped to the backing CVar.
|
||||
/// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
|
||||
/// </param>
|
||||
/// <returns>The option instance backing the added option.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Note that percentage values are represented as ratios in code, i.e. a value of 100% is "1".
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public OptionSliderFloatCVar AddOptionPercentSlider(
|
||||
CVarDef<float> cVar,
|
||||
OptionSlider slider,
|
||||
float min = 0,
|
||||
float max = 1,
|
||||
float scale = 1)
|
||||
{
|
||||
return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a slider option, backed by a simple integer CVar.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar represented by the slider.</param>
|
||||
/// <param name="slider">The UI control for the option.</param>
|
||||
/// <param name="min">The minimum value the slider should allow.</param>
|
||||
/// <param name="max">The maximum value the slider should allow.</param>
|
||||
/// <param name="format">
|
||||
/// An optional delegate used to format the textual value display of the slider.
|
||||
/// If not provided, the default behavior is to directly format the integer value as text.
|
||||
/// </param>
|
||||
/// <returns>The option instance backing the added option.</returns>
|
||||
public OptionSliderIntCVar AddOptionSlider(
|
||||
CVarDef<int> cVar,
|
||||
OptionSlider slider,
|
||||
int min,
|
||||
int max,
|
||||
Func<OptionSliderIntCVar, int, string>? format = null)
|
||||
{
|
||||
return AddOption(new OptionSliderIntCVar(this, _cfg, cVar, slider, min, max, format ?? FormatInt));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a drop-down option, backed by a CVar.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar represented by the drop-down.</param>
|
||||
/// <param name="dropDown">The UI control for the option.</param>
|
||||
/// <param name="options">
|
||||
/// The set of options that will be shown in the drop-down. Items are ordered as provided.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the CVar being controlled.</typeparam>
|
||||
/// <returns>The option instance backing the added option.</returns>
|
||||
public OptionDropDownCVar<T> AddOptionDropDown<T>(
|
||||
CVarDef<T> cVar,
|
||||
OptionDropDown dropDown,
|
||||
IReadOnlyCollection<OptionDropDownCVar<T>.ValueOption> options)
|
||||
where T : notnull
|
||||
{
|
||||
return AddOption(new OptionDropDownCVar<T>(this, _cfg, cVar, dropDown, options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the control row. This should be called after all options have been added.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (var option in _options)
|
||||
{
|
||||
option.LoadValue();
|
||||
}
|
||||
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-loads options in the settings from backing values.
|
||||
/// Should be called when the options window is opened to make sure all values are up-to-date.
|
||||
/// </summary>
|
||||
public void ReloadValues()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by <see cref="BaseOption"/> to signal that an option's value changed through user interaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="BaseOption"/> implementations should not call this function directly,
|
||||
/// instead they should call <see cref="BaseOption.ValueChanged"/>.
|
||||
/// </remarks>
|
||||
public void ValueChanged()
|
||||
{
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
private void UpdateButtonState()
|
||||
{
|
||||
var anyModified = _options.Any(option => option.IsModified());
|
||||
var anyModifiedFromDefault = _options.Any(option => option.IsModifiedFromDefault());
|
||||
|
||||
DefaultButton.Disabled = !anyModifiedFromDefault;
|
||||
ApplyButton.Disabled = !anyModified;
|
||||
ResetButton.Disabled = !anyModified;
|
||||
}
|
||||
|
||||
private void ApplyButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var option in _options)
|
||||
{
|
||||
if (option.IsModified())
|
||||
option.SaveValue();
|
||||
}
|
||||
|
||||
_cfg.SaveToFile();
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
private void ResetButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var option in _options)
|
||||
{
|
||||
option.LoadValue();
|
||||
}
|
||||
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
private void DefaultButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var option in _options)
|
||||
{
|
||||
option.ResetToDefault();
|
||||
}
|
||||
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
private string FormatPercent(OptionSliderFloatCVar slider, float value)
|
||||
{
|
||||
return _loc.GetString("ui-options-value-percent", ("value", value));
|
||||
}
|
||||
|
||||
private static string FormatInt(OptionSliderIntCVar slider, int value)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class of a single "option" for <see cref="OptionsTabControlRow"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Implementations of this class handle loading values from backing storage or defaults,
|
||||
/// handling UI controls, and saving. The main <see cref="OptionsTabControlRow"/> does not know what a CVar is.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="BaseOptionCVar{TValue}"/> is a derived class that makes it easier to work with options
|
||||
/// backed by a single CVar.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="controller">The control row that owns this option.</param>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public abstract class BaseOption(OptionsTabControlRow controller)
|
||||
{
|
||||
/// <summary>
|
||||
/// Should be called by derived implementations to indicate that their value changed, due to user interaction.
|
||||
/// </summary>
|
||||
protected virtual void ValueChanged()
|
||||
{
|
||||
controller.ValueChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the value represented by this option from its backing store, into the UI state.
|
||||
/// </summary>
|
||||
public abstract void LoadValue();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the value in the UI state to the backing store.
|
||||
/// </summary>
|
||||
public abstract void SaveValue();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the UI state to that of the factory-default value. This should not write to the backing store.
|
||||
/// </summary>
|
||||
public abstract void ResetToDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Called to check if this option's UI value is different from the backing store value.
|
||||
/// </summary>
|
||||
/// <returns>If true, the UI value is different and was modified by the user.</returns>
|
||||
public abstract bool IsModified();
|
||||
|
||||
/// <summary>
|
||||
/// Called to check if this option's UI value is different from the backing store's default value.
|
||||
/// </summary>
|
||||
/// <returns>If true, the UI value is different.</returns>
|
||||
public abstract bool IsModifiedFromDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derived class of <see cref="BaseOption"/> intended for making mappings to simple CVars easier.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the CVar.</typeparam>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public abstract class BaseOptionCVar<TValue> : BaseOption
|
||||
where TValue : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised immediately when the UI value of this option is changed by the user, even before saving.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This can be used to update parts of the options UI based on the state of a checkbox.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event Action<TValue>? ImmediateValueChanged;
|
||||
|
||||
private readonly IConfigurationManager _cfg;
|
||||
private readonly CVarDef<TValue> _cVar;
|
||||
|
||||
/// <summary>
|
||||
/// Sets and gets the actual CVar value to/from the frontend UI state or control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In the simplest case, this function should set a UI control's state to represent the CVar,
|
||||
/// and inversely conver the UI control's state to the CVar value. For simple controls like a checkbox or slider,
|
||||
/// this just means passing through their value property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract TValue Value { get; set; }
|
||||
|
||||
protected BaseOptionCVar(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<TValue> cVar)
|
||||
: base(controller)
|
||||
{
|
||||
_cfg = cfg;
|
||||
_cVar = cVar;
|
||||
}
|
||||
|
||||
public override void LoadValue()
|
||||
{
|
||||
Value = _cfg.GetCVar(_cVar);
|
||||
}
|
||||
|
||||
public override void SaveValue()
|
||||
{
|
||||
_cfg.SetCVar(_cVar, Value);
|
||||
}
|
||||
|
||||
public override void ResetToDefault()
|
||||
{
|
||||
Value = _cVar.DefaultValue;
|
||||
}
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return !IsValueEqual(Value, _cfg.GetCVar(_cVar));
|
||||
}
|
||||
|
||||
public override bool IsModifiedFromDefault()
|
||||
{
|
||||
return !IsValueEqual(Value, _cVar.DefaultValue);
|
||||
}
|
||||
|
||||
protected virtual bool IsValueEqual(TValue a, TValue b)
|
||||
{
|
||||
// Use different logic for floats so there's some error margin.
|
||||
// This check is handled cleanly at compile-time by the JIT.
|
||||
if (typeof(TValue) == typeof(float))
|
||||
return MathHelper.CloseToPercent((float) (object) a, (float) (object) b);
|
||||
|
||||
return EqualityComparer<TValue>.Default.Equals(a, b);
|
||||
}
|
||||
|
||||
protected override void ValueChanged()
|
||||
{
|
||||
base.ValueChanged();
|
||||
|
||||
ImmediateValueChanged?.Invoke(Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of a CVar option that simply corresponds with a <see cref="CheckBox"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Generally, you should just call <c>AddOption</c> methods on <see cref="OptionsTabControlRow"/>
|
||||
/// instead of instantiating this type directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public sealed class OptionCheckboxCVar : BaseOptionCVar<bool>
|
||||
{
|
||||
private readonly CheckBox _checkBox;
|
||||
private readonly bool _invert;
|
||||
|
||||
protected override bool Value
|
||||
{
|
||||
get => _checkBox.Pressed ^ _invert;
|
||||
set => _checkBox.Pressed = value ^ _invert;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this type.
|
||||
/// </summary>
|
||||
/// <param name="controller">The control row that owns this option.</param>
|
||||
/// <param name="cfg">The configuration manager to get and set values from.</param>
|
||||
/// <param name="cVar">The CVar that is being controlled by this option.</param>
|
||||
/// <param name="checkBox">The UI control for the option.</param>
|
||||
/// <param name="invert">
|
||||
/// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
|
||||
/// such as <see cref="OptionsTabControlRow.AddOptionCheckBox"/> instead of instantiating this type directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public OptionCheckboxCVar(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<bool> cVar,
|
||||
CheckBox checkBox,
|
||||
bool invert)
|
||||
: base(controller, cfg, cVar)
|
||||
{
|
||||
_checkBox = checkBox;
|
||||
_invert = invert;
|
||||
checkBox.OnToggled += _ =>
|
||||
{
|
||||
ValueChanged();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of a CVar option that simply corresponds with a floating-point <see cref="OptionSlider"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public sealed class OptionSliderFloatCVar : BaseOptionCVar<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Scale with which to multiply slider values when mapped to the backing CVar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
|
||||
/// </remarks>
|
||||
public float Scale { get; }
|
||||
|
||||
private readonly OptionSlider _slider;
|
||||
private readonly Func<OptionSliderFloatCVar, float, string> _format;
|
||||
|
||||
protected override float Value
|
||||
{
|
||||
get => _slider.Slider.Value * Scale;
|
||||
set
|
||||
{
|
||||
_slider.Slider.Value = value / Scale;
|
||||
UpdateLabelValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
|
||||
/// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="controller">The control row that owns this option.</param>
|
||||
/// <param name="cfg">The configuration manager to get and set values from.</param>
|
||||
/// <param name="cVar">The CVar that is being controlled by this option.</param>
|
||||
/// <param name="slider">The UI control for the option.</param>
|
||||
/// <param name="minValue">The minimum value the slider should allow.</param>
|
||||
/// <param name="maxValue">The maximum value the slider should allow.</param>
|
||||
/// <param name="scale">
|
||||
/// Scale with which to multiply slider values when mapped to the backing CVar. See <see cref="Scale"/>.
|
||||
/// </param>
|
||||
/// <param name="format">Function that will be called to format the value display next to the slider.</param>
|
||||
public OptionSliderFloatCVar(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<float> cVar,
|
||||
OptionSlider slider,
|
||||
float minValue,
|
||||
float maxValue,
|
||||
float scale,
|
||||
Func<OptionSliderFloatCVar, float, string> format) : base(controller, cfg, cVar)
|
||||
{
|
||||
Scale = scale;
|
||||
_slider = slider;
|
||||
_format = format;
|
||||
|
||||
slider.Slider.MinValue = minValue;
|
||||
slider.Slider.MaxValue = maxValue;
|
||||
|
||||
slider.Slider.OnValueChanged += _ =>
|
||||
{
|
||||
ValueChanged();
|
||||
UpdateLabelValue();
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateLabelValue()
|
||||
{
|
||||
_slider.ValueLabel.Text = _format(this, _slider.Slider.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of a CVar option that simply corresponds with an integer <see cref="OptionSlider"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public sealed class OptionSliderIntCVar : BaseOptionCVar<int>
|
||||
{
|
||||
private readonly OptionSlider _slider;
|
||||
private readonly Func<OptionSliderIntCVar, int, string> _format;
|
||||
|
||||
protected override int Value
|
||||
{
|
||||
get => (int) _slider.Slider.Value;
|
||||
set
|
||||
{
|
||||
_slider.Slider.Value = value;
|
||||
UpdateLabelValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
|
||||
/// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="controller">The control row that owns this option.</param>
|
||||
/// <param name="cfg">The configuration manager to get and set values from.</param>
|
||||
/// <param name="cVar">The CVar that is being controlled by this option.</param>
|
||||
/// <param name="slider">The UI control for the option.</param>
|
||||
/// <param name="minValue">The minimum value the slider should allow.</param>
|
||||
/// <param name="maxValue">The maximum value the slider should allow.</param>
|
||||
/// <param name="format">Function that will be called to format the value display next to the slider.</param>
|
||||
public OptionSliderIntCVar(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<int> cVar,
|
||||
OptionSlider slider,
|
||||
int minValue,
|
||||
int maxValue,
|
||||
Func<OptionSliderIntCVar, int, string> format) : base(controller, cfg, cVar)
|
||||
{
|
||||
_slider = slider;
|
||||
_format = format;
|
||||
|
||||
slider.Slider.MinValue = minValue;
|
||||
slider.Slider.MaxValue = maxValue;
|
||||
slider.Slider.Rounded = true;
|
||||
|
||||
slider.Slider.OnValueChanged += _ =>
|
||||
{
|
||||
ValueChanged();
|
||||
UpdateLabelValue();
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateLabelValue()
|
||||
{
|
||||
_slider.ValueLabel.Text = _format(this, (int) _slider.Slider.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of a CVar option via a drop-down.
|
||||
/// </summary>
|
||||
/// <seealso cref="OptionsTabControlRow"/>
|
||||
public sealed class OptionDropDownCVar<T> : BaseOptionCVar<T> where T : notnull
|
||||
{
|
||||
private readonly OptionDropDown _dropDown;
|
||||
private readonly ItemEntry[] _entries;
|
||||
|
||||
protected override T Value
|
||||
{
|
||||
get => (T) _dropDown.Button.SelectedMetadata!;
|
||||
set => _dropDown.Button.SelectId(FindValueId(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
|
||||
/// such as <see cref="OptionsTabControlRow.AddOptionDropDown{T}"/> instead of instantiating this type directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="controller">The control row that owns this option.</param>
|
||||
/// <param name="cfg">The configuration manager to get and set values from.</param>
|
||||
/// <param name="cVar">The CVar that is being controlled by this option.</param>
|
||||
/// <param name="dropDown">The UI control for the option.</param>
|
||||
/// <param name="options">The list of options shown to the user.</param>
|
||||
public OptionDropDownCVar(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<T> cVar,
|
||||
OptionDropDown dropDown,
|
||||
IReadOnlyCollection<ValueOption> options) : base(controller, cfg, cVar)
|
||||
{
|
||||
if (options.Count == 0)
|
||||
throw new ArgumentException("Need at least one option!");
|
||||
|
||||
_dropDown = dropDown;
|
||||
_entries = new ItemEntry[options.Count];
|
||||
|
||||
var button = dropDown.Button;
|
||||
var i = 0;
|
||||
foreach (var option in options)
|
||||
{
|
||||
_entries[i] = new ItemEntry
|
||||
{
|
||||
Key = option.Key,
|
||||
};
|
||||
|
||||
button.AddItem(option.Label, i);
|
||||
button.SetItemMetadata(button.GetIdx(i), option.Key);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
dropDown.Button.OnItemSelected += args =>
|
||||
{
|
||||
dropDown.Button.SelectId(args.Id);
|
||||
ValueChanged();
|
||||
};
|
||||
}
|
||||
|
||||
private int FindValueId(T value)
|
||||
{
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
if (IsValueEqual(_entries[i].Key, value))
|
||||
return i;
|
||||
}
|
||||
|
||||
// This will just default select the first entry or whatever.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single option for a drop-down.
|
||||
/// </summary>
|
||||
/// <param name="key">The value that this option has. This is what will be written to the CVar if selected.</param>
|
||||
/// <param name="label">The visual text shown to the user for the option.</param>
|
||||
/// <seealso cref="OptionDropDownCVar{T}"/>
|
||||
/// <seealso cref="OptionsTabControlRow.AddOptionDropDown{T}"/>
|
||||
public sealed class ValueOption(T key, string label)
|
||||
{
|
||||
/// <summary>
|
||||
/// The value that this option has. This is what will be written to the CVar if selected.
|
||||
/// </summary>
|
||||
public readonly T Key = key;
|
||||
|
||||
/// <summary>
|
||||
/// The visual text shown to the user for the option.
|
||||
/// </summary>
|
||||
public readonly string Label = label;
|
||||
}
|
||||
|
||||
private struct ItemEntry
|
||||
{
|
||||
public T Key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
||||
<ui:OptionSlider Name="ChatWindowOpacitySlider" Title="{Loc 'ui-options-chat-window-opacity'}" />
|
||||
<ui:OptionSlider Name="ScreenShakeIntensitySlider" Title="{Loc 'ui-options-screen-shake-intensity'}" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AccessibilityTab : Control
|
||||
{
|
||||
public AccessibilityTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
|
||||
Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +1,27 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:Content.Client.Stylesheets"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
|
||||
<Label Text="{Loc 'ui-options-volume-label'}"
|
||||
FontColorOverride="{x:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<BoxContainer Orientation="Vertical" Margin="0 3 0 0">
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-master-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="MasterVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="MasterVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-midi-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="MidiVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="MidiVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-ambient-music-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="AmbientMusicVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="AmbientMusicVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-ambience-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="AmbienceVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="AmbienceVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-lobby-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="LobbyVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="LobbyVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-interface-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="InterfaceVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="InterfaceVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-tts-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="TtsVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="200"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="TtsVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-ambience-max-sounds'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="AmbienceSoundsSlider"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="AmbienceSoundsLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<ui:OptionSlider Name="SliderVolumeMaster" Title="{Loc 'ui-options-master-volume'}"
|
||||
Margin="0 0 0 8" />
|
||||
<!-- Corvax-TTS-Start --><ui:OptionSlider Name="SliderVolumeTts" Title="{Loc 'ui-options-tts-volume'}" /><!-- Corvax-TTS-End -->
|
||||
<ui:OptionSlider Name="SliderVolumeMidi" Title="{Loc 'ui-options-midi-volume'}" />
|
||||
<ui:OptionSlider Name="SliderVolumeAmbientMusic" Title="{Loc 'ui-options-ambient-music-volume'}" />
|
||||
<ui:OptionSlider Name="SliderVolumeAmbience" Title="{Loc 'ui-options-ambience-volume'}" />
|
||||
<ui:OptionSlider Name="SliderVolumeLobby" Title="{Loc 'ui-options-lobby-volume'}" />
|
||||
<ui:OptionSlider Name="SliderVolumeInterface" Title="{Loc 'ui-options-interface-volume'}" />
|
||||
<ui:OptionSlider Name="SliderMaxAmbienceSounds" Title="{Loc 'ui-options-ambience-max-sounds'}"
|
||||
Margin="0 0 0 8" />
|
||||
<CheckBox Name="LobbyMusicCheckBox" Text="{Loc 'ui-options-lobby-music'}" />
|
||||
<CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
|
||||
<CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
|
||||
<CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Align="End"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<Button Name="ResetButton"
|
||||
Text="{Loc 'ui-options-reset-all'}"
|
||||
StyleClasses="Caution"
|
||||
HorizontalExpand="True"
|
||||
HorizontalAlignment="Right" />
|
||||
<Control MinSize="2 0" />
|
||||
<Button Name="ApplyButton"
|
||||
Text="{Loc 'ui-options-apply'}"
|
||||
TextAlign="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -4,216 +4,72 @@ using Content.Shared.Corvax.CCCVars;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs
|
||||
namespace Content.Client.Options.UI.Tabs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AudioTab : Control
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AudioTab : Control
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IAudioManager _audio = default!;
|
||||
|
||||
public AudioTab()
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
private readonly IAudioManager _audio;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
public AudioTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
var masterVolume = Control.AddOptionPercentSlider(
|
||||
CVars.AudioMasterVolume,
|
||||
SliderVolumeMaster,
|
||||
scale: ContentAudioSystem.MasterVolumeMultiplier);
|
||||
masterVolume.ImmediateValueChanged += OnMasterVolumeSliderChanged;
|
||||
|
||||
_audio = IoCManager.Resolve<IAudioManager>();
|
||||
LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
|
||||
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
Control.AddOptionPercentSlider(
|
||||
CVars.MidiVolume,
|
||||
SliderVolumeMidi,
|
||||
scale: ContentAudioSystem.MidiVolumeMultiplier);
|
||||
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
ResetButton.OnPressed += OnResetButtonPressed;
|
||||
MasterVolumeSlider.OnValueChanged += OnMasterVolumeSliderChanged;
|
||||
MidiVolumeSlider.OnValueChanged += OnMidiVolumeSliderChanged;
|
||||
AmbientMusicVolumeSlider.OnValueChanged += OnAmbientMusicVolumeSliderChanged;
|
||||
AmbienceVolumeSlider.OnValueChanged += OnAmbienceVolumeSliderChanged;
|
||||
AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
|
||||
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
|
||||
InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
|
||||
TtsVolumeSlider.OnValueChanged += OnTtsVolumeSliderChanged; // Corvax-TTS
|
||||
LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
|
||||
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
|
||||
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
|
||||
AdminSoundsCheckBox.OnToggled += OnAdminSoundsCheckToggled;
|
||||
Control.AddOptionPercentSlider(
|
||||
CCVars.AmbientMusicVolume,
|
||||
SliderVolumeAmbientMusic,
|
||||
scale: ContentAudioSystem.AmbientMusicMultiplier);
|
||||
|
||||
AmbienceSoundsSlider.MinValue = _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured);
|
||||
AmbienceSoundsSlider.MaxValue = _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured);
|
||||
Control.AddOptionPercentSlider(
|
||||
CCVars.AmbienceVolume,
|
||||
SliderVolumeAmbience,
|
||||
scale: ContentAudioSystem.AmbienceMultiplier);
|
||||
|
||||
Reset();
|
||||
}
|
||||
Control.AddOptionPercentSlider(
|
||||
CCVars.LobbyMusicVolume,
|
||||
SliderVolumeLobby,
|
||||
scale: ContentAudioSystem.LobbyMultiplier);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
ApplyButton.OnPressed -= OnApplyButtonPressed;
|
||||
ResetButton.OnPressed -= OnResetButtonPressed;
|
||||
MasterVolumeSlider.OnValueChanged -= OnMasterVolumeSliderChanged;
|
||||
MidiVolumeSlider.OnValueChanged -= OnMidiVolumeSliderChanged;
|
||||
AmbientMusicVolumeSlider.OnValueChanged -= OnAmbientMusicVolumeSliderChanged;
|
||||
AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
|
||||
LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
|
||||
InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
|
||||
TtsVolumeSlider.OnValueChanged -= OnTtsVolumeSliderChanged; // Corvax-TTS
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
Control.AddOptionPercentSlider(
|
||||
CCVars.InterfaceVolume,
|
||||
SliderVolumeInterface,
|
||||
scale: ContentAudioSystem.InterfaceMultiplier);
|
||||
|
||||
private void OnLobbyVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
Control.AddOptionSlider(
|
||||
CCVars.MaxAmbientSources,
|
||||
SliderMaxAmbienceSounds,
|
||||
_cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured),
|
||||
_cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured));
|
||||
|
||||
private void OnInterfaceVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
Control.AddOptionCheckBox(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.EventMusicEnabled, EventMusicCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox);
|
||||
|
||||
private void OnAmbientMusicVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
private void OnAmbienceVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnAmbienceSoundsSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnMasterVolumeSliderChanged(Range range)
|
||||
{
|
||||
_audio.SetMasterGain(MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnMidiVolumeSliderChanged(Range range)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
// Corvax-TTS-Start
|
||||
private void OnTtsVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
// Corvax-TTS-End
|
||||
|
||||
private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
private void OnRestartSoundsCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
private void OnEventMusicCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnAdminSoundsCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CVars.AudioMasterVolume, MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
|
||||
// Want the CVar updated values to have the multiplier applied
|
||||
// For the UI we just display 0-100 still elsewhere
|
||||
_cfg.SetCVar(CVars.MidiVolume, MidiVolumeSlider.Value / 100f * ContentAudioSystem.MidiVolumeMultiplier);
|
||||
_cfg.SetCVar(CCVars.AmbienceVolume, AmbienceVolumeSlider.Value / 100f * ContentAudioSystem.AmbienceMultiplier);
|
||||
_cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
|
||||
_cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
|
||||
_cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
|
||||
_cfg.SetCVar(CCCVars.TTSVolume, TtsVolumeSlider.Value / 100f * ContentAudioSystem.TtsMultiplier); // Corvax-TTS
|
||||
|
||||
_cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
|
||||
|
||||
_cfg.SetCVar(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.EventMusicEnabled, EventMusicCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox.Pressed);
|
||||
_cfg.SaveToFile();
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
MasterVolumeSlider.Value = _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier;
|
||||
MidiVolumeSlider.Value = _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier;
|
||||
AmbienceVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier;
|
||||
AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
|
||||
LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
|
||||
InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
|
||||
TtsVolumeSlider.Value = _cfg.GetCVar(CCCVars.TTSVolume) * 100f / ContentAudioSystem.TtsMultiplier; // Corvax-TTS
|
||||
|
||||
AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
|
||||
|
||||
LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
|
||||
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void UpdateChanges()
|
||||
{
|
||||
// y'all need jesus.
|
||||
var isMasterVolumeSame =
|
||||
Math.Abs(MasterVolumeSlider.Value - _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier) < 0.01f;
|
||||
var isMidiVolumeSame =
|
||||
Math.Abs(MidiVolumeSlider.Value - _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier) < 0.01f;
|
||||
var isAmbientVolumeSame =
|
||||
Math.Abs(AmbienceVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier) < 0.01f;
|
||||
var isAmbientMusicVolumeSame =
|
||||
Math.Abs(AmbientMusicVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier) < 0.01f;
|
||||
var isLobbyVolumeSame =
|
||||
Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
|
||||
var isInterfaceVolumeSame =
|
||||
Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
|
||||
var isTtsVolumeSame =
|
||||
Math.Abs(TtsVolumeSlider.Value - _cfg.GetCVar(CCCVars.TTSVolume) * 100f / ContentAudioSystem.TtsMultiplier) < 0.01f;
|
||||
|
||||
var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
|
||||
var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
|
||||
var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
|
||||
&& isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
|
||||
isEverythingSame = isEverythingSame && isTtsVolumeSame; // Corvax-TTS
|
||||
ApplyButton.Disabled = isEverythingSame;
|
||||
ResetButton.Disabled = isEverythingSame;
|
||||
MasterVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", MasterVolumeSlider.Value / 100));
|
||||
MidiVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", MidiVolumeSlider.Value / 100));
|
||||
AmbientMusicVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", AmbientMusicVolumeSlider.Value / 100));
|
||||
AmbienceVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", AmbienceVolumeSlider.Value / 100));
|
||||
LobbyVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
|
||||
InterfaceVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
|
||||
TtsVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", TtsVolumeSlider.Value / 100)); // Corvax-TTS
|
||||
AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
|
||||
}
|
||||
private void OnMasterVolumeSliderChanged(float value)
|
||||
{
|
||||
// TODO: I was thinking of giving OptionsTabControlRow a flag to "set CVar immediately", but I'm deferring that
|
||||
// until there's a proper system for enforcing people don't close the window with pending changes.
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,38 @@
|
||||
<tabs:GraphicsTab xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs">
|
||||
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
|
||||
<CheckBox Name="VSyncCheckBox" Text="{Loc 'ui-options-vsync'}" />
|
||||
<CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-lighting-label'}" />
|
||||
<Control MinSize="4 0" />
|
||||
<OptionButton Name="LightingPresetOption" MinSize="100 0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-scale-label'}" />
|
||||
<Control MinSize="4 0" />
|
||||
<OptionButton Name="UIScaleOption" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8">
|
||||
<!-- Display -->
|
||||
<Label Text="{Loc 'ui-options-display-label'}" StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="VSyncCheckBox" Text="{Loc 'ui-options-vsync'}" />
|
||||
<CheckBox Name="FullscreenCheckBox" Text="{Loc 'ui-options-fullscreen'}" />
|
||||
|
||||
<!-- Quality -->
|
||||
<Label Text="{Loc 'ui-options-quality-label'}" StyleClasses="LabelKeyText"/>
|
||||
<ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
|
||||
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
|
||||
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
|
||||
|
||||
<!-- Interface -->
|
||||
<Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>
|
||||
<ui:OptionDropDown Name="DropDownUIScale" Title="{Loc 'ui-options-scale-label'}" />
|
||||
<CheckBox Name="ViewportStretchCheckBox" Text="{Loc 'ui-options-vp-stretch'}" />
|
||||
<BoxContainer Name="ViewportScaleBox" Orientation="Horizontal">
|
||||
<Label Name="ViewportScaleText" Margin="8 0" />
|
||||
<Slider Name="ViewportScaleSlider"
|
||||
MinValue="1"
|
||||
MaxValue="5"
|
||||
Rounded="True"
|
||||
MinWidth="200" />
|
||||
</BoxContainer>
|
||||
<ui:OptionSlider Name="ViewportScaleSlider" Title="{Loc ui-options-vp-scale}" />
|
||||
<ui:OptionSlider Name="ViewportWidthSlider" Title="{Loc ui-options-vp-width}" />
|
||||
<CheckBox Name="IntegerScalingCheckBox"
|
||||
Text="{Loc 'ui-options-vp-integer-scaling'}"
|
||||
ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
|
||||
<CheckBox Name="ViewportVerticalFitCheckBox"
|
||||
Text="{Loc 'ui-options-vp-vertical-fit'}"
|
||||
ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
|
||||
|
||||
<!-- Misc -->
|
||||
<Label Text="{Loc 'ui-options-misc-label'}" StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="ViewportWidthSliderDisplay" />
|
||||
<Control MinSize="4 0" />
|
||||
<Slider Name="ViewportWidthSlider"
|
||||
Rounded="True"
|
||||
MinWidth="200" />
|
||||
</BoxContainer>
|
||||
<CheckBox Name="IntegerScalingCheckBox"
|
||||
Text="{Loc 'ui-options-vp-integer-scaling'}"
|
||||
ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
|
||||
<CheckBox Name="ViewportVerticalFitCheckBox"
|
||||
Text="{Loc 'ui-options-vp-vertical-fit'}"
|
||||
ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
|
||||
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
|
||||
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
|
||||
<CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
|
||||
</BoxContainer>
|
||||
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
|
||||
<Button Name="ApplyButton"
|
||||
Text="{Loc 'ui-options-apply'}"
|
||||
TextAlign="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
</controls:StripeBack>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
</BoxContainer>
|
||||
</tabs:GraphicsTab>
|
||||
|
||||
@@ -7,220 +7,141 @@ using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs
|
||||
namespace Content.Client.Options.UI.Tabs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GraphicsTab : Control
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GraphicsTab : Control
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public GraphicsTab()
|
||||
{
|
||||
private static readonly float[] UIScaleOptions =
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
|
||||
Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
|
||||
Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
|
||||
|
||||
Control.AddOptionDropDown(
|
||||
CVars.DisplayUIScale,
|
||||
DropDownUIScale,
|
||||
[
|
||||
new OptionDropDownCVar<float>.ValueOption(
|
||||
0f,
|
||||
Loc.GetString("ui-options-scale-auto", ("scale", UserInterfaceManager.DefaultUIScale))),
|
||||
new OptionDropDownCVar<float>.ValueOption(0.75f, Loc.GetString("ui-options-scale-75")),
|
||||
new OptionDropDownCVar<float>.ValueOption(1.00f, Loc.GetString("ui-options-scale-100")),
|
||||
new OptionDropDownCVar<float>.ValueOption(1.25f, Loc.GetString("ui-options-scale-125")),
|
||||
new OptionDropDownCVar<float>.ValueOption(1.50f, Loc.GetString("ui-options-scale-150")),
|
||||
new OptionDropDownCVar<float>.ValueOption(1.75f, Loc.GetString("ui-options-scale-175")),
|
||||
new OptionDropDownCVar<float>.ValueOption(2.00f, Loc.GetString("ui-options-scale-200")),
|
||||
]);
|
||||
|
||||
var vpStretch = Control.AddOptionCheckBox(CCVars.ViewportStretch, ViewportStretchCheckBox);
|
||||
var vpVertFit = Control.AddOptionCheckBox(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox);
|
||||
Control.AddOptionSlider(
|
||||
CCVars.ViewportFixedScaleFactor,
|
||||
ViewportScaleSlider,
|
||||
1,
|
||||
5,
|
||||
(_, value) => Loc.GetString("ui-options-vp-scale-value", ("scale", value)));
|
||||
|
||||
vpStretch.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
|
||||
vpVertFit.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
|
||||
|
||||
Control.AddOptionSlider(
|
||||
CCVars.ViewportWidth,
|
||||
ViewportWidthSlider,
|
||||
(int)ViewportWidthSlider.Slider.MinValue,
|
||||
(int)ViewportWidthSlider.Slider.MaxValue);
|
||||
|
||||
Control.AddOption(new OptionIntegerScaling(Control, _cfg, IntegerScalingCheckBox));
|
||||
Control.AddOptionCheckBox(CCVars.ViewportScaleRender, ViewportLowResCheckBox, invert: true);
|
||||
Control.AddOptionCheckBox(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.HudFpsCounterVisible, FpsCounterCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
|
||||
_cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
|
||||
|
||||
UpdateViewportWidthRange();
|
||||
UpdateViewportSettingsVisibility();
|
||||
}
|
||||
|
||||
private void UpdateViewportSettingsVisibility()
|
||||
{
|
||||
ViewportScaleSlider.Visible = !ViewportStretchCheckBox.Pressed;
|
||||
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportWidthSlider.Visible = !ViewportStretchCheckBox.Pressed || !ViewportVerticalFitCheckBox.Pressed;
|
||||
}
|
||||
|
||||
private void UpdateViewportWidthRange()
|
||||
{
|
||||
var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
|
||||
var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
|
||||
|
||||
ViewportWidthSlider.Slider.MinValue = min;
|
||||
ViewportWidthSlider.Slider.MaxValue = max;
|
||||
}
|
||||
|
||||
private sealed class OptionLightingQuality : BaseOption
|
||||
{
|
||||
private readonly IConfigurationManager _cfg;
|
||||
private readonly OptionDropDown _dropDown;
|
||||
|
||||
private const int QualityVeryLow = 0;
|
||||
private const int QualityLow = 1;
|
||||
private const int QualityMedium = 2;
|
||||
private const int QualityHigh = 3;
|
||||
|
||||
private const int QualityDefault = QualityMedium;
|
||||
|
||||
public OptionLightingQuality(OptionsTabControlRow controller, IConfigurationManager cfg, OptionDropDown dropDown) : base(controller)
|
||||
{
|
||||
0f,
|
||||
0.75f,
|
||||
1f,
|
||||
1.25f,
|
||||
1.50f,
|
||||
1.75f,
|
||||
2f
|
||||
};
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public GraphicsTab()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
VSyncCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FullscreenCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
|
||||
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-very-low"));
|
||||
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-low"));
|
||||
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-medium"));
|
||||
LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-high"));
|
||||
LightingPresetOption.OnItemSelected += OnLightingQualityChanged;
|
||||
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-auto",
|
||||
("scale", UserInterfaceManager.DefaultUIScale)));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-75"));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-100"));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-125"));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-150"));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-175"));
|
||||
UIScaleOption.AddItem(Loc.GetString("ui-options-scale-200"));
|
||||
UIScaleOption.OnItemSelected += OnUIScaleChanged;
|
||||
|
||||
ViewportStretchCheckBox.OnToggled += _ =>
|
||||
{
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
ViewportScaleSlider.OnValueChanged += _ =>
|
||||
{
|
||||
UpdateApplyButton();
|
||||
UpdateViewportScale();
|
||||
};
|
||||
|
||||
ViewportWidthSlider.OnValueChanged += _ =>
|
||||
{
|
||||
UpdateViewportWidthDisplay();
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
ViewportVerticalFitCheckBox.OnToggled += _ =>
|
||||
{
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ParallaxLowQualityCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FpsCounterCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
VSyncCheckBox.Pressed = _cfg.GetCVar(CVars.DisplayVSync);
|
||||
FullscreenCheckBox.Pressed = ConfigIsFullscreen;
|
||||
LightingPresetOption.SelectId(GetConfigLightingQuality());
|
||||
UIScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
|
||||
ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
|
||||
ViewportVerticalFitCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
ViewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality);
|
||||
FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
ViewportWidthSlider.Value = _cfg.GetCVar(CCVars.ViewportWidth);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
|
||||
_cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
|
||||
|
||||
UpdateViewportWidthRange();
|
||||
UpdateViewportWidthDisplay();
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
_cfg = cfg;
|
||||
_dropDown = dropDown;
|
||||
var button = dropDown.Button;
|
||||
button.AddItem(Loc.GetString("ui-options-lighting-very-low"), QualityVeryLow);
|
||||
button.AddItem(Loc.GetString("ui-options-lighting-low"), QualityLow);
|
||||
button.AddItem(Loc.GetString("ui-options-lighting-medium"), QualityMedium);
|
||||
button.AddItem(Loc.GetString("ui-options-lighting-high"), QualityHigh);
|
||||
button.OnItemSelected += OnOptionSelected;
|
||||
}
|
||||
|
||||
private void OnUIScaleChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
private void OnOptionSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
UIScaleOption.SelectId(args.Id);
|
||||
UpdateApplyButton();
|
||||
_dropDown.Button.SelectId(obj.Id);
|
||||
ValueChanged();
|
||||
}
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
public override void LoadValue()
|
||||
{
|
||||
_cfg.SetCVar(CVars.DisplayVSync, VSyncCheckBox.Pressed);
|
||||
SetConfigLightingQuality(LightingPresetOption.SelectedId);
|
||||
|
||||
_cfg.SetCVar(CVars.DisplayWindowMode,
|
||||
(int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed));
|
||||
_cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[UIScaleOption.SelectedId]);
|
||||
_cfg.SetCVar(CCVars.ViewportStretch, ViewportStretchCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) ViewportScaleSlider.Value);
|
||||
_cfg.SetCVar(CCVars.ViewportSnapToleranceMargin,
|
||||
IntegerScalingCheckBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0);
|
||||
_cfg.SetCVar(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ViewportScaleRender, !ViewportLowResCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ViewportWidth, (int) ViewportWidthSlider.Value);
|
||||
|
||||
_cfg.SaveToFile();
|
||||
UpdateApplyButton();
|
||||
_dropDown.Button.SelectId(GetConfigLightingQuality());
|
||||
}
|
||||
|
||||
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
public override void SaveValue()
|
||||
{
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
LightingPresetOption.SelectId(args.Id);
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
private void UpdateApplyButton()
|
||||
{
|
||||
var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(CVars.DisplayVSync);
|
||||
var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen;
|
||||
var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
|
||||
var isUIScaleSame = MathHelper.CloseToPercent(UIScaleOptions[UIScaleOption.SelectedId], ConfigUIScale);
|
||||
var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
var isIntegerScalingSame = IntegerScalingCheckBox.Pressed == (_cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0);
|
||||
var isVPVerticalFitSame = ViewportVerticalFitCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
var isVPResSame = ViewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality);
|
||||
var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
var isWidthSame = (int) ViewportWidthSlider.Value == _cfg.GetCVar(CCVars.ViewportWidth);
|
||||
|
||||
ApplyButton.Disabled = isVSyncSame &&
|
||||
isFullscreenSame &&
|
||||
isLightingQualitySame &&
|
||||
isUIScaleSame &&
|
||||
isVPStretchSame &&
|
||||
isVPScaleSame &&
|
||||
isIntegerScalingSame &&
|
||||
isVPVerticalFitSame &&
|
||||
isVPResSame &&
|
||||
isPLQSame &&
|
||||
isFpsCounterVisibleSame &&
|
||||
isWidthSame;
|
||||
}
|
||||
|
||||
private bool ConfigIsFullscreen =>
|
||||
_cfg.GetCVar(CVars.DisplayWindowMode) == (int) WindowMode.Fullscreen;
|
||||
|
||||
public void UpdateProperties()
|
||||
{
|
||||
FullscreenCheckBox.Pressed = ConfigIsFullscreen;
|
||||
}
|
||||
|
||||
|
||||
private float ConfigUIScale => _cfg.GetCVar(CVars.DisplayUIScale);
|
||||
|
||||
private int GetConfigLightingQuality()
|
||||
{
|
||||
var val = _cfg.GetCVar(CVars.LightResolutionScale);
|
||||
var soft = _cfg.GetCVar(CVars.LightSoftShadows);
|
||||
if (val <= 0.125)
|
||||
switch (_dropDown.Button.SelectedId)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if ((val <= 0.5) && !soft)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (val <= 0.5)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetConfigLightingQuality(int value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
case QualityVeryLow:
|
||||
_cfg.SetCVar(CVars.LightResolutionScale, 0.125f);
|
||||
_cfg.SetCVar(CVars.LightSoftShadows, false);
|
||||
_cfg.SetCVar(CVars.LightBlur, false);
|
||||
break;
|
||||
case 1:
|
||||
case QualityLow:
|
||||
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
|
||||
_cfg.SetCVar(CVars.LightSoftShadows, false);
|
||||
_cfg.SetCVar(CVars.LightBlur, true);
|
||||
break;
|
||||
case 2:
|
||||
default: // = QualityMedium
|
||||
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
|
||||
_cfg.SetCVar(CVars.LightSoftShadows, true);
|
||||
_cfg.SetCVar(CVars.LightBlur, true);
|
||||
break;
|
||||
case 3:
|
||||
case QualityHigh:
|
||||
_cfg.SetCVar(CVars.LightResolutionScale, 1);
|
||||
_cfg.SetCVar(CVars.LightSoftShadows, true);
|
||||
_cfg.SetCVar(CVars.LightBlur, true);
|
||||
@@ -228,40 +149,83 @@ namespace Content.Client.Options.UI.Tabs
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetConfigUIScalePreset(float value)
|
||||
public override void ResetToDefault()
|
||||
{
|
||||
for (var i = 0; i < UIScaleOptions.Length; i++)
|
||||
_dropDown.Button.SelectId(QualityDefault);
|
||||
}
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return _dropDown.Button.SelectedId != GetConfigLightingQuality();
|
||||
}
|
||||
|
||||
public override bool IsModifiedFromDefault()
|
||||
{
|
||||
return _dropDown.Button.SelectedId != QualityDefault;
|
||||
}
|
||||
|
||||
private int GetConfigLightingQuality()
|
||||
{
|
||||
var val = _cfg.GetCVar(CVars.LightResolutionScale);
|
||||
var soft = _cfg.GetCVar(CVars.LightSoftShadows);
|
||||
if (val <= 0.125)
|
||||
return QualityVeryLow;
|
||||
|
||||
if ((val <= 0.5) && !soft)
|
||||
return QualityLow;
|
||||
|
||||
if (val <= 0.5)
|
||||
return QualityMedium;
|
||||
|
||||
return QualityHigh;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class OptionFullscreen : BaseOptionCVar<int>
|
||||
{
|
||||
private readonly CheckBox _checkBox;
|
||||
|
||||
protected override int Value
|
||||
{
|
||||
get => _checkBox.Pressed ? (int) WindowMode.Fullscreen : (int) WindowMode.Windowed;
|
||||
set => _checkBox.Pressed = (value == (int) WindowMode.Fullscreen);
|
||||
}
|
||||
|
||||
public OptionFullscreen(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CheckBox checkBox)
|
||||
: base(controller, cfg, CVars.DisplayWindowMode)
|
||||
{
|
||||
_checkBox = checkBox;
|
||||
_checkBox.OnToggled += _ =>
|
||||
{
|
||||
if (MathHelper.CloseToPercent(UIScaleOptions[i], value))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ValueChanged();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
private sealed class OptionIntegerScaling : BaseOptionCVar<int>
|
||||
{
|
||||
private readonly CheckBox _checkBox;
|
||||
|
||||
protected override int Value
|
||||
{
|
||||
get => _checkBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0;
|
||||
set => _checkBox.Pressed = (value != 0);
|
||||
}
|
||||
|
||||
private void UpdateViewportScale()
|
||||
public OptionIntegerScaling(
|
||||
OptionsTabControlRow controller,
|
||||
IConfigurationManager cfg,
|
||||
CheckBox checkBox)
|
||||
: base(controller, cfg, CCVars.ViewportSnapToleranceMargin)
|
||||
{
|
||||
ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
|
||||
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed;
|
||||
ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
|
||||
}
|
||||
|
||||
private void UpdateViewportWidthRange()
|
||||
{
|
||||
var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
|
||||
var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
|
||||
|
||||
ViewportWidthSlider.MinValue = min;
|
||||
ViewportWidthSlider.MaxValue = max;
|
||||
}
|
||||
|
||||
private void UpdateViewportWidthDisplay()
|
||||
{
|
||||
ViewportWidthSliderDisplay.Text = Loc.GetString("ui-options-vp-width", ("width", (int) ViewportWidthSlider.Value));
|
||||
_checkBox = checkBox;
|
||||
_checkBox.OnToggled += _ =>
|
||||
{
|
||||
ValueChanged();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +1,34 @@
|
||||
<tabs:MiscTab xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
|
||||
xmlns:xNamespace="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:Content.Client.Stylesheets">
|
||||
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
|
||||
<Label Text="{Loc 'ui-options-general-ui-style'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-hud-theme'}" />
|
||||
<Control MinSize="4 0" />
|
||||
<OptionButton Name="HudThemeOption" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-hud-layout'}" />
|
||||
<Control MinSize="4 0" />
|
||||
<OptionButton Name="HudLayoutOption" />
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'ui-options-general-accessibility'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-chat-window-opacity'}" Margin="8 0" />
|
||||
<Slider Name="ChatWindowOpacitySlider"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
MinWidth="200" />
|
||||
<Label Name="ChatWindowOpacityLabel" Margin="8 0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
|
||||
<Slider Name="ScreenShakeIntensitySlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
Rounded="True"
|
||||
MinWidth="200" />
|
||||
<Label Name="ScreenShakeIntensityLabel" Margin="8 0" />
|
||||
</BoxContainer>
|
||||
<ui:OptionDropDown Name="DropDownHudTheme" Title="{Loc 'ui-options-hud-theme'}" />
|
||||
<ui:OptionDropDown Name="DropDownHudLayout" Title="{Loc 'ui-options-hud-layout'}" />
|
||||
<Label Text="{Loc 'ui-options-general-discord'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="DiscordRich" Text="{Loc 'ui-options-discordrich'}" />
|
||||
<Label Text="{Loc 'ui-options-general-speech'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" />
|
||||
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
||||
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||
<Label Text="{Loc 'ui-options-general-cursor'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
|
||||
<CheckBox Name="ShowCombatModeIndicatorsCheckBox" Text="{Loc 'ui-options-show-combat-mode-indicators'}" />
|
||||
<Label Text="{Loc 'ui-options-general-storage'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="OpaqueStorageWindowCheckBox" Text="{Loc 'ui-options-opaque-storage-window'}" />
|
||||
<CheckBox Name="StaticStorageUI" Text="{Loc 'ui-options-static-storage-ui'}" />
|
||||
<!-- <CheckBox Name="ToggleWalk" Text="{Loc 'ui-options-hotkey-toggle-walk'}" /> -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
|
||||
<Button Name="ApplyButton"
|
||||
Text="{Loc 'ui-options-apply'}"
|
||||
TextAlign="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
</controls:StripeBack>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
</BoxContainer>
|
||||
</tabs:MiscTab>
|
||||
|
||||
@@ -5,201 +5,54 @@ using Content.Shared.HUD;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs
|
||||
namespace Content.Client.Options.UI.Tabs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiscTab : Control
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiscTab : Control
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public MiscTab()
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
private readonly Dictionary<string, int> _hudThemeIdToIndex = new();
|
||||
|
||||
public MiscTab()
|
||||
var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList();
|
||||
themes.Sort();
|
||||
var themeEntries = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var gear in themes)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var themes = _prototypeManager.EnumeratePrototypes<HudThemePrototype>().ToList();
|
||||
themes.Sort();
|
||||
foreach (var gear in themes)
|
||||
{
|
||||
HudThemeOption.AddItem(Loc.GetString(gear.Name));
|
||||
_hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1));
|
||||
}
|
||||
|
||||
var hudLayout = _cfg.GetCVar(CCVars.UILayout);
|
||||
var id = 0;
|
||||
foreach (var layout in Enum.GetValues(typeof(ScreenType)))
|
||||
{
|
||||
var name = layout.ToString()!;
|
||||
HudLayoutOption.AddItem(name, id);
|
||||
if (name == hudLayout)
|
||||
{
|
||||
HudLayoutOption.SelectId(id);
|
||||
}
|
||||
HudLayoutOption.SetItemMetadata(id, name);
|
||||
|
||||
id++;
|
||||
}
|
||||
|
||||
HudLayoutOption.OnItemSelected += args =>
|
||||
{
|
||||
HudLayoutOption.SelectId(args.Id);
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
|
||||
HudThemeOption.OnItemSelected += OnHudThemeChanged;
|
||||
DiscordRich.OnToggled += OnCheckBoxToggled;
|
||||
ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
|
||||
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
|
||||
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
|
||||
// ToggleWalk.OnToggled += OnCheckBoxToggled;
|
||||
StaticStorageUI.OnToggled += OnCheckBoxToggled;
|
||||
|
||||
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
|
||||
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
|
||||
ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
|
||||
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
|
||||
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||
ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
||||
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
|
||||
ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
|
||||
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
|
||||
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
|
||||
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
|
||||
|
||||
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
UpdateApplyButton();
|
||||
themeEntries.Add(new OptionDropDownCVar<string>.ValueOption(gear.ID, Loc.GetString(gear.Name)));
|
||||
}
|
||||
|
||||
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
var layoutEntries = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var layout in Enum.GetValues(typeof(ScreenType)))
|
||||
{
|
||||
UpdateApplyButton();
|
||||
layoutEntries.Add(new OptionDropDownCVar<string>.ValueOption(layout.ToString()!, layout.ToString()!));
|
||||
}
|
||||
|
||||
private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
HudThemeOption.SelectId(args.Id);
|
||||
UpdateApplyButton();
|
||||
}
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
|
||||
private void OnChatWindowOpacitySliderChanged(Range range)
|
||||
{
|
||||
ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
|
||||
("opacity", range.Value));
|
||||
UpdateApplyButton();
|
||||
}
|
||||
Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
|
||||
Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
|
||||
|
||||
private void OnScreenShakeIntensitySliderChanged(Range obj)
|
||||
{
|
||||
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
foreach (var theme in _prototypeManager.EnumeratePrototypes<HudThemePrototype>())
|
||||
{
|
||||
if (_hudThemeIdToIndex[theme.ID] != HudThemeOption.SelectedId)
|
||||
continue;
|
||||
_cfg.SetCVar(CVars.InterfaceTheme, theme.ID);
|
||||
break;
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CVars.DiscordEnabled, DiscordRich.Pressed);
|
||||
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
|
||||
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
|
||||
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
|
||||
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
|
||||
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
|
||||
|
||||
if (HudLayoutOption.SelectedMetadata is string opt)
|
||||
{
|
||||
_cfg.SetCVar(CCVars.UILayout, opt);
|
||||
}
|
||||
|
||||
_cfg.SaveToFile();
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
private void UpdateApplyButton()
|
||||
{
|
||||
var isHudThemeSame = HudThemeOption.SelectedId == _hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0);
|
||||
var isLayoutSame = HudLayoutOption.SelectedMetadata is string opt && opt == _cfg.GetCVar(CCVars.UILayout);
|
||||
var isDiscordSame = DiscordRich.Pressed == _cfg.GetCVar(CVars.DiscordEnabled);
|
||||
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
|
||||
var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
|
||||
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||
var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
||||
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
|
||||
var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
|
||||
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
|
||||
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
|
||||
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
|
||||
|
||||
ApplyButton.Disabled = isHudThemeSame &&
|
||||
isLayoutSame &&
|
||||
isDiscordSame &&
|
||||
isShowHeldItemSame &&
|
||||
isCombatModeIndicatorsSame &&
|
||||
isOpaqueStorageWindow &&
|
||||
isOocPatronColorShowSame &&
|
||||
isLoocShowSame &&
|
||||
isFancyChatSame &&
|
||||
isFancyBackgroundSame &&
|
||||
isEnableColorNameSame &&
|
||||
isColorblindFriendly &&
|
||||
isReducedMotionSame &&
|
||||
isChatWindowOpacitySame &&
|
||||
isScreenShakeIntensitySame &&
|
||||
// isToggleWalkSame &&
|
||||
isStaticStorageUISame;
|
||||
}
|
||||
Control.AddOptionCheckBox(CVars.DiscordEnabled, DiscordRich);
|
||||
Control.AddOptionCheckBox(CCVars.ShowOocPatronColor, ShowOocPatronColor);
|
||||
Control.AddOptionCheckBox(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.HudHeldItemShow, ShowHeldItemCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Animations;
|
||||
using Content.Shared.Hands;
|
||||
@@ -69,7 +69,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
|
||||
public void CloseStorageWindow(Entity<StorageComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
if (!_openStorages.Contains((entity, entity.Comp)))
|
||||
|
||||
@@ -52,20 +52,12 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
Alert = alert;
|
||||
_severity = severity;
|
||||
|
||||
_spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity);
|
||||
if (_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
|
||||
{
|
||||
var icon = Alert.GetIcon(_severity);
|
||||
if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
|
||||
sprite.LayerSetSprite(layer, icon);
|
||||
}
|
||||
|
||||
_icon = new SpriteView
|
||||
{
|
||||
Scale = new Vector2(2, 2)
|
||||
};
|
||||
_icon.SetEntity(_spriteViewEntity);
|
||||
|
||||
SetupIcon();
|
||||
|
||||
Children.Add(_icon);
|
||||
_cooldownGraphic = new CooldownGraphic
|
||||
@@ -113,6 +105,36 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
_cooldownGraphic.FromTime(Cooldown.Value.Start, Cooldown.Value.End);
|
||||
}
|
||||
|
||||
private void SetupIcon()
|
||||
{
|
||||
if (!_entityManager.Deleted(_spriteViewEntity))
|
||||
_entityManager.QueueDeleteEntity(_spriteViewEntity);
|
||||
|
||||
_spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity);
|
||||
if (_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
|
||||
{
|
||||
var icon = Alert.GetIcon(_severity);
|
||||
if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
|
||||
sprite.LayerSetSprite(layer, icon);
|
||||
}
|
||||
|
||||
_icon.SetEntity(_spriteViewEntity);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
SetupIcon();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
if (!_entityManager.Deleted(_spriteViewEntity))
|
||||
_entityManager.QueueDeleteEntity(_spriteViewEntity);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -4,10 +4,11 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.VendingMachines.UI
|
||||
{
|
||||
@@ -15,6 +16,10 @@ namespace Content.Client.VendingMachines.UI
|
||||
public sealed partial class VendingMachineMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
|
||||
|
||||
public event Action<ItemList.ItemListSelectedEventArgs>? OnItemSelected;
|
||||
public event Action<string>? OnSearchChanged;
|
||||
@@ -37,6 +42,22 @@ namespace Content.Client.VendingMachines.UI
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
// Don't clean up dummies during disposal or we'll just have to spawn them again
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
// Delete any dummy items we spawned
|
||||
foreach (var entity in _dummies.Values)
|
||||
{
|
||||
_entityManager.QueueDeleteEntity(entity);
|
||||
}
|
||||
_dummies.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list of available items on the vending machine interface
|
||||
/// and sets icons based on their prototypes
|
||||
@@ -73,11 +94,16 @@ namespace Content.Client.VendingMachines.UI
|
||||
vendingItem.Text = string.Empty;
|
||||
vendingItem.Icon = null;
|
||||
|
||||
var itemName = entry.ID;
|
||||
if (!_dummies.TryGetValue(entry.ID, out var dummy))
|
||||
{
|
||||
dummy = _entityManager.Spawn(entry.ID);
|
||||
_dummies.Add(entry.ID, dummy);
|
||||
}
|
||||
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
Texture? icon = null;
|
||||
if (_prototypeManager.TryIndex<EntityPrototype>(entry.ID, out var prototype))
|
||||
{
|
||||
itemName = prototype.Name;
|
||||
icon = spriteSystem.GetPrototypeIcon(prototype).Default;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Commands;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class ForceMapTest
|
||||
{
|
||||
private const string DefaultMapName = "Empty";
|
||||
private const string BadMapName = "asdf_asd-fa__sdfAsd_f"; // Hopefully no one ever names a map this...
|
||||
private const string TestMapEligibleName = "ForceMapTestEligible";
|
||||
private const string TestMapIneligibleName = "ForceMapTestIneligible";
|
||||
|
||||
[TestPrototypes]
|
||||
private static readonly string TestMaps = @$"
|
||||
- type: gameMap
|
||||
id: {TestMapIneligibleName}
|
||||
mapName: {TestMapIneligibleName}
|
||||
mapPath: /Maps/Test/empty.yml
|
||||
minPlayers: 20
|
||||
maxPlayers: 80
|
||||
stations:
|
||||
Empty:
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationNameSetup
|
||||
mapNameTemplate: ""Empty""
|
||||
|
||||
- type: gameMap
|
||||
id: {TestMapEligibleName}
|
||||
mapName: {TestMapEligibleName}
|
||||
mapPath: /Maps/Test/empty.yml
|
||||
minPlayers: 0
|
||||
stations:
|
||||
Empty:
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationNameSetup
|
||||
mapNameTemplate: ""Empty""
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task TestForceMapCommand()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.EntMan;
|
||||
var configManager = server.ResolveDependency<IConfigurationManager>();
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var gameMapMan = server.ResolveDependency<IGameMapManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Make sure we're set to the default map
|
||||
Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(DefaultMapName),
|
||||
$"Test didn't start on expected map ({DefaultMapName})!");
|
||||
|
||||
// Try changing to a map that doesn't exist
|
||||
consoleHost.ExecuteCommand($"forcemap {BadMapName}");
|
||||
Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(DefaultMapName),
|
||||
$"Forcemap succeeded with a map that does not exist ({BadMapName})!");
|
||||
|
||||
// Try changing to a valid map
|
||||
consoleHost.ExecuteCommand($"forcemap {TestMapEligibleName}");
|
||||
Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(TestMapEligibleName),
|
||||
$"Forcemap failed with a valid map ({TestMapEligibleName})");
|
||||
|
||||
// Try changing to a map that exists but is ineligible
|
||||
consoleHost.ExecuteCommand($"forcemap {TestMapIneligibleName}");
|
||||
Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(TestMapIneligibleName),
|
||||
$"Forcemap failed with valid but ineligible map ({TestMapIneligibleName})!");
|
||||
|
||||
// Try clearing the force-selected map
|
||||
consoleHost.ExecuteCommand("forcemap \"\"");
|
||||
Assert.That(gameMapMan.GetSelectedMap(), Is.Null,
|
||||
$"Running 'forcemap \"\"' did not clear the forced map!");
|
||||
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
configManager.SetCVar(CCVars.GameMap, DefaultMapName);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Internals;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(InternalsSystem))]
|
||||
public sealed class AutoInternalsTests
|
||||
{
|
||||
[Test]
|
||||
public async Task TestInternalsAutoActivateInSpaceForStationSpawn()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
var stationSpawning = server.System<StationSpawningSystem>();
|
||||
var atmos = server.System<AtmosphereSystem>();
|
||||
var internals = server.System<InternalsSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var profile = new HumanoidCharacterProfile();
|
||||
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
|
||||
{
|
||||
Prototype = "TestInternalsDummy"
|
||||
}, profile, station: null);
|
||||
|
||||
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
|
||||
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");
|
||||
|
||||
server.EntMan.DeleteEntity(dummy);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestInternalsAutoActivateInSpaceForEntitySpawn()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
var atmos = server.System<AtmosphereSystem>();
|
||||
var internals = server.System<InternalsSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var dummy = server.EntMan.Spawn("TestInternalsDummyEntity", testMap.MapCoords);
|
||||
|
||||
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
|
||||
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");
|
||||
|
||||
server.EntMan.DeleteEntity(dummy);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: playTimeTracker
|
||||
id: PlayTimeInternalsDummy
|
||||
|
||||
- type: startingGear
|
||||
id: InternalsDummyGear
|
||||
equipment:
|
||||
mask: ClothingMaskBreath
|
||||
suitstorage: OxygenTankFilled
|
||||
|
||||
- type: job
|
||||
id: TestInternalsDummy
|
||||
playTimeTracker: PlayTimeInternalsDummy
|
||||
startingGear: InternalsDummyGear
|
||||
|
||||
- type: entity
|
||||
id: TestInternalsDummyEntity
|
||||
parent: MobHuman
|
||||
components:
|
||||
- type: Loadout
|
||||
prototypes: [InternalsDummyGear]
|
||||
";
|
||||
}
|
||||
Generated
+1915
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ConnectionLogTimeIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_connection_log_time",
|
||||
table: "connection_log",
|
||||
column: "time");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_connection_log_time",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1960
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BanTemplate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ban_template",
|
||||
columns: table => new
|
||||
{
|
||||
ban_template_id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
title = table.Column<string>(type: "text", nullable: false),
|
||||
length = table.Column<TimeSpan>(type: "interval", nullable: false),
|
||||
reason = table.Column<string>(type: "text", nullable: false),
|
||||
exempt_flags = table.Column<int>(type: "integer", nullable: false),
|
||||
severity = table.Column<int>(type: "integer", nullable: false),
|
||||
auto_delete = table.Column<bool>(type: "boolean", nullable: false),
|
||||
hidden = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ban_template", x => x.ban_template_id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ban_template");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,6 +512,51 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("assigned_user_id", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("ban_template_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("auto_delete");
|
||||
|
||||
b.Property<int>("ExemptFlags")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("exempt_flags");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("hidden");
|
||||
|
||||
b.Property<TimeSpan>("Length")
|
||||
.HasColumnType("interval")
|
||||
.HasColumnName("length");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<int>("Severity")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("severity");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_ban_template");
|
||||
|
||||
b.ToTable("ban_template", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -559,6 +604,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.HasIndex("ServerId")
|
||||
.HasDatabaseName("IX_connection_log_server_id");
|
||||
|
||||
b.HasIndex("Time");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("connection_log", null, t =>
|
||||
|
||||
Generated
+1840
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ConnectionLogTimeIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_connection_log_time",
|
||||
table: "connection_log",
|
||||
column: "time");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_connection_log_time",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1883
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BanTemplate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ban_template",
|
||||
columns: table => new
|
||||
{
|
||||
ban_template_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
title = table.Column<string>(type: "TEXT", nullable: false),
|
||||
length = table.Column<TimeSpan>(type: "TEXT", nullable: false),
|
||||
reason = table.Column<string>(type: "TEXT", nullable: false),
|
||||
exempt_flags = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
severity = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
auto_delete = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
hidden = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ban_template", x => x.ban_template_id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ban_template");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,6 +483,49 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("assigned_user_id", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("ban_template_id");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("auto_delete");
|
||||
|
||||
b.Property<int>("ExemptFlags")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("exempt_flags");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("hidden");
|
||||
|
||||
b.Property<TimeSpan>("Length")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("length");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<int>("Severity")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("severity");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_ban_template");
|
||||
|
||||
b.ToTable("ban_template", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -528,6 +571,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.HasIndex("ServerId")
|
||||
.HasDatabaseName("IX_connection_log_server_id");
|
||||
|
||||
b.HasIndex("Time");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("connection_log", (string)null);
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Content.Server.Database
|
||||
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
|
||||
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
||||
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -187,6 +188,9 @@ namespace Content.Server.Database
|
||||
modelBuilder.Entity<ConnectionLog>()
|
||||
.HasIndex(p => p.UserId);
|
||||
|
||||
modelBuilder.Entity<ConnectionLog>()
|
||||
.HasIndex(p => p.Time);
|
||||
|
||||
modelBuilder.Entity<ConnectionLog>()
|
||||
.Property(p => p.ServerId)
|
||||
.HasDefaultValue(0);
|
||||
@@ -1132,4 +1136,57 @@ namespace Content.Server.Database
|
||||
[Required]
|
||||
public string RoleId { get; set; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a template that admins can use to quickly fill out ban information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This information is not currently used by the game itself, but it is used by SS14.Admin.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class BanTemplate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title of the ban template. This is purely for reference by admins and not copied into the ban.
|
||||
/// </summary>
|
||||
public required string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How long the ban should last. 0 for permanent.
|
||||
/// </summary>
|
||||
public TimeSpan Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reason for the ban.
|
||||
/// </summary>
|
||||
/// <seealso cref="ServerBan.Reason"/>
|
||||
public string Reason { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Exemptions granted to the ban.
|
||||
/// </summary>
|
||||
/// <seealso cref="ServerBan.ExemptFlags"/>
|
||||
public ServerBanExemptFlags ExemptFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the ban
|
||||
/// </summary>
|
||||
/// <seealso cref="ServerBan.Severity"/>
|
||||
public NoteSeverity Severity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ban will be automatically deleted once expired.
|
||||
/// </summary>
|
||||
/// <seealso cref="ServerBan.AutoDelete"/>
|
||||
public bool AutoDelete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ban is not visible to players in the remarks menu.
|
||||
/// </summary>
|
||||
/// <seealso cref="ServerBan.Hidden"/>
|
||||
public bool Hidden { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
|
||||
public sealed partial class LogWireAction : ComponentWireAction<AccessReaderComponent>
|
||||
{
|
||||
public override Color Color { get; set; } = Color.Blue;
|
||||
public override string Name { get; set; } = "wire-name-log";
|
||||
|
||||
[DataField]
|
||||
public int PulseTimeout = 30;
|
||||
|
||||
[DataField]
|
||||
public LocId PulseLog = "log-wire-pulse-access-log";
|
||||
|
||||
private AccessReaderSystem _access = default!;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
return comp.LoggingDisabled ? StatusLightState.Off : StatusLightState.On;
|
||||
}
|
||||
|
||||
public override object StatusKey => AccessWireActionKey.Status;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_access = EntityManager.System<AccessReaderSystem>();
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, PulseTimeoutKey.Key);
|
||||
comp.LoggingDisabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
comp.LoggingDisabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
_access.LogAccess((wire.Owner, comp), Loc.GetString(PulseLog));
|
||||
comp.LoggingDisabled = true;
|
||||
WiresSystem.StartWireAction(wire.Owner, PulseTimeout, PulseTimeoutKey.Key, new TimedWireEvent(AwaitPulseCancel, wire));
|
||||
}
|
||||
|
||||
public override void Update(Wire wire)
|
||||
{
|
||||
if (!IsPowered(wire.Owner))
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, PulseTimeoutKey.Key);
|
||||
}
|
||||
|
||||
private void AwaitPulseCancel(Wire wire)
|
||||
{
|
||||
if (!wire.IsCut && EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var comp))
|
||||
comp.LoggingDisabled = false;
|
||||
}
|
||||
|
||||
private enum PulseTimeoutKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("underPressureLockoutThreshold")]
|
||||
public float UnderPressureLockoutThreshold = 60; // this must be tuned in conjunction with atmos.mmos_spacing_speed
|
||||
public float UnderPressureLockoutThreshold = 80; // this must be tuned in conjunction with atmos.mmos_spacing_speed
|
||||
|
||||
/// <summary>
|
||||
/// Pressure locked vents still leak a little (leading to eventual pressurization of sealed sections)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Corvax.Interfaces.Server;
|
||||
using Content.Corvax.Interfaces.Shared;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Preferences.Managers;
|
||||
@@ -12,7 +14,9 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
/*
|
||||
@@ -53,6 +57,7 @@ namespace Content.Server.Connection
|
||||
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
private ISharedSponsorsManager? _sponsorsMgr; // Corvax-Sponsors
|
||||
private IServerVPNGuardManager? _vpnGuardMgr; // Corvax-VPNGuard
|
||||
|
||||
@@ -66,6 +71,7 @@ namespace Content.Server.Connection
|
||||
IoCManager.Instance!.TryResolveType(out _sponsorsMgr); // Corvax-Sponsors
|
||||
_netMgr.Connecting += NetMgrOnConnecting;
|
||||
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
||||
_plyMgr.PlayerStatusChanged += PlayerStatusChanged;
|
||||
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
|
||||
// _netMgr.HandleApprovalCallback = HandleApproval;
|
||||
}
|
||||
@@ -134,6 +140,42 @@ namespace Content.Server.Connection
|
||||
}
|
||||
}
|
||||
|
||||
private async void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
|
||||
{
|
||||
if (args.NewStatus == SessionStatus.Connected)
|
||||
{
|
||||
AdminAlertIfSharedConnection(args.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private void AdminAlertIfSharedConnection(ICommonSession newSession)
|
||||
{
|
||||
var playerThreshold = _cfg.GetCVar(CCVars.AdminAlertMinPlayersSharingConnection);
|
||||
if (playerThreshold < 0)
|
||||
return;
|
||||
|
||||
var addr = newSession.Channel.RemoteEndPoint.Address;
|
||||
|
||||
var otherConnectionsFromAddress = _plyMgr.Sessions.Where(session =>
|
||||
session.Status is SessionStatus.Connected or SessionStatus.InGame
|
||||
&& session.Channel.RemoteEndPoint.Address.Equals(addr)
|
||||
&& session.UserId != newSession.UserId)
|
||||
.ToList();
|
||||
|
||||
var otherConnectionCount = otherConnectionsFromAddress.Count;
|
||||
if (otherConnectionCount + 1 < playerThreshold) // Add one for the total, not just others, using the address
|
||||
return;
|
||||
|
||||
var username = newSession.Name;
|
||||
var otherUsernames = string.Join(", ",
|
||||
otherConnectionsFromAddress.Select(session => session.Name));
|
||||
|
||||
_chatManager.SendAdminAlert(Loc.GetString("admin-alert-shared-connection",
|
||||
("player", username),
|
||||
("otherCount", otherConnectionCount),
|
||||
("otherList", otherUsernames)));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Jesus H Christ what is this utter mess of a function
|
||||
* TODO: Break this apart into is constituent steps.
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Connection;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class GrantConnectBypassCommand : LocalizedCommands
|
||||
{
|
||||
private static readonly TimeSpan DefaultDuration = TimeSpan.FromHours(1);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.Ame.Components;
|
||||
using Content.Shared.Construction;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
namespace Content.Server.Construction.Conditions;
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class AmeShieldIntegrity : IGraphCondition
|
||||
{
|
||||
[DataField]
|
||||
public float IntegrityThreshold = 80;
|
||||
|
||||
/// <summary>
|
||||
/// If true, checks for the integrity being above the threshold.
|
||||
/// if false, checks for it being below.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CheckAbove = true;
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
{
|
||||
if (!entityManager.TryGetComponent<AmeShieldComponent>(uid, out var shield))
|
||||
return true;
|
||||
|
||||
if (CheckAbove)
|
||||
{
|
||||
return shield.CoreIntegrity >= IntegrityThreshold;
|
||||
}
|
||||
return shield.CoreIntegrity < IntegrityThreshold;
|
||||
}
|
||||
|
||||
public bool DoExamine(ExaminedEvent args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||
{
|
||||
yield return new ConstructionGuideEntry();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -76,6 +77,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, DragDropTargetEvent>(OnDragDropOn);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, DestructionEventArgs>(OnDestruction);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, BeforeExplodeEvent>(OnExploded);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<InteractionVerb>>(AddInsertVerb);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<AlternativeVerb>>(AddDisposalAltVerbs);
|
||||
@@ -778,6 +780,12 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
Joints.RecursiveClearJoints(inserted);
|
||||
UpdateVisualState(uid, component);
|
||||
}
|
||||
|
||||
private void OnExploded(Entity<DisposalUnitComponent> ent, ref BeforeExplodeEvent args)
|
||||
{
|
||||
args.Contents.AddRange(ent.Comp.Container.ContainedEntities);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Content.Server.Doors.Systems
|
||||
if (!TryComp<DoorComponent>(uid, out var doorComponent))
|
||||
return;
|
||||
|
||||
if (args.AlarmType == AtmosAlarmType.Normal || args.AlarmType == AtmosAlarmType.Warning)
|
||||
if (args.AlarmType == AtmosAlarmType.Normal)
|
||||
{
|
||||
if (doorComponent.State == DoorState.Closed)
|
||||
_doorSystem.TryOpen(uid);
|
||||
|
||||
@@ -29,8 +29,19 @@ namespace Content.Server.GameTicking.Commands
|
||||
var gameMap = IoCManager.Resolve<IGameMapManager>();
|
||||
var name = args[0];
|
||||
|
||||
// An empty string clears the forced map
|
||||
if (!string.IsNullOrEmpty(name) && !gameMap.CheckMapExists(name))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-map-not-found", ("map", name)));
|
||||
return;
|
||||
}
|
||||
|
||||
_configurationManager.SetCVar(CCVars.GameMap, name);
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-success", ("map", name)));
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-cleared"));
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-success", ("map", name)));
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -19,7 +18,6 @@ namespace Content.Server.Labels
|
||||
{
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public const string ContainerName = "paper_label";
|
||||
|
||||
@@ -27,7 +25,6 @@ namespace Content.Server.Labels
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LabelComponent, MapInitEvent>(OnLabelCompMapInit);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<PaperLabelComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
@@ -35,17 +32,6 @@ namespace Content.Server.Labels
|
||||
SubscribeLocalEvent<PaperLabelComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnLabelCompMapInit(EntityUid uid, LabelComponent component, MapInitEvent args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(component.CurrentLabel))
|
||||
{
|
||||
component.CurrentLabel = Loc.GetString(component.CurrentLabel);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply or remove a label on an entity.
|
||||
/// </summary>
|
||||
@@ -59,7 +45,7 @@ namespace Content.Server.Labels
|
||||
label = EnsureComp<LabelComponent>(uid);
|
||||
|
||||
label.CurrentLabel = text;
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
NameMod.RefreshNameModifiers(uid);
|
||||
|
||||
Dirty(uid, label);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class ContainmentFieldSystem : EntitySystem
|
||||
{
|
||||
var otherBody = args.OtherEntity;
|
||||
|
||||
if (HasComp<SpaceGarbageComponent>(otherBody))
|
||||
if (component.DestroyGarbage && HasComp<SpaceGarbageComponent>(otherBody))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("comp-field-vaporized", ("entity", otherBody)), uid, PopupType.LargeCaution);
|
||||
QueueDel(otherBody);
|
||||
|
||||
@@ -65,6 +65,13 @@ public sealed partial class AccessReaderComponent : Component
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public int AccessLogLimit = 20;
|
||||
|
||||
/// <summary>
|
||||
/// If true logging on successful access uses will be disabled.
|
||||
/// Can be set by LOG wire.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool LoggingDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not emag interactions have an effect on this.
|
||||
/// </summary>
|
||||
|
||||
@@ -382,18 +382,15 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an access
|
||||
/// Logs an access for a specific entity.
|
||||
/// </summary>
|
||||
/// <param name="ent">The reader to log the access on</param>
|
||||
/// <param name="accessor">The accessor to log</param>
|
||||
private void LogAccess(Entity<AccessReaderComponent> ent, EntityUid accessor)
|
||||
public void LogAccess(Entity<AccessReaderComponent> ent, EntityUid accessor)
|
||||
{
|
||||
if (IsPaused(ent))
|
||||
if (IsPaused(ent) || ent.Comp.LoggingDisabled)
|
||||
return;
|
||||
|
||||
if (ent.Comp.AccessLog.Count >= ent.Comp.AccessLogLimit)
|
||||
ent.Comp.AccessLog.Dequeue();
|
||||
|
||||
string? name = null;
|
||||
if (TryComp<NameIdentifierComponent>(accessor, out var nameIdentifier))
|
||||
name = nameIdentifier.FullIdentifier;
|
||||
@@ -404,7 +401,21 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
&& idCard.Comp is { BypassLogging: false, FullName: not null })
|
||||
name = idCard.Comp.FullName;
|
||||
|
||||
name ??= Loc.GetString("access-reader-unknown-id");
|
||||
LogAccess(ent, name ?? Loc.GetString("access-reader-unknown-id"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an access with a predetermined name
|
||||
/// </summary>
|
||||
/// <param name="ent">The reader to log the access on</param>
|
||||
/// <param name="name">The name to log as</param>
|
||||
public void LogAccess(Entity<AccessReaderComponent> ent, string name)
|
||||
{
|
||||
if (IsPaused(ent) || ent.Comp.LoggingDisabled)
|
||||
return;
|
||||
|
||||
if (ent.Comp.AccessLog.Count >= ent.Comp.AccessLogLimit)
|
||||
ent.Comp.AccessLog.Dequeue();
|
||||
|
||||
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
|
||||
ent.Comp.AccessLog.Enqueue(new AccessRecord(stationTime, name));
|
||||
|
||||
@@ -286,7 +286,8 @@ namespace Content.Shared.Atmos
|
||||
/// (The pressure threshold is so low that it doesn't make sense to do any calculations,
|
||||
/// so it just applies this flat value).
|
||||
/// </summary>
|
||||
public const int LowPressureDamage = 4;
|
||||
// Original value is 4, buff back when we have proper ways for players to deal with breaches.
|
||||
public const int LowPressureDamage = 1;
|
||||
|
||||
public const float WindowHeatTransferCoefficient = 0.1f;
|
||||
|
||||
|
||||
@@ -832,6 +832,15 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> ServerBanErasePlayer =
|
||||
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you set this to 0 or 1 then it will alert on every connection, so probably don't do that.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> AdminAlertMinPlayersSharingConnection =
|
||||
CVarDef.Create("admin.alert.min_players_sharing_connection", -1, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum explosion intensity to create an admin alert message. -1 to disable the alert.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
@@ -27,7 +28,8 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit);
|
||||
// Wait until the character has all their organs before we give them their loadout
|
||||
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedBodySystem)]);
|
||||
}
|
||||
|
||||
public static string GetJobPrototype(string? loadout)
|
||||
|
||||
@@ -54,7 +54,8 @@ public sealed class PacificationSystem : EntitySystem
|
||||
&& !(_timing.CurTime > user.Comp.NextPopupTime))
|
||||
return;
|
||||
|
||||
_popup.PopupClient(Loc.GetString(reason, ("entity", target)), user, user);
|
||||
var targetName = Identity.Entity(target, EntityManager);
|
||||
_popup.PopupClient(Loc.GetString(reason, ("entity", targetName)), user, user);
|
||||
user.Comp.NextPopupTime = _timing.CurTime + user.Comp.PopupCooldown;
|
||||
user.Comp.LastAttackedEntity = target;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public abstract class SharedFirelockSystem : EntitySystem
|
||||
|
||||
// Access/Prying
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||
SubscribeLocalEvent<FirelockComponent, BeforePryEvent>(OnBeforePry);
|
||||
SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||
SubscribeLocalEvent<FirelockComponent, PriedEvent>(OnAfterPried);
|
||||
|
||||
@@ -60,6 +61,14 @@ public abstract class SharedFirelockSystem : EntitySystem
|
||||
WarnPlayer((uid, component), args.User.Value);
|
||||
}
|
||||
|
||||
private void OnBeforePry(EntityUid uid, FirelockComponent component, ref BeforePryEvent args)
|
||||
{
|
||||
if (args.Cancelled || !component.Powered || args.StrongPry || args.PryPowered)
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
|
||||
{
|
||||
WarnPlayer((uid, component), args.User);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace Content.Shared.Examine
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GroupExamineComponent, GetVerbsEvent<ExamineVerb>>(OnGroupExamineVerb);
|
||||
|
||||
_ghostQuery = GetEntityQuery<GhostComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
@@ -44,6 +45,8 @@ namespace Content.Shared.Examine
|
||||
|
||||
protected const float ExamineBlurrinessMult = 2.5f;
|
||||
|
||||
private EntityQuery<GhostComponent> _ghostQuery;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new examine tooltip with arbitrary info.
|
||||
/// </summary>
|
||||
@@ -54,6 +57,10 @@ namespace Content.Shared.Examine
|
||||
if (IsClientSide(entity))
|
||||
return true;
|
||||
|
||||
// Ghosts can see everything.
|
||||
if (_ghostQuery.HasComp(examiner))
|
||||
return true;
|
||||
|
||||
// check if the mob is in critical or dead
|
||||
if (MobStateSystem.IsIncapacitated(examiner))
|
||||
return false;
|
||||
|
||||
@@ -7,14 +7,27 @@ namespace Content.Shared.Labels.EntitySystems;
|
||||
|
||||
public abstract partial class SharedLabelSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly NameModifierSystem NameMod = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LabelComponent, MapInitEvent>(OnLabelCompMapInit);
|
||||
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<LabelComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnLabelCompMapInit(EntityUid uid, LabelComponent component, MapInitEvent args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(component.CurrentLabel))
|
||||
{
|
||||
component.CurrentLabel = Loc.GetString(component.CurrentLabel);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
NameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
public virtual void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null){}
|
||||
|
||||
private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args)
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class SharedMagicMirrorSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(uid, component.Target.Value))
|
||||
if (!_interaction.InRangeUnobstructed(component.Target.Value, uid))
|
||||
args.Result = BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
|
||||
|
||||
@@ -145,9 +145,12 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
// If you put invalid ones first but that's your fault for not using sensible defaults
|
||||
if (loadouts.Count < groupProto.MinLimit)
|
||||
{
|
||||
for (var i = 0; i < Math.Min(groupProto.MinLimit, groupProtoLoadouts.Count); i++) // Corvax-Loadout: Use groupProtoLoadouts instead of groupProto.Loadouts
|
||||
foreach (var protoId in groupProtoLoadouts) // Corvax-Loadout: Use groupProtoLoadouts instead of groupProto.Loadouts
|
||||
{
|
||||
if (!protoManager.TryIndex(groupProtoLoadouts[i], out var loadoutProto)) // Corvax-Loadout
|
||||
if (loadouts.Count >= groupProto.MinLimit)
|
||||
break;
|
||||
|
||||
if (!protoManager.TryIndex(protoId, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
var defaultLoadout = new Loadout()
|
||||
@@ -214,11 +217,10 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
if (groupProto.MinLimit > 0)
|
||||
{
|
||||
// Apply any loadouts we can.
|
||||
var addedCount = 0;
|
||||
foreach (var protoId in groupProto.Loadouts)
|
||||
{
|
||||
// Reached the limit, time to stop
|
||||
if (addedCount >= groupProto.MinLimit)
|
||||
if (loadouts.Count >= groupProto.MinLimit)
|
||||
break;
|
||||
|
||||
if (!protoManager.TryIndex(protoId, out var loadoutProto))
|
||||
@@ -235,7 +237,6 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
|
||||
loadouts.Add(defaultLoadout);
|
||||
Apply(loadoutProto);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,26 @@ public sealed partial class PryingComponent : Component
|
||||
/// Cancel to stop the entity from being pried open.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BeforePryEvent(EntityUid User, bool PryPowered, bool Force)
|
||||
public record struct BeforePryEvent(EntityUid User, bool PryPowered, bool Force, bool StrongPry)
|
||||
{
|
||||
public readonly EntityUid User = User;
|
||||
|
||||
/// <summary>
|
||||
/// Whether prying should be allowed even if whatever is being pried is powered.
|
||||
/// </summary>
|
||||
public readonly bool PryPowered = PryPowered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether prying should be allowed to go through under most circumstances. (E.g. airlock is bolted).
|
||||
/// Systems may still wish to ignore this occasionally.
|
||||
/// </summary>
|
||||
public readonly bool Force = Force;
|
||||
|
||||
/// <summary>
|
||||
/// Whether anything other than bare hands were used. This should only be false if prying is being performed without a prying comp.
|
||||
/// </summary>
|
||||
public readonly bool StrongPry = StrongPry;
|
||||
|
||||
public string? Message;
|
||||
|
||||
public bool Cancelled;
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed class PryingSystem : EntitySystem
|
||||
|
||||
if (comp != null || Resolve(user, ref comp, false))
|
||||
{
|
||||
canev = new BeforePryEvent(user, comp.PryPowered, comp.Force);
|
||||
canev = new BeforePryEvent(user, comp.PryPowered, comp.Force, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -119,7 +119,7 @@ public sealed class PryingSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
canev = new BeforePryEvent(user, false, false);
|
||||
canev = new BeforePryEvent(user, false, false, false);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(target, ref canev);
|
||||
|
||||
@@ -18,4 +18,10 @@ public sealed partial class ContainmentFieldComponent : Component
|
||||
/// </summary>
|
||||
[DataField("maxMass")]
|
||||
public float MaxMass = 10000f;
|
||||
|
||||
/// <summary>
|
||||
/// Should field vaporize garbage that collides with it?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool DestroyGarbage = true;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared.Sound.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Base sound emitter which defines most of the data fields.
|
||||
/// Accepts both single sounds and sound collections.
|
||||
/// </summary>
|
||||
public abstract partial class BaseEmitSoundComponent : Component
|
||||
{
|
||||
public static readonly AudioParams DefaultParams = AudioParams.Default.WithVolume(-2f);
|
||||
namespace Content.Shared.Sound.Components;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("sound", required: true)]
|
||||
public SoundSpecifier? Sound;
|
||||
}
|
||||
/// <summary>
|
||||
/// Base sound emitter which defines most of the data fields.
|
||||
/// Accepts both single sounds and sound collections.
|
||||
/// </summary>
|
||||
public abstract partial class BaseEmitSoundComponent : Component
|
||||
{
|
||||
public static readonly AudioParams DefaultParams = AudioParams.Default.WithVolume(-2f);
|
||||
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(required: true)]
|
||||
public SoundSpecifier? Sound;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
subs.Event<BoundUIClosedEvent>(OnBoundUIClosed);
|
||||
});
|
||||
|
||||
SubscribeLocalEvent<StorageComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<StorageComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<StorageComponent, GetVerbsEvent<ActivationVerb>>(AddUiVerb);
|
||||
SubscribeLocalEvent<StorageComponent, ComponentGetState>(OnStorageGetState);
|
||||
@@ -133,6 +134,11 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
UpdatePrototypeCache();
|
||||
}
|
||||
|
||||
private void OnRemove(Entity<StorageComponent> entity, ref ComponentRemove args)
|
||||
{
|
||||
_ui.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<StorageComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
UseDelay.SetLength(entity.Owner, entity.Comp.QuickInsertCooldown, QuickInsertUseDelayID);
|
||||
|
||||
@@ -21,42 +21,17 @@ public sealed partial class ReflectComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("reflects")]
|
||||
public ReflectType Reflects = ReflectType.Energy | ReflectType.NonEnergy;
|
||||
|
||||
/// <summary>
|
||||
/// Probability for a projectile to be reflected.
|
||||
/// </summary>
|
||||
[DataField("reflectProb"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public float ReflectProb = 0.25f;
|
||||
|
||||
[DataField("spread"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public Angle Spread = Angle.FromDegrees(45);
|
||||
|
||||
[DataField("soundOnReflect")]
|
||||
public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Is the deflection an innate power or something actively maintained? If true, this component grants a flat
|
||||
/// deflection chance rather than a chance that degrades when moving/weightless/stunned/etc.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Innate = false;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum probability for a projectile to be reflected.
|
||||
/// </summary>
|
||||
[DataField("reflectProb"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public float ReflectProb = 0.25f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum velocity a wielder can move at before losing effectiveness.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float VelocityBeforeNotMaxProb = 2.5f; // Walking speed for a human. Suitable for a weightless deflector like an e-sword.
|
||||
|
||||
/// <summary>
|
||||
/// The velocity a wielder has to be moving at to use the minimum effectiveness value.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float VelocityBeforeMinProb = 4.5f; // Sprinting speed for a human. Suitable for a weightless deflector like an e-sword.
|
||||
|
||||
/// <summary>
|
||||
/// Minimum probability for a projectile to be reflected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinReflectProb = 0.1f;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -3,18 +3,16 @@ using System.Numerics;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -38,134 +36,73 @@ public sealed class ReflectSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
|
||||
[ValidatePrototypeId<AlertPrototype>]
|
||||
private const string DeflectingAlert = "Deflecting";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReflectComponent, ProjectileReflectAttemptEvent>(OnObjectReflectProjectileAttempt);
|
||||
SubscribeLocalEvent<ReflectComponent, HitScanReflectAttemptEvent>(OnObjectReflectHitscanAttempt);
|
||||
SubscribeLocalEvent<ReflectComponent, ProjectileReflectAttemptEvent>(OnReflectCollide);
|
||||
SubscribeLocalEvent<ReflectComponent, HitScanReflectAttemptEvent>(OnReflectHitscan);
|
||||
SubscribeLocalEvent<ReflectComponent, GotEquippedEvent>(OnReflectEquipped);
|
||||
SubscribeLocalEvent<ReflectComponent, GotUnequippedEvent>(OnReflectUnequipped);
|
||||
SubscribeLocalEvent<ReflectComponent, GotEquippedHandEvent>(OnReflectHandEquipped);
|
||||
SubscribeLocalEvent<ReflectComponent, GotUnequippedHandEvent>(OnReflectHandUnequipped);
|
||||
SubscribeLocalEvent<ReflectComponent, ItemToggledEvent>(OnToggleReflect);
|
||||
|
||||
SubscribeLocalEvent<ReflectUserComponent, ProjectileReflectAttemptEvent>(OnUserProjectileReflectAttempt);
|
||||
SubscribeLocalEvent<ReflectUserComponent, HitScanReflectAttemptEvent>(OnUserHitscanReflectAttempt);
|
||||
SubscribeLocalEvent<ReflectUserComponent, ProjectileReflectAttemptEvent>(OnReflectUserCollide);
|
||||
SubscribeLocalEvent<ReflectUserComponent, HitScanReflectAttemptEvent>(OnReflectUserHitscan);
|
||||
}
|
||||
|
||||
private void OnUserHitscanReflectAttempt(Entity<ReflectUserComponent> user, ref HitScanReflectAttemptEvent args)
|
||||
private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, ref HitScanReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Reflected)
|
||||
return;
|
||||
|
||||
if (!UserCanReflect(user, out var bestReflectorUid))
|
||||
return;
|
||||
|
||||
if (!TryReflectHitscan(user.Owner, bestReflectorUid.Value, args.Shooter, args.SourceItem, args.Direction, out var dir))
|
||||
return;
|
||||
|
||||
args.Direction = dir.Value;
|
||||
args.Reflected = true;
|
||||
}
|
||||
|
||||
private void OnUserProjectileReflectAttempt(Entity<ReflectUserComponent> user, ref ProjectileReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<ReflectiveComponent>(args.ProjUid, out var reflectiveComponent))
|
||||
return;
|
||||
|
||||
if (!UserCanReflect(user, out var bestReflectorUid, (args.ProjUid, reflectiveComponent)))
|
||||
return;
|
||||
|
||||
if (!TryReflectProjectile(user, bestReflectorUid.Value, (args.ProjUid, args.Component)))
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnObjectReflectHitscanAttempt(Entity<ReflectComponent> obj, ref HitScanReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Reflected || (obj.Comp.Reflects & args.Reflective) == 0x0)
|
||||
return;
|
||||
|
||||
if (!TryReflectHitscan(obj, obj, args.Shooter, args.SourceItem, args.Direction, out var dir))
|
||||
return;
|
||||
|
||||
args.Direction = dir.Value;
|
||||
args.Reflected = true;
|
||||
}
|
||||
|
||||
private void OnObjectReflectProjectileAttempt(Entity<ReflectComponent> obj, ref ProjectileReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryReflectProjectile(obj, obj, (args.ProjUid, args.Component)))
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can a user reflect something that's hit them? Returns true if so, and the best reflector available in the user's equipment.
|
||||
/// </summary>
|
||||
private bool UserCanReflect(Entity<ReflectUserComponent> user, [NotNullWhen(true)] out Entity<ReflectComponent>? bestReflector, Entity<ReflectiveComponent>? projectile = null)
|
||||
{
|
||||
bestReflector = null;
|
||||
|
||||
foreach (var entityUid in _inventorySystem.GetHandOrInventoryEntities(user.Owner, SlotFlags.WITHOUT_POCKET))
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
{
|
||||
if (!TryComp<ReflectComponent>(entityUid, out var comp))
|
||||
if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir))
|
||||
continue;
|
||||
|
||||
if (!comp.Enabled)
|
||||
args.Direction = dir.Value;
|
||||
args.Reflected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args)
|
||||
{
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
{
|
||||
if (!TryReflectProjectile(uid, ent, args.ProjUid))
|
||||
continue;
|
||||
|
||||
if (bestReflector != null && bestReflector.Value.Comp.ReflectProb >= comp.ReflectProb)
|
||||
continue;
|
||||
args.Cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (projectile != null && (comp.Reflects & projectile.Value.Comp.Reflective) == 0x0)
|
||||
continue;
|
||||
private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref ProjectileReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
bestReflector = (entityUid, comp);
|
||||
if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
|
||||
{
|
||||
if (!Resolve(reflector, ref reflect, false) ||
|
||||
!reflect.Enabled ||
|
||||
!TryComp<ReflectiveComponent>(projectile, out var reflective) ||
|
||||
(reflect.Reflects & reflective.Reflective) == 0x0 ||
|
||||
!_random.Prob(reflect.ReflectProb) ||
|
||||
!TryComp<PhysicsComponent>(projectile, out var physics))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return bestReflector != null;
|
||||
}
|
||||
|
||||
private bool TryReflectProjectile(EntityUid user, Entity<ReflectComponent> reflector, Entity<ProjectileComponent> projectile)
|
||||
{
|
||||
if (
|
||||
// Is it on?
|
||||
!reflector.Comp.Enabled ||
|
||||
// Is the projectile deflectable?
|
||||
!TryComp<ReflectiveComponent>(projectile, out var reflective) ||
|
||||
// Does the deflector deflect the type of projecitle?
|
||||
(reflector.Comp.Reflects & reflective.Reflective) == 0x0 ||
|
||||
// Is the projectile correctly set up with physics?
|
||||
!TryComp<PhysicsComponent>(projectile, out var physics) ||
|
||||
// If the user of the reflector is a mob with stamina, is it capable of deflecting?
|
||||
TryComp<StaminaComponent>(user, out var staminaComponent) && staminaComponent.Critical ||
|
||||
_standing.IsDown(reflector)
|
||||
)
|
||||
return false;
|
||||
|
||||
// If this dice roll fails, the shot isn't deflected
|
||||
if (!_random.Prob(GetReflectChance(reflector)))
|
||||
return false;
|
||||
|
||||
// Below handles what happens after being deflected.
|
||||
var rotation = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2).Opposite();
|
||||
var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite();
|
||||
var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics);
|
||||
var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user);
|
||||
var newVelocity = rotation.RotateVec(relativeVelocity);
|
||||
@@ -182,52 +119,63 @@ public sealed class ReflectSystem : EntitySystem
|
||||
if (_netManager.IsServer)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("reflect-shot"), user);
|
||||
_audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
|
||||
_audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectile.Comp.Weapon)} shot by {projectile.Comp.Shooter}");
|
||||
if (Resolve(projectile, ref projectileComp, false))
|
||||
{
|
||||
_adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}");
|
||||
|
||||
projectile.Comp.Shooter = user;
|
||||
projectile.Comp.Weapon = user;
|
||||
Dirty(projectile);
|
||||
projectileComp.Shooter = user;
|
||||
projectileComp.Weapon = user;
|
||||
Dirty(projectile, projectileComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args)
|
||||
{
|
||||
if (args.Reflected ||
|
||||
(component.Reflects & args.Reflective) == 0x0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, out var dir))
|
||||
{
|
||||
args.Direction = dir.Value;
|
||||
args.Reflected = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryReflectHitscan(
|
||||
EntityUid user,
|
||||
Entity<ReflectComponent> reflector,
|
||||
EntityUid reflector,
|
||||
EntityUid? shooter,
|
||||
EntityUid shotSource,
|
||||
Vector2 direction,
|
||||
[NotNullWhen(true)] out Vector2? newDirection)
|
||||
{
|
||||
if (
|
||||
// Is the reflector enabled?
|
||||
!reflector.Comp.Enabled ||
|
||||
// If the user is a mob with stamina, is it capable of deflecting?
|
||||
TryComp<StaminaComponent>(user, out var staminaComponent) && staminaComponent.Critical ||
|
||||
_standing.IsDown(user))
|
||||
if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
|
||||
!reflect.Enabled ||
|
||||
!_random.Prob(reflect.ReflectProb))
|
||||
{
|
||||
newDirection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this dice roll fails, the shot is not deflected.
|
||||
if (!_random.Prob(GetReflectChance(reflector)))
|
||||
{
|
||||
newDirection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Below handles what happens after being deflected.
|
||||
if (_netManager.IsServer)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("reflect-shot"), user);
|
||||
_audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
|
||||
_audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
|
||||
}
|
||||
|
||||
var spread = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2);
|
||||
var spread = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2);
|
||||
newDirection = -spread.RotateVec(direction);
|
||||
|
||||
if (shooter != null)
|
||||
@@ -238,106 +186,52 @@ public sealed class ReflectSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private float GetReflectChance(Entity<ReflectComponent> reflector)
|
||||
{
|
||||
/*
|
||||
* The rules of deflection are as follows:
|
||||
* If you innately reflect things via magic, biology etc., you always have a full chance.
|
||||
* If you are standing up and standing still, you're prepared to deflect and have full chance.
|
||||
* If you have velocity, your deflection chance depends on your velocity, clamped.
|
||||
* If you are floating, your chance is the minimum value possible.
|
||||
*/
|
||||
|
||||
if (reflector.Comp.Innate)
|
||||
return reflector.Comp.ReflectProb;
|
||||
|
||||
if (_gravity.IsWeightless(reflector))
|
||||
return reflector.Comp.MinReflectProb;
|
||||
|
||||
if (!TryComp<PhysicsComponent>(reflector, out var reflectorPhysics))
|
||||
return reflector.Comp.ReflectProb;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
reflector.Comp.MinReflectProb,
|
||||
reflector.Comp.ReflectProb,
|
||||
// Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_.
|
||||
1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflector.Comp.VelocityBeforeNotMaxProb) / (reflector.Comp.VelocityBeforeMinProb - reflector.Comp.VelocityBeforeNotMaxProb), 0, 1)
|
||||
);
|
||||
}
|
||||
|
||||
private void OnReflectEquipped(Entity<ReflectComponent> reflector, ref GotEquippedEvent args)
|
||||
private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
EnsureComp<ReflectUserComponent>(args.Equipee);
|
||||
|
||||
if (reflector.Comp.Enabled)
|
||||
EnableAlert(args.Equipee);
|
||||
}
|
||||
|
||||
private void OnReflectUnequipped(Entity<ReflectComponent> reflector, ref GotUnequippedEvent args)
|
||||
private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args)
|
||||
{
|
||||
RefreshReflectUser(args.Equipee);
|
||||
}
|
||||
|
||||
private void OnReflectHandEquipped(Entity<ReflectComponent> reflector, ref GotEquippedHandEvent args)
|
||||
private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
EnsureComp<ReflectUserComponent>(args.User);
|
||||
|
||||
if (reflector.Comp.Enabled)
|
||||
EnableAlert(args.User);
|
||||
}
|
||||
|
||||
private void OnReflectHandUnequipped(Entity<ReflectComponent> reflector, ref GotUnequippedHandEvent args)
|
||||
private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args)
|
||||
{
|
||||
RefreshReflectUser(args.User);
|
||||
}
|
||||
|
||||
private void OnToggleReflect(Entity<ReflectComponent> reflector, ref ItemToggledEvent args)
|
||||
private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args)
|
||||
{
|
||||
reflector.Comp.Enabled = args.Activated;
|
||||
Dirty(reflector);
|
||||
|
||||
if (args.User == null)
|
||||
return;
|
||||
|
||||
if (reflector.Comp.Enabled)
|
||||
EnableAlert(args.User.Value);
|
||||
else
|
||||
DisableAlert(args.User.Value);
|
||||
comp.Enabled = args.Activated;
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes whether someone has reflection potential, so we can raise directed events on them.
|
||||
/// Refreshes whether someone has reflection potential so we can raise directed events on them.
|
||||
/// </summary>
|
||||
private void RefreshReflectUser(EntityUid user)
|
||||
{
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.WITHOUT_POCKET))
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
{
|
||||
if (!HasComp<ReflectComponent>(ent))
|
||||
continue;
|
||||
|
||||
EnsureComp<ReflectUserComponent>(user);
|
||||
EnableAlert(user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemCompDeferred<ReflectUserComponent>(user);
|
||||
DisableAlert(user);
|
||||
}
|
||||
|
||||
private void EnableAlert(EntityUid alertee)
|
||||
{
|
||||
_alerts.ShowAlert(alertee, DeflectingAlert);
|
||||
}
|
||||
|
||||
private void DisableAlert(EntityUid alertee)
|
||||
{
|
||||
_alerts.ClearAlert(alertee, DeflectingAlert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
source: "https://github.com/ss220-space/Paradise/blob/05043bcfb35c2e7bcf1efbdbfbf976e1a2504bc2/sound/effects/epsilon.ogg"
|
||||
|
||||
- files: ["siren.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from Parsdise SS13"
|
||||
source: "https://github.com/ParadiseSS13/Paradise/blob/master/sound/effects/new_siren.ogg"
|
||||
license: "CC0-1.0"
|
||||
copyright: "Created by Bhijin & Myr for Space Station 14"
|
||||
source: "https://github.com/space-wizards/space-station-14"
|
||||
|
||||
- files: ["redalert.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
|
||||
Binary file not shown.
@@ -6,4 +6,9 @@
|
||||
- files: ["clearly_nuclear.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Created by mryikes"
|
||||
source: "https://www.youtube.com/watch?v=chix8uz-oUQ"
|
||||
source: "https://www.youtube.com/watch?v=chix8uz-oUQ"
|
||||
|
||||
- files: ["sound_station_14.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Created by Donchan"
|
||||
source: "https://www.youtube.com/watch?v=4nUpeYLKUns"
|
||||
Binary file not shown.
@@ -330,5 +330,19 @@ Entries:
|
||||
id: 40
|
||||
time: '2024-06-21T12:06:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29258
|
||||
- author: Chief-Engineer
|
||||
changes:
|
||||
- message: grant_connect_bypass now requires moderator permissions instead of admin.
|
||||
type: Tweak
|
||||
id: 41
|
||||
time: '2024-06-24T21:56:29.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29406
|
||||
- author: Chief-Engineer
|
||||
changes:
|
||||
- message: Added the ability to alert for shared connections.
|
||||
type: Add
|
||||
id: 42
|
||||
time: '2024-06-26T13:43:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29405
|
||||
Name: Admin
|
||||
Order: 1
|
||||
|
||||
+191
-210
@@ -1,214 +1,4 @@
|
||||
Entries:
|
||||
- author: Tayrtahn
|
||||
changes:
|
||||
- message: Some devices may have broken wiring at the start of each round.
|
||||
type: Add
|
||||
id: 6299
|
||||
time: '2024-04-04T06:28:09.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26695
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Turning off thrusters and gyroscopes now stop consume power.
|
||||
type: Fix
|
||||
id: 6300
|
||||
time: '2024-04-04T06:28:33.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26690
|
||||
- author: Aexxie
|
||||
changes:
|
||||
- message: Added the option to disable your OOC Patron name color.
|
||||
type: Add
|
||||
id: 6301
|
||||
time: '2024-04-04T07:20:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26653
|
||||
- author: DinoWattz
|
||||
changes:
|
||||
- message: Fixed pocket slots being able to hide character snout markings.
|
||||
type: Fix
|
||||
id: 6302
|
||||
time: '2024-04-04T08:35:44.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26708
|
||||
- author: VasilisThePikachu
|
||||
changes:
|
||||
- message: Mop buckets and Janitorial Trolleys will not spill their contents as
|
||||
soon as they are pushed.
|
||||
type: Fix
|
||||
- message: Mop buckets are no longer solid. So you can now walk through them.
|
||||
type: Fix
|
||||
id: 6303
|
||||
time: '2024-04-04T08:39:55.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26706
|
||||
- author: ZeroDayDaemon
|
||||
changes:
|
||||
- message: Lowered the number of ducks in the duck crate and reduced the price of
|
||||
it.
|
||||
type: Tweak
|
||||
id: 6304
|
||||
time: '2024-04-04T19:30:13.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26729
|
||||
- author: PrPleGoo
|
||||
changes:
|
||||
- message: The medical HUD's brightness now scales with lighting again.
|
||||
type: Tweak
|
||||
- message: Most HUD icon's brightness now scale with lighting.
|
||||
type: Tweak
|
||||
id: 6305
|
||||
time: '2024-04-04T23:05:01.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26726
|
||||
- author: Daemon
|
||||
changes:
|
||||
- message: Practice projectiles have been given standardized minimal damage.
|
||||
type: Tweak
|
||||
- message: The practice laser gun now works with shooting targets.
|
||||
type: Tweak
|
||||
id: 6306
|
||||
time: '2024-04-05T03:15:02.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26731
|
||||
- author: Daemon
|
||||
changes:
|
||||
- message: Shooting targets can now have their popup type changed with a left click
|
||||
to show total damage, damage of a single hit, both, or just a notice that it
|
||||
was hit.
|
||||
type: Tweak
|
||||
id: 6307
|
||||
time: '2024-04-05T07:19:41.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26734
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: Baseball bats now require a knife to craft
|
||||
type: Tweak
|
||||
id: 6308
|
||||
time: '2024-04-05T15:41:35.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26742
|
||||
- author: deltanedas
|
||||
changes:
|
||||
- message: Making fultons now requires cloth and they are faster to make.
|
||||
type: Tweak
|
||||
id: 6309
|
||||
time: '2024-04-05T15:42:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26747
|
||||
- author: superjj18
|
||||
changes:
|
||||
- message: Broadcasting works again!
|
||||
type: Fix
|
||||
id: 6310
|
||||
time: '2024-04-05T17:29:22.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26750
|
||||
- author: Golinth
|
||||
changes:
|
||||
- message: Fixed mindshield icons being glow in the dark
|
||||
type: Fix
|
||||
id: 6311
|
||||
time: '2024-04-05T20:35:32.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26754
|
||||
- author: Blackern5000
|
||||
changes:
|
||||
- message: Added sap
|
||||
type: Add
|
||||
- message: Added syrup, it can be made by boiling sap.
|
||||
type: Add
|
||||
- message: Dionae now bleed sap instead of water
|
||||
type: Tweak
|
||||
id: 6312
|
||||
time: '2024-04-05T21:06:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25748
|
||||
- author: osjarw
|
||||
changes:
|
||||
- message: Thin firelocks are now constructable/deconstructable
|
||||
type: Fix
|
||||
id: 6313
|
||||
time: '2024-04-06T01:55:31.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26745
|
||||
- author: BITTERLYNX
|
||||
changes:
|
||||
- message: Rodents are more visually pleasing to burn
|
||||
type: Fix
|
||||
- message: mothroach and hammy also more visually pleasing to burn
|
||||
type: Fix
|
||||
id: 6314
|
||||
time: '2024-04-06T04:41:23.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26758
|
||||
- author: Plykiya
|
||||
changes:
|
||||
- message: You can now carefully walk over glass shards and D4.
|
||||
type: Tweak
|
||||
id: 6315
|
||||
time: '2024-04-06T04:49:14.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26763
|
||||
- author: PursuitInAshes
|
||||
changes:
|
||||
- message: Sake Bottles can now be found in the booze-o-mat.
|
||||
type: Tweak
|
||||
id: 6316
|
||||
time: '2024-04-06T20:16:47.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26776
|
||||
- author: osjarw
|
||||
changes:
|
||||
- message: Removed broken anom behaviour, which causes APE shots to fly through
|
||||
the anom.
|
||||
type: Remove
|
||||
id: 6317
|
||||
time: '2024-04-06T22:27:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26775
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: Water coolers show how full/what they're full of again.
|
||||
type: Fix
|
||||
id: 6318
|
||||
time: '2024-04-06T23:58:57.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26784
|
||||
- author: Crotalus
|
||||
changes:
|
||||
- message: Show missing materials in lathe tooltip
|
||||
type: Add
|
||||
id: 6319
|
||||
time: '2024-04-08T03:16:11.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26795
|
||||
- author: chromiumboy
|
||||
changes:
|
||||
- message: Fixed the RCD not being able to build on top of puddles
|
||||
type: Fix
|
||||
- message: RCD constructions can no longer be rotated while in progress
|
||||
type: Tweak
|
||||
- message: The RCD now reports construction mode changes to its user
|
||||
type: Add
|
||||
id: 6320
|
||||
time: '2024-04-08T03:17:29.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26792
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Bombsuit and jani bombsuit are similar now.
|
||||
type: Tweak
|
||||
id: 6321
|
||||
time: '2024-04-08T15:05:58.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26806
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Clothing restock crate was splitted to clothing and autodrobe restock
|
||||
crates.
|
||||
type: Tweak
|
||||
- message: Clothing is cheaper.
|
||||
type: Tweak
|
||||
id: 6322
|
||||
time: '2024-04-08T15:10:58.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26805
|
||||
- author: Hanzdegloker
|
||||
changes:
|
||||
- message: Spears can now be quipped to your suit slot and cost slightly more to
|
||||
make.
|
||||
type: Tweak
|
||||
id: 6323
|
||||
time: '2024-04-08T15:34:35.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26724
|
||||
- author: KittenColony
|
||||
changes:
|
||||
- message: Added 13 new gauze wraps for moth that fit their twig bodies
|
||||
type: Add
|
||||
- message: Gauze markings have been moved to the Overlay category, allowing gauze
|
||||
to not take up marking points
|
||||
type: Tweak
|
||||
id: 6324
|
||||
time: '2024-04-09T08:04:51.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25481
|
||||
- author: Killerqu00
|
||||
changes:
|
||||
- message: Quartermasters can now skip a single bounty in the list once every 15
|
||||
@@ -3850,3 +3640,194 @@
|
||||
id: 6798
|
||||
time: '2024-06-21T10:50:53.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29293
|
||||
- author: Macoron
|
||||
changes:
|
||||
- message: Fixed SSD indicator for dwarfs.
|
||||
type: Fix
|
||||
id: 6799
|
||||
time: '2024-06-22T01:11:40.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29310
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Deconstructing a damaged AME core now yields steel instead of a full
|
||||
AME flatpack.
|
||||
type: Tweak
|
||||
id: 6800
|
||||
time: '2024-06-22T04:15:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29317
|
||||
- author: Errant
|
||||
changes:
|
||||
- message: Items should no longer be missing from backpacks at spawn.
|
||||
type: Fix
|
||||
id: 6801
|
||||
time: '2024-06-22T10:43:18.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29336
|
||||
- author: Donchan, marboww
|
||||
changes:
|
||||
- message: New Nuke Detonation Song "Sound Station 14"
|
||||
type: Add
|
||||
id: 6802
|
||||
time: '2024-06-22T15:09:41.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29345
|
||||
- author: deltanedas
|
||||
changes:
|
||||
- message: Doors access logging can now be disabled by snipping or pulsing a LOG
|
||||
wire.
|
||||
type: Tweak
|
||||
id: 6803
|
||||
time: '2024-06-22T15:12:58.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29094
|
||||
- author: notafet
|
||||
changes:
|
||||
- message: Reduce air alarm sensitivity to plasma, tritium, and carbon dioxide.
|
||||
type: Tweak
|
||||
id: 6804
|
||||
time: '2024-06-22T15:22:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29331
|
||||
- author: Damn Feds
|
||||
changes:
|
||||
- message: thief toolbox kits now better describe contents
|
||||
type: Tweak
|
||||
id: 6805
|
||||
time: '2024-06-22T15:44:18.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27771
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Prying reinforced tile now will give you back metal rod
|
||||
type: Tweak
|
||||
id: 6806
|
||||
time: '2024-06-22T15:47:02.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29084
|
||||
- author: nikthechampiongr
|
||||
changes:
|
||||
- message: Locked firelocks can no longer be pried by hand when powered.
|
||||
type: Tweak
|
||||
id: 6807
|
||||
time: '2024-06-22T17:49:50.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29221
|
||||
- author: Floofi
|
||||
changes:
|
||||
- message: Added lemon juice to the Boozemat!
|
||||
type: Add
|
||||
id: 6808
|
||||
time: '2024-06-22T23:46:19.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27465
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: You can now put hats on Medibots
|
||||
type: Add
|
||||
id: 6809
|
||||
time: '2024-06-23T00:28:20.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28584
|
||||
- author: WarMechanic
|
||||
changes:
|
||||
- message: The TEG now powers itself once started.
|
||||
type: Tweak
|
||||
id: 6810
|
||||
time: '2024-06-23T01:46:31.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29072
|
||||
- author: eoineoineoin
|
||||
changes:
|
||||
- message: Hand labelers can now apply labels to buttons, switches, and levers
|
||||
type: Tweak
|
||||
id: 6811
|
||||
time: '2024-06-23T14:52:30.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29378
|
||||
- author: Tayrtahn
|
||||
changes:
|
||||
- message: Jugs in the ChemVend are labeled again.
|
||||
type: Fix
|
||||
id: 6812
|
||||
time: '2024-06-23T17:31:34.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29178
|
||||
- author: DrEnzyme
|
||||
changes:
|
||||
- message: Add bagels and poppy seed bagels.
|
||||
type: Add
|
||||
id: 6813
|
||||
time: '2024-06-23T19:33:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/24799
|
||||
- author: PJB3005
|
||||
changes:
|
||||
- message: Ghosts can now examine details on objects from any range.
|
||||
type: Tweak
|
||||
id: 6814
|
||||
time: '2024-06-24T15:36:53.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29404
|
||||
- author: Emisse
|
||||
changes:
|
||||
- message: atmos, elite syndie, and deathsquad hardsuits are now the only fireproof
|
||||
suits
|
||||
type: Tweak
|
||||
id: 6815
|
||||
time: '2024-06-24T22:03:05.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29416
|
||||
- author: Bhijn and Myr
|
||||
changes:
|
||||
- message: The colors of the LEDs on the heater and freezer thermomachines has been
|
||||
changed from red/green (respectively) to orange/cyan (respectively). In laymen's
|
||||
terms, this makes thermomachines far more reasonably differentiable for those
|
||||
with colorblindness.
|
||||
type: Tweak
|
||||
id: 6816
|
||||
time: '2024-06-24T22:40:20.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29397
|
||||
- author: Elysium206
|
||||
changes:
|
||||
- message: Increased Security Riot shields durability significantly and makes actively
|
||||
blocking better
|
||||
type: Tweak
|
||||
id: 6817
|
||||
time: '2024-06-25T03:56:46.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29239
|
||||
- author: Tayrtahn
|
||||
changes:
|
||||
- message: Fixed space ninja's starting with their internals disabled.
|
||||
type: Fix
|
||||
id: 6818
|
||||
time: '2024-06-25T06:28:48.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29213
|
||||
- author: PJB3005
|
||||
changes:
|
||||
- message: Low pressure damage now does only 1/4th as much damage. This is a band-aid
|
||||
until better mechanics exists for the crew to deal with breaches of various
|
||||
kinds.
|
||||
type: Remove
|
||||
id: 6819
|
||||
time: '2024-06-26T00:48:26.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29478
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Blood now has less satiation.
|
||||
type: Tweak
|
||||
id: 6820
|
||||
time: '2024-06-26T02:27:11.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29433
|
||||
- author: Veritable-Calamity
|
||||
changes:
|
||||
- message: Moldy food can now be collected in the Janitors' trash bags.
|
||||
type: Fix
|
||||
id: 6821
|
||||
time: '2024-06-26T03:26:59.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29380
|
||||
- author: Plykiya
|
||||
changes:
|
||||
- message: You can now interact with magic mirrors.
|
||||
type: Fix
|
||||
id: 6822
|
||||
time: '2024-06-26T14:25:11.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29491
|
||||
- author: Doomsdrayk
|
||||
changes:
|
||||
- message: Disposal units no longer protect their contents from nuclear explosions.
|
||||
type: Fix
|
||||
id: 6823
|
||||
time: '2024-06-26T14:25:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29438
|
||||
- author: Tayrtahn
|
||||
changes:
|
||||
- message: Pills and pill canisters no longer have two labels.
|
||||
type: Fix
|
||||
id: 6824
|
||||
time: '2024-06-27T02:08:57.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29499
|
||||
|
||||
@@ -36,6 +36,7 @@ limit = 10.0
|
||||
see_own_notes = true
|
||||
deadmin_on_join = true
|
||||
new_player_threshold = 600
|
||||
alert.min_players_sharing_connection = 2
|
||||
|
||||
[worldgen]
|
||||
enabled = true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
admin-alert-shared-connection = {$player} is sharing a connection with {$otherCount} connected player(s): {$otherList}
|
||||
@@ -1,3 +1,4 @@
|
||||
admin-player-actions-reason = Reason
|
||||
admin-player-actions-bans = Ban List
|
||||
admin-player-actions-notes = Notes
|
||||
admin-player-actions-kick = Kick
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
player-list-filter = Filter
|
||||
@@ -0,0 +1 @@
|
||||
admin-ui-teleport = Teleport
|
||||
@@ -0,0 +1,9 @@
|
||||
admin-ui-blueprint-map = Map
|
||||
admin-ui-blueprint-path = Path
|
||||
admin-ui-blueprint-x = X
|
||||
admin-ui-blueprint-y = Y
|
||||
admin-ui-blueprint-rotation = Rotation
|
||||
|
||||
admin-ui-blueprint-teleport = Teleport to
|
||||
admin-ui-blueprint-reset = Reset to default
|
||||
admin-ui-blueprint-load = Load Blueprint
|
||||
@@ -0,0 +1,12 @@
|
||||
admin-ui-atmos-add = Add Atmos
|
||||
admin-ui-atmos-add-gas = Add Gas
|
||||
admin-ui-atmos-fill-gas = Fill Gas
|
||||
admin-ui-atmos-set-temperature = Set Temperature
|
||||
|
||||
admin-ui-atmos-grid = Grid
|
||||
admin-ui-atmos-grid-current = Current
|
||||
admin-ui-atmos-tile-x = TileX
|
||||
admin-ui-atmos-tile-y = TileY
|
||||
admin-ui-atmos-gas = Gas
|
||||
admin-ui-atmos-gas-amount = Amount
|
||||
admin-ui-atmos-temperature = Temperature
|
||||
@@ -1,2 +1,9 @@
|
||||
object-tab-entity-id = Entity ID
|
||||
object-tab-object-name = Object name
|
||||
|
||||
object-tab-object-type = Object type:
|
||||
object-tab-object-search = Search...
|
||||
|
||||
object-tab-object-type-grids = Grids
|
||||
object-tab-object-type-maps = Maps
|
||||
object-tab-object-type-stations = Stations
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
player-tab-username = Username
|
||||
player-tab-player-count = Players: { $count }
|
||||
player-tab-username = Username
|
||||
player-tab-character = Character
|
||||
player-tab-job = Job
|
||||
player-tab-antagonist = Antagonist
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
administration-ui-round-tab-start-round = Start Round
|
||||
administration-ui-round-tab-end-round = End Round
|
||||
administration-ui-round-tab-restart-round = Restart Round
|
||||
administration-ui-round-tab-restart-round-now = Restart NOW
|
||||
|
||||
|
||||
@@ -107,6 +107,3 @@ alerts-revenant-essence-desc = The power of souls. It sustains you and is used f
|
||||
|
||||
alerts-revenant-corporeal-name = Corporeal
|
||||
alerts-revenant-corporeal-desc = You have manifested physically. People around you can see and hurt you.
|
||||
|
||||
alerts-deflecting-name = Deflecting
|
||||
alerts-deflecting-desc = You have a chance to deflect incoming projectiles. Standing still or moving slowly will increase this chance.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user