mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
Merge remote-tracking branch 'wizards/master' into upstream-sync
# Conflicts: # Resources/Prototypes/Datasets/Names/borer.yml # Resources/Prototypes/Datasets/Names/diona.yml # Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json # Resources/Textures/Tiles/plating_burnt.png
This commit is contained in:
23
.github/workflows/labeler-review.yml
vendored
Normal file
23
.github/workflows/labeler-review.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: "Labels: Approved"
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
jobs:
|
||||
add_label:
|
||||
# Change the repository name after you've made sure the team name is correct for your fork!
|
||||
if: ${{ (github.repository == 'space-wizards/space-station-14') && (github.event.review.state == 'APPROVED') }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tspascoal/get-user-teams-membership@v3
|
||||
id: checkUserMember
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
team: "content-maintainers,junior-maintainers"
|
||||
GITHUB_TOKEN: ${{ secrets.LABELER_PAT }}
|
||||
- if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "S: Approved"
|
||||
@@ -258,13 +258,13 @@ namespace Content.Client.Actions
|
||||
|
||||
public void LinkAllActions(ActionsComponent? actions = null)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } user ||
|
||||
!Resolve(user, ref actions, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_playerManager.LocalEntity is not { } user ||
|
||||
!Resolve(user, ref actions, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LinkActions?.Invoke(actions);
|
||||
LinkActions?.Invoke(actions);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace Content.Client.Administration.UI.BanPanel;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
public event Action<string?, (IPAddress, int)?, bool, byte[]?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
|
||||
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
|
||||
public event Action<string>? PlayerChanged;
|
||||
private string? PlayerUsername { get; set; }
|
||||
private (IPAddress, int)? IpAddress { get; set; }
|
||||
private byte[]? Hwid { get; set; }
|
||||
private ImmutableTypedHwid? Hwid { get; set; }
|
||||
private double TimeEntered { get; set; }
|
||||
private uint Multiplier { get; set; }
|
||||
private bool HasBanFlag { get; set; }
|
||||
@@ -371,9 +371,8 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
private void OnHwidChanged()
|
||||
{
|
||||
var hwidString = HwidLine.Text;
|
||||
var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '=');
|
||||
Hwid = new byte[length];
|
||||
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _))
|
||||
ImmutableTypedHwid? hwid = null;
|
||||
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !ImmutableTypedHwid.TryParse(hwidString, out hwid))
|
||||
{
|
||||
ErrorLevel |= ErrorLevelEnum.Hwid;
|
||||
HwidLine.ModulateSelfOverride = Color.Red;
|
||||
@@ -390,7 +389,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
Hwid = null;
|
||||
return;
|
||||
}
|
||||
Hwid = Convert.FromHexString(hwidString);
|
||||
Hwid = hwid;
|
||||
}
|
||||
|
||||
private void OnTypeChanged()
|
||||
|
||||
@@ -159,6 +159,7 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
SecretCheckBox.Pressed = false;
|
||||
SeverityOption.Disabled = false;
|
||||
PermanentCheckBox.Pressed = true;
|
||||
SubmitButton.Disabled = true;
|
||||
UpdatePermanentCheckboxFields();
|
||||
break;
|
||||
case (int) NoteType.Message: // Message: these are shown to the player when they log on
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PipeColorVisualsComponent : Component
|
||||
{
|
||||
}
|
||||
public sealed partial class PipeColorVisualsComponent : Component;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:Content.Client.Stylesheets"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical" HorizontalExpand ="True" Margin="0 0 0 3">
|
||||
@@ -62,7 +61,7 @@
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- If the alarm is inactive, this is label is diplayed instead -->
|
||||
<!-- If the alarm is inactive, this is label is displayed instead -->
|
||||
<Label Name="NoDataLabel" Text="{Loc 'atmos-alerts-window-no-data-available'}" HorizontalAlignment="Center" Margin="0 15" FontColorOverride="#a9a9a9" ReservesSpace="False" Visible="False"></Label>
|
||||
|
||||
<!-- Silencing progress bar -->
|
||||
|
||||
@@ -136,8 +136,9 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
GasGridContainer.RemoveAllChildren();
|
||||
|
||||
var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
|
||||
var keyValuePairs = gasData.ToList();
|
||||
|
||||
if (gasData.Count() == 0)
|
||||
if (keyValuePairs.Count == 0)
|
||||
{
|
||||
// No other gases
|
||||
var gasLabel = new Label()
|
||||
@@ -158,13 +159,11 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
else
|
||||
{
|
||||
// Add an entry for each gas
|
||||
foreach ((var gas, (var mol, var percent, var alert)) in gasData)
|
||||
foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
|
||||
{
|
||||
var gasPercent = (FixedPoint2)0f;
|
||||
gasPercent = percent * 100f;
|
||||
FixedPoint2 gasPercent = percent * 100f;
|
||||
|
||||
if (!_gasShorthands.TryGetValue(gas, out var gasShorthand))
|
||||
gasShorthand = "X";
|
||||
var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "X");
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
|
||||
_menu = new AtmosAlertsComputerWindow(this, Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
@@ -24,9 +22,6 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
|
||||
|
||||
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
|
||||
|
||||
if (castState == null)
|
||||
return;
|
||||
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
_menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'atmos-alerts-window-title'}"
|
||||
Resizable="False"
|
||||
SetSize="1120 750"
|
||||
|
||||
@@ -4,8 +4,6 @@ using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
@@ -19,7 +17,7 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, AppearanceChangeEvent>(OnAppearanceChanged, after: new[] { typeof(SubFloorHideSystem) });
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, AppearanceChangeEvent>(OnAppearanceChanged, after: [typeof(SubFloorHideSystem)]);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentInit args)
|
||||
@@ -84,7 +82,8 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
|
||||
layer.Visible &= visible;
|
||||
|
||||
if (!visible) continue;
|
||||
if (!visible)
|
||||
continue;
|
||||
|
||||
layer.Color = color;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor;
|
||||
|
||||
@@ -27,7 +22,7 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
|
||||
{
|
||||
foreach (var visLayer in component.HideOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer))
|
||||
if (args.Sprite.LayerMapTryGet(visLayer, out var powerVisibilityLayer))
|
||||
args.Sprite.LayerSetVisible(powerVisibilityLayer, powered);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +31,7 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
|
||||
{
|
||||
foreach (var (setLayer, powerState) in component.SetOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(setLayer, out int setStateLayer))
|
||||
if (args.Sprite.LayerMapTryGet(setLayer, out var setStateLayer))
|
||||
args.Sprite.LayerSetState(setStateLayer, new RSI.StateId(powerState));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI;
|
||||
|
||||
@@ -78,6 +74,7 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing) _window?.Dispose();
|
||||
if (disposing)
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -59,7 +58,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
AirAlarmMode.Fill => "air-alarm-ui-mode-fill",
|
||||
AirAlarmMode.Panic => "air-alarm-ui-mode-panic",
|
||||
AirAlarmMode.None => "air-alarm-ui-mode-none",
|
||||
_ => "error"
|
||||
_ => "error",
|
||||
};
|
||||
_modes.AddItem(Loc.GetString(text));
|
||||
}
|
||||
@@ -70,7 +69,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
|
||||
};
|
||||
|
||||
_autoMode.OnToggled += args =>
|
||||
_autoMode.OnToggled += _ =>
|
||||
{
|
||||
AutoModeChanged!.Invoke(_autoMode.Pressed);
|
||||
};
|
||||
@@ -176,22 +175,18 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
|
||||
public static Color ColorForThreshold(float amount, AtmosAlarmThreshold threshold)
|
||||
{
|
||||
threshold.CheckThreshold(amount, out AtmosAlarmType curAlarm);
|
||||
threshold.CheckThreshold(amount, out var curAlarm);
|
||||
return ColorForAlarm(curAlarm);
|
||||
}
|
||||
|
||||
public static Color ColorForAlarm(AtmosAlarmType curAlarm)
|
||||
{
|
||||
if(curAlarm == AtmosAlarmType.Danger)
|
||||
return curAlarm switch
|
||||
{
|
||||
return StyleNano.DangerousRedFore;
|
||||
}
|
||||
else if(curAlarm == AtmosAlarmType.Warning)
|
||||
{
|
||||
return StyleNano.ConcerningOrangeFore;
|
||||
}
|
||||
|
||||
return StyleNano.GoodGreenFore;
|
||||
AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
|
||||
AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
|
||||
_ => StyleNano.GoodGreenFore,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
@@ -25,7 +21,7 @@ public sealed partial class PumpControl : BoxContainer
|
||||
private OptionButton _pressureCheck => CPressureCheck;
|
||||
private FloatSpinBox _externalBound => CExternalBound;
|
||||
private FloatSpinBox _internalBound => CInternalBound;
|
||||
private Button _copySettings => CCopySettings;
|
||||
private Button _copySettings => CCopySettings;
|
||||
|
||||
public PumpControl(GasVentPumpData data, string address)
|
||||
{
|
||||
@@ -86,7 +82,7 @@ public sealed partial class PumpControl : BoxContainer
|
||||
_data.PressureChecks = (VentPressureBound) args.Id;
|
||||
PumpDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
PumpDataCopied?.Invoke(_data);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
@@ -27,7 +21,7 @@ public sealed partial class ScrubberControl : BoxContainer
|
||||
private OptionButton _pumpDirection => CPumpDirection;
|
||||
private FloatSpinBox _volumeRate => CVolumeRate;
|
||||
private CheckBox _wideNet => CWideNet;
|
||||
private Button _copySettings => CCopySettings;
|
||||
private Button _copySettings => CCopySettings;
|
||||
|
||||
private GridContainer _gases => CGasContainer;
|
||||
private Dictionary<Gas, Button> _gasControls = new();
|
||||
@@ -77,7 +71,7 @@ public sealed partial class ScrubberControl : BoxContainer
|
||||
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
|
||||
ScrubberDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
ScrubberDataCopied?.Invoke(_data);
|
||||
|
||||
@@ -43,7 +43,8 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
var label = new RichTextLabel();
|
||||
|
||||
var fractionGas = amount / data.TotalMoles;
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
|
||||
("gas", $"{gas}"),
|
||||
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(100 * fractionGas):0.##}")));
|
||||
@@ -53,9 +54,9 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
var threshold = data.GasThresholds[gas];
|
||||
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
|
||||
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
|
||||
gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
|
||||
{
|
||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
||||
OnThresholdUpdate!(_address, type, alarmThreshold, arg3);
|
||||
};
|
||||
|
||||
_gasThresholds.Add(gas, gasThresholdControl);
|
||||
@@ -64,7 +65,8 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
|
||||
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
|
||||
PressureThresholdContainer.AddChild(_pressureThreshold);
|
||||
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
|
||||
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"),
|
||||
data.TemperatureThreshold,
|
||||
AtmosMonitorThresholdType.Temperature);
|
||||
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
|
||||
|
||||
@@ -103,7 +105,8 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
}
|
||||
|
||||
var fractionGas = amount / data.TotalMoles;
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
|
||||
("gas", $"{gas}"),
|
||||
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(100 * fractionGas):0.##}")));
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
// holy FUCK
|
||||
// this technically works because some of this you can *not* do in XAML but holy FUCK
|
||||
@@ -115,29 +111,38 @@ public sealed partial class ThresholdControl : BoxContainer
|
||||
_enabled.Pressed = !_threshold.Ignore;
|
||||
}
|
||||
|
||||
private String LabelForBound(string boundType) //<todo.eoin Replace this with enums
|
||||
private string LabelForBound(string boundType) //<todo.eoin Replace this with enums
|
||||
{
|
||||
return Loc.GetString($"air-alarm-ui-thresholds-{boundType}");
|
||||
}
|
||||
|
||||
public void UpdateThresholdData(AtmosAlarmThreshold threshold, float currentAmount)
|
||||
{
|
||||
threshold.CheckThreshold(currentAmount, out AtmosAlarmType alarm, out AtmosMonitorThresholdBound which);
|
||||
threshold.CheckThreshold(currentAmount, out var alarm, out var bound);
|
||||
|
||||
var upperDangerState = AtmosAlarmType.Normal;
|
||||
var lowerDangerState = AtmosAlarmType.Normal;
|
||||
var upperWarningState = AtmosAlarmType.Normal;
|
||||
var lowerWarningState = AtmosAlarmType.Normal;
|
||||
|
||||
if(alarm == AtmosAlarmType.Danger)
|
||||
switch (alarm)
|
||||
{
|
||||
if(which == AtmosMonitorThresholdBound.Upper) upperDangerState = alarm;
|
||||
else lowerDangerState = alarm;
|
||||
}
|
||||
else if(alarm == AtmosAlarmType.Warning)
|
||||
{
|
||||
if(which == AtmosMonitorThresholdBound.Upper) upperWarningState = alarm;
|
||||
else lowerWarningState = alarm;
|
||||
case AtmosAlarmType.Danger:
|
||||
{
|
||||
if (bound == AtmosMonitorThresholdBound.Upper)
|
||||
upperDangerState = alarm;
|
||||
else
|
||||
lowerDangerState = alarm;
|
||||
break;
|
||||
}
|
||||
case AtmosAlarmType.Warning:
|
||||
{
|
||||
if (bound == AtmosMonitorThresholdBound.Upper)
|
||||
upperWarningState = alarm;
|
||||
else
|
||||
lowerWarningState = alarm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_upperBoundControl.SetValue(threshold.UpperBound.Value);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
@@ -101,14 +102,9 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
else
|
||||
{
|
||||
// Red-Green-Blue interpolation
|
||||
if (interp < 0.5f)
|
||||
{
|
||||
res = Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
|
||||
}
|
||||
res = interp < 0.5f
|
||||
? Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2)
|
||||
: Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
|
||||
}
|
||||
|
||||
res = res.WithAlpha(0.75f);
|
||||
@@ -181,7 +177,10 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
handle.DrawCircle(tileCentre, 0.05f, Color.Black);
|
||||
}
|
||||
|
||||
private void CheckAndShowBlockDir(AtmosDebugOverlayData data, DrawingHandleWorld handle, AtmosDirection dir,
|
||||
private void CheckAndShowBlockDir(
|
||||
AtmosDebugOverlayData data,
|
||||
DrawingHandleWorld handle,
|
||||
AtmosDirection dir,
|
||||
Vector2 tileCentre)
|
||||
{
|
||||
if (!data.BlockDirection.HasFlag(dir))
|
||||
@@ -243,7 +242,7 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
|
||||
var moles = data.Moles == null
|
||||
? "No Air"
|
||||
: data.Moles.Sum().ToString();
|
||||
: data.Moles.Sum().ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
handle.DrawString(_font, pos, $"Moles: {moles}");
|
||||
pos += offset;
|
||||
@@ -263,7 +262,12 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
private void GetGrids(MapId mapId, Box2Rotated box)
|
||||
{
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, (EntityUid uid, MapGridComponent grid,
|
||||
_mapManager.FindGridsIntersecting(
|
||||
mapId,
|
||||
box,
|
||||
ref _grids,
|
||||
(EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
|
||||
{
|
||||
if (_system.TileData.TryGetValue(uid, out var data))
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Clothing;
|
||||
|
||||
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
|
||||
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.4" Margin="0 0 5 0">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
|
||||
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
|
||||
</BoxContainer>
|
||||
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
|
||||
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
|
||||
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.6">
|
||||
<Button Name="FavoriteButton" Visible="false" HorizontalExpand="False"
|
||||
HorizontalAlignment="Right" Margin="0 0 0 15"/>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="MenuGridViewButton" ToggleMode="True" Text="{Loc construction-menu-grid-view}"/>
|
||||
<Button Name="FavoriteButton" Visible="false"/>
|
||||
</BoxContainer>
|
||||
<Control>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
|
||||
@@ -25,11 +25,16 @@ namespace Content.Client.Construction.UI
|
||||
OptionButton OptionCategories { get; }
|
||||
|
||||
bool EraseButtonPressed { get; set; }
|
||||
bool GridViewButtonPressed { get; set; }
|
||||
bool BuildButtonPressed { get; set; }
|
||||
|
||||
ItemList Recipes { get; }
|
||||
ItemList RecipeStepList { get; }
|
||||
|
||||
|
||||
ScrollContainer RecipesGridScrollContainer { get; }
|
||||
GridContainer RecipesGrid { get; }
|
||||
|
||||
event EventHandler<(string search, string catagory)> PopulateRecipes;
|
||||
event EventHandler<ItemList.Item?> RecipeSelected;
|
||||
event EventHandler RecipeFavorited;
|
||||
@@ -72,9 +77,16 @@ namespace Content.Client.Construction.UI
|
||||
set => EraseButton.Pressed = value;
|
||||
}
|
||||
|
||||
public bool GridViewButtonPressed
|
||||
{
|
||||
get => MenuGridViewButton.Pressed;
|
||||
set => MenuGridViewButton.Pressed = value;
|
||||
}
|
||||
|
||||
public ConstructionMenu()
|
||||
{
|
||||
SetSize = MinSize = new Vector2(720, 320);
|
||||
SetSize = new Vector2(560, 450);
|
||||
MinSize = new Vector2(560, 320);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -102,6 +114,9 @@ namespace Content.Client.Construction.UI
|
||||
EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed);
|
||||
|
||||
FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
MenuGridViewButton.OnPressed += _ =>
|
||||
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
|
||||
}
|
||||
|
||||
public event EventHandler? ClearAllGhosts;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -11,7 +12,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
@@ -33,10 +33,12 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
private readonly IConstructionMenuView _constructionView;
|
||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private ConstructionSystem? _constructionSystem;
|
||||
private ConstructionPrototype? _selected;
|
||||
private List<ConstructionPrototype> _favoritedRecipes = [];
|
||||
private Dictionary<string, TextureButton> _recipeButtons = new();
|
||||
private string _selectedCategory = string.Empty;
|
||||
private string _favoriteCatName = "construction-category-favorites";
|
||||
private string _forAllCategoryName = "construction-category-all";
|
||||
@@ -85,6 +87,7 @@ namespace Content.Client.Construction.UI
|
||||
IoCManager.InjectDependencies(this);
|
||||
_constructionView = new ConstructionMenu();
|
||||
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
// This is required so that if we load after the system is initialized, we can bind to it immediately
|
||||
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
|
||||
@@ -150,12 +153,24 @@ namespace Content.Client.Construction.UI
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
|
||||
{
|
||||
if (recipe is null)
|
||||
{
|
||||
_selected = null;
|
||||
_constructionView.ClearRecipeInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
_selected = recipe;
|
||||
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
|
||||
{
|
||||
var (search, category) = args;
|
||||
var recipesList = _constructionView.Recipes;
|
||||
|
||||
recipesList.Clear();
|
||||
var recipes = new List<ConstructionPrototype>();
|
||||
|
||||
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
|
||||
@@ -201,12 +216,73 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
|
||||
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
recipesList.Add(GetItem(recipe, recipesList));
|
||||
}
|
||||
var recipesList = _constructionView.Recipes;
|
||||
recipesList.Clear();
|
||||
|
||||
// There is apparently no way to set which
|
||||
var recipesGrid = _constructionView.RecipesGrid;
|
||||
recipesGrid.RemoveAllChildren();
|
||||
|
||||
_constructionView.RecipesGridScrollContainer.Visible = _constructionView.GridViewButtonPressed;
|
||||
_constructionView.Recipes.Visible = !_constructionView.GridViewButtonPressed;
|
||||
|
||||
if (_constructionView.GridViewButtonPressed)
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
var itemButton = new TextureButton
|
||||
{
|
||||
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.Name,
|
||||
ToolTip = recipe.Name,
|
||||
Scale = new Vector2(1.35f),
|
||||
ToggleMode = true,
|
||||
};
|
||||
var itemButtonPanelContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
|
||||
Children = { itemButton },
|
||||
};
|
||||
|
||||
itemButton.OnToggled += buttonToggledEventArgs =>
|
||||
{
|
||||
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
}
|
||||
|
||||
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Name] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
recipesList.Add(GetItem(recipe, recipesList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectGridButton(TextureButton button, bool select)
|
||||
{
|
||||
if (button.Parent is not PanelContainer buttonPanel)
|
||||
return;
|
||||
|
||||
button.Modulate = select ? Color.Green : Color.White;
|
||||
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
||||
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
||||
}
|
||||
|
||||
private void PopulateCategories(string? selectCategory = null)
|
||||
@@ -257,11 +333,10 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
private void PopulateInfo(ConstructionPrototype prototype)
|
||||
{
|
||||
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
|
||||
_constructionView.ClearRecipeInfo();
|
||||
|
||||
_constructionView.SetRecipeInfo(
|
||||
prototype.Name, prototype.Description, spriteSys.Frame0(prototype.Icon),
|
||||
prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
|
||||
prototype.Type != ConstructionType.Item,
|
||||
!_favoritedRecipes.Contains(prototype));
|
||||
|
||||
@@ -274,7 +349,6 @@ namespace Content.Client.Construction.UI
|
||||
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
|
||||
return;
|
||||
|
||||
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
foreach (var entry in guide.Entries)
|
||||
{
|
||||
@@ -290,20 +364,20 @@ namespace Content.Client.Construction.UI
|
||||
// The padding needs to be applied regardless of text length... (See PadLeft documentation)
|
||||
text = text.PadLeft(text.Length + entry.Padding);
|
||||
|
||||
var icon = entry.Icon != null ? spriteSys.Frame0(entry.Icon) : Texture.Transparent;
|
||||
var icon = entry.Icon != null ? _spriteSystem.Frame0(entry.Icon) : Texture.Transparent;
|
||||
stepList.AddItem(text, icon, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
|
||||
private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
|
||||
{
|
||||
return new(itemList)
|
||||
{
|
||||
Metadata = recipe,
|
||||
Text = recipe.Name,
|
||||
Icon = recipe.Icon.Frame0(),
|
||||
Icon = _spriteSystem.Frame0(recipe.Icon),
|
||||
TooltipEnabled = true,
|
||||
TooltipText = recipe.Description
|
||||
TooltipText = recipe.Description,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using Content.Shared.Electrocution;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Electrocution;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the Electrocution HUD to entities with the ShowElectrocutionHUDComponent.
|
||||
/// </summary>
|
||||
public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem<ElectrocutionHUDVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ShowElectrocutionHUDComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<ShowElectrocutionHUDComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ShowElectrocutionHUDComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<ShowElectrocutionHUDComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<ShowElectrocutionHUDComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
ShowHUD();
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<ShowElectrocutionHUDComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
RemoveHUD();
|
||||
}
|
||||
|
||||
private void OnInit(Entity<ShowElectrocutionHUDComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
if (_playerMan.LocalEntity == ent)
|
||||
{
|
||||
ShowHUD();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<ShowElectrocutionHUDComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (_playerMan.LocalEntity == ent)
|
||||
{
|
||||
RemoveHUD();
|
||||
}
|
||||
}
|
||||
|
||||
// Show the HUD to the client.
|
||||
// We have to look for all current entities that can be electrified and toggle the HUD layer on if they are.
|
||||
private void ShowHUD()
|
||||
{
|
||||
var electrifiedQuery = AllEntityQuery<ElectrocutionHUDVisualsComponent, AppearanceComponent, SpriteComponent>();
|
||||
while (electrifiedQuery.MoveNext(out var uid, out var _, out var appearanceComp, out var spriteComp))
|
||||
{
|
||||
if (!AppearanceSystem.TryGetData<bool>(uid, ElectrifiedVisuals.IsElectrified, out var electrified, appearanceComp))
|
||||
continue;
|
||||
|
||||
if (electrified)
|
||||
spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, true);
|
||||
else
|
||||
spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the HUD from the client.
|
||||
// Find all current entities that can be electrified and hide the HUD layer.
|
||||
private void RemoveHUD()
|
||||
{
|
||||
var electrifiedQuery = AllEntityQuery<ElectrocutionHUDVisualsComponent, AppearanceComponent, SpriteComponent>();
|
||||
while (electrifiedQuery.MoveNext(out var uid, out var _, out var appearanceComp, out var spriteComp))
|
||||
{
|
||||
|
||||
spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle the HUD layer if an entity becomes (de-)electrified
|
||||
protected override void OnAppearanceChange(EntityUid uid, ElectrocutionHUDVisualsComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!AppearanceSystem.TryGetData<bool>(uid, ElectrifiedVisuals.IsElectrified, out var electrified, args.Component))
|
||||
return;
|
||||
|
||||
var player = _playerMan.LocalEntity;
|
||||
if (electrified && HasComp<ShowElectrocutionHUDComponent>(player))
|
||||
args.Sprite.LayerSetVisible(ElectrifiedLayers.HUD, true);
|
||||
else
|
||||
args.Sprite.LayerSetVisible(ElectrifiedLayers.HUD, false);
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Content.Client.Eui
|
||||
/// </summary>
|
||||
protected void SendMessage(EuiMessageBase msg)
|
||||
{
|
||||
var netMsg = _netManager.CreateNetMessage<MsgEuiMessage>();
|
||||
var netMsg = new MsgEuiMessage();
|
||||
netMsg.Id = Id;
|
||||
netMsg.Message = msg;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Content.Client.Info
|
||||
AddInfoButton("server-info-website-button", CCVars.InfoLinksWebsite);
|
||||
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
|
||||
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
|
||||
AddInfoButton("server-info-telegram-button", CCVars.InfoLinksTelegram);
|
||||
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };
|
||||
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Strip.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
@@ -29,10 +30,13 @@ namespace Content.Client.Inventory
|
||||
[UsedImplicitly]
|
||||
public sealed class StrippableBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
private readonly ExamineSystem _examine;
|
||||
private readonly InventorySystem _inv;
|
||||
private readonly SharedCuffableSystem _cuffable;
|
||||
private readonly StrippableSystem _strippable;
|
||||
|
||||
[ViewVariables]
|
||||
private const int ButtonSeparation = 4;
|
||||
@@ -51,6 +55,8 @@ namespace Content.Client.Inventory
|
||||
_examine = EntMan.System<ExamineSystem>();
|
||||
_inv = EntMan.System<InventorySystem>();
|
||||
_cuffable = EntMan.System<SharedCuffableSystem>();
|
||||
_strippable = EntMan.System<StrippableSystem>();
|
||||
|
||||
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
|
||||
}
|
||||
|
||||
@@ -185,9 +191,15 @@ namespace Content.Client.Inventory
|
||||
return;
|
||||
|
||||
if (ev.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_examine.DoExamine(slot.Entity.Value);
|
||||
ev.Handle();
|
||||
}
|
||||
else if (ev.Function == EngineKeyFunctions.UseSecondary)
|
||||
{
|
||||
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(slot.Entity.Value);
|
||||
ev.Handle();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInventoryButton(EntityUid invUid, string slotId, InventoryComponent inv)
|
||||
@@ -198,7 +210,8 @@ namespace Content.Client.Inventory
|
||||
var entity = container.ContainedEntity;
|
||||
|
||||
// If this is a full pocket, obscure the real entity
|
||||
if (entity != null && slotDef.StripHidden)
|
||||
// this does not work for modified clients because they are still sent the real entity
|
||||
if (entity != null && _strippable.IsStripHidden(slotDef, _player.LocalEntity))
|
||||
entity = _virtualHiddenEntity;
|
||||
|
||||
var button = new SlotButton(new SlotData(slotDef, container));
|
||||
|
||||
@@ -21,6 +21,22 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
SubscribeLocalEvent<HandheldLightComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
|
||||
/// </remarks>
|
||||
public override bool TurnOff(Entity<HandheldLightComponent> ent, bool makeNoise = true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
|
||||
/// </remarks>
|
||||
public override bool TurnOn(EntityUid user, Entity<HandheldLightComponent> uid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Content.Client.Lobby
|
||||
return;
|
||||
}
|
||||
|
||||
Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
|
||||
Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
|
||||
string text;
|
||||
|
||||
if (_gameTicker.Paused)
|
||||
@@ -136,6 +136,10 @@ namespace Content.Client.Lobby
|
||||
{
|
||||
text = Loc.GetString(seconds < -5 ? "lobby-state-right-now-question" : "lobby-state-right-now-confirmation");
|
||||
}
|
||||
else if (difference.TotalHours >= 1)
|
||||
{
|
||||
text = $"{Math.Floor(difference.TotalHours)}:{difference.Minutes:D2}:{difference.Seconds:D2}";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = $"{difference.Minutes}:{difference.Seconds:D2}";
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed partial class StencilOverlay
|
||||
var matty = Matrix3x2.Multiply(matrix, invMatrix);
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
|
||||
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile))
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed partial class StencilOverlay : Overlay
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly SharedMapSystem _map;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly WeatherSystem _weather;
|
||||
|
||||
@@ -33,11 +34,12 @@ public sealed partial class StencilOverlay : Overlay
|
||||
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
|
||||
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SharedMapSystem map, SpriteSystem sprite, WeatherSystem weather)
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
|
||||
_parallax = parallax;
|
||||
_transform = transform;
|
||||
_map = map;
|
||||
_sprite = sprite;
|
||||
_weather = weather;
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
@@ -10,13 +10,14 @@ public sealed class StencilOverlaySystem : EntitySystem
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly ParallaxSystem _parallax = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _sprite, _weather));
|
||||
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _map, _sprite, _weather));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
@@ -116,7 +116,7 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
|
||||
protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 gridToView, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
|
||||
{
|
||||
var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp);
|
||||
var minimapScale = MinimapScale;
|
||||
@@ -264,7 +264,7 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
Extensions.EnsureLength(ref _allVertices, totalData);
|
||||
|
||||
_drawJob.MidPoint = midpoint;
|
||||
_drawJob.Matrix = matrix;
|
||||
_drawJob.Matrix = gridToView;
|
||||
_drawJob.MinimapScale = minimapScale;
|
||||
_drawJob.Vertices = gridData.Vertices;
|
||||
_drawJob.ScaledVertices = _allVertices;
|
||||
@@ -286,7 +286,7 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
|
||||
private record struct GridDrawJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 16;
|
||||
public int BatchSize => 64;
|
||||
|
||||
public float MinimapScale;
|
||||
public Vector2 MidPoint;
|
||||
@@ -297,12 +297,7 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var vert = Vertices[index];
|
||||
var adjustedVert = Vector2.Transform(vert, Matrix);
|
||||
adjustedVert = adjustedVert with { Y = -adjustedVert.Y };
|
||||
|
||||
var scaledVert = ScalePosition(adjustedVert, MinimapScale, MidPoint);
|
||||
ScaledVertices[index] = scaledVert;
|
||||
ScaledVertices[index] = Vector2.Transform(Vertices[index], Matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed partial class NavScreen : BoxContainer
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private SharedTransformSystem _xformSystem;
|
||||
|
||||
private EntityUid? _consoleEntity; // Entity of controlling console
|
||||
private EntityUid? _shuttleEntity;
|
||||
|
||||
public NavScreen()
|
||||
@@ -35,6 +36,12 @@ public sealed partial class NavScreen : BoxContainer
|
||||
_shuttleEntity = shuttle;
|
||||
}
|
||||
|
||||
public void SetConsole(EntityUid? console)
|
||||
{
|
||||
_consoleEntity = console;
|
||||
NavRadar.SetConsole(console);
|
||||
}
|
||||
|
||||
private void OnIFFTogglePressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
NavRadar.ShowIFF ^= true;
|
||||
|
||||
@@ -138,6 +138,7 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
|
||||
{
|
||||
var coordinates = _entManager.GetCoordinates(cState.NavState.Coordinates);
|
||||
NavContainer.SetShuttle(coordinates?.EntityId);
|
||||
NavContainer.SetConsole(owner);
|
||||
MapContainer.SetShuttle(coordinates?.EntityId);
|
||||
MapContainer.SetConsole(owner);
|
||||
|
||||
|
||||
@@ -107,16 +107,19 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
DrawCircles(handle);
|
||||
var gridNent = EntManager.GetNetEntity(GridEntity);
|
||||
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
|
||||
var ourGridMatrix = _xformSystem.GetWorldMatrix(GridEntity.Value);
|
||||
var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
|
||||
var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix);
|
||||
var ourGridToWorld = _xformSystem.GetWorldMatrix(GridEntity.Value);
|
||||
var selectedDockToOurGrid = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
|
||||
var selectedDockToWorld = Matrix3x2.Multiply(selectedDockToOurGrid, ourGridToWorld);
|
||||
|
||||
Matrix3x2.Invert(worldFromDock, out var offsetMatrix);
|
||||
Box2 viewBoundsWorld = Matrix3Helpers.TransformBox(selectedDockToWorld, new Box2(-WorldRangeVector, WorldRangeVector));
|
||||
|
||||
Matrix3x2.Invert(selectedDockToWorld, out var worldToSelectedDock);
|
||||
var selectedDockToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||
|
||||
// Draw nearby grids
|
||||
var controlBounds = PixelSizeBox;
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(gridXform.MapID, new Box2(mapPos.Position - WorldRangeVector, mapPos.Position + WorldRangeVector), ref _grids);
|
||||
_mapManager.FindGridsIntersecting(gridXform.MapID, viewBoundsWorld, ref _grids);
|
||||
|
||||
// offset the dotted-line position to the bounds.
|
||||
Vector2? viewedDockPos = _viewedState != null ? MidPointVector : null;
|
||||
@@ -136,11 +139,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
if (grid.Owner != GridEntity && !_shuttles.CanDraw(grid.Owner, iffComp: iffComp))
|
||||
continue;
|
||||
|
||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, offsetMatrix);
|
||||
var curGridToWorld = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||
var curGridToView = curGridToWorld * worldToSelectedDock * selectedDockToView;
|
||||
var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp);
|
||||
|
||||
DrawGrid(handle, matty, grid, color);
|
||||
DrawGrid(handle, curGridToView, grid, color);
|
||||
|
||||
// Draw any docks on that grid
|
||||
if (!DockState.Docks.TryGetValue(EntManager.GetNetEntity(grid), out var gridDocks))
|
||||
@@ -151,23 +154,24 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
if (ViewedDock == dock.Entity)
|
||||
continue;
|
||||
|
||||
var position = Vector2.Transform(dock.Coordinates.Position, matty);
|
||||
|
||||
var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle);
|
||||
var scaledPos = ScalePosition(position with {Y = -position.Y});
|
||||
|
||||
if (!controlBounds.Contains(scaledPos.Floored()))
|
||||
// This box is the AABB of all the vertices we draw below.
|
||||
var dockRenderBoundsLocal = new Box2(-0.5f, -0.7f, 0.5f, 0.5f);
|
||||
var currentDockToCurGrid = Matrix3Helpers.CreateTransform(dock.Coordinates.Position, dock.Angle);
|
||||
var currentDockToWorld = Matrix3x2.Multiply(currentDockToCurGrid, curGridToWorld);
|
||||
var dockRenderBoundsWorld = Matrix3Helpers.TransformBox(currentDockToWorld, dockRenderBoundsLocal);
|
||||
if (!viewBoundsWorld.Intersects(dockRenderBoundsWorld))
|
||||
continue;
|
||||
|
||||
// Draw the dock's collision
|
||||
var collisionBL = Vector2.Transform(dock.Coordinates.Position +
|
||||
Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), matty);
|
||||
Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), curGridToView);
|
||||
var collisionBR = Vector2.Transform(dock.Coordinates.Position +
|
||||
Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), matty);
|
||||
Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), curGridToView);
|
||||
var collisionTR = Vector2.Transform(dock.Coordinates.Position +
|
||||
Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), matty);
|
||||
Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), curGridToView);
|
||||
var collisionTL = Vector2.Transform(dock.Coordinates.Position +
|
||||
Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), matty);
|
||||
Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), curGridToView);
|
||||
|
||||
var verts = new[]
|
||||
{
|
||||
@@ -181,13 +185,6 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
collisionBL,
|
||||
};
|
||||
|
||||
for (var i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var vert = verts[i];
|
||||
vert.Y = -vert.Y;
|
||||
verts[i] = ScalePosition(vert);
|
||||
}
|
||||
|
||||
var collisionCenter = verts[0] + verts[1] + verts[3] + verts[5];
|
||||
|
||||
var otherDockConnection = Color.ToSrgb(Color.Pink);
|
||||
@@ -195,10 +192,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection);
|
||||
|
||||
// Draw the dock itself
|
||||
var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), matty);
|
||||
var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), matty);
|
||||
var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), matty);
|
||||
var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), matty);
|
||||
var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), curGridToView);
|
||||
var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), curGridToView);
|
||||
var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), curGridToView);
|
||||
var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), curGridToView);
|
||||
|
||||
verts = new[]
|
||||
{
|
||||
@@ -212,13 +209,6 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
dockBL
|
||||
};
|
||||
|
||||
for (var i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var vert = verts[i];
|
||||
vert.Y = -vert.Y;
|
||||
verts[i] = ScalePosition(vert);
|
||||
}
|
||||
|
||||
Color otherDockColor;
|
||||
|
||||
if (HighlightedDock == dock.Entity)
|
||||
@@ -253,9 +243,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
collisionCenter /= 4;
|
||||
var range = viewedDockPos.Value - collisionCenter;
|
||||
|
||||
if (range.Length() < SharedDockingSystem.DockingHiglightRange * MinimapScale)
|
||||
var maxRange = SharedDockingSystem.DockingHiglightRange * MinimapScale;
|
||||
var maxRangeSq = maxRange * maxRange;
|
||||
if (range.LengthSquared() < maxRangeSq)
|
||||
{
|
||||
if (_viewedState?.GridDockedWith == null)
|
||||
if (dock.GridDockedWith == null)
|
||||
{
|
||||
var coordsOne = EntManager.GetCoordinates(_viewedState!.Coordinates);
|
||||
var coordsTwo = EntManager.GetCoordinates(dock.Coordinates);
|
||||
@@ -265,10 +257,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
var rotA = _xformSystem.GetWorldRotation(coordsOne.EntityId) + _viewedState!.Angle;
|
||||
var rotB = _xformSystem.GetWorldRotation(coordsTwo.EntityId) + dock.Angle;
|
||||
|
||||
var distance = (mapOne.Position - mapTwo.Position).Length();
|
||||
var distanceSq = (mapOne.Position - mapTwo.Position).LengthSquared();
|
||||
|
||||
var inAlignment = _dockSystem.InAlignment(mapOne, rotA, mapTwo, rotB);
|
||||
var canDock = distance < SharedDockingSystem.DockRange && inAlignment;
|
||||
var maxDockDistSq = SharedDockingSystem.DockRange * SharedDockingSystem.DockRange;
|
||||
var canDock = distanceSq < maxDockDistSq && inAlignment;
|
||||
|
||||
if (dockButton != null)
|
||||
dockButton.Disabled = !canDock || !canDockChange;
|
||||
@@ -297,7 +290,8 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
||||
{
|
||||
// Because it's being layed out top-down we have to arrange for first frame.
|
||||
container.Arrange(PixelRect);
|
||||
var containerPos = scaledPos / UIScale - container.DesiredSize / 2 - new Vector2(0f, 0.75f) * MinimapScale;
|
||||
var dockPositionInView = Vector2.Transform(dock.Coordinates.Position, curGridToView);
|
||||
var containerPos = dockPositionInView / UIScale - container.DesiredSize / 2 - new Vector2(0f, 0.75f) * MinimapScale;
|
||||
SetPosition(container, containerPos);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
/// </summary>
|
||||
private EntityCoordinates? _coordinates;
|
||||
|
||||
/// <summary>
|
||||
/// Entity of controlling console
|
||||
/// </summary>
|
||||
private EntityUid? _consoleEntity;
|
||||
|
||||
private Angle? _rotation;
|
||||
|
||||
private Dictionary<NetEntity, List<DockingPortState>> _docks = new();
|
||||
@@ -57,6 +62,11 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
_rotation = angle;
|
||||
}
|
||||
|
||||
public void SetConsole(EntityUid? consoleEntity)
|
||||
{
|
||||
_consoleEntity = consoleEntity;
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
@@ -139,40 +149,35 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
}
|
||||
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
var offset = _coordinates.Value.Position;
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
|
||||
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
|
||||
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||
var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
|
||||
var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
|
||||
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||
|
||||
// Draw our grid in detail
|
||||
var ourGridId = xform.GridUid;
|
||||
if (EntManager.TryGetComponent<MapGridComponent>(ourGridId, out var ourGrid) &&
|
||||
fixturesQuery.HasComponent(ourGridId.Value))
|
||||
{
|
||||
var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value);
|
||||
var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert);
|
||||
var ourGridToWorld = _transform.GetWorldMatrix(ourGridId.Value);
|
||||
var ourGridToShuttle = Matrix3x2.Multiply(ourGridToWorld, worldToShuttle);
|
||||
var ourGridToView = ourGridToShuttle * shuttleToView;
|
||||
var color = _shuttles.GetIFFColor(ourGridId.Value, self: true);
|
||||
|
||||
DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color);
|
||||
DrawDocks(handle, ourGridId.Value, matrix);
|
||||
DrawGrid(handle, ourGridToView, (ourGridId.Value, ourGrid), color);
|
||||
DrawDocks(handle, ourGridId.Value, ourGridToView);
|
||||
}
|
||||
|
||||
var invertedPosition = _coordinates.Value.Position - offset;
|
||||
invertedPosition.Y = -invertedPosition.Y;
|
||||
// Don't need to transform the InvWorldMatrix again as it's already offset to its position.
|
||||
|
||||
// Draw radar position on the station
|
||||
var radarPos = invertedPosition;
|
||||
const float radarVertRadius = 2f;
|
||||
|
||||
var radarPosVerts = new Vector2[]
|
||||
{
|
||||
ScalePosition(radarPos + new Vector2(0f, -radarVertRadius)),
|
||||
ScalePosition(radarPos + new Vector2(radarVertRadius / 2f, 0f)),
|
||||
ScalePosition(radarPos + new Vector2(0f, radarVertRadius)),
|
||||
ScalePosition(radarPos + new Vector2(radarVertRadius / -2f, 0f)),
|
||||
ScalePosition(new Vector2(0f, -radarVertRadius)),
|
||||
ScalePosition(new Vector2(radarVertRadius / 2f, 0f)),
|
||||
ScalePosition(new Vector2(0f, radarVertRadius)),
|
||||
ScalePosition(new Vector2(radarVertRadius / -2f, 0f)),
|
||||
};
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime);
|
||||
@@ -197,8 +202,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
if (!_shuttles.CanDraw(gUid, gridBody, iff))
|
||||
continue;
|
||||
|
||||
var gridMatrix = _transform.GetWorldMatrix(gUid);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
|
||||
var curGridToWorld = _transform.GetWorldMatrix(gUid);
|
||||
var curGridToView = curGridToWorld * worldToShuttle * shuttleToView;
|
||||
|
||||
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
|
||||
@@ -213,8 +218,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
{
|
||||
var gridBounds = grid.Comp.LocalAABB;
|
||||
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
|
||||
gridCentre.Y = -gridCentre.Y;
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, curGridToView);
|
||||
|
||||
var distance = gridCentre.Length();
|
||||
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
|
||||
@@ -230,9 +234,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
// y-offset the control to always render below the grid (vertically)
|
||||
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f;
|
||||
|
||||
// The actual position in the UI. We centre the label by offsetting the matrix position
|
||||
// by half the label's width, plus the y-offset
|
||||
var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset);
|
||||
// The actual position in the UI.
|
||||
var gridScaledPosition = gridCentre - new Vector2(0, -yOffset);
|
||||
|
||||
// Normalize the grid position if it exceeds the viewport bounds
|
||||
// normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging
|
||||
@@ -264,18 +267,32 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
var gridAABB = gridMatrix.TransformBox(grid.Comp.LocalAABB);
|
||||
var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
|
||||
|
||||
// Skip drawing if it's out of range.
|
||||
if (!gridAABB.Intersects(viewAABB))
|
||||
continue;
|
||||
|
||||
DrawGrid(handle, matty, grid, labelColor);
|
||||
DrawDocks(handle, gUid, matty);
|
||||
DrawGrid(handle, curGridToView, grid, labelColor);
|
||||
DrawDocks(handle, gUid, curGridToView);
|
||||
}
|
||||
|
||||
// If we've set the controlling console, and it's on a different grid
|
||||
// to the shuttle itself, then draw an additional marker to help the
|
||||
// player determine where they are relative to the shuttle.
|
||||
if (_consoleEntity != null && xformQuery.TryGetComponent(_consoleEntity, out var consoleXform))
|
||||
{
|
||||
if (consoleXform.ParentUid != _coordinates.Value.EntityId)
|
||||
{
|
||||
var consolePositionWorld = _transform.GetWorldPosition((EntityUid)_consoleEntity);
|
||||
var p = Vector2.Transform(consolePositionWorld, worldToShuttle * shuttleToView);
|
||||
handle.DrawCircle(p, 5, Color.ToSrgb(Color.Cyan), true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix)
|
||||
private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 gridToView)
|
||||
{
|
||||
if (!ShowDocks)
|
||||
return;
|
||||
@@ -283,33 +300,32 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
const float DockScale = 0.6f;
|
||||
var nent = EntManager.GetNetEntity(uid);
|
||||
|
||||
const float sqrt2 = 1.41421356f;
|
||||
const float dockRadius = DockScale * sqrt2;
|
||||
// Worst-case bounds used to cull a dock:
|
||||
Box2 viewBounds = new Box2(-dockRadius, -dockRadius, Size.X + dockRadius, Size.Y + dockRadius);
|
||||
if (_docks.TryGetValue(nent, out var docks))
|
||||
{
|
||||
foreach (var state in docks)
|
||||
{
|
||||
var position = state.Coordinates.Position;
|
||||
var uiPosition = Vector2.Transform(position, matrix);
|
||||
|
||||
if (uiPosition.Length() > (WorldRange * 2f) - DockScale)
|
||||
var positionInView = Vector2.Transform(position, gridToView);
|
||||
if (!viewBounds.Contains(positionInView))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = Color.ToSrgb(Color.Magenta);
|
||||
|
||||
var verts = new[]
|
||||
{
|
||||
Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix),
|
||||
Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix),
|
||||
Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix),
|
||||
Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix),
|
||||
Vector2.Transform(position + new Vector2(-DockScale, -DockScale), gridToView),
|
||||
Vector2.Transform(position + new Vector2(DockScale, -DockScale), gridToView),
|
||||
Vector2.Transform(position + new Vector2(DockScale, DockScale), gridToView),
|
||||
Vector2.Transform(position + new Vector2(-DockScale, DockScale), gridToView),
|
||||
};
|
||||
|
||||
for (var i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var vert = verts[i];
|
||||
vert.Y = -vert.Y;
|
||||
verts[i] = ScalePosition(vert);
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color.WithAlpha(0.8f));
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
@@ -18,8 +19,13 @@ public sealed partial class LawDisplay : Control
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
|
||||
private static readonly TimeSpan PressCooldown = TimeSpan.FromSeconds(3);
|
||||
|
||||
private readonly Dictionary<Button, TimeSpan> _nextAllowedPress = new();
|
||||
|
||||
public LawDisplay(EntityUid uid, SiliconLaw law, HashSet<string>? radioChannels)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -47,9 +53,12 @@ public sealed partial class LawDisplay : Control
|
||||
MinWidth = 75,
|
||||
};
|
||||
|
||||
_nextAllowedPress[localButton] = TimeSpan.Zero;
|
||||
|
||||
localButton.OnPressed += _ =>
|
||||
{
|
||||
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Local);
|
||||
_nextAllowedPress[localButton] = _timing.CurTime + PressCooldown;
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(localButton);
|
||||
@@ -71,6 +80,8 @@ public sealed partial class LawDisplay : Control
|
||||
MinWidth = 75,
|
||||
};
|
||||
|
||||
_nextAllowedPress[radioChannelButton] = TimeSpan.Zero;
|
||||
|
||||
radioChannelButton.OnPressed += _ =>
|
||||
{
|
||||
switch (radioChannel)
|
||||
@@ -80,9 +91,21 @@ public sealed partial class LawDisplay : Control
|
||||
default:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
}
|
||||
_nextAllowedPress[radioChannelButton] = _timing.CurTime + PressCooldown;
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(radioChannelButton);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
foreach (var (button, nextPress) in _nextAllowedPress)
|
||||
{
|
||||
button.Disabled = curTime < nextPress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Singularity.EntitySystems;
|
||||
using Content.Shared.Singularity.Components;
|
||||
|
||||
namespace Content.Client.Singularity.EntitySystems;
|
||||
namespace Content.Client.Singularity.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// The client-side version of <see cref="SharedEventHorizonSystem"/>.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Content.Shared.Singularity.EntitySystems;
|
||||
using Content.Shared.Singularity.Components;
|
||||
|
||||
namespace Content.Client.Singularity.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// The client-side version of <see cref="SharedSingularityGeneratorSystem"/>.
|
||||
/// Manages <see cref="SingularityGeneratorComponent"/>s.
|
||||
/// Exists to make relevant signal handlers (ie: <see cref="SharedSingularityGeneratorSystem.OnEmagged"/>) work on the client.
|
||||
/// </summary>
|
||||
public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
|
||||
{}
|
||||
@@ -5,7 +5,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Singularity.EntitySystems;
|
||||
namespace Content.Client.Singularity.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// The client-side version of <see cref="SharedSingularitySystem"/>.
|
||||
|
||||
@@ -16,9 +16,8 @@ using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
namespace Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
#pragma warning disable RA0003
|
||||
[Virtual]
|
||||
public partial class ChatBox : UIWidget
|
||||
#pragma warning restore RA0003
|
||||
{
|
||||
private readonly ChatUIController _controller;
|
||||
private readonly IEntityManager _entManager;
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
||||
buttonHeading.AddStyleClass(ContainerButton.StyleClassButton);
|
||||
buttonHeading.Label.HorizontalAlignment = HAlignment.Center;
|
||||
buttonHeading.Label.HorizontalExpand = true;
|
||||
buttonHeading.Margin = new Thickness(8, 0, 8, 2);
|
||||
|
||||
var body = new CollapsibleBody
|
||||
{
|
||||
|
||||
@@ -83,22 +83,27 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
_handsSystem.UIHandClick(_playerHandsComponent, hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||
{
|
||||
_handsSystem.UIHandOpenContextMenu(hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||
{
|
||||
_handsSystem.UIHandActivate(hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||
{
|
||||
_handsSystem.UIHandAltActivateItem(hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_handsSystem.UIInventoryExamine(hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,17 +11,21 @@
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SeparationOverride="5"
|
||||
>
|
||||
<GridContainer
|
||||
Rows="2"
|
||||
HSeparationOverride="5"
|
||||
VSeparationOverride="5"
|
||||
HorizontalExpand="True">
|
||||
<ui:MenuButton
|
||||
Name="EscapeButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/Interface/hamburger.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static ic:EngineKeyFunctions.EscapeMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-escape-menu-button-tooltip'}"
|
||||
MinSize="70 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenRight}"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="GuidebookButton"
|
||||
@@ -29,7 +33,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/VerbIcons/information.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-guide-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenGuidebook}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -39,7 +43,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/character.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-character-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCharacterMenu}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -49,7 +53,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/emotes.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-emotes-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenEmotesMenu}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -59,7 +63,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/hammer.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCraftingMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-crafting-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -69,7 +73,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/fist.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenActionsMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-actions-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -79,7 +83,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/gavel.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAdminMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-admin-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -89,7 +93,7 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/sandbox.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenSandboxWindow}"
|
||||
ToolTip="{Loc 'game-hud-open-sandbox-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
@@ -99,8 +103,9 @@
|
||||
Icon="{xe:Tex '/Textures/Interface/info.svg.192dpi.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAHelp}"
|
||||
ToolTip="{Loc 'ui-options-function-open-a-help'}"
|
||||
MinSize="42 64"
|
||||
MinSize="48 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenLeft}"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
</GridContainer>
|
||||
</widgets:GameTopMenuBar>
|
||||
|
||||
@@ -307,12 +307,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
_entity.GetNetEntity(storageEnt),
|
||||
new ItemStorageLocation(DraggingRotation, position)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new StorageRemoveItemEvent(
|
||||
_entity.GetNetEntity(draggingGhost.Entity),
|
||||
_entity.GetNetEntity(storageEnt)));
|
||||
}
|
||||
|
||||
_menuDragHelper.EndDrag();
|
||||
_container?.BuildItemPieces();
|
||||
|
||||
@@ -206,8 +206,7 @@ namespace Content.Client.Wires.UI
|
||||
(_statusContainer = new GridContainer
|
||||
{
|
||||
Margin = new Thickness(8, 4),
|
||||
// TODO: automatically change columns count.
|
||||
Columns = 3
|
||||
Rows = 2
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -227,7 +226,8 @@ namespace Content.Client.Wires.UI
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
|
||||
});
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
SetSize = new Vector2(320, 200);
|
||||
SetHeight = 200;
|
||||
MinWidth = 320;
|
||||
}
|
||||
|
||||
|
||||
@@ -503,6 +503,8 @@ namespace Content.Client.Wires.UI
|
||||
|
||||
public StatusLight(StatusLightData data, IResourceCache resourceCache)
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Right;
|
||||
|
||||
var hsv = Color.ToHsv(data.Color);
|
||||
hsv.Z /= 2;
|
||||
var dimColor = Color.FromHsv(hsv);
|
||||
|
||||
@@ -32,9 +32,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
// No bans on record
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
|
||||
});
|
||||
|
||||
// Try to pardon a ban that does not exist
|
||||
@@ -43,9 +43,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
// Still no bans on record
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
|
||||
});
|
||||
|
||||
var banReason = "test";
|
||||
@@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
// Should have one ban on record now
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1));
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(5);
|
||||
@@ -70,13 +70,13 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2"));
|
||||
|
||||
// The existing ban is unaffected
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
|
||||
|
||||
var ban = await sDatabase.GetServerBanAsync(1);
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(ban, Is.Not.Null);
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1));
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||
|
||||
// Check that it matches
|
||||
Assert.That(ban.Id, Is.EqualTo(1));
|
||||
@@ -95,7 +95,7 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1"));
|
||||
|
||||
// No bans should be returned
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
||||
|
||||
// Direct id lookup returns a pardoned ban
|
||||
var pardonedBan = await sDatabase.GetServerBanAsync(1);
|
||||
@@ -105,7 +105,7 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
Assert.That(pardonedBan, Is.Not.Null);
|
||||
|
||||
// The list is still returned since that ignores pardons
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1));
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||
|
||||
Assert.That(pardonedBan.Id, Is.EqualTo(1));
|
||||
Assert.That(pardonedBan.UserId, Is.EqualTo(clientId));
|
||||
@@ -133,13 +133,13 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
// No bans should be returned
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null);
|
||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
||||
|
||||
// Direct id lookup returns a pardoned ban
|
||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
|
||||
|
||||
// The list is still returned since that ignores pardons
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1));
|
||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||
});
|
||||
|
||||
// Reconnect client. Slightly faster than dirtying the pair.
|
||||
|
||||
@@ -80,7 +80,8 @@ namespace Content.IntegrationTests.Tests
|
||||
"Reach",
|
||||
"Train",
|
||||
"Oasis",
|
||||
"Cog"
|
||||
"Cog",
|
||||
"Amber"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
|
||||
2072
Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs
generated
Normal file
2072
Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ModernHwid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "server_role_ban",
|
||||
type: "integer",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "server_ban",
|
||||
type: "integer",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "last_seen_hwid_type",
|
||||
table: "player",
|
||||
type: "integer",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "connection_log",
|
||||
type: "integer",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "server_role_ban");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "server_ban");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_seen_hwid_type",
|
||||
table: "player");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
2076
Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs
generated
Normal file
2076
Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ConnectionTrust : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "trust",
|
||||
table: "connection_log",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "trust",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,20 +512,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("assigned_user_id", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Blacklist",
|
||||
b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("PK_blacklist");
|
||||
|
||||
b.ToTable("blacklist", (string) null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -571,6 +557,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("ban_template", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("PK_blacklist");
|
||||
|
||||
b.ToTable("blacklist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -589,10 +588,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("denied");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<int>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
@@ -603,6 +598,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("time");
|
||||
|
||||
b.Property<float>("Trust")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("trust");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
@@ -718,10 +717,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("inet")
|
||||
.HasColumnName("last_seen_address");
|
||||
|
||||
b.Property<byte[]>("LastSeenHWId")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
b.Property<DateTime>("LastSeenTime")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_seen_time");
|
||||
@@ -1065,10 +1060,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("hidden");
|
||||
@@ -1199,10 +1190,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("hidden");
|
||||
@@ -1644,6 +1631,34 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_connection_log_server_server_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ConnectionLogId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("connection_log_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ConnectionLogId");
|
||||
|
||||
b1.ToTable("connection_log");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ConnectionLogId")
|
||||
.HasConstraintName("FK_connection_log_connection_log_connection_log_id");
|
||||
});
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
@@ -1659,6 +1674,37 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("PlayerId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("last_seen_hwid_type");
|
||||
|
||||
b1.HasKey("PlayerId");
|
||||
|
||||
b1.ToTable("player");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasConstraintName("FK_player_player_player_id");
|
||||
});
|
||||
|
||||
b.Navigation("LastSeenHWId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Profile", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Preference", "Preference")
|
||||
@@ -1753,8 +1799,36 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasForeignKey("RoundId")
|
||||
.HasConstraintName("FK_server_ban_round_round_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerBanId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("server_ban_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ServerBanId");
|
||||
|
||||
b1.ToTable("server_ban");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerBanId")
|
||||
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
|
||||
});
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("LastEditedBy");
|
||||
|
||||
b.Navigation("Round");
|
||||
@@ -1802,8 +1876,36 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasForeignKey("RoundId")
|
||||
.HasConstraintName("FK_server_role_ban_round_round_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerRoleBanId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("server_role_ban_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ServerRoleBanId");
|
||||
|
||||
b1.ToTable("server_role_ban");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerRoleBanId")
|
||||
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
|
||||
});
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("LastEditedBy");
|
||||
|
||||
b.Navigation("Round");
|
||||
|
||||
1995
Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs
generated
Normal file
1995
Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ModernHwid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "server_role_ban",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "server_ban",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "last_seen_hwid_type",
|
||||
table: "player",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "hwid_type",
|
||||
table: "connection_log",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "server_role_ban");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "server_ban");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_seen_hwid_type",
|
||||
table: "player");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hwid_type",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
1999
Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs
generated
Normal file
1999
Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ConnectionTrust : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "trust",
|
||||
table: "connection_log",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "trust",
|
||||
table: "connection_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,19 +483,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("assigned_user_id", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Blacklist",
|
||||
b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("PK_blacklist");
|
||||
|
||||
b.ToTable("blacklist", (string) null);
|
||||
});
|
||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -539,6 +526,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("ban_template", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("PK_blacklist");
|
||||
|
||||
b.ToTable("blacklist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -555,10 +555,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("denied");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<int>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
@@ -569,6 +565,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("time");
|
||||
|
||||
b.Property<float>("Trust")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("trust");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
@@ -675,10 +675,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_seen_address");
|
||||
|
||||
b.Property<byte[]>("LastSeenHWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
b.Property<DateTime>("LastSeenTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_seen_time");
|
||||
@@ -1003,10 +999,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("hidden");
|
||||
@@ -1131,10 +1123,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expiration_time");
|
||||
|
||||
b.Property<byte[]>("HWId")
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b.Property<bool>("Hidden")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("hidden");
|
||||
@@ -1566,6 +1554,34 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_connection_log_server_server_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ConnectionLogId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("connection_log_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ConnectionLogId");
|
||||
|
||||
b1.ToTable("connection_log");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ConnectionLogId")
|
||||
.HasConstraintName("FK_connection_log_connection_log_connection_log_id");
|
||||
});
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
@@ -1581,6 +1597,37 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("PlayerId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("last_seen_hwid_type");
|
||||
|
||||
b1.HasKey("PlayerId");
|
||||
|
||||
b1.ToTable("player");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasConstraintName("FK_player_player_player_id");
|
||||
});
|
||||
|
||||
b.Navigation("LastSeenHWId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Profile", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Preference", "Preference")
|
||||
@@ -1675,8 +1722,36 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasForeignKey("RoundId")
|
||||
.HasConstraintName("FK_server_ban_round_round_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerBanId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_ban_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ServerBanId");
|
||||
|
||||
b1.ToTable("server_ban");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerBanId")
|
||||
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
|
||||
});
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("LastEditedBy");
|
||||
|
||||
b.Navigation("Round");
|
||||
@@ -1724,8 +1799,36 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasForeignKey("RoundId")
|
||||
.HasConstraintName("FK_server_role_ban_round_round_id");
|
||||
|
||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerRoleBanId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_role_ban_id");
|
||||
|
||||
b1.Property<byte[]>("Hwid")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("hwid");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("hwid_type");
|
||||
|
||||
b1.HasKey("ServerRoleBanId");
|
||||
|
||||
b1.ToTable("server_role_ban");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerRoleBanId")
|
||||
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
|
||||
});
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("HWId");
|
||||
|
||||
b.Navigation("LastEditedBy");
|
||||
|
||||
b.Navigation("Round");
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
@@ -327,6 +329,47 @@ namespace Content.Server.Database
|
||||
.HasForeignKey(w => w.PlayerUserId)
|
||||
.HasPrincipalKey(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Changes for modern HWID integration
|
||||
modelBuilder.Entity<Player>()
|
||||
.OwnsOne(p => p.LastSeenHWId)
|
||||
.Property(p => p.Hwid)
|
||||
.HasColumnName("last_seen_hwid");
|
||||
|
||||
modelBuilder.Entity<Player>()
|
||||
.OwnsOne(p => p.LastSeenHWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
modelBuilder.Entity<ServerBan>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Hwid)
|
||||
.HasColumnName("hwid");
|
||||
|
||||
modelBuilder.Entity<ServerBan>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
modelBuilder.Entity<ServerRoleBan>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Hwid)
|
||||
.HasColumnName("hwid");
|
||||
|
||||
modelBuilder.Entity<ServerRoleBan>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
modelBuilder.Entity<ConnectionLog>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Hwid)
|
||||
.HasColumnName("hwid");
|
||||
|
||||
modelBuilder.Entity<ConnectionLog>()
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
}
|
||||
|
||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||
@@ -520,7 +563,7 @@ namespace Content.Server.Database
|
||||
public string LastSeenUserName { get; set; } = null!;
|
||||
public DateTime LastSeenTime { get; set; }
|
||||
public IPAddress LastSeenAddress { get; set; } = null!;
|
||||
public byte[]? LastSeenHWId { get; set; }
|
||||
public TypedHwid? LastSeenHWId { get; set; }
|
||||
|
||||
// Data that changes with each round
|
||||
public List<Round> Rounds { get; set; } = null!;
|
||||
@@ -669,7 +712,7 @@ namespace Content.Server.Database
|
||||
int Id { get; set; }
|
||||
Guid? PlayerUserId { get; set; }
|
||||
NpgsqlInet? Address { get; set; }
|
||||
byte[]? HWId { get; set; }
|
||||
TypedHwid? HWId { get; set; }
|
||||
DateTime BanTime { get; set; }
|
||||
DateTime? ExpirationTime { get; set; }
|
||||
string Reason { get; set; }
|
||||
@@ -754,7 +797,7 @@ namespace Content.Server.Database
|
||||
/// <summary>
|
||||
/// Hardware ID of the banned player.
|
||||
/// </summary>
|
||||
public byte[]? HWId { get; set; }
|
||||
public TypedHwid? HWId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time when the ban was applied by an administrator.
|
||||
@@ -892,7 +935,7 @@ namespace Content.Server.Database
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
public IPAddress Address { get; set; } = null!;
|
||||
public byte[]? HWId { get; set; }
|
||||
public TypedHwid? HWId { get; set; }
|
||||
|
||||
public ConnectionDenyReason? Denied { get; set; }
|
||||
|
||||
@@ -909,6 +952,8 @@ namespace Content.Server.Database
|
||||
|
||||
public List<ServerBanHit> BanHits { get; set; } = null!;
|
||||
public Server Server { get; set; } = null!;
|
||||
|
||||
public float Trust { get; set; }
|
||||
}
|
||||
|
||||
public enum ConnectionDenyReason : byte
|
||||
@@ -946,7 +991,7 @@ namespace Content.Server.Database
|
||||
public Guid? PlayerUserId { get; set; }
|
||||
[Required] public TimeSpan PlaytimeAtNote { get; set; }
|
||||
public NpgsqlInet? Address { get; set; }
|
||||
public byte[]? HWId { get; set; }
|
||||
public TypedHwid? HWId { get; set; }
|
||||
|
||||
public DateTime BanTime { get; set; }
|
||||
|
||||
@@ -1207,4 +1252,37 @@ namespace Content.Server.Database
|
||||
/// <seealso cref="ServerBan.Hidden"/>
|
||||
public bool Hidden { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A hardware ID value together with its <see cref="HwidType"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ImmutableTypedHwid"/>
|
||||
[Owned]
|
||||
public sealed class TypedHwid
|
||||
{
|
||||
public byte[] Hwid { get; set; } = default!;
|
||||
public HwidType Type { get; set; }
|
||||
|
||||
[return: NotNullIfNotNull(nameof(immutable))]
|
||||
public static implicit operator TypedHwid?(ImmutableTypedHwid? immutable)
|
||||
{
|
||||
if (immutable == null)
|
||||
return null;
|
||||
|
||||
return new TypedHwid
|
||||
{
|
||||
Hwid = immutable.Hwid.ToArray(),
|
||||
Type = immutable.Type,
|
||||
};
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(hwid))]
|
||||
public static implicit operator ImmutableTypedHwid?(TypedHwid? hwid)
|
||||
{
|
||||
if (hwid == null)
|
||||
return null;
|
||||
|
||||
return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
public class SnakeCaseConvention :
|
||||
public partial class SnakeCaseConvention :
|
||||
IEntityTypeAddedConvention,
|
||||
IEntityTypeAnnotationChangedConvention,
|
||||
IPropertyAddedConvention,
|
||||
@@ -99,22 +99,27 @@ namespace Content.Server.Database
|
||||
|
||||
public static string RewriteName(string name)
|
||||
{
|
||||
var regex = new Regex("[A-Z]+", RegexOptions.Compiled);
|
||||
return regex.Replace(
|
||||
name,
|
||||
(Match match) => {
|
||||
if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) {
|
||||
return match.Value;
|
||||
return UpperCaseLocator()
|
||||
.Replace(
|
||||
name,
|
||||
(Match match) => {
|
||||
if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) {
|
||||
return match.Value;
|
||||
}
|
||||
if (match.Value == "HWI")
|
||||
return (match.Index == 0 ? "" : "_") + "hwi";
|
||||
if (match.Index == 0)
|
||||
return match.Value.ToLower();
|
||||
if (match.Length > 1)
|
||||
return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}";
|
||||
|
||||
// Do not add a _ if there is already one before this. This happens with owned entities.
|
||||
if (name[match.Index - 1] == '_')
|
||||
return match.Value.ToLower();
|
||||
|
||||
return "_" + match.Value.ToLower();
|
||||
}
|
||||
if (match.Value == "HWI")
|
||||
return (match.Index == 0 ? "" : "_") + "hwi";
|
||||
if (match.Index == 0)
|
||||
return match.Value.ToLower();
|
||||
if (match.Length > 1)
|
||||
return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}";
|
||||
return "_" + match.Value.ToLower();
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
public virtual void ProcessEntityTypeAdded(
|
||||
@@ -332,5 +337,8 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("[A-Z]+", RegexOptions.Compiled)]
|
||||
private static partial Regex UpperCaseLocator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed class BanListEui : BaseEui
|
||||
|
||||
private async Task LoadBans(NetUserId userId)
|
||||
{
|
||||
foreach (var ban in await _db.GetServerBansAsync(null, userId, null))
|
||||
foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null))
|
||||
{
|
||||
SharedServerUnban? unban = null;
|
||||
if (ban.Unban is { } unbanDef)
|
||||
@@ -74,7 +74,7 @@ public sealed class BanListEui : BaseEui
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
hwid = ban.HWId?.ToString();
|
||||
}
|
||||
|
||||
Bans.Add(new SharedServerBan(
|
||||
@@ -95,7 +95,7 @@ public sealed class BanListEui : BaseEui
|
||||
|
||||
private async Task LoadRoleBans(NetUserId userId)
|
||||
{
|
||||
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null))
|
||||
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null))
|
||||
{
|
||||
SharedServerUnban? unban = null;
|
||||
if (ban.Unban is { } unbanDef)
|
||||
@@ -115,7 +115,7 @@ public sealed class BanListEui : BaseEui
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
hwid = ban.HWId?.ToString();
|
||||
}
|
||||
RoleBans.Add(new SharedServerRoleBan(
|
||||
ban.Id,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Content.Server.Administration.Managers;
|
||||
@@ -8,7 +7,6 @@ using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Administration;
|
||||
@@ -27,7 +25,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
private NetUserId? PlayerId { get; set; }
|
||||
private string PlayerName { get; set; } = string.Empty;
|
||||
private IPAddress? LastAddress { get; set; }
|
||||
private ImmutableArray<byte>? LastHwid { get; set; }
|
||||
private ImmutableTypedHwid? LastHwid { get; set; }
|
||||
private const int Ipv4_CIDR = 32;
|
||||
private const int Ipv6_CIDR = 64;
|
||||
|
||||
@@ -51,7 +49,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
switch (msg)
|
||||
{
|
||||
case BanPanelEuiStateMsg.CreateBanRequest r:
|
||||
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase);
|
||||
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid, r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase);
|
||||
break;
|
||||
case BanPanelEuiStateMsg.GetPlayerInfoRequest r:
|
||||
ChangePlayer(r.PlayerUsername);
|
||||
@@ -59,7 +57,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
}
|
||||
}
|
||||
|
||||
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray<byte>? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase)
|
||||
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableTypedHwid? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase)
|
||||
{
|
||||
if (!_admins.HasAdminFlag(Player, AdminFlags.Ban))
|
||||
{
|
||||
@@ -155,7 +153,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId);
|
||||
}
|
||||
|
||||
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableArray<byte>? lastHwid)
|
||||
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableTypedHwid? lastHwid)
|
||||
{
|
||||
PlayerId = playerId;
|
||||
PlayerName = playerName;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class BanListCommand : LocalizedCommands
|
||||
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastHWId, false);
|
||||
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
|
||||
|
||||
if (bans.Count == 0)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class RoleBanListCommand : IConsoleCommand
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
|
||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastHWId, includeUnbanned);
|
||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned);
|
||||
|
||||
if (bans.Count == 0)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
|
||||
var netChannel = player.Channel;
|
||||
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
|
||||
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false);
|
||||
var modernHwids = netChannel.UserData.ModernHWIds;
|
||||
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
|
||||
|
||||
var userRoleBans = new List<ServerRoleBanDef>();
|
||||
foreach (var ban in roleBans)
|
||||
@@ -132,7 +133,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
}
|
||||
|
||||
#region Server Bans
|
||||
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason)
|
||||
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
|
||||
{
|
||||
DateTimeOffset? expires = null;
|
||||
if (minutes > 0)
|
||||
@@ -166,9 +167,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
var addressRangeString = addressRange != null
|
||||
? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
|
||||
: "null";
|
||||
var hwidString = hwid != null
|
||||
? string.Concat(hwid.Value.Select(x => x.ToString("x2")))
|
||||
: "null";
|
||||
var hwidString = hwid?.ToString() ?? "null";
|
||||
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
|
||||
|
||||
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
|
||||
@@ -208,6 +207,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
UserId = player.UserId,
|
||||
Address = player.Channel.RemoteEndPoint.Address,
|
||||
HWId = player.Channel.UserData.HWId,
|
||||
ModernHWIds = player.Channel.UserData.ModernHWIds,
|
||||
// It's possible for the player to not have cached data loading yet due to coincidental timing.
|
||||
// If this is the case, we assume they have all flags to avoid false-positives.
|
||||
ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
|
||||
@@ -228,7 +228,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
#region Job Bans
|
||||
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
|
||||
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
|
||||
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
|
||||
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface IBanManager
|
||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
||||
/// <param name="severity">Severity of the resulting ban note</param>
|
||||
/// <param name="reason">Reason for the ban</param>
|
||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason);
|
||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason);
|
||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
|
||||
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId);
|
||||
|
||||
@@ -37,7 +37,7 @@ public interface IBanManager
|
||||
/// <param name="reason">Reason for the ban</param>
|
||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
||||
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
|
||||
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
|
||||
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
|
||||
|
||||
/// <summary>
|
||||
/// Pardons a role ban for the specified target, username or GUID
|
||||
|
||||
@@ -5,16 +5,42 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Connection;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Database;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration
|
||||
{
|
||||
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId, string Username);
|
||||
/// <summary>
|
||||
/// Contains data resolved via <see cref="IPlayerLocator"/>.
|
||||
/// </summary>
|
||||
/// <param name="UserId">The ID of the located user.</param>
|
||||
/// <param name="LastAddress">The last known IP address that the user connected with.</param>
|
||||
/// <param name="LastHWId">
|
||||
/// The last known HWID that the user connected with.
|
||||
/// This should be used for placing new records involving HWIDs, such as bans.
|
||||
/// For looking up data based on HWID, use combined <see cref="LastLegacyHWId"/> and <see cref="LastModernHWIds"/>.
|
||||
/// </param>
|
||||
/// <param name="Username">The last known username for the user connected with.</param>
|
||||
/// <param name="LastLegacyHWId">
|
||||
/// The last known legacy HWID value this user connected with. Only use for old lookups!
|
||||
/// </param>
|
||||
/// <param name="LastModernHWIds">
|
||||
/// The set of last known modern HWIDs the user connected with.
|
||||
/// </param>
|
||||
public sealed record LocatedPlayerData(
|
||||
NetUserId UserId,
|
||||
IPAddress? LastAddress,
|
||||
ImmutableTypedHwid? LastHWId,
|
||||
string Username,
|
||||
ImmutableArray<byte>? LastLegacyHWId,
|
||||
ImmutableArray<ImmutableArray<byte>> LastModernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Utilities for finding user IDs that extend to more than the server database.
|
||||
@@ -67,63 +93,42 @@ namespace Content.Server.Administration
|
||||
{
|
||||
// Check people currently on the server, the easiest case.
|
||||
if (_playerManager.TryGetSessionByUsername(playerName, out var session))
|
||||
{
|
||||
var userId = session.UserId;
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.HWId;
|
||||
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||
}
|
||||
return ReturnForSession(session);
|
||||
|
||||
// Check database for past players.
|
||||
var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
|
||||
if (record != null)
|
||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||
return ReturnForPlayerRecord(record);
|
||||
|
||||
// If all else fails, ask the auth server.
|
||||
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
||||
var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}";
|
||||
using var resp = await _httpClient.GetAsync(requestUri, cancel);
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
_sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
|
||||
|
||||
if (responseData == null)
|
||||
{
|
||||
_sawmill.Error("Auth server returned null response!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||
return await HandleAuthServerResponse(resp, cancel);
|
||||
}
|
||||
|
||||
public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
|
||||
{
|
||||
// Check people currently on the server, the easiest case.
|
||||
if (_playerManager.TryGetSessionById(userId, out var session))
|
||||
{
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.HWId;
|
||||
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||
}
|
||||
return ReturnForSession(session);
|
||||
|
||||
// Check database for past players.
|
||||
var record = await _db.GetPlayerRecordByUserId(userId, cancel);
|
||||
if (record != null)
|
||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||
return ReturnForPlayerRecord(record);
|
||||
|
||||
// If all else fails, ask the auth server.
|
||||
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
||||
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
|
||||
using var resp = await _httpClient.GetAsync(requestUri, cancel);
|
||||
|
||||
return await HandleAuthServerResponse(resp, cancel);
|
||||
}
|
||||
|
||||
private async Task<LocatedPlayerData?> HandleAuthServerResponse(HttpResponseMessage resp, CancellationToken cancel)
|
||||
{
|
||||
if (resp.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
@@ -134,14 +139,40 @@ namespace Content.Server.Administration
|
||||
}
|
||||
|
||||
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
|
||||
|
||||
if (responseData == null)
|
||||
{
|
||||
_sawmill.Error("Auth server returned null response!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName, null, []);
|
||||
}
|
||||
|
||||
private static LocatedPlayerData ReturnForSession(ICommonSession session)
|
||||
{
|
||||
var userId = session.UserId;
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.GetModernHwid();
|
||||
return new LocatedPlayerData(
|
||||
userId,
|
||||
address,
|
||||
hwId,
|
||||
session.Name,
|
||||
session.Channel.UserData.HWId,
|
||||
session.Channel.UserData.ModernHWIds);
|
||||
}
|
||||
|
||||
private static LocatedPlayerData ReturnForPlayerRecord(PlayerRecord record)
|
||||
{
|
||||
var hwid = record.HWId;
|
||||
|
||||
return new LocatedPlayerData(
|
||||
record.UserId,
|
||||
record.LastSeenAddress,
|
||||
hwid,
|
||||
record.LastSeenUserName,
|
||||
hwid is { Type: HwidType.Legacy } ? hwid.Hwid : null,
|
||||
hwid is { Type: HwidType.Modern } ? [hwid.Hwid] : []);
|
||||
}
|
||||
|
||||
public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
|
||||
|
||||
@@ -173,11 +173,11 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
{
|
||||
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
|
||||
// This won't get associated ip or hwid bans but they were not placed on this account anyways
|
||||
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null)).Count;
|
||||
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count;
|
||||
// Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
|
||||
// The only way to distinguish whether a role ban is the same is to compare the ban time.
|
||||
// This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
|
||||
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null)).DistinctBy(rb => rb.BanTime).Count();
|
||||
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
|
||||
// Check if the user has been banned
|
||||
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null);
|
||||
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null);
|
||||
if (ban != null)
|
||||
{
|
||||
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace Content.Server.Announcements
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = string.Join(' ', new ArraySegment<string>(args, 1, args.Length-1));
|
||||
// Explicit IEnumerable<string> due to overload ambiguity on .NET 9
|
||||
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
|
||||
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
|
||||
}
|
||||
shell.WriteLine("Sent!");
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class GasProducerAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -56,8 +57,11 @@ public sealed class GasProducerAnomalySystem : EntitySystem
|
||||
return;
|
||||
|
||||
var localpos = xform.Coordinates.Position;
|
||||
var tilerefs = grid.GetLocalTilesIntersecting(
|
||||
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
|
||||
var tilerefs = _map.GetLocalTilesIntersecting(
|
||||
xform.GridUid.Value,
|
||||
grid,
|
||||
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius)))
|
||||
.ToArray();
|
||||
|
||||
if (tilerefs.Length == 0)
|
||||
return;
|
||||
|
||||
@@ -48,7 +48,9 @@ public sealed partial class AtmosMonitorComponent : Component
|
||||
[DataField("gasThresholds")]
|
||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||
|
||||
// Stores a reference to the gas on the tile this is on.
|
||||
/// <summary>
|
||||
/// Stores a reference to the gas on the tile this entity is on (or the pipe network it monitors; see <see cref="MonitorsPipeNet"/>).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GasMixture? TileGas;
|
||||
|
||||
@@ -65,4 +67,19 @@ public sealed partial class AtmosMonitorComponent : Component
|
||||
/// </summary>
|
||||
[DataField("registeredDevices")]
|
||||
public HashSet<string> RegisteredDevices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether this device monitors its own internal pipe network rather than the surrounding atmosphere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If 'true', the entity will require a NodeContainerComponent with one or more PipeNodes to function.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool MonitorsPipeNet = false;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the pipe node that this device is monitoring.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string NodeNameMonitoredPipe = "monitored";
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -25,6 +28,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainerSystem = default!;
|
||||
|
||||
// Commands
|
||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||
@@ -56,8 +60,15 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
|
||||
private void OnAtmosDeviceEnterAtmosphere(EntityUid uid, AtmosMonitorComponent atmosMonitor, ref AtmosDeviceEnabledEvent args)
|
||||
{
|
||||
if (atmosMonitor.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, atmosMonitor.NodeNameMonitoredPipe, out var pipeNode))
|
||||
{
|
||||
atmosMonitor.TileGas = pipeNode.Air;
|
||||
return;
|
||||
}
|
||||
|
||||
atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, AtmosMonitorComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.TemperatureThresholdId != null)
|
||||
@@ -206,7 +217,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
if (args.Grid == null)
|
||||
if (args.Grid == null)
|
||||
return;
|
||||
|
||||
// if we're not monitoring atmos, don't bother
|
||||
@@ -215,6 +226,10 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
&& component.GasThresholds == null)
|
||||
return;
|
||||
|
||||
// If monitoring a pipe network, get its most recent gas mixture
|
||||
if (component.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, component.NodeNameMonitoredPipe, out var pipeNode))
|
||||
component.TileGas = pipeNode.Air;
|
||||
|
||||
UpdateState(uid, component.TileGas, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Globalization;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Inventory;
|
||||
@@ -14,6 +14,7 @@ using Content.Shared.Bed.Cryostorage;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.StationRecords;
|
||||
@@ -26,7 +27,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Content.Server.Bed.Cryostorage;
|
||||
|
||||
|
||||
@@ -424,7 +424,7 @@ public record struct PriceCalculationEvent()
|
||||
[ByRefEvent]
|
||||
public record struct EstimatedPriceCalculationEvent()
|
||||
{
|
||||
public EntityPrototype Prototype;
|
||||
public required EntityPrototype Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The total price of the entity.
|
||||
|
||||
@@ -118,11 +118,14 @@ namespace Content.Server.Connection
|
||||
|
||||
var serverId = (await _serverDbEntry.ServerEntity).Id;
|
||||
|
||||
var hwid = e.UserData.GetModernHwid();
|
||||
var trust = e.UserData.Trust;
|
||||
|
||||
if (deny != null)
|
||||
{
|
||||
var (reason, msg, banHits) = deny.Value;
|
||||
|
||||
var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, reason, serverId);
|
||||
var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, reason, serverId);
|
||||
if (banHits is { Count: > 0 })
|
||||
await _db.AddServerBanHitsAsync(id, banHits);
|
||||
|
||||
@@ -134,12 +137,12 @@ namespace Content.Server.Connection
|
||||
}
|
||||
else
|
||||
{
|
||||
await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, null, serverId);
|
||||
await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, null, serverId);
|
||||
|
||||
if (!ServerPreferencesManager.ShouldStorePrefs(e.AuthType))
|
||||
return;
|
||||
|
||||
await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, e.UserData.HWId);
|
||||
await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, hwid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +200,9 @@ namespace Content.Server.Connection
|
||||
hwId = null;
|
||||
}
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
|
||||
var modernHwid = e.UserData.ModernHWIds;
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
|
||||
if (bans.Count > 0)
|
||||
{
|
||||
var firstBan = bans[0];
|
||||
|
||||
24
Content.Server/Connection/UserDataExt.cs
Normal file
24
Content.Server/Connection/UserDataExt.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="NetUserData"/>.
|
||||
/// </summary>
|
||||
public static class UserDataExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the preferred HWID that should be used for new records related to a player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Players can have zero or more HWIDs, but for logging things like connection logs we generally
|
||||
/// only want a single one. This method returns a nullable method.
|
||||
/// </remarks>
|
||||
public static ImmutableTypedHwid? GetModernHwid(this NetUserData userData)
|
||||
{
|
||||
return userData.ModernHWIds.Length == 0
|
||||
? null
|
||||
: new ImmutableTypedHwid(userData.ModernHWIds[0], HwidType.Modern);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Server.IP;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
@@ -52,9 +53,28 @@ public static class BanMatcher
|
||||
return true;
|
||||
}
|
||||
|
||||
return player.HWId is { Length: > 0 } hwIdVar
|
||||
&& ban.HWId != null
|
||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Value.AsSpan());
|
||||
switch (ban.HWId?.Type)
|
||||
{
|
||||
case HwidType.Legacy:
|
||||
if (player.HWId is { Length: > 0 } hwIdVar
|
||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case HwidType.Modern:
|
||||
if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
|
||||
{
|
||||
foreach (var hwid in modernHwIdVar)
|
||||
{
|
||||
if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,10 +93,15 @@ public static class BanMatcher
|
||||
public IPAddress? Address;
|
||||
|
||||
/// <summary>
|
||||
/// The hardware ID of the player.
|
||||
/// The LEGACY hardware ID of the player. Corresponds with <see cref="NetUserData.HWId"/>.
|
||||
/// </summary>
|
||||
public ImmutableArray<byte>? HWId;
|
||||
|
||||
/// <summary>
|
||||
/// The modern hardware IDs of the player. Corresponds with <see cref="NetUserData.ModernHWIds"/>.
|
||||
/// </summary>
|
||||
public ImmutableArray<ImmutableArray<byte>>? ModernHWIds;
|
||||
|
||||
/// <summary>
|
||||
/// Exemption flags the player has been granted.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
@@ -121,7 +120,7 @@ public sealed record PlayerRecord(
|
||||
string LastSeenUserName,
|
||||
DateTimeOffset LastSeenTime,
|
||||
IPAddress LastSeenAddress,
|
||||
ImmutableArray<byte>? HWId);
|
||||
ImmutableTypedHwid? HWId);
|
||||
|
||||
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
@@ -13,7 +12,7 @@ namespace Content.Server.Database
|
||||
public int? Id { get; }
|
||||
public NetUserId? UserId { get; }
|
||||
public (IPAddress address, int cidrMask)? Address { get; }
|
||||
public ImmutableArray<byte>? HWId { get; }
|
||||
public ImmutableTypedHwid? HWId { get; }
|
||||
|
||||
public DateTimeOffset BanTime { get; }
|
||||
public DateTimeOffset? ExpirationTime { get; }
|
||||
@@ -28,7 +27,7 @@ namespace Content.Server.Database
|
||||
public ServerBanDef(int? id,
|
||||
NetUserId? userId,
|
||||
(IPAddress, int)? address,
|
||||
ImmutableArray<byte>? hwId,
|
||||
TypedHwid? hwId,
|
||||
DateTimeOffset banTime,
|
||||
DateTimeOffset? expirationTime,
|
||||
int? roundId,
|
||||
|
||||
@@ -396,12 +396,14 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
|
||||
public abstract Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId);
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an user's ban history.
|
||||
@@ -410,13 +412,15 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Include pardoned and expired bans.</param>
|
||||
/// <returns>The user's ban history.</returns>
|
||||
public abstract Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned);
|
||||
|
||||
public abstract Task AddServerBanAsync(ServerBanDef serverBan);
|
||||
@@ -507,11 +511,13 @@ namespace Content.Server.Database
|
||||
/// <param name="address">The IP address of the user.</param>
|
||||
/// <param name="userId">The NetUserId of the user.</param>
|
||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
||||
/// <returns>The user's role ban history.</returns>
|
||||
public abstract Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned);
|
||||
|
||||
public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
|
||||
@@ -601,7 +607,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId)
|
||||
ImmutableTypedHwid? hwId)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
@@ -618,7 +624,7 @@ namespace Content.Server.Database
|
||||
record.LastSeenTime = DateTime.UtcNow;
|
||||
record.LastSeenAddress = address;
|
||||
record.LastSeenUserName = userName;
|
||||
record.LastSeenHWId = hwId.ToArray();
|
||||
record.LastSeenHWId = hwId;
|
||||
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
@@ -664,7 +670,7 @@ namespace Content.Server.Database
|
||||
player.LastSeenUserName,
|
||||
new DateTimeOffset(NormalizeDatabaseTime(player.LastSeenTime)),
|
||||
player.LastSeenAddress,
|
||||
player.LastSeenHWId?.ToImmutableArray());
|
||||
player.LastSeenHWId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -673,11 +679,11 @@ namespace Content.Server.Database
|
||||
/*
|
||||
* CONNECTION LOG
|
||||
*/
|
||||
public abstract Task<int> AddConnectionLogAsync(
|
||||
NetUserId userId,
|
||||
public abstract Task<int> AddConnectionLogAsync(NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId);
|
||||
|
||||
|
||||
@@ -69,12 +69,14 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The hardware ID of the user.</param>
|
||||
/// <param name="hwId">The legacy HWID of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
|
||||
Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId);
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an user's ban history.
|
||||
@@ -82,13 +84,15 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
|
||||
/// <returns>The user's ban history.</returns>
|
||||
Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned=true);
|
||||
|
||||
Task AddServerBanAsync(ServerBanDef serverBan);
|
||||
@@ -137,12 +141,14 @@ namespace Content.Server.Database
|
||||
/// <param name="address">The IP address of the user.</param>
|
||||
/// <param name="userId">The NetUserId of the user.</param>
|
||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
||||
/// <returns>The user's role ban history.</returns>
|
||||
Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned = true);
|
||||
|
||||
Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan);
|
||||
@@ -180,7 +186,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId);
|
||||
ImmutableTypedHwid? hwId);
|
||||
Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default);
|
||||
Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default);
|
||||
#endregion
|
||||
@@ -191,7 +197,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId);
|
||||
|
||||
@@ -480,20 +487,22 @@ namespace Content.Server.Database
|
||||
public Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId));
|
||||
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds));
|
||||
}
|
||||
|
||||
public Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned=true)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, includeUnbanned));
|
||||
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
||||
}
|
||||
|
||||
public Task AddServerBanAsync(ServerBanDef serverBan)
|
||||
@@ -537,10 +546,11 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned = true)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, includeUnbanned));
|
||||
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
||||
}
|
||||
|
||||
public Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
|
||||
@@ -582,7 +592,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId)
|
||||
ImmutableTypedHwid? hwId)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.UpdatePlayerRecord(userId, userName, address, hwId));
|
||||
@@ -604,12 +614,13 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, denied, serverId));
|
||||
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
|
||||
}
|
||||
|
||||
public Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.IP;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
@@ -73,7 +74,8 @@ namespace Content.Server.Database
|
||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
{
|
||||
@@ -84,7 +86,7 @@ namespace Content.Server.Database
|
||||
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned: false, exempt, newPlayer)
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer)
|
||||
.OrderByDescending(b => b.BanTime);
|
||||
|
||||
var ban = await query.FirstOrDefaultAsync();
|
||||
@@ -94,7 +96,9 @@ namespace Content.Server.Database
|
||||
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId, bool includeUnbanned)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
{
|
||||
@@ -105,7 +109,7 @@ namespace Content.Server.Database
|
||||
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt, newPlayer);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer);
|
||||
|
||||
var queryBans = await query.ToArrayAsync();
|
||||
var bans = new List<ServerBanDef>(queryBans.Length);
|
||||
@@ -127,6 +131,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbGuardImpl db,
|
||||
bool includeUnbanned,
|
||||
ServerBanExemptFlags? exemptFlags,
|
||||
@@ -134,16 +139,11 @@ namespace Content.Server.Database
|
||||
{
|
||||
DebugTools.Assert(!(address == null && userId == null && hwId == null));
|
||||
|
||||
IQueryable<ServerBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = db.PgDbContext.Ban
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
var query = MakeBanLookupQualityShared<ServerBan, ServerUnban>(
|
||||
userId,
|
||||
hwId,
|
||||
modernHWIds,
|
||||
db.PgDbContext.Ban);
|
||||
|
||||
if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP))
|
||||
{
|
||||
@@ -156,15 +156,6 @@ namespace Content.Server.Database
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = db.PgDbContext.Ban
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
DebugTools.Assert(
|
||||
query != null,
|
||||
"At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
|
||||
@@ -186,6 +177,49 @@ namespace Content.Server.Database
|
||||
return query.Distinct();
|
||||
}
|
||||
|
||||
private static IQueryable<TBan>? MakeBanLookupQualityShared<TBan, TUnban>(
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbSet<TBan> set)
|
||||
where TBan : class, IBanCommon<TUnban>
|
||||
where TUnban : class, IUnbanCommon
|
||||
{
|
||||
IQueryable<TBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (modernHWIds != null)
|
||||
{
|
||||
foreach (var modernHwid in modernHWIds)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static ServerBanDef? ConvertBan(ServerBan? ban)
|
||||
{
|
||||
if (ban == null)
|
||||
@@ -211,7 +245,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
ban.BanTime,
|
||||
ban.ExpirationTime,
|
||||
ban.RoundId,
|
||||
@@ -249,7 +283,7 @@ namespace Content.Server.Database
|
||||
db.PgDbContext.Ban.Add(new ServerBan
|
||||
{
|
||||
Address = serverBan.Address.ToNpgsqlInet(),
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
@@ -297,6 +331,7 @@ namespace Content.Server.Database
|
||||
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
@@ -306,7 +341,7 @@ namespace Content.Server.Database
|
||||
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
var query = MakeRoleBanLookupQuery(address, userId, hwId, db, includeUnbanned)
|
||||
var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned)
|
||||
.OrderByDescending(b => b.BanTime);
|
||||
|
||||
return await QueryRoleBans(query);
|
||||
@@ -334,19 +369,15 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbGuardImpl db,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
IQueryable<ServerRoleBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = db.PgDbContext.RoleBan
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
var query = MakeBanLookupQualityShared<ServerRoleBan, ServerRoleUnban>(
|
||||
userId,
|
||||
hwId,
|
||||
modernHWIds,
|
||||
db.PgDbContext.RoleBan);
|
||||
|
||||
if (address != null)
|
||||
{
|
||||
@@ -357,15 +388,6 @@ namespace Content.Server.Database
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = db.PgDbContext.RoleBan
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (!includeUnbanned)
|
||||
{
|
||||
query = query?.Where(p =>
|
||||
@@ -402,7 +424,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
ban.BanTime,
|
||||
ban.ExpirationTime,
|
||||
ban.RoundId,
|
||||
@@ -440,7 +462,7 @@ namespace Content.Server.Database
|
||||
var ban = new ServerRoleBan
|
||||
{
|
||||
Address = serverRoleBan.Address.ToNpgsqlInet(),
|
||||
HWId = serverRoleBan.HWId?.ToArray(),
|
||||
HWId = serverRoleBan.HWId,
|
||||
Reason = serverRoleBan.Reason,
|
||||
Severity = serverRoleBan.Severity,
|
||||
BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
|
||||
@@ -476,7 +498,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
@@ -488,9 +511,10 @@ namespace Content.Server.Database
|
||||
Time = DateTime.UtcNow,
|
||||
UserId = userId.UserId,
|
||||
UserName = userName,
|
||||
HWId = hwId.ToArray(),
|
||||
HWId = hwId,
|
||||
Denied = denied,
|
||||
ServerId = serverId
|
||||
ServerId = serverId,
|
||||
Trust = trust,
|
||||
};
|
||||
|
||||
db.PgDbContext.ConnectionLog.Add(connectionLog);
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.IP;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
@@ -80,22 +81,24 @@ namespace Content.Server.Database
|
||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned: false)).FirstOrDefault();
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned)).ToList();
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList();
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ServerBanDef>> GetServerBanQueryAsync(
|
||||
@@ -103,6 +106,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
@@ -119,6 +123,7 @@ namespace Content.Server.Database
|
||||
UserId = userId,
|
||||
ExemptFlags = exempt ?? default,
|
||||
HWId = hwId,
|
||||
ModernHWIds = modernHWIds,
|
||||
IsNewPlayer = newPlayer,
|
||||
};
|
||||
|
||||
@@ -161,7 +166,7 @@ namespace Content.Server.Database
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
BanTime = serverBan.BanTime.UtcDateTime,
|
||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
@@ -205,6 +210,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
@@ -214,7 +220,7 @@ namespace Content.Server.Database
|
||||
var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned);
|
||||
|
||||
return queryBans
|
||||
.Where(b => RoleBanMatches(b, address, userId, hwId))
|
||||
.Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds))
|
||||
.Select(ConvertRoleBan)
|
||||
.ToList()!;
|
||||
}
|
||||
@@ -237,7 +243,8 @@ namespace Content.Server.Database
|
||||
ServerRoleBan ban,
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value))
|
||||
{
|
||||
@@ -249,7 +256,27 @@ namespace Content.Server.Database
|
||||
return true;
|
||||
}
|
||||
|
||||
return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId);
|
||||
switch (ban.HWId?.Type)
|
||||
{
|
||||
case HwidType.Legacy:
|
||||
if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case HwidType.Modern:
|
||||
if (modernHWIds != null)
|
||||
{
|
||||
foreach (var modernHWId in modernHWIds)
|
||||
{
|
||||
if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan)
|
||||
@@ -262,7 +289,7 @@ namespace Content.Server.Database
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
BanTime = serverBan.BanTime.UtcDateTime,
|
||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
@@ -316,7 +343,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
||||
@@ -376,7 +403,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
||||
@@ -412,7 +439,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
@@ -424,9 +452,10 @@ namespace Content.Server.Database
|
||||
Time = DateTime.UtcNow,
|
||||
UserId = userId.UserId,
|
||||
UserName = userName,
|
||||
HWId = hwId.ToArray(),
|
||||
HWId = hwId,
|
||||
Denied = denied,
|
||||
ServerId = serverId
|
||||
ServerId = serverId,
|
||||
Trust = trust,
|
||||
};
|
||||
|
||||
db.SqliteDbContext.ConnectionLog.Add(connectionLog);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
@@ -10,7 +9,7 @@ public sealed class ServerRoleBanDef
|
||||
public int? Id { get; }
|
||||
public NetUserId? UserId { get; }
|
||||
public (IPAddress address, int cidrMask)? Address { get; }
|
||||
public ImmutableArray<byte>? HWId { get; }
|
||||
public ImmutableTypedHwid? HWId { get; }
|
||||
|
||||
public DateTimeOffset BanTime { get; }
|
||||
public DateTimeOffset? ExpirationTime { get; }
|
||||
@@ -26,7 +25,7 @@ public sealed class ServerRoleBanDef
|
||||
int? id,
|
||||
NetUserId? userId,
|
||||
(IPAddress, int)? address,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
DateTimeOffset banTime,
|
||||
DateTimeOffset? expirationTime,
|
||||
int? roundId,
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
private EntityQuery<CarpRiftsConditionComponent> _objQuery;
|
||||
@@ -156,7 +157,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
}
|
||||
|
||||
// cant put a rift on solars
|
||||
foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform), RiftTileRadius), false))
|
||||
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(_transform.GetWorldPosition(xform), RiftTileRadius), false))
|
||||
{
|
||||
if (!tile.IsSpace(_tileDef))
|
||||
continue;
|
||||
|
||||
@@ -121,7 +121,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
activated.TimeLeft -= frameTime;
|
||||
if (activated.TimeLeft <= 0 || !IsPowered(uid, electrified, transform))
|
||||
{
|
||||
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, false);
|
||||
_appearance.SetData(uid, ElectrifiedVisuals.ShowSparks, false);
|
||||
RemComp<ActivatedElectrifiedComponent>(uid);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
return false;
|
||||
|
||||
EnsureComp<ActivatedElectrifiedComponent>(uid);
|
||||
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, true);
|
||||
_appearance.SetData(uid, ElectrifiedVisuals.ShowSparks, true);
|
||||
|
||||
siemens *= electrified.SiemensCoefficient;
|
||||
if (!DoCommonElectrocutionAttempt(targetUid, uid, ref siemens) || siemens <= 0)
|
||||
@@ -488,15 +488,4 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
}
|
||||
_audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
|
||||
}
|
||||
|
||||
public void SetElectrifiedWireCut(Entity<ElectrifiedComponent> ent, bool value)
|
||||
{
|
||||
if (ent.Comp.IsWireCut == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.IsWireCut = value;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +134,6 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
}
|
||||
drain.Accumulator -= drain.DrainFrequency;
|
||||
|
||||
// Disable ambient sound from emptying manually
|
||||
if (!drain.AutoDrain)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!managerQuery.TryGetComponent(uid, out var manager))
|
||||
continue;
|
||||
|
||||
@@ -160,41 +153,44 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
// This will ensure that UnitsPerSecond is per second...
|
||||
var amount = drain.UnitsPerSecond * drain.DrainFrequency;
|
||||
|
||||
_puddles.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
|
||||
|
||||
if (_puddles.Count == 0)
|
||||
if (drain.AutoDrain)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
}
|
||||
_puddles.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
|
||||
|
||||
_ambientSoundSystem.SetAmbience(uid, true);
|
||||
|
||||
amount /= _puddles.Count;
|
||||
|
||||
foreach (var puddle in _puddles)
|
||||
{
|
||||
// Queue the solution deletion if it's empty. EvaporationSystem might also do this
|
||||
// but queuedelete should be pretty safe.
|
||||
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
|
||||
if (_puddles.Count == 0)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(puddle);
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Removes the lowest of:
|
||||
// the drain component's units per second adjusted for # of puddles
|
||||
// the puddle's remaining volume (making it cleanly zero)
|
||||
// the drain's remaining volume in its buffer.
|
||||
var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
|
||||
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
|
||||
_ambientSoundSystem.SetAmbience(uid, true);
|
||||
|
||||
drainSolution.AddSolution(transferSolution, _prototypeManager);
|
||||
amount /= _puddles.Count;
|
||||
|
||||
if (puddleSolution.Volume <= 0)
|
||||
foreach (var puddle in _puddles)
|
||||
{
|
||||
QueueDel(puddle);
|
||||
// Queue the solution deletion if it's empty. EvaporationSystem might also do this
|
||||
// but queuedelete should be pretty safe.
|
||||
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(puddle);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Removes the lowest of:
|
||||
// the drain component's units per second adjusted for # of puddles
|
||||
// the puddle's remaining volume (making it cleanly zero)
|
||||
// the drain's remaining volume in its buffer.
|
||||
var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
|
||||
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
|
||||
|
||||
drainSolution.AddSolution(transferSolution, _prototypeManager);
|
||||
|
||||
if (puddleSolution.Volume <= 0)
|
||||
{
|
||||
QueueDel(puddle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ using Content.Server.Spawners.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
@@ -457,71 +457,4 @@ namespace Content.Server.GameTicking
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised broadcast before a player is spawned by the GameTicker.
|
||||
/// You can use this event to spawn a player off-station on late-join but also at round start.
|
||||
/// When this event is handled, the GameTicker will not perform its own player-spawning logic.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs
|
||||
{
|
||||
public ICommonSession Player { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public EntityUid Station { get; }
|
||||
|
||||
public PlayerBeforeSpawnEvent(ICommonSession player,
|
||||
HumanoidCharacterProfile profile,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
EntityUid station)
|
||||
{
|
||||
Player = player;
|
||||
Profile = profile;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Station = station;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised both directed and broadcast when a player has been spawned by the GameTicker.
|
||||
/// You can use this to handle people late-joining, or to handle people being spawned at round start.
|
||||
/// Can be used to give random players a role, modify their equipment, etc.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerSpawnCompleteEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Mob { get; }
|
||||
public ICommonSession Player { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public bool Silent { get; }
|
||||
public EntityUid Station { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
|
||||
// Ex. If this is the 27th person to join, this will be 27.
|
||||
public int JoinOrder { get; }
|
||||
|
||||
public PlayerSpawnCompleteEvent(EntityUid mob,
|
||||
ICommonSession player,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
bool silent,
|
||||
int joinOrder,
|
||||
EntityUid station,
|
||||
HumanoidCharacterProfile profile)
|
||||
{
|
||||
Mob = mob;
|
||||
Player = player;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Silent = silent;
|
||||
Station = station;
|
||||
Profile = profile;
|
||||
JoinOrder = joinOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Mind;
|
||||
using Content.Server.Points;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Points;
|
||||
using Content.Shared.Storage;
|
||||
|
||||
@@ -34,10 +34,11 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
[Dependency] private readonly GatewaySystem _gateway = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string PlanetNames = "names_borer";
|
||||
[ValidatePrototypeId<LocalizedDatasetPrototype>]
|
||||
private const string PlanetNames = "NamesBorer";
|
||||
|
||||
// TODO:
|
||||
// Fix shader some more
|
||||
@@ -102,7 +103,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
var mapId = _mapManager.CreateMap();
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index<DatasetPrototype>(PlanetNames), seed);
|
||||
var gatewayName = _salvage.GetFTLName(_protoManager.Index<LocalizedDatasetPrototype>(PlanetNames), seed);
|
||||
_metadata.SetEntityName(mapUid, gatewayName);
|
||||
|
||||
var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset));
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Console;
|
||||
using Content.Server.GameTicking;
|
||||
|
||||
namespace Content.Server.Ghost
|
||||
{
|
||||
@@ -23,6 +25,14 @@ namespace Content.Server.Ghost
|
||||
return;
|
||||
}
|
||||
|
||||
var gameTicker = _entities.System<GameTicker>();
|
||||
if (!gameTicker.PlayerGameStatuses.TryGetValue(player.UserId, out var playerStatus) ||
|
||||
playerStatus is not PlayerGameStatus.JoinedGame)
|
||||
{
|
||||
shell.WriteLine("ghost-command-error-lobby");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is { Valid: true } frozen &&
|
||||
_entities.HasComponent<AdminFrozenComponent>(frozen))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
@@ -32,14 +34,13 @@ using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Server.Ghost
|
||||
{
|
||||
public sealed class GhostSystem : SharedGhostSystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly FollowerSystem _followerSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -323,6 +324,8 @@ namespace Content.Server.Ghost
|
||||
|
||||
private void WarpTo(EntityUid uid, EntityUid target)
|
||||
{
|
||||
_adminLog.Add(LogType.GhostWarp, $"{ToPrettyString(uid)} ghost warped to {ToPrettyString(target)}");
|
||||
|
||||
if ((TryComp(target, out WarpPointComponent? warp) && warp.Follow) || HasComp<MobStateComponent>(target))
|
||||
{
|
||||
_followerSystem.StartFollowingEntity(uid, target);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Ghost.Roles.Raffles;
|
||||
@@ -77,13 +77,13 @@ namespace Content.Server.Ghost.Roles
|
||||
|
||||
if (isProto)
|
||||
{
|
||||
if (!_protoManager.TryIndex<GhostRoleRaffleSettingsPrototype>(args[4], out var proto))
|
||||
if (!_protoManager.TryIndex<GhostRoleRaffleSettingsPrototype>(args[3], out var proto))
|
||||
{
|
||||
var validProtos = string.Join(", ",
|
||||
_protoManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>().Select(p => p.ID)
|
||||
);
|
||||
|
||||
shell.WriteLine($"{args[4]} is not a valid raffle settings prototype. Valid options: {validProtos}");
|
||||
shell.WriteLine($"{args[3]} is not a valid raffle settings prototype. Valid options: {validProtos}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
var coords = Transform(args.User).Coordinates;
|
||||
var handsEnt = Spawn(component.SelectedEntity, coords);
|
||||
_adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(args.User)} used {ToPrettyString(uid)} which spawned {ToPrettyString(handsEnt)}");
|
||||
EnsureComp<ItemComponent>(handsEnt); // For insane mode.
|
||||
if (component.Wrapper is not null)
|
||||
Spawn(component.Wrapper, coords);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Rounding;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -46,7 +45,6 @@ namespace Content.Server.Light.EntitySystems
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
|
||||
|
||||
@@ -179,25 +177,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToggleLightVerb(Entity<HandheldLightComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !ent.Comp.ToggleOnInteract)
|
||||
return;
|
||||
|
||||
var @event = args;
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("verb-common-toggle-light"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
|
||||
Act = ent.Comp.Activated
|
||||
? () => TurnOff(ent)
|
||||
: () => TurnOn(@event.User, ent)
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public bool TurnOff(Entity<HandheldLightComponent> ent, bool makeNoise = true)
|
||||
public override bool TurnOff(Entity<HandheldLightComponent> ent, bool makeNoise = true)
|
||||
{
|
||||
if (!ent.Comp.Activated || !_lights.TryGetLight(ent, out var pointLightComponent))
|
||||
{
|
||||
@@ -211,7 +191,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TurnOn(EntityUid user, Entity<HandheldLightComponent> uid)
|
||||
public override bool TurnOn(EntityUid user, Entity<HandheldLightComponent> uid)
|
||||
{
|
||||
var component = uid.Comp;
|
||||
if (component.Activated || !_lights.TryGetLight(uid, out var pointLightComponent))
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Medical.CrewMonitoring;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -14,8 +13,10 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -383,7 +384,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
|
||||
// Get mob total damage crit threshold
|
||||
int? totalDamageThreshold = null;
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, Shared.Mobs.MobState.Critical, out var critThreshold))
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
|
||||
totalDamageThreshold = critThreshold.Value.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
|
||||
@@ -11,8 +11,8 @@ public sealed partial class PathfindingSystem
|
||||
/// </summary>
|
||||
public record struct BreadthPathArgs()
|
||||
{
|
||||
public Vector2i Start;
|
||||
public List<Vector2i> Ends;
|
||||
public required Vector2i Start;
|
||||
public required List<Vector2i> Ends;
|
||||
|
||||
public bool Diagonals = false;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user