mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Merge pull request #1278 from NullWanderer/2024/06/01-upstream-merge
2024/06/01 upstream merge
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
|
||||
/// subscriptions
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
|
||||
[CategoriesColumn]
|
||||
public class ComponentQueryBenchmark
|
||||
{
|
||||
public const string Map = "Maps/atlas.yml";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private EntityQuery<ItemComponent> _itemQuery;
|
||||
private EntityQuery<ClothingComponent> _clothingQuery;
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityUid[] _items = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
|
||||
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
|
||||
_mapQuery = _entMan.GetEntityQuery<MapComponent>();
|
||||
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
_items = new EntityUid[_entMan.Count<ItemComponent>()];
|
||||
var i = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out _))
|
||||
{
|
||||
_items[i++] = uid;
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task Cleanup()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
#region TryComp
|
||||
|
||||
/// <summary>
|
||||
/// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("TryComp")]
|
||||
public int TryComp()
|
||||
{
|
||||
var hashCode = 0;
|
||||
foreach (var uid in _items)
|
||||
{
|
||||
if (_clothingQuery.TryGetComponent(uid, out var clothing))
|
||||
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TryComp"/> that is meant to always fail to get a component.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("TryComp")]
|
||||
public int TryCompFail()
|
||||
{
|
||||
var hashCode = 0;
|
||||
foreach (var uid in _items)
|
||||
{
|
||||
if (_mapQuery.TryGetComponent(uid, out var map))
|
||||
hashCode = HashCode.Combine(hashCode, map.GetHashCode());
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TryComp"/> that is meant to always succeed getting a component.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("TryComp")]
|
||||
public int TryCompSucceed()
|
||||
{
|
||||
var hashCode = 0;
|
||||
foreach (var uid in _items)
|
||||
{
|
||||
if (_itemQuery.TryGetComponent(uid, out var item))
|
||||
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TryComp"/> that uses `Resolve()` to try get the component.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("TryComp")]
|
||||
public int Resolve()
|
||||
{
|
||||
var hashCode = 0;
|
||||
foreach (var uid in _items)
|
||||
{
|
||||
DoResolve(uid, ref hashCode);
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
|
||||
{
|
||||
if (_clothingQuery.Resolve(uid, ref clothing, false))
|
||||
hash = HashCode.Combine(hash, clothing.GetHashCode());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enumeration
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Item Enumerator")]
|
||||
public int SingleItemEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var item))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Item Enumerator")]
|
||||
public int DoubleItemEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent>();
|
||||
while (enumerator.MoveNext(out _, out var item))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Item Enumerator")]
|
||||
public int TripleItemEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out _, out _, out var xform))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Airlock Enumerator")]
|
||||
public int SingleAirlockEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent>();
|
||||
while (enumerator.MoveNext(out var airlock))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Airlock Enumerator")]
|
||||
public int DoubleAirlockEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent>();
|
||||
while (enumerator.MoveNext(out _, out var door))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, door.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Airlock Enumerator")]
|
||||
public int TripleAirlockEnumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out _, out _, out var xform))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("Events")]
|
||||
public int StructEvents()
|
||||
{
|
||||
var ev = new QueryBenchEvent();
|
||||
foreach (var uid in _items)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
return ev.HashCode;
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public struct QueryBenchEvent
|
||||
{
|
||||
public int HashCode;
|
||||
}
|
||||
|
||||
public sealed class QueryBenchSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ClothingComponent, QueryBenchEvent>(OnEvent);
|
||||
}
|
||||
|
||||
private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
|
||||
{
|
||||
args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
[Virtual]
|
||||
public class EntityQueryBenchmark
|
||||
{
|
||||
public const string Map = "Maps/atlas.yml";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new MapId(10);
|
||||
private EntityQuery<ClothingComponent> _clothingQuery;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(null);
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
|
||||
|
||||
// Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
|
||||
/*
|
||||
var entCount = _entMan.EntityCount;
|
||||
var itemCount = _entMan.Count<ItemComponent>();
|
||||
var clothingCount = _entMan.Count<ClothingComponent>();
|
||||
var itemRatio = (float) itemCount / entCount;
|
||||
var clothingRatio = (float) clothingCount / entCount;
|
||||
Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
|
||||
*/
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task Cleanup()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int HasComponent()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (_entMan.HasComponent<ClothingComponent>(uid))
|
||||
hashCode = HashCode.Combine(hashCode, uid.Id);
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int HasComponentQuery()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (_clothingQuery.HasComponent(uid))
|
||||
hashCode = HashCode.Combine(hashCode, uid.Id);
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int TryGetComponent()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
|
||||
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int TryGetComponentQuery()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (_clothingQuery.TryGetComponent(uid, out var clothing))
|
||||
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all entities with both an item and clothing component.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public int Enumerator()
|
||||
{
|
||||
var hashCode = 0;
|
||||
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent, ClothingComponent>();
|
||||
while (enumerator.MoveNext(out var _, out var clothing))
|
||||
{
|
||||
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class MapLoadBenchmark
|
||||
public void Setup()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(null);
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
var server = _pair.Server;
|
||||
|
||||
@@ -49,7 +49,7 @@ public class PvsBenchmark
|
||||
#if !DEBUG
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
#endif
|
||||
PoolManager.Startup(null);
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
@@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
public async Task SetupAsync()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(null);
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Bwoink.ChannelSelector.OnSelectionChanged += sel =>
|
||||
{
|
||||
if (sel is not null)
|
||||
if (sel is null)
|
||||
{
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
Title = Loc.GetString("bwoink-none-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sel.OverallPlaytime != null)
|
||||
{
|
||||
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
|
||||
}
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
|
||||
if (sel.OverallPlaytime != null)
|
||||
{
|
||||
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Content.Client.Administration.UI.CustomControls
|
||||
private List<PlayerInfo> _playerList = new();
|
||||
private readonly List<PlayerInfo> _sortedPlayerList = new();
|
||||
|
||||
public event Action<PlayerInfo>? OnSelectionChanged;
|
||||
public event Action<PlayerInfo?>? OnSelectionChanged;
|
||||
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
|
||||
|
||||
public Func<PlayerInfo, string, string>? OverrideText;
|
||||
@@ -41,12 +41,19 @@ namespace Content.Client.Administration.UI.CustomControls
|
||||
PlayerListContainer.ItemPressed += PlayerListItemPressed;
|
||||
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
|
||||
PlayerListContainer.GenerateItem += GenerateButton;
|
||||
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
|
||||
PopulateList(_adminSystem.PlayerList);
|
||||
FilterLineEdit.OnTextChanged += _ => FilterList();
|
||||
_adminSystem.PlayerListChanged += PopulateList;
|
||||
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
|
||||
}
|
||||
|
||||
private void PlayerListNoItemSelected()
|
||||
{
|
||||
_selectedPlayer = null;
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData {Info: var selectedPlayer})
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:pt="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="PlayerCount" HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
|
||||
Text="{Loc Player Count}" />
|
||||
<Button Name="ShowDisconnectedButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
|
||||
Text="{Loc player-tab-show-disconnected}" ToggleMode="True"/>
|
||||
<Button Name="OverlayButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
|
||||
Text="{Loc player-tab-overlay}" ToggleMode="True"/>
|
||||
<Label Name="PlayerCount" HorizontalExpand="True" Text="{Loc Player Count}" />
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
|
||||
PlaceHolder="{Loc player-tab-filter-line-edit-placeholder}" />
|
||||
<Button Name="ShowDisconnectedButton" HorizontalExpand="True"
|
||||
Text="{Loc player-tab-show-disconnected}" ToggleMode="True" />
|
||||
<Button Name="OverlayButton" HorizontalExpand="True" Text="{Loc player-tab-overlay}" ToggleMode="True" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 5" />
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Name="PlayerList">
|
||||
<pt:PlayerTabHeader Name="ListHeader" />
|
||||
<cc:HSeparator />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<Control MinSize="0 5"/>
|
||||
<pt:PlayerTabHeader Name="ListHeader"/>
|
||||
<cc:HSeparator/>
|
||||
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -28,15 +29,14 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
public event Action<PlayerTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
public PlayerTab()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
RobustXamlLoader.Load(this);
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
_adminSystem.PlayerListChanged += RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
@@ -46,8 +46,17 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
|
||||
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
SearchList.GenerateItem += GenerateButton;
|
||||
SearchList.DataFilterCondition += DataFilterCondition;
|
||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
#region Antag Overlay
|
||||
|
||||
private void OverlayEnabled()
|
||||
{
|
||||
OverlayButton.Pressed = true;
|
||||
@@ -70,6 +79,8 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void ShowDisconnectedPressed(ButtonEventArgs args)
|
||||
{
|
||||
_showDisconnected = args.Button.Pressed;
|
||||
@@ -92,14 +103,10 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
}
|
||||
}
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
foreach (var child in PlayerList.Children.ToArray())
|
||||
{
|
||||
if (child is PlayerTabEntry)
|
||||
child.Dispose();
|
||||
}
|
||||
|
||||
_players = players;
|
||||
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
|
||||
|
||||
@@ -108,29 +115,66 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
|
||||
UpdateHeaderSymbols();
|
||||
|
||||
var useAltColor = false;
|
||||
foreach (var player in sortedPlayers)
|
||||
{
|
||||
if (!_showDisconnected && !player.Connected)
|
||||
continue;
|
||||
|
||||
var entry = new PlayerTabEntry(player.Username,
|
||||
player.CharacterName,
|
||||
player.IdentityName,
|
||||
player.StartingJob,
|
||||
player.Antag ? "YES" : "NO",
|
||||
new StyleBoxFlat(useAltColor ? _altColor : _defaultColor),
|
||||
player.Connected,
|
||||
player.PlaytimeString);
|
||||
entry.PlayerEntity = player.NetEntity;
|
||||
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(entry, args);
|
||||
entry.ToolTip = Loc.GetString("player-tab-entry-tooltip");
|
||||
PlayerList.AddChild(entry);
|
||||
|
||||
useAltColor ^= true;
|
||||
}
|
||||
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
|
||||
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
|
||||
button.AddChild(entry);
|
||||
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
|
||||
/// If all characters are lowercase, the comparison ignores case.
|
||||
/// If there is an uppercase character, the comparison is case sensitive.
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="listData"></param>
|
||||
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
{
|
||||
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
|
||||
return false;
|
||||
|
||||
if (!_showDisconnected && !info.Connected)
|
||||
return false;
|
||||
|
||||
if (IsAllLower(filter))
|
||||
{
|
||||
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!playerString.Contains(filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAllLower(string input)
|
||||
{
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (char.IsLetter(c) && !char.IsLower(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Header
|
||||
|
||||
private void UpdateHeaderSymbols()
|
||||
{
|
||||
ListHeader.ResetHeaderText();
|
||||
@@ -174,5 +218,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ContainerButton xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer Name="BackgroundColorPanel"/>
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Name="BackgroundColorPanel">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
@@ -15,17 +15,18 @@
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="JobLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="OverallPlaytimeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</ContainerButton>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -6,23 +7,24 @@ using Robust.Client.UserInterface.XAML;
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTabEntry : ContainerButton
|
||||
public sealed partial class PlayerTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity? PlayerEntity;
|
||||
|
||||
public PlayerTabEntry(string username, string character, string identity, string job, string antagonist, StyleBox styleBox, bool connected, string overallPlaytime)
|
||||
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
UsernameLabel.Text = username;
|
||||
if (!connected)
|
||||
UsernameLabel.Text = player.Username;
|
||||
if (!player.Connected)
|
||||
UsernameLabel.StyleClasses.Add("Disabled");
|
||||
JobLabel.Text = job;
|
||||
CharacterLabel.Text = character;
|
||||
if (identity != character)
|
||||
CharacterLabel.Text += $" [{identity}]";
|
||||
AntagonistLabel.Text = antagonist;
|
||||
BackgroundColorPanel.PanelOverride = styleBox;
|
||||
OverallPlaytimeLabel.Text = overallPlaytime;
|
||||
JobLabel.Text = player.StartingJob;
|
||||
CharacterLabel.Text = player.CharacterName;
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,23 +19,25 @@
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="JobLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-job}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-antagonist}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="PlaytimeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-playtime}"
|
||||
MouseFilter="Pass"/>
|
||||
MouseFilter="Pass"
|
||||
ToolTip="{Loc player-tab-entry-tooltip}"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
ClearAlerts?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void AlertClicked(AlertType alertType)
|
||||
public void AlertClicked(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
RaiseNetworkEvent(new ClickAlertEvent(alertType));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
@@ -36,28 +37,38 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not GasTileOverlayState state)
|
||||
return;
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
// is this a delta or full state?
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in state.Chunks)
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Shared.Cabinet;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Cabinet;
|
||||
|
||||
public sealed class ItemCabinetSystem : SharedItemCabinetSystem
|
||||
{
|
||||
protected override void UpdateAppearance(EntityUid uid, ItemCabinetComponent? cabinet = null)
|
||||
{
|
||||
if (!Resolve(uid, ref cabinet))
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
var state = cabinet.Opened ? cabinet.OpenState : cabinet.ClosedState;
|
||||
if (state != null)
|
||||
sprite.LayerSetState(ItemCabinetVisualLayers.Door, state);
|
||||
sprite.LayerSetVisible(ItemCabinetVisualLayers.ContainsItem, cabinet.CabinetSlot.HasItem);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ItemCabinetVisualLayers
|
||||
{
|
||||
Door,
|
||||
ContainsItem
|
||||
}
|
||||
@@ -56,34 +56,43 @@ namespace Content.Client.Decals
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DecalGridState state)
|
||||
return;
|
||||
|
||||
// is this a delta or full state?
|
||||
_removedChunks.Clear();
|
||||
Dictionary<Vector2i, DecalChunk> modifiedChunks;
|
||||
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
case DecalGridDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(key))
|
||||
_removedChunks.Add(key);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(key))
|
||||
_removedChunks.Add(key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
case DecalGridState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(key))
|
||||
_removedChunks.Add(key);
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(key))
|
||||
_removedChunks.Add(key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (_removedChunks.Count > 0)
|
||||
RemoveChunks(gridUid, gridComp, _removedChunks);
|
||||
|
||||
if (state.Chunks.Count > 0)
|
||||
UpdateChunks(gridUid, gridComp, state.Chunks);
|
||||
if (modifiedChunks.Count > 0)
|
||||
UpdateChunks(gridUid, gridComp, modifiedChunks);
|
||||
}
|
||||
|
||||
private void OnChunkUpdate(DecalChunkUpdateEvent ev)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
|
||||
public sealed class FirelockSystem : EntitySystem
|
||||
public sealed class FirelockSystem : SharedFirelockSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ namespace Content.Client.Examine
|
||||
|
||||
if (knowTarget)
|
||||
{
|
||||
var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player));
|
||||
var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]");
|
||||
var itemName = FormattedMessage.EscapeText(Identity.Name(target, EntityManager, player));
|
||||
var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]");
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(labelMessage);
|
||||
hBox.AddChild(label);
|
||||
@@ -248,7 +248,7 @@ namespace Content.Client.Examine
|
||||
else
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(FormattedMessage.FromMarkup("[bold]???[/bold]"));
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow("[bold]???[/bold]"));
|
||||
hBox.AddChild(label);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Content.Client.FlavorText
|
||||
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
CFlavorTextInput.Placeholder = new Rope.Leaf(loc.GetString("flavor-text-placeholder"));
|
||||
CFlavorTextInput.OnKeyBindDown += _ => FlavorTextChanged();
|
||||
CFlavorTextInput.OnTextChanged += _ => FlavorTextChanged();
|
||||
}
|
||||
|
||||
public void FlavorTextChanged()
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed partial class GravitySystem
|
||||
{
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (!TryComp<TransformComponent>(localPlayer, out var xform) ||
|
||||
if (!TryComp(localPlayer, out TransformComponent? xform) ||
|
||||
xform.GridUid != uid && xform.MapUid != uid)
|
||||
{
|
||||
return;
|
||||
@@ -46,7 +46,7 @@ public sealed partial class GravitySystem
|
||||
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (!TryComp<TransformComponent>(localPlayer, out var xform))
|
||||
if (!TryComp(localPlayer, out TransformComponent? xform))
|
||||
return;
|
||||
|
||||
if (xform.GridUid != uid ||
|
||||
|
||||
@@ -47,6 +47,17 @@
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="PlantMetabolismsContainer" HorizontalExpand="True">
|
||||
<Collapsible>
|
||||
<CollapsibleHeading Title="{Loc 'guidebook-reagent-plant-metabolisms-header'}"/>
|
||||
<CollapsibleBody>
|
||||
<BoxContainer Name="PlantMetabolismsDescriptionContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="10 0 10 0"
|
||||
HorizontalExpand="True"/>
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
<BoxContainer Margin="10 5 10 10" HorizontalExpand="True">
|
||||
<!-- The troublemaker !-->
|
||||
<RichTextLabel Name="ReagentDescription" HorizontalAlignment="Left"/>
|
||||
|
||||
@@ -157,6 +157,39 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PlantMetabolisms
|
||||
if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistryPlant) &&
|
||||
guideEntryRegistryPlant.PlantMetabolisms != null &&
|
||||
guideEntryRegistryPlant.PlantMetabolisms.Count > 0)
|
||||
{
|
||||
PlantMetabolismsDescriptionContainer.Children.Clear();
|
||||
var metabolismLabel = new RichTextLabel();
|
||||
metabolismLabel.SetMarkup(Loc.GetString("guidebook-reagent-plant-metabolisms-rate"));
|
||||
var descriptionLabel = new RichTextLabel
|
||||
{
|
||||
Margin = new Thickness(25, 0, 10, 0)
|
||||
};
|
||||
var descMsg = new FormattedMessage();
|
||||
var descriptionsCount = guideEntryRegistryPlant.PlantMetabolisms.Count;
|
||||
var i = 0;
|
||||
foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms)
|
||||
{
|
||||
descMsg.AddMarkup(effectString);
|
||||
i++;
|
||||
if (i < descriptionsCount)
|
||||
descMsg.PushNewline();
|
||||
}
|
||||
descriptionLabel.SetMessage(descMsg);
|
||||
|
||||
PlantMetabolismsDescriptionContainer.AddChild(metabolismLabel);
|
||||
PlantMetabolismsDescriptionContainer.AddChild(descriptionLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlantMetabolismsContainer.Visible = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
GenerateSources(reagent);
|
||||
|
||||
FormattedMessage description = new();
|
||||
|
||||
@@ -148,7 +148,7 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
|
||||
public void FakeClientActivateInWorld(EntityUid activated)
|
||||
{
|
||||
var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated);
|
||||
var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated, true);
|
||||
RaiseLocalEvent(activated, activateMsg);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,8 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case InstrumentBandResponseBuiMessage bandRx:
|
||||
_bandMenu?.Populate(bandRx.Nearby, EntMan);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (message is InstrumentBandResponseBuiMessage bandRx)
|
||||
_bandMenu?.Populate(bandRx.Nearby, EntMan);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
|
||||
@@ -4,24 +4,6 @@ using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Client.Interactable
|
||||
{
|
||||
public sealed class InteractionSystem : SharedInteractionSystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!EntityManager.EntityExists(target))
|
||||
return false;
|
||||
|
||||
if (!_container.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
if (!HasComp<StorageComponent>(container.Owner))
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
// Need to return if UI is open or not
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO Remove Shared prefix
|
||||
public sealed class InteractionSystem : SharedInteractionSystem;
|
||||
}
|
||||
|
||||
@@ -73,11 +73,6 @@ public sealed class DragDropHelper<T>
|
||||
_cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true);
|
||||
}
|
||||
|
||||
~DragDropHelper()
|
||||
{
|
||||
_cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell the helper that the mouse button was pressed down on
|
||||
/// a target, thus a drag has the possibility to begin for this target.
|
||||
|
||||
@@ -21,7 +21,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Client.Inventory.ClientInventorySystem;
|
||||
using static Robust.Client.UserInterface.Control;
|
||||
|
||||
@@ -53,9 +52,13 @@ namespace Content.Client.Inventory
|
||||
_inv = EntMan.System<InventorySystem>();
|
||||
_cuffable = EntMan.System<SharedCuffableSystem>();
|
||||
|
||||
// TODO update name when identity changes
|
||||
var title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
|
||||
_strippingMenu = new StrippingMenu(title, this);
|
||||
_strippingMenu.OnClose += Close;
|
||||
|
||||
// TODO use global entity
|
||||
// BUIs are opened and closed while applying comp sates, so spawning entities here is probably not the best idea.
|
||||
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lobby;
|
||||
@@ -70,12 +69,9 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_profileEditor?.RefreshFlavorText();
|
||||
});
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, args =>
|
||||
{
|
||||
_profileEditor?.RefreshAntags();
|
||||
_profileEditor?.RefreshJobs();
|
||||
_profileEditor?.RefreshLoadouts();
|
||||
});
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
|
||||
}
|
||||
|
||||
private LobbyCharacterPreviewPanel? GetLobbyPreview()
|
||||
@@ -193,6 +189,13 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
PreviewPanel.SetSummaryText(humanoid.Summary);
|
||||
}
|
||||
|
||||
private void RefreshProfileEditor()
|
||||
{
|
||||
_profileEditor?.RefreshAntags();
|
||||
_profileEditor?.RefreshJobs();
|
||||
_profileEditor?.RefreshLoadouts();
|
||||
}
|
||||
|
||||
private void SaveProfile()
|
||||
{
|
||||
DebugTools.Assert(EditedProfile != null);
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'observe-warning-1'}"/>
|
||||
<Label Text="{Loc 'observe-warning-2'}"/>
|
||||
<BoxContainer Orientation="Horizontal" >
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="NevermindButton" Text="{Loc 'observe-nevermind'}" SizeFlagsStretchRatio="1"/>
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<cc:CommandButton Command="observe" Name="ObserveButton" StyleClasses="Caution" Text="{Loc 'observe-confirm'}" SizeFlagsStretchRatio="1"/>
|
||||
<cc:CommandButton Command="observe" Name="ObserveButton" StyleClasses="Caution" Text="{Loc 'observe-confirm'}" SizeFlagsStretchRatio="1"/>
|
||||
<cc:CommandButton Command="observe admin" Name="ObserveAsAdminButton" Text="{Loc 'observe-as-admin'}" SizeFlagsStretchRatio="1" Visible="False"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Administration.Managers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -9,11 +11,22 @@ namespace Content.Client.Lobby.UI;
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ObserveWarningWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public ObserveWarningWindow()
|
||||
{
|
||||
Title = Loc.GetString("observe-warning-window-title");
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
var player = _playerManager.LocalSession;
|
||||
|
||||
if (player != null && _adminManager.IsAdmin(player))
|
||||
{
|
||||
ObserveButton.Text = Loc.GetString("observe-as-player");
|
||||
ObserveAsAdminButton.Visible = true;
|
||||
ObserveAsAdminButton.OnPressed += _ => { this.Close(); };
|
||||
}
|
||||
|
||||
ObserveButton.OnPressed += _ => { this.Close(); };
|
||||
NevermindButton.OnPressed += _ => { this.Close(); };
|
||||
|
||||
@@ -61,7 +61,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
{
|
||||
if (_dragging == null) return;
|
||||
|
||||
if (_lastMousePosition != null && TryComp<TransformComponent>(_dragging.Value, out var xform) &&
|
||||
if (_lastMousePosition != null && TryComp(_dragging.Value, out TransformComponent? xform) &&
|
||||
TryComp<PhysicsComponent>(_dragging.Value, out var body) &&
|
||||
xform.MapID == _lastMousePosition.Value.MapId)
|
||||
{
|
||||
@@ -104,7 +104,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position));
|
||||
}
|
||||
|
||||
if (!TryComp<TransformComponent>(_dragging, out var xform))
|
||||
if (!TryComp(_dragging, out TransformComponent? xform))
|
||||
{
|
||||
StopDragging();
|
||||
return;
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed partial class ArticleEditorPanel : Control
|
||||
|
||||
TextEditPanel.Visible = !_preview;
|
||||
PreviewPanel.Visible = _preview;
|
||||
PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope));
|
||||
PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope));
|
||||
}
|
||||
|
||||
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
||||
|
||||
@@ -5,9 +5,27 @@ namespace Content.Client.Message;
|
||||
|
||||
public static class RichTextLabelExt
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the labels markup.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invalid markup will cause exceptions to be thrown. Don't use this for user input!
|
||||
/// </remarks>
|
||||
public static RichTextLabel SetMarkup(this RichTextLabel label, string markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkup(markup));
|
||||
return label;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the labels markup.<br/>
|
||||
/// Uses <c>FormatedMessage.FromMarkupPermissive</c> which treats invalid markup as text.
|
||||
/// </summary>
|
||||
public static RichTextLabel SetMarkupPermissive(this RichTextLabel label, string markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkupPermissive(markup));
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,51 +10,56 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, ComponentStartup>(OnOcclusionStartup);
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, ComponentShutdown>(OnOcclusionShutdown);
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, AfterAutoHandleStateEvent>(OnOcclusionAuto);
|
||||
}
|
||||
|
||||
private void OnOcclusionAuto(EntityUid uid, FloorOcclusionComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnOcclusionAuto(Entity<FloorOcclusionComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
SetEnabled(uid, component, component.Enabled);
|
||||
SetShader(ent.Owner, ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void OnOcclusionStartup(EntityUid uid, FloorOcclusionComponent component, ComponentStartup args)
|
||||
private void OnOcclusionStartup(Entity<FloorOcclusionComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
if (component.Enabled && TryComp<SpriteComponent>(uid, out var sprite))
|
||||
SetShader(sprite, true);
|
||||
SetShader(ent.Owner, ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
protected override void SetEnabled(EntityUid uid, FloorOcclusionComponent component, bool enabled)
|
||||
private void OnOcclusionShutdown(Entity<FloorOcclusionComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (component.Enabled == enabled)
|
||||
SetShader(ent.Owner, false);
|
||||
}
|
||||
|
||||
protected override void SetEnabled(Entity<FloorOcclusionComponent> entity)
|
||||
{
|
||||
SetShader(entity.Owner, entity.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void SetShader(Entity<SpriteComponent?> sprite, bool enabled)
|
||||
{
|
||||
if (!_spriteQuery.Resolve(sprite.Owner, ref sprite.Comp, false))
|
||||
return;
|
||||
|
||||
base.SetEnabled(uid, component, enabled);
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
SetShader(sprite, enabled);
|
||||
}
|
||||
|
||||
private void SetShader(SpriteComponent sprite, bool enabled)
|
||||
{
|
||||
var shader = _proto.Index<ShaderPrototype>("HorizontalCut").Instance();
|
||||
|
||||
if (sprite.PostShader is not null && sprite.PostShader != shader)
|
||||
if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
sprite.PostShader = shader;
|
||||
sprite.Comp.PostShader = shader;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.PostShader = null;
|
||||
sprite.Comp.PostShader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<tabs:GraphicsTab Name="GraphicsTab" />
|
||||
<tabs:KeyRebindTab Name="KeyRebindTab" />
|
||||
<tabs:AudioTab Name="AudioTab" />
|
||||
<tabs:NetworkTab Name="NetworkTab" />
|
||||
<dtabs:DeltaTab Name="DeltaTab" />
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -19,8 +19,7 @@ namespace Content.Client.Options.UI
|
||||
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
|
||||
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
|
||||
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network"));
|
||||
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
|
||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
|
||||
|
||||
UpdateTabs();
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Content.Client.Options.UI.Tabs.NetworkTab">
|
||||
<BoxContainer Orientation="Vertical" >
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
|
||||
<CheckBox Name="NetPredictCheckbox" Text="{Loc 'ui-options-net-predict'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
|
||||
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetInterpRatioSlider"
|
||||
ToolTip="{Loc 'ui-options-net-interp-ratio-tooltip'}"
|
||||
MaxValue="8"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="NetInterpRatioLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
|
||||
<Label Text="{Loc 'ui-options-net-predict-tick-bias'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetPredictTickBiasSlider"
|
||||
ToolTip="{Loc 'ui-options-net-predict-tick-bias-tooltip'}"
|
||||
MaxValue="6"
|
||||
MinValue="0"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="NetPredictTickBiasLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
|
||||
<Label Text="{Loc 'ui-options-net-pvs-spawn'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetPvsSpawnSlider"
|
||||
ToolTip="{Loc 'ui-options-net-pvs-spawn-tooltip'}"
|
||||
MaxValue="150"
|
||||
MinValue="20"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="NetPvsSpawnLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
|
||||
<Label Text="{Loc 'ui-options-net-pvs-entry'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetPvsEntrySlider"
|
||||
ToolTip="{Loc 'ui-options-net-pvs-entry-tooltip'}"
|
||||
MaxValue="500"
|
||||
MinValue="20"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="NetPvsEntryLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 10 4 10">
|
||||
<Label Text="{Loc 'ui-options-net-pvs-leave'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetPvsLeaveSlider"
|
||||
ToolTip="{Loc 'ui-options-net-pvs-leave-tooltip'}"
|
||||
MaxValue="300"
|
||||
MinValue="20"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="NetPvsLeaveLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Align="End"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<Button Name="ResetButton"
|
||||
Text="{Loc 'ui-options-reset-all'}"
|
||||
StyleClasses="Caution"
|
||||
HorizontalExpand="True"
|
||||
HorizontalAlignment="Right" />
|
||||
<Button Name="DefaultButton"
|
||||
Text="{Loc 'ui-options-default'}"
|
||||
TextAlign="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
<Control MinSize="2 0" />
|
||||
<Button Name="ApplyButton"
|
||||
Text="{Loc 'ui-options-apply'}"
|
||||
TextAlign="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -1,125 +0,0 @@
|
||||
using System.Globalization;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Client.GameStates;
|
||||
using Content.Client.Entry;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NetworkTab : Control
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateMan = default!;
|
||||
|
||||
public NetworkTab()
|
||||
{
|
||||
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
ResetButton.OnPressed += OnResetButtonPressed;
|
||||
DefaultButton.OnPressed += OnDefaultButtonPressed;
|
||||
NetPredictCheckbox.OnToggled += OnPredictToggled;
|
||||
NetInterpRatioSlider.OnValueChanged += OnSliderChanged;
|
||||
NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
|
||||
NetPredictTickBiasSlider.OnValueChanged += OnSliderChanged;
|
||||
NetPvsSpawnSlider.OnValueChanged += OnSliderChanged;
|
||||
NetPvsEntrySlider.OnValueChanged += OnSliderChanged;
|
||||
NetPvsLeaveSlider.OnValueChanged += OnSliderChanged;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
ApplyButton.OnPressed -= OnApplyButtonPressed;
|
||||
ResetButton.OnPressed -= OnResetButtonPressed;
|
||||
DefaultButton.OnPressed -= OnDefaultButtonPressed;
|
||||
NetPredictCheckbox.OnToggled -= OnPredictToggled;
|
||||
NetInterpRatioSlider.OnValueChanged -= OnSliderChanged;
|
||||
NetPredictTickBiasSlider.OnValueChanged -= OnSliderChanged;
|
||||
NetPvsSpawnSlider.OnValueChanged -= OnSliderChanged;
|
||||
NetPvsEntrySlider.OnValueChanged -= OnSliderChanged;
|
||||
NetPvsLeaveSlider.OnValueChanged -= OnSliderChanged;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void OnPredictToggled(BaseButton.ButtonToggledEventArgs obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnSliderChanged(Robust.Client.UserInterface.Controls.Range range)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
|
||||
_cfg.SetCVar(CVars.NetPredictTickBias, (int) NetPredictTickBiasSlider.Value);
|
||||
_cfg.SetCVar(CVars.NetPVSEntityBudget, (int) NetPvsSpawnSlider.Value);
|
||||
_cfg.SetCVar(CVars.NetPVSEntityEnterBudget, (int) NetPvsEntrySlider.Value);
|
||||
_cfg.SetCVar(CVars.NetPVSEntityExitBudget, (int) NetPvsLeaveSlider.Value);
|
||||
_cfg.SetCVar(CVars.NetPredict, NetPredictCheckbox.Pressed);
|
||||
|
||||
_cfg.SaveToFile();
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void OnDefaultButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
NetPredictTickBiasSlider.Value = CVars.NetPredictTickBias.DefaultValue;
|
||||
NetPvsSpawnSlider.Value = CVars.NetPVSEntityBudget.DefaultValue;
|
||||
NetPvsEntrySlider.Value = CVars.NetPVSEntityEnterBudget.DefaultValue;
|
||||
NetPvsLeaveSlider.Value = CVars.NetPVSEntityExitBudget.DefaultValue;
|
||||
NetInterpRatioSlider.Value = CVars.NetBufferSize.DefaultValue + _stateMan.MinBufferSize;
|
||||
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
|
||||
NetPredictTickBiasSlider.Value = _cfg.GetCVar(CVars.NetPredictTickBias);
|
||||
NetPvsSpawnSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityBudget);
|
||||
NetPvsEntrySlider.Value = _cfg.GetCVar(CVars.NetPVSEntityEnterBudget);
|
||||
NetPvsLeaveSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
|
||||
NetPredictCheckbox.Pressed = _cfg.GetCVar(CVars.NetPredict);
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void UpdateChanges()
|
||||
{
|
||||
var isEverythingSame =
|
||||
NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize &&
|
||||
NetPredictTickBiasSlider.Value == _cfg.GetCVar(CVars.NetPredictTickBias) &&
|
||||
NetPredictCheckbox.Pressed == _cfg.GetCVar(CVars.NetPredict) &&
|
||||
NetPvsSpawnSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityBudget) &&
|
||||
NetPvsEntrySlider.Value == _cfg.GetCVar(CVars.NetPVSEntityEnterBudget) &&
|
||||
NetPvsLeaveSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
|
||||
|
||||
ApplyButton.Disabled = isEverythingSame;
|
||||
ResetButton.Disabled = isEverythingSame;
|
||||
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString(CultureInfo.InvariantCulture);
|
||||
NetPredictTickBiasLabel.Text = NetPredictTickBiasSlider.Value.ToString(CultureInfo.InvariantCulture);
|
||||
NetPvsSpawnLabel.Text = NetPvsSpawnSlider.Value.ToString(CultureInfo.InvariantCulture);
|
||||
NetPvsEntryLabel.Text = NetPvsEntrySlider.Value.ToString(CultureInfo.InvariantCulture);
|
||||
NetPvsLeaveLabel.Text = NetPvsLeaveSlider.Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// TODO disable / grey-out the predict and interp sliders if prediction is disabled.
|
||||
// Currently no option to do this, but should be added to the slider control in general
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Systems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using System.Numerics;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Client.UserInterface.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
@@ -79,6 +78,10 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
continue;
|
||||
}
|
||||
|
||||
// we are all progressing towards death every day
|
||||
if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress)
|
||||
continue;
|
||||
|
||||
var worldPosition = _transform.GetWorldPosition(xform);
|
||||
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
|
||||
|
||||
@@ -91,10 +94,6 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
var widthOfMob = bounds.Width * EyeManager.PixelsPerMeter;
|
||||
|
||||
var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter);
|
||||
|
||||
// we are all progressing towards death every day
|
||||
(float ratio, bool inCrit) deathProgress = CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent);
|
||||
|
||||
var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit);
|
||||
|
||||
// Hardcoded width of the progress bar because it doesn't match the texture.
|
||||
@@ -122,10 +121,13 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Returns a ratio between 0 and 1, and whether the entity is in crit.
|
||||
/// </summary>
|
||||
private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
|
||||
private (float ratio, bool inCrit)? CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
|
||||
{
|
||||
if (_mobStateSystem.IsAlive(uid, component))
|
||||
{
|
||||
if (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold)
|
||||
return null;
|
||||
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) &&
|
||||
!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds))
|
||||
return (1, false);
|
||||
|
||||
@@ -14,27 +14,40 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
|
||||
private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not NavMapComponentState state)
|
||||
return;
|
||||
Dictionary<Vector2i, int[]> modifiedChunks;
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons;
|
||||
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
case NavMapDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
beacons = delta.Beacons;
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
case NavMapState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
modifiedChunks = state.Chunks;
|
||||
beacons = state.Beacons;
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (origin, chunk) in state.Chunks)
|
||||
foreach (var (origin, chunk) in modifiedChunks)
|
||||
{
|
||||
var newChunk = new NavMapChunk(origin);
|
||||
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
||||
@@ -42,7 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
}
|
||||
|
||||
component.Beacons.Clear();
|
||||
foreach (var (nuid, beacon) in state.Beacons)
|
||||
foreach (var (nuid, beacon) in beacons)
|
||||
{
|
||||
component.Beacons[nuid] = beacon;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Players.JobWhitelist;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client;
|
||||
@@ -24,6 +25,7 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
|
||||
private readonly Dictionary<string, TimeSpan> _roles = new();
|
||||
private readonly List<string> _roleBans = new();
|
||||
private readonly List<string> _jobWhitelists = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -36,6 +38,7 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
// Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
|
||||
_net.RegisterNetMessage<MsgRoleBans>(RxRoleBans);
|
||||
_net.RegisterNetMessage<MsgPlayTime>(RxPlayTime);
|
||||
_net.RegisterNetMessage<MsgJobWhitelist>(RxJobWhitelist);
|
||||
_net.RegisterNetMessage<MsgWhitelist>(RxWhitelist);
|
||||
|
||||
_client.RunLevelChanged += ClientOnRunLevelChanged;
|
||||
@@ -80,6 +83,13 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
Updated?.Invoke();
|
||||
}
|
||||
|
||||
private void RxJobWhitelist(MsgJobWhitelist message)
|
||||
{
|
||||
_jobWhitelists.Clear();
|
||||
_jobWhitelists.AddRange(message.Whitelist);
|
||||
Updated?.Invoke();
|
||||
}
|
||||
|
||||
public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
reason = null;
|
||||
@@ -90,6 +100,9 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckWhitelist(job, out reason))
|
||||
return false;
|
||||
|
||||
var player = _playerManager.LocalSession;
|
||||
if (player == null)
|
||||
return true;
|
||||
@@ -117,6 +130,21 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
return reason == null;
|
||||
}
|
||||
|
||||
public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
reason = default;
|
||||
if (!_cfg.GetCVar(CCVars.GameRoleWhitelist))
|
||||
return true;
|
||||
|
||||
if (job.Whitelisted && !_jobWhitelists.Contains(job.ID))
|
||||
{
|
||||
reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public TimeSpan FetchOverallPlaytime()
|
||||
{
|
||||
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Containers;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Popups
|
||||
{
|
||||
@@ -29,11 +31,11 @@ namespace Content.Client.Popups
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public IReadOnlyList<WorldPopupLabel> WorldLabels => _aliveWorldLabels;
|
||||
public IReadOnlyList<CursorPopupLabel> CursorLabels => _aliveCursorLabels;
|
||||
public IReadOnlyCollection<WorldPopupLabel> WorldLabels => _aliveWorldLabels.Values;
|
||||
public IReadOnlyCollection<CursorPopupLabel> CursorLabels => _aliveCursorLabels.Values;
|
||||
|
||||
private readonly List<WorldPopupLabel> _aliveWorldLabels = new();
|
||||
private readonly List<CursorPopupLabel> _aliveCursorLabels = new();
|
||||
private readonly Dictionary<WorldPopupData, WorldPopupLabel> _aliveWorldLabels = new();
|
||||
private readonly Dictionary<CursorPopupData, CursorPopupLabel> _aliveCursorLabels = new();
|
||||
|
||||
public const float MinimumPopupLifetime = 0.7f;
|
||||
public const float MaximumPopupLifetime = 5f;
|
||||
@@ -65,6 +67,15 @@ namespace Content.Client.Popups
|
||||
.RemoveOverlay<PopupOverlay>();
|
||||
}
|
||||
|
||||
private void WrapAndRepeatPopup(PopupLabel existingLabel, string popupMessage)
|
||||
{
|
||||
existingLabel.TotalTime = 0;
|
||||
existingLabel.Repeats += 1;
|
||||
existingLabel.Text = Loc.GetString("popup-system-repeated-popup-stacking-wrap",
|
||||
("popup-message", popupMessage),
|
||||
("count", existingLabel.Repeats));
|
||||
}
|
||||
|
||||
private void PopupMessage(string? message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
|
||||
{
|
||||
if (message == null)
|
||||
@@ -78,13 +89,20 @@ namespace Content.Client.Popups
|
||||
_replayRecording.RecordClientMessage(new PopupCoordinatesEvent(message, type, GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
var popupData = new WorldPopupData(message, type, coordinates, entity);
|
||||
if (_aliveWorldLabels.TryGetValue(popupData, out var existingLabel))
|
||||
{
|
||||
WrapAndRepeatPopup(existingLabel, popupData.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var label = new WorldPopupLabel(coordinates)
|
||||
{
|
||||
Text = message,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
_aliveWorldLabels.Add(label);
|
||||
_aliveWorldLabels.Add(popupData, label);
|
||||
}
|
||||
|
||||
#region Abstract Method Implementations
|
||||
@@ -113,13 +131,20 @@ namespace Content.Client.Popups
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
_replayRecording.RecordClientMessage(new PopupCursorEvent(message, type));
|
||||
|
||||
var popupData = new CursorPopupData(message, type);
|
||||
if (_aliveCursorLabels.TryGetValue(popupData, out var existingLabel))
|
||||
{
|
||||
WrapAndRepeatPopup(existingLabel, popupData.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var label = new CursorPopupLabel(_inputManager.MouseScreenPosition)
|
||||
{
|
||||
Text = message,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
_aliveCursorLabels.Add(label);
|
||||
_aliveCursorLabels.Add(popupData, label);
|
||||
}
|
||||
|
||||
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
|
||||
@@ -249,27 +274,37 @@ namespace Content.Client.Popups
|
||||
if (_aliveWorldLabels.Count == 0 && _aliveCursorLabels.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _aliveWorldLabels.Count; i++)
|
||||
if (_aliveWorldLabels.Count > 0)
|
||||
{
|
||||
var label = _aliveWorldLabels[i];
|
||||
label.TotalTime += frameTime;
|
||||
|
||||
if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
|
||||
var aliveWorldToRemove = new ValueList<WorldPopupData>();
|
||||
foreach (var (data, label) in _aliveWorldLabels)
|
||||
{
|
||||
_aliveWorldLabels.RemoveSwap(i);
|
||||
i--;
|
||||
label.TotalTime += frameTime;
|
||||
if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
|
||||
{
|
||||
aliveWorldToRemove.Add(data);
|
||||
}
|
||||
}
|
||||
foreach (var data in aliveWorldToRemove)
|
||||
{
|
||||
_aliveWorldLabels.Remove(data);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _aliveCursorLabels.Count; i++)
|
||||
if (_aliveCursorLabels.Count > 0)
|
||||
{
|
||||
var label = _aliveCursorLabels[i];
|
||||
label.TotalTime += frameTime;
|
||||
|
||||
if (label.TotalTime > GetPopupLifetime(label))
|
||||
var aliveCursorToRemove = new ValueList<CursorPopupData>();
|
||||
foreach (var (data, label) in _aliveCursorLabels)
|
||||
{
|
||||
_aliveCursorLabels.RemoveSwap(i);
|
||||
i--;
|
||||
label.TotalTime += frameTime;
|
||||
if (label.TotalTime > GetPopupLifetime(label))
|
||||
{
|
||||
aliveCursorToRemove.Add(data);
|
||||
}
|
||||
}
|
||||
foreach (var data in aliveCursorToRemove)
|
||||
{
|
||||
_aliveCursorLabels.Remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,29 +314,32 @@ namespace Content.Client.Popups
|
||||
public PopupType Type = PopupType.Small;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public float TotalTime { get; set; }
|
||||
public int Repeats = 1;
|
||||
}
|
||||
|
||||
public sealed class CursorPopupLabel : PopupLabel
|
||||
{
|
||||
public ScreenCoordinates InitialPos;
|
||||
|
||||
public CursorPopupLabel(ScreenCoordinates screenCoords)
|
||||
{
|
||||
InitialPos = screenCoords;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WorldPopupLabel : PopupLabel
|
||||
public sealed class WorldPopupLabel(EntityCoordinates coordinates) : PopupLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// The original EntityCoordinates of the label.
|
||||
/// </summary>
|
||||
public EntityCoordinates InitialPos;
|
||||
|
||||
public WorldPopupLabel(EntityCoordinates coordinates)
|
||||
{
|
||||
InitialPos = coordinates;
|
||||
}
|
||||
public EntityCoordinates InitialPos = coordinates;
|
||||
}
|
||||
|
||||
public sealed class CursorPopupLabel(ScreenCoordinates screenCoords) : PopupLabel
|
||||
{
|
||||
public ScreenCoordinates InitialPos = screenCoords;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private record struct WorldPopupData(
|
||||
string Message,
|
||||
PopupType Type,
|
||||
EntityCoordinates Coordinates,
|
||||
EntityUid? Entity);
|
||||
|
||||
[UsedImplicitly]
|
||||
private record struct CursorPopupData(
|
||||
string Message,
|
||||
PopupType Type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
public sealed class ActivatableUIRequiresPowerSystem : EntitySystem
|
||||
public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, ActivatableUIOpenAttemptEvent>(OnActivate);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
protected override void OnActivate(Entity<ActivatableUIRequiresPowerComponent> ent, ref ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
// Client can't predict the power properly at the moment so rely upon the server to do it.
|
||||
if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<WiresPanelComponent>(ent.Owner, out var panel) && panel.Open)
|
||||
return;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Power.Components;
|
||||
|
||||
namespace Content.Client.Power.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Client.Power.Components;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ApcPowerReceiverComponentState state)
|
||||
return;
|
||||
|
||||
component.Powered = state.Powered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Client.Power.Components;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public static class StaticPowerSystem
|
||||
{
|
||||
// Using this makes the call shorter.
|
||||
// ReSharper disable once UnusedParameter.Global
|
||||
public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null)
|
||||
{
|
||||
if (receiver == null && !entManager.TryGetComponent(uid, out receiver))
|
||||
return false;
|
||||
|
||||
return receiver.Powered;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.Alerts;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Revenant;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -42,7 +41,7 @@ public sealed class RevenantSystem : EntitySystem
|
||||
|
||||
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
{
|
||||
if (args.Alert.AlertType != AlertType.Essence)
|
||||
if (args.Alert.ID != ent.Comp.EssenceAlert)
|
||||
return;
|
||||
|
||||
var sprite = args.SpriteViewEnt.Comp;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class SalvageSystem : SharedSalvageSystem
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (!TryComp<TransformComponent>(player, out var xform) ||
|
||||
if (!TryComp(player, out TransformComponent? xform) ||
|
||||
!TryComp<SalvageExpeditionComponent>(xform.MapUid, out var expedition) ||
|
||||
expedition.Stage < ExpeditionStage.MusicCountdown)
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
var change = ChangeRate * frameTime;
|
||||
|
||||
if (TryComp<TransformComponent>(player, out var playerXform) &&
|
||||
if (TryComp(player, out TransformComponent? playerXform) &&
|
||||
_stateManager.CurrentState is GameplayState state &&
|
||||
spriteQuery.TryGetComponent(player, out var playerSprite))
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
_window.OpenCentered();
|
||||
_window.OnNameConfirm += SendDeviceName;
|
||||
_window.OnNetworkConfirm += SendSelectedNetwork;
|
||||
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
private void SendSelectedNetwork(int idx)
|
||||
@@ -63,7 +63,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window!.Dispose();
|
||||
_window?.Dispose();
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class TippyUIController : UIController
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
}
|
||||
sprite.Rotation = 0;
|
||||
tippy.Label.SetMarkup(_currentMessage.Msg);
|
||||
tippy.Label.SetMarkupPermissive(_currentMessage.Msg);
|
||||
tippy.Label.Visible = false;
|
||||
tippy.LabelPanel.Visible = false;
|
||||
tippy.Visible = true;
|
||||
|
||||
@@ -8,7 +8,8 @@ using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
public sealed class ListContainer : Control
|
||||
[Virtual]
|
||||
public class ListContainer : Control
|
||||
{
|
||||
public const string StylePropertySeparation = "separation";
|
||||
public const string StyleClassListContainerButton = "list-container-button";
|
||||
@@ -21,9 +22,26 @@ public sealed class ListContainer : Control
|
||||
set => _buttonGroup = value ? new ButtonGroup() : null;
|
||||
}
|
||||
public bool Toggle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when creating a button on the UI.
|
||||
/// The provided <see cref="ListContainerButton"/> is the generated button that Controls should be parented to.
|
||||
/// </summary>
|
||||
public Action<ListData, ListContainerButton>? GenerateItem;
|
||||
public Action<BaseButton.ButtonEventArgs?, ListData?>? ItemPressed;
|
||||
public Action<GUIBoundKeyEventArgs, ListData?>? ItemKeyBindDown;
|
||||
|
||||
/// <inheritdoc cref="BaseButton.OnPressed"/>
|
||||
public Action<BaseButton.ButtonEventArgs, ListData>? ItemPressed;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a KeyBind is pressed on a ListContainerButton.
|
||||
/// </summary>
|
||||
public Action<GUIBoundKeyEventArgs, ListData>? ItemKeyBindDown;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the selected item does not exist in the new data when PopulateList is called.
|
||||
/// </summary>
|
||||
public Action? NoItemSelected;
|
||||
|
||||
public IReadOnlyList<ListData> Data => _data;
|
||||
|
||||
private const int DefaultSeparation = 3;
|
||||
@@ -72,11 +90,11 @@ public sealed class ListContainer : Control
|
||||
_vScrollBar.OnValueChanged += ScrollValueChanged;
|
||||
}
|
||||
|
||||
public void PopulateList(IReadOnlyList<ListData> data)
|
||||
public virtual void PopulateList(IReadOnlyList<ListData> data)
|
||||
{
|
||||
if ((_itemHeight == 0 || _data is {Count: 0}) && data.Count > 0)
|
||||
{
|
||||
ListContainerButton control = new(data[0]);
|
||||
ListContainerButton control = new(data[0], 0);
|
||||
GenerateItem?.Invoke(data[0], control);
|
||||
control.Measure(Vector2Helpers.Infinity);
|
||||
_itemHeight = control.DesiredSize.Y;
|
||||
@@ -97,7 +115,7 @@ public sealed class ListContainer : Control
|
||||
if (_selected != null && !data.Contains(_selected))
|
||||
{
|
||||
_selected = null;
|
||||
ItemPressed?.Invoke(null, null);
|
||||
NoItemSelected?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +134,7 @@ public sealed class ListContainer : Control
|
||||
if (_buttons.TryGetValue(data, out var button) && Toggle)
|
||||
button.Pressed = true;
|
||||
_selected = data;
|
||||
button ??= new ListContainerButton(data);
|
||||
button ??= new ListContainerButton(data, _data.IndexOf(data));
|
||||
OnItemPressed(new BaseButton.ButtonEventArgs(button,
|
||||
new GUIBoundKeyEventArgs(EngineKeyFunctions.UIClick, BoundKeyState.Up,
|
||||
new ScreenCoordinates(0, 0, WindowId.Main), true, Vector2.Zero, Vector2.Zero)));
|
||||
@@ -260,7 +278,7 @@ public sealed class ListContainer : Control
|
||||
toRemove.Remove(data);
|
||||
else
|
||||
{
|
||||
button = new ListContainerButton(data);
|
||||
button = new ListContainerButton(data, i);
|
||||
button.OnPressed += OnItemPressed;
|
||||
button.OnKeyBindDown += args => OnItemKeyBindDown(button, args);
|
||||
button.ToggleMode = Toggle;
|
||||
@@ -360,11 +378,14 @@ public sealed class ListContainer : Control
|
||||
public sealed class ListContainerButton : ContainerButton, IEntityControl
|
||||
{
|
||||
public readonly ListData Data;
|
||||
|
||||
public readonly int Index;
|
||||
// public PanelContainer Background;
|
||||
|
||||
public ListContainerButton(ListData data)
|
||||
public ListContainerButton(ListData data, int index)
|
||||
{
|
||||
Data = data;
|
||||
Index = index;
|
||||
// AddChild(Background = new PanelContainer
|
||||
// {
|
||||
// HorizontalExpand = true,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
public sealed class SearchListContainer : ListContainer
|
||||
{
|
||||
private LineEdit? _searchBar;
|
||||
private List<ListData> _unfilteredData = new();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LineEdit"/> that is used to filter the list data.
|
||||
/// </summary>
|
||||
public LineEdit? SearchBar
|
||||
{
|
||||
get => _searchBar;
|
||||
set
|
||||
{
|
||||
if (_searchBar is not null)
|
||||
_searchBar.OnTextChanged -= FilterList;
|
||||
|
||||
_searchBar = value;
|
||||
|
||||
if (_searchBar is null)
|
||||
return;
|
||||
|
||||
_searchBar.OnTextChanged += FilterList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs over the ListData to determine if it should pass the filter.
|
||||
/// </summary>
|
||||
public Func<string, ListData, bool>? DataFilterCondition = null;
|
||||
|
||||
public override void PopulateList(IReadOnlyList<ListData> data)
|
||||
{
|
||||
_unfilteredData = data.ToList();
|
||||
FilterList();
|
||||
}
|
||||
|
||||
private void FilterList(LineEdit.LineEditEventArgs obj)
|
||||
{
|
||||
FilterList();
|
||||
}
|
||||
|
||||
private void FilterList()
|
||||
{
|
||||
var filterText = SearchBar?.Text;
|
||||
|
||||
if (DataFilterCondition is null || string.IsNullOrEmpty(filterText))
|
||||
{
|
||||
base.PopulateList(_unfilteredData);
|
||||
return;
|
||||
}
|
||||
|
||||
var filteredData = new List<ListData>();
|
||||
foreach (var data in _unfilteredData)
|
||||
{
|
||||
if (!DataFilterCondition(filterText, data))
|
||||
continue;
|
||||
|
||||
filteredData.Add(data);
|
||||
}
|
||||
|
||||
base.PopulateList(filteredData);
|
||||
}
|
||||
}
|
||||
@@ -119,9 +119,4 @@ public class ActionButtonContainer : GridContainer
|
||||
yield return button;
|
||||
}
|
||||
}
|
||||
|
||||
~ActionButtonContainer()
|
||||
{
|
||||
UserInterfaceManager.GetUIController<ActionUIController>().RemoveActionContainer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,12 +177,15 @@ public sealed class AdminUIController : UIController,
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerTabEntryKeyBindDown(PlayerTabEntry entry, GUIBoundKeyEventArgs args)
|
||||
private void PlayerTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data)
|
||||
{
|
||||
if (entry.PlayerEntity == null)
|
||||
if (data is not PlayerListData {Info: var info})
|
||||
return;
|
||||
|
||||
var entity = entry.PlayerEntity.Value;
|
||||
if (info.NetEntity == null)
|
||||
return;
|
||||
|
||||
var entity = info.NetEntity.Value;
|
||||
var function = args.Function;
|
||||
|
||||
if (function == EngineKeyFunctions.UIClick)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts;
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
|
||||
SyncAlerts();
|
||||
}
|
||||
|
||||
private void OnAlertPressed(object? sender, AlertType e)
|
||||
private void OnAlertPressed(object? sender, ProtoId<AlertPrototype> e)
|
||||
{
|
||||
_alertsSystem?.AlertClicked(e);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts.Widgets;
|
||||
|
||||
@@ -21,8 +22,10 @@ public sealed partial class AlertsUI : UIWidget
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
public void SyncControls(AlertsSystem alertsSystem,
|
||||
AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey,
|
||||
AlertState> alertStates)
|
||||
{
|
||||
// remove any controls with keys no longer present
|
||||
if (SyncRemoveControls(alertStates))
|
||||
@@ -46,7 +49,7 @@ public sealed partial class AlertsUI : UIWidget
|
||||
_alertControls.Clear();
|
||||
}
|
||||
|
||||
public event EventHandler<AlertType>? AlertPressed;
|
||||
public event EventHandler<ProtoId<AlertPrototype>>? AlertPressed;
|
||||
|
||||
private bool SyncRemoveControls(IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
{
|
||||
@@ -88,7 +91,7 @@ public sealed partial class AlertsUI : UIWidget
|
||||
}
|
||||
|
||||
if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
|
||||
existingAlertControl.Alert.AlertType == newAlert.AlertType)
|
||||
existingAlertControl.Alert.ID == newAlert.ID)
|
||||
{
|
||||
// key is the same, simply update the existing control severity / cooldown
|
||||
existingAlertControl.SetSeverity(alertState.Severity);
|
||||
@@ -155,6 +158,6 @@ public sealed partial class AlertsUI : UIWidget
|
||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
AlertPressed?.Invoke(this, control.Alert.AlertType);
|
||||
AlertPressed?.Invoke(this, control.Alert.ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,4 @@ public sealed class ItemSlotButtonContainer : ItemSlotUIContainer<SlotControl>
|
||||
{
|
||||
_inventoryController = UserInterfaceManager.GetUIController<InventoryUIController>();
|
||||
}
|
||||
|
||||
~ItemSlotButtonContainer()
|
||||
{
|
||||
_inventoryController.RemoveSlotGroup(SlotGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
|
||||
public sealed class ItemGridPiece : Control
|
||||
public sealed class ItemGridPiece : Control, IEntityControl
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly StorageUIController _storageController;
|
||||
@@ -287,6 +287,8 @@ public sealed class ItemGridPiece : Control
|
||||
var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1);
|
||||
return actualSize * new Vector2i(8, 8);
|
||||
}
|
||||
|
||||
public EntityUid? UiEntity => Entity;
|
||||
}
|
||||
|
||||
public enum ItemGridPieceMarks
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Verbs;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -115,6 +116,13 @@ namespace Content.Client.Verbs.UI
|
||||
private void FillVerbPopup(ContextMenuPopup popup)
|
||||
{
|
||||
HashSet<string> listedCategories = new();
|
||||
var extras = new ValueList<string>(ExtraCategories.Count);
|
||||
|
||||
foreach (var cat in ExtraCategories)
|
||||
{
|
||||
extras.Add(cat.Text);
|
||||
}
|
||||
|
||||
foreach (var verb in CurrentVerbs)
|
||||
{
|
||||
if (verb.Category == null)
|
||||
@@ -122,17 +130,15 @@ namespace Content.Client.Verbs.UI
|
||||
var element = new VerbMenuElement(verb);
|
||||
_context.AddElement(popup, element);
|
||||
}
|
||||
else if (listedCategories.Add(verb.Category.Text))
|
||||
// Add the category if it's not an extra (this is to avoid shuffling if we're filling from server verbs response).
|
||||
else if (!extras.Contains(verb.Category.Text) && listedCategories.Add(verb.Category.Text))
|
||||
AddVerbCategory(verb.Category, popup);
|
||||
}
|
||||
|
||||
if (ExtraCategories != null)
|
||||
foreach (var category in ExtraCategories)
|
||||
{
|
||||
foreach (var category in ExtraCategories)
|
||||
{
|
||||
if (listedCategories.Add(category.Text))
|
||||
AddVerbCategory(category, popup);
|
||||
}
|
||||
if (listedCategories.Add(category.Text))
|
||||
AddVerbCategory(category, popup);
|
||||
}
|
||||
|
||||
popup.InvalidateMeasure();
|
||||
|
||||
@@ -82,7 +82,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
|
||||
const float BufferDistance = 0.1f;
|
||||
|
||||
if (TryComp<TransformComponent>(gun.TetherEntity, out var tetherXform) &&
|
||||
if (TryComp(gun.TetherEntity, out TransformComponent? tetherXform) &&
|
||||
tetherXform.Coordinates.TryDistance(EntityManager, TransformSystem, coords, out var distance) &&
|
||||
distance < BufferDistance)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
private readonly Dictionary<string, object> _modifiedClientCvars = new();
|
||||
private readonly Dictionary<string, object> _modifiedServerCvars = new();
|
||||
|
||||
private void OnServerCvarChanged(CVarChangeInfo args)
|
||||
{
|
||||
_modifiedServerCvars.TryAdd(args.Name, args.OldValue);
|
||||
}
|
||||
|
||||
private void OnClientCvarChanged(CVarChangeInfo args)
|
||||
{
|
||||
_modifiedClientCvars.TryAdd(args.Name, args.OldValue);
|
||||
}
|
||||
|
||||
internal void ClearModifiedCvars()
|
||||
{
|
||||
_modifiedClientCvars.Clear();
|
||||
_modifiedServerCvars.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts any cvars that were modified during a test back to their original values.
|
||||
/// </summary>
|
||||
public async Task RevertModifiedCvars()
|
||||
{
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
foreach (var (name, value) in _modifiedServerCvars)
|
||||
{
|
||||
if (Server.CfgMan.GetCVar(name).Equals(value))
|
||||
continue;
|
||||
Server.Log.Info($"Resetting cvar {name} to {value}");
|
||||
Server.CfgMan.SetCVar(name, value);
|
||||
}
|
||||
|
||||
// I just love order dependent cvars
|
||||
if (_modifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik))
|
||||
Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik);
|
||||
|
||||
});
|
||||
|
||||
await Client.WaitPost(() =>
|
||||
{
|
||||
foreach (var (name, value) in _modifiedClientCvars)
|
||||
{
|
||||
if (Client.CfgMan.GetCVar(name).Equals(value))
|
||||
continue;
|
||||
|
||||
var flags = Client.CfgMan.GetCVarFlags(name);
|
||||
if (flags.HasFlag(CVar.REPLICATED) && flags.HasFlag(CVar.SERVER))
|
||||
continue;
|
||||
|
||||
Client.Log.Info($"Resetting cvar {name} to {value}");
|
||||
Client.CfgMan.SetCVar(name, value);
|
||||
}
|
||||
});
|
||||
|
||||
ClearModifiedCvars();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -128,4 +131,29 @@ public sealed partial class TestPair
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for enabling or disabling a antag role
|
||||
/// </summary>
|
||||
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
|
||||
{
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
|
||||
var prefs = prefMan.GetPreferences(Client.User!.Value);
|
||||
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
|
||||
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;
|
||||
|
||||
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
|
||||
var newProfile = profile.WithAntagPreference(id, value);
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
|
||||
});
|
||||
|
||||
// And why the fuck does it always create a new preference and profile object instead of just reusing them?
|
||||
var newPrefs = prefMan.GetPreferences(Client.User.Value);
|
||||
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
|
||||
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
TestMap = null;
|
||||
}
|
||||
|
||||
await RevertModifiedCvars();
|
||||
|
||||
var usageTime = Watch.Elapsed;
|
||||
Watch.Restart();
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms");
|
||||
@@ -132,6 +134,7 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round.");
|
||||
Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false);
|
||||
Assert.That(gameTicker.DummyTicker, Is.False);
|
||||
Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
await Server.WaitPost(() => gameTicker.RestartRound());
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
@@ -58,6 +59,9 @@ public sealed partial class TestPair
|
||||
(Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut);
|
||||
ActivateContext(testOut);
|
||||
|
||||
Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged;
|
||||
Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged;
|
||||
|
||||
if (!settings.NoLoadTestPrototypes)
|
||||
await LoadPrototypes(testPrototypes!);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public static partial class PoolManager
|
||||
(CCVars.NPCMaxUpdates.Name, "999999"),
|
||||
(CVars.ThreadParallelCount.Name, "1"),
|
||||
(CCVars.GameRoleTimers.Name, "false"),
|
||||
(CCVars.GameRoleWhitelist.Name, "false"),
|
||||
(CCVars.GridFill.Name, "false"),
|
||||
(CCVars.PreloadGrids.Name, "false"),
|
||||
(CCVars.ArrivalsShuttles.Name, "false"),
|
||||
|
||||
@@ -15,11 +15,8 @@ public static partial class PoolManager
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.DeclaredOnly;
|
||||
|
||||
private static void DiscoverTestPrototypes(Assembly? assembly = null)
|
||||
private static void DiscoverTestPrototypes(Assembly assembly)
|
||||
{
|
||||
assembly ??= typeof(PoolManager).Assembly;
|
||||
_testPrototypes.Clear();
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
foreach (var field in type.GetFields(Flags))
|
||||
|
||||
@@ -42,6 +42,8 @@ public static partial class PoolManager
|
||||
private static bool _dead;
|
||||
private static Exception? _poolFailureReason;
|
||||
|
||||
private static HashSet<Assembly> _contentAssemblies = default!;
|
||||
|
||||
public static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer(
|
||||
PoolSettings poolSettings,
|
||||
TextWriter testOut)
|
||||
@@ -54,12 +56,7 @@ public static partial class PoolManager
|
||||
LoadConfigAndUserData = false,
|
||||
LoadContentResources = !poolSettings.NoLoadContent,
|
||||
},
|
||||
ContentAssemblies = new[]
|
||||
{
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Server.Entry.EntryPoint).Assembly,
|
||||
typeof(PoolManager).Assembly
|
||||
}
|
||||
ContentAssemblies = _contentAssemblies.ToArray()
|
||||
};
|
||||
|
||||
var logHandler = new PoolTestLogHandler("SERVER");
|
||||
@@ -68,11 +65,11 @@ public static partial class PoolManager
|
||||
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
// Server-only systems (i.e., systems that subscribe to events with server-only components)
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
entSysMan.LoadExtraSystemType<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
|
||||
entSysMan.LoadExtraSystemType<InteractionSystemTests.TestInteractionSystem>();
|
||||
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
|
||||
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
|
||||
|
||||
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
|
||||
IoCManager.Resolve<IConfigurationManager>()
|
||||
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
|
||||
@@ -140,7 +137,7 @@ public static partial class PoolManager
|
||||
{
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Client.Entry.EntryPoint).Assembly,
|
||||
typeof(PoolManager).Assembly
|
||||
typeof(PoolManager).Assembly,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,6 +270,8 @@ public static partial class PoolManager
|
||||
$"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms");
|
||||
await testOut.WriteLineAsync(
|
||||
$"{nameof(GetServerClientPair)}: Returning pair {pair.Id}");
|
||||
|
||||
pair.ClearModifiedCvars();
|
||||
pair.Settings = poolSettings;
|
||||
pair.TestHistory.Add(currentTestName);
|
||||
pair.Watch.Restart();
|
||||
@@ -422,13 +421,26 @@ we are just going to end this here to save a lot of time. This is the exception
|
||||
/// <summary>
|
||||
/// Initialize the pool manager.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly to search for to discover extra test prototypes.</param>
|
||||
public static void Startup(Assembly? assembly)
|
||||
/// <param name="extraAssemblies">Assemblies to search for to discover extra prototypes and systems.</param>
|
||||
public static void Startup(params Assembly[] extraAssemblies)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Already initialized");
|
||||
|
||||
_initialized = true;
|
||||
DiscoverTestPrototypes(assembly);
|
||||
_contentAssemblies =
|
||||
[
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Server.Entry.EntryPoint).Assembly,
|
||||
typeof(PoolManager).Assembly
|
||||
];
|
||||
_contentAssemblies.UnionWith(extraAssemblies);
|
||||
|
||||
_testPrototypes.Clear();
|
||||
DiscoverTestPrototypes(typeof(PoolManager).Assembly);
|
||||
foreach (var assembly in extraAssemblies)
|
||||
{
|
||||
DiscoverTestPrototypes(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed class PoolManagerTestEventHandler
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
PoolManager.Startup(typeof(PoolManagerTestEventHandler).Assembly);
|
||||
PoolManager.Startup();
|
||||
// If the tests seem to be stuck, we try to end it semi-nicely
|
||||
_ = Task.Delay(MaximumTotalTestingTimeLimit).ContinueWith(_ =>
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
components:
|
||||
- type: Buckle
|
||||
- type: Hands
|
||||
- type: ComplexInteraction
|
||||
- type: InputMover
|
||||
- type: Body
|
||||
prototype: Human
|
||||
|
||||
@@ -350,8 +350,12 @@ namespace Content.IntegrationTests.Tests
|
||||
"DebrisFeaturePlacerController", // Above.
|
||||
"LoadedChunk", // Worldgen chunk loading malding.
|
||||
"BiomeSelection", // Whaddya know, requires config.
|
||||
"ActivatableUI", // Requires enum key
|
||||
};
|
||||
|
||||
// TODO TESTS
|
||||
// auto ignore any components that have a "required" data field.
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
||||
components:
|
||||
- type: Cuffable
|
||||
- type: Hands
|
||||
- type: ComplexInteraction
|
||||
- type: Body
|
||||
prototype: Human
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.Alert;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
{
|
||||
@@ -45,8 +44,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
Assert.That(alerts, Is.Not.Null);
|
||||
var alertCount = alerts.Count;
|
||||
|
||||
alertsSystem.ShowAlert(playerUid, AlertType.Debug1);
|
||||
alertsSystem.ShowAlert(playerUid, AlertType.Debug2);
|
||||
alertsSystem.ShowAlert(playerUid, "Debug1");
|
||||
alertsSystem.ShowAlert(playerUid, "Debug2");
|
||||
|
||||
Assert.That(alerts, Has.Count.EqualTo(alertCount + 2));
|
||||
});
|
||||
@@ -87,14 +86,14 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
// we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order.
|
||||
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3));
|
||||
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||
var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2 };
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
|
||||
var expectedIDs = new[] { "HumanHealth", "Debug1", "Debug2" };
|
||||
Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
alertsSystem.ClearAlert(playerUid, AlertType.Debug1);
|
||||
alertsSystem.ClearAlert(playerUid, "Debug1");
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(5);
|
||||
@@ -104,8 +103,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
// we should be seeing 2 alerts now because one was cleared
|
||||
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2));
|
||||
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||
var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug2 };
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
|
||||
var expectedIDs = new[] { "HumanHealth", "Debug2" };
|
||||
Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameRules;
|
||||
|
||||
// Once upon a time, players in the lobby weren't ever considered eligible for antag roles.
|
||||
// Lets not let that happen again.
|
||||
[TestFixture]
|
||||
public sealed class AntagPreferenceTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestLobbyPlayersValid()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
var server = pair.Server;
|
||||
var client = pair.Client;
|
||||
var ticker = server.System<GameTicker>();
|
||||
var sys = server.System<AntagSelectionSystem>();
|
||||
|
||||
// Initially in the lobby
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor"));
|
||||
var rule = new Entity<AntagSelectionComponent>(uid, server.EntMan.GetComponent<AntagSelectionComponent>(uid));
|
||||
var def = rule.Comp.Definitions.Single();
|
||||
|
||||
// IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby.
|
||||
// Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that.
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
|
||||
// By default, traitor/antag preferences are disabled, so the pool should be empty.
|
||||
var sessions = new List<ICommonSession>{pair.Player!};
|
||||
var pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
// Opt into the traitor role.
|
||||
await pair.SetAntagPref("Traitor", true);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(1));
|
||||
pool.TryPickAndTake(pair.Server.ResolveDependency<IRobustRandom>(), out var picked);
|
||||
Assert.That(picked, Is.EqualTo(pair.Player));
|
||||
Assert.That(sessions.Count, Is.EqualTo(1));
|
||||
|
||||
// opt back out
|
||||
await pair.SetAntagPref("Traitor", false);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,6 @@ public sealed class NukeOpsTest
|
||||
var invSys = server.System<InventorySystem>();
|
||||
var factionSys = server.System<NpcFactionSystem>();
|
||||
|
||||
// test urist is a noob let him be nukie
|
||||
server.CfgMan.SetCVar(CCVars.GameRoleTimers, false);
|
||||
|
||||
Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False);
|
||||
server.CfgMan.SetCVar(CCVars.GridFill, true);
|
||||
|
||||
// Initially in the lobby
|
||||
@@ -61,6 +57,9 @@ public sealed class NukeOpsTest
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Opt into the nukies role.
|
||||
await pair.SetAntagPref("NukeopsCommander", true);
|
||||
|
||||
// There are no grids or maps
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
|
||||
Assert.That(entMan.Count<MapGridComponent>(), Is.Zero);
|
||||
@@ -75,8 +74,6 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.Count<NukeOpsShuttleComponent>(), Is.Zero);
|
||||
Assert.That(entMan.Count<NukeOperativeSpawnerComponent>(), Is.Zero);
|
||||
|
||||
/* DeltaV - test temporarily disabled until someone smart fixes opting in during test
|
||||
|
||||
// Ready up and start nukeops
|
||||
await pair.WaitClientCommand("toggleready True");
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
@@ -201,10 +198,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero));
|
||||
}
|
||||
|
||||
DeltaV - end of commenting out */
|
||||
|
||||
ticker.SetGamePreset((GamePresetPrototype?)null);
|
||||
server.CfgMan.SetCVar(CCVars.GridFill, false);
|
||||
await pair.SetAntagPref("NukeopsCommander", false);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Gravity;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Gravity
|
||||
@@ -38,6 +39,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var alertsSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<AlertsSystem>();
|
||||
var weightlessAlert = SharedGravitySystem.WeightlessAlert;
|
||||
|
||||
EntityUid human = default;
|
||||
|
||||
@@ -56,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// No gravity without a gravity generator
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless));
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert));
|
||||
|
||||
generatorUid = entityManager.SpawnEntity("WeightlessGravityGeneratorDummy", entityManager.GetComponent<TransformComponent>(human).Coordinates);
|
||||
});
|
||||
@@ -66,7 +68,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless), Is.False);
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert), Is.False);
|
||||
|
||||
// This should kill gravity
|
||||
entityManager.DeleteEntity(generatorUid);
|
||||
@@ -76,7 +78,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless));
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert));
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Interaction;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -64,6 +65,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
sEntities.EnsureComponent<ComplexInteractionComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, coords);
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
@@ -205,6 +207,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
sEntities.EnsureComponent<ComplexInteractionComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, new MapCoordinates(new Vector2(SharedInteractionSystem.InteractionRange - 0.1f, 0), mapId));
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
@@ -347,6 +350,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
{
|
||||
user = sEntities.SpawnEntity(null, coords);
|
||||
sEntities.EnsureComponent<HandsComponent>(user);
|
||||
sEntities.EnsureComponent<ComplexInteractionComponent>(user);
|
||||
handSys.AddHand(user, "hand", HandLocation.Left);
|
||||
target = sEntities.SpawnEntity(null, coords);
|
||||
item = sEntities.SpawnEntity(null, coords);
|
||||
@@ -407,7 +411,6 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
public sealed class TestInteractionSystem : EntitySystem
|
||||
{
|
||||
public EntityEventHandler<InteractUsingEvent>? InteractUsingEvent;
|
||||
|
||||
@@ -137,6 +137,7 @@ public abstract partial class InteractionTest
|
||||
prototype: Aghost
|
||||
- type: DoAfter
|
||||
- type: Hands
|
||||
- type: ComplexInteraction
|
||||
- type: MindContainer
|
||||
- type: Stripping
|
||||
- type: Tag
|
||||
|
||||
@@ -156,4 +156,20 @@ public sealed class GhostTests
|
||||
await data.Pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGhostGridNotTerminating()
|
||||
{
|
||||
var data = await SetupData();
|
||||
|
||||
Assert.DoesNotThrowAsync(async () =>
|
||||
{
|
||||
// Delete the grid
|
||||
await data.Server.WaitPost(() => data.SEntMan.DeleteEntity(data.MapData.Grid.Owner));
|
||||
});
|
||||
|
||||
await data.Pair.RunTicksSync(5);
|
||||
|
||||
await data.Pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests
|
||||
[TestOf(typeof(RoundRestartCleanupEvent))]
|
||||
public sealed class ResettingEntitySystemTests
|
||||
{
|
||||
[Reflect(false)]
|
||||
public sealed class TestRoundRestartCleanupEvent : EntitySystem
|
||||
{
|
||||
public bool HasBeenReset { get; set; }
|
||||
@@ -49,8 +48,6 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
system.HasBeenReset = false;
|
||||
|
||||
Assert.That(system.HasBeenReset, Is.False);
|
||||
|
||||
gameTicker.RestartRound();
|
||||
|
||||
Assert.That(system.HasBeenReset);
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Content.IntegrationTests.Tests
|
||||
id: HumanVendingDummy
|
||||
components:
|
||||
- type: Hands
|
||||
- type: ComplexInteraction
|
||||
- type: Body
|
||||
prototype: Human
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.MapRenderer
|
||||
if (!CommandLineArguments.TryParse(args, out var arguments))
|
||||
return;
|
||||
|
||||
PoolManager.Startup(null);
|
||||
PoolManager.Startup();
|
||||
if (arguments.Maps.Count == 0)
|
||||
{
|
||||
Console.WriteLine("Didn't specify any maps to paint! Loading the map list...");
|
||||
|
||||
+1913
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RoleWhitelist : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "role_whitelists",
|
||||
columns: table => new
|
||||
{
|
||||
player_user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
role_id = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id });
|
||||
table.ForeignKey(
|
||||
name: "FK_role_whitelists_player_player_user_id",
|
||||
column: x => x.player_user_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "role_whitelists");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -900,6 +900,22 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("profile_role_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_user_id");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("role_id");
|
||||
|
||||
b.HasKey("PlayerUserId", "RoleId")
|
||||
.HasName("PK_role_whitelists");
|
||||
|
||||
b.ToTable("role_whitelists", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1623,6 +1639,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("JobWhitelists")
|
||||
.HasForeignKey("PlayerUserId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_role_whitelists_player_player_user_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -1822,6 +1851,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("AdminWatchlistsLastEdited");
|
||||
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
+1838
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RoleWhitelist : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "role_whitelists",
|
||||
columns: table => new
|
||||
{
|
||||
player_user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
role_id = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id });
|
||||
table.ForeignKey(
|
||||
name: "FK_role_whitelists_player_player_user_id",
|
||||
column: x => x.player_user_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "role_whitelists");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,6 +847,22 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("profile_role_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerUserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_user_id");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("role_id");
|
||||
|
||||
b.HasKey("PlayerUserId", "RoleId")
|
||||
.HasName("PK_role_whitelists");
|
||||
|
||||
b.ToTable("role_whitelists", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1548,6 +1564,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("JobWhitelists")
|
||||
.HasForeignKey("PlayerUserId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_role_whitelists_player_player_user_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -1747,6 +1776,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("AdminWatchlistsLastEdited");
|
||||
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Content.Server.Database
|
||||
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
||||
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
|
||||
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
||||
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -314,6 +315,13 @@ namespace Content.Server.Database
|
||||
.HasForeignKey(ban => ban.LastEditedById)
|
||||
.HasPrincipalKey(author => author.UserId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
modelBuilder.Entity<RoleWhitelist>()
|
||||
.HasOne(w => w.Player)
|
||||
.WithMany(p => p.JobWhitelists)
|
||||
.HasForeignKey(w => w.PlayerUserId)
|
||||
.HasPrincipalKey(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||
@@ -530,6 +538,7 @@ namespace Content.Server.Database
|
||||
public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
|
||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
||||
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||
}
|
||||
|
||||
[Table("whitelist")]
|
||||
@@ -1099,4 +1108,15 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
public bool Dismissed { get; set; }
|
||||
}
|
||||
|
||||
[PrimaryKey(nameof(PlayerUserId), nameof(RoleId))]
|
||||
public class RoleWhitelist
|
||||
{
|
||||
[Required, ForeignKey("Player")]
|
||||
public Guid PlayerUserId { get; set; }
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
public string RoleId { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
@@ -47,5 +48,12 @@ namespace Content.Server.Abilities.Mime
|
||||
/// </summary>
|
||||
[DataField("vowCooldown")]
|
||||
public TimeSpan VowCooldown = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowAlert = "VowOfSilence";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowBrokenAlert = "VowBroken";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Muting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Alert;
|
||||
@@ -56,7 +55,7 @@ namespace Content.Server.Abilities.Mime
|
||||
private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args)
|
||||
{
|
||||
EnsureComp<MutedComponent>(uid);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ShowAlert(uid, component.VowAlert);
|
||||
_actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid);
|
||||
//Nyano - Summary: Add Psionic Ability to Mime.
|
||||
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
|
||||
@@ -123,8 +122,8 @@ namespace Content.Server.Abilities.Mime
|
||||
mimePowers.VowBroken = true;
|
||||
mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown;
|
||||
RemComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowBroken);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity);
|
||||
}
|
||||
|
||||
@@ -146,8 +145,8 @@ namespace Content.Server.Abilities.Mime
|
||||
mimePowers.ReadyToRepent = false;
|
||||
mimePowers.VowBroken = false;
|
||||
AddComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowBroken);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
_idCard.TryChangeJobDepartment(targetId, job);
|
||||
}
|
||||
|
||||
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);
|
||||
|
||||
if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
|
||||
{
|
||||
_sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag.");
|
||||
@@ -168,8 +170,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
This current implementation is pretty shit as it logs 27 entries (27 lines) if someone decides to give themselves AA*/
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
|
||||
private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (component.ActionEntities is not {} actionEnts)
|
||||
|
||||
@@ -65,13 +65,23 @@ public sealed class BanListEui : BaseEui
|
||||
unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
|
||||
}
|
||||
|
||||
(string, int cidrMask)? ip = ("*Hidden*", 0);
|
||||
var hwid = "*Hidden*";
|
||||
|
||||
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
|
||||
{
|
||||
ip = ban.Address is { } address
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
}
|
||||
|
||||
Bans.Add(new SharedServerBan(
|
||||
ban.Id,
|
||||
ban.UserId,
|
||||
ban.Address is { } address
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null,
|
||||
ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()),
|
||||
ip,
|
||||
hwid,
|
||||
ban.BanTime.UtcDateTime,
|
||||
ban.ExpirationTime?.UtcDateTime,
|
||||
ban.Reason,
|
||||
@@ -96,13 +106,22 @@ public sealed class BanListEui : BaseEui
|
||||
unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
|
||||
}
|
||||
|
||||
(string, int cidrMask)? ip = ("*Hidden*", 0);
|
||||
var hwid = "*Hidden*";
|
||||
|
||||
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
|
||||
{
|
||||
ip = ban.Address is { } address
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
}
|
||||
RoleBans.Add(new SharedServerRoleBan(
|
||||
ban.Id,
|
||||
ban.UserId,
|
||||
ban.Address is { } address
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null,
|
||||
ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()),
|
||||
ip,
|
||||
hwid,
|
||||
ban.BanTime.UtcDateTime,
|
||||
ban.ExpirationTime?.UtcDateTime,
|
||||
ban.Reason,
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.AdminWho)]
|
||||
public sealed class AdminWhoCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "adminwho";
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceUiCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "announceui";
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class BanExemptionUpdateCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
@@ -61,7 +61,7 @@ public sealed class BanExemptionUpdateCommand : LocalizedCommands
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class BanExemptionGetCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
sealed class DSay : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class FaxUiCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "faxui";
|
||||
@@ -27,4 +27,3 @@ public sealed class FaxUiCommand : IConsoleCommand
|
||||
eui.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class JobWhitelistAddCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly JobWhitelistManager _jobWhitelist = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
public override string Command => "jobwhitelistadd";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||
("properAmount", 2),
|
||||
("currentAmount", args.Length)));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var player = args[0].Trim();
|
||||
var job = new ProtoId<JobPrototype>(args[1].Trim());
|
||||
if (!_prototypes.TryIndex(job, out var jobPrototype))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id)));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||
if (data != null)
|
||||
{
|
||||
var guid = data.UserId;
|
||||
var isWhitelisted = await _db.IsJobWhitelisted(guid, job);
|
||||
if (isWhitelisted)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted",
|
||||
("player", player),
|
||||
("jobId", job.Id),
|
||||
("jobName", jobPrototype.LocalizedName)));
|
||||
return;
|
||||
}
|
||||
|
||||
_jobWhitelist.AddWhitelist(guid, job);
|
||||
shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added",
|
||||
("player", player),
|
||||
("jobId", job.Id),
|
||||
("jobName", jobPrototype.LocalizedName)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
_players.Sessions.Select(s => s.Name),
|
||||
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
_prototypes.EnumeratePrototypes<JobPrototype>().Select(p => p.ID),
|
||||
Loc.GetString("cmd-jobwhitelist-hint-job"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class GetJobWhitelistCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
public override string Command => "jobwhitelistget";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("This command needs at least one argument.");
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var player = string.Join(' ', args).Trim();
|
||||
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||
if (data != null)
|
||||
{
|
||||
var guid = data.UserId;
|
||||
var whitelists = await _db.GetJobWhitelists(guid);
|
||||
if (whitelists.Count == 0)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for",
|
||||
("player", player),
|
||||
("jobs", string.Join(", ", whitelists))));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
_players.Sessions.Select(s => s.Name),
|
||||
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class RemoveJobWhitelistCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly JobWhitelistManager _jobWhitelist = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
public override string Command => "jobwhitelistremove";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||
("properAmount", 2),
|
||||
("currentAmount", args.Length)));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var player = args[0].Trim();
|
||||
var job = new ProtoId<JobPrototype>(args[1].Trim());
|
||||
if (!_prototypes.TryIndex(job, out var jobPrototype))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job)));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||
if (data != null)
|
||||
{
|
||||
var guid = data.UserId;
|
||||
var isWhitelisted = await _db.IsJobWhitelisted(guid, job);
|
||||
if (!isWhitelisted)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted",
|
||||
("player", player),
|
||||
("jobId", job.Id),
|
||||
("jobName", jobPrototype.LocalizedName)));
|
||||
return;
|
||||
}
|
||||
|
||||
_jobWhitelist.RemoveWhitelist(guid, job);
|
||||
shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed",
|
||||
("player", player),
|
||||
("jobId", job.Id),
|
||||
("jobName", jobPrototype.LocalizedName)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
_players.Sessions.Select(s => s.Name),
|
||||
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
_prototypes.EnumeratePrototypes<JobPrototype>().Select(p => p.ID),
|
||||
Loc.GetString("cmd-jobwhitelist-hint-job"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user