Merge pull request #234 from crystallpunk-14/revert-233-revert-230-ed-09-06-2024-upstream
Revert "Revert "Ed 09 06 2024 upstream""
This commit is contained in:
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -15,6 +15,7 @@
|
||||
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404
|
||||
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
|
||||
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
|
||||
/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
|
||||
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
|
||||
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
|
||||
|
||||
@@ -23,6 +24,7 @@
|
||||
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
|
||||
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
|
||||
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
|
||||
/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
|
||||
/Content.*/Body/ @DrSmugleaf
|
||||
/Content.YAMLLinter @DrSmugleaf
|
||||
/Content.Shared/Damage/ @DrSmugleaf
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -58,15 +56,20 @@ public class PvsBenchmark
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
|
||||
_sys = _entMan.System<SharedTransformSystem>();
|
||||
|
||||
SetupAsync().Wait();
|
||||
}
|
||||
|
||||
private async Task SetupAsync()
|
||||
{
|
||||
// Spawn the map
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
await _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);
|
||||
}).Wait();
|
||||
});
|
||||
|
||||
// Get list of ghost warp positions
|
||||
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
|
||||
@@ -76,17 +79,19 @@ public class PvsBenchmark
|
||||
|
||||
Array.Resize(ref _players, PlayerCount);
|
||||
|
||||
// Spawn "Players".
|
||||
_pair.Server.WaitPost(() =>
|
||||
// Spawn "Players"
|
||||
_players = await _pair.Server.AddDummySessions(PlayerCount);
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
var mind = _pair.Server.System<MindSystem>();
|
||||
for (var i = 0; i < PlayerCount; i++)
|
||||
{
|
||||
var pos = _spawns[i % _spawns.Length];
|
||||
var uid =_entMan.SpawnEntity("MobHuman", pos);
|
||||
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
|
||||
_players[i] = new DummySession{AttachedEntity = uid};
|
||||
mind.ControlMob(_players[i].UserId, uid);
|
||||
}
|
||||
}).Wait();
|
||||
});
|
||||
|
||||
// Repeatedly move players around so that they "explore" the map and see lots of entities.
|
||||
// This will populate their PVS data with out-of-view entities.
|
||||
@@ -168,20 +173,4 @@ public class PvsBenchmark
|
||||
}).Wait();
|
||||
_pair.Server.PvsTick(_players);
|
||||
}
|
||||
|
||||
private sealed class DummySession : ICommonSession
|
||||
{
|
||||
public SessionStatus Status => SessionStatus.InGame;
|
||||
public EntityUid? AttachedEntity {get; set; }
|
||||
public NetUserId UserId => default;
|
||||
public string Name => string.Empty;
|
||||
public short Ping => default;
|
||||
public INetChannel Channel { get; set; } = default!;
|
||||
public LoginType AuthType => default;
|
||||
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
|
||||
public DateTime ConnectedTime { get; set; }
|
||||
public SessionState State => default!;
|
||||
public SessionData Data => default!;
|
||||
public bool ClientSide { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
|
||||
Text="{Loc Object type:}" />
|
||||
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
|
||||
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
</BoxContainer>
|
||||
<cc:HSeparator/>
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Name="ObjectList">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<ot:ObjectsTabHeader Name="ListHeader"/>
|
||||
<cc:HSeparator/>
|
||||
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Client.Station;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly List<ObjectsTabEntry> _objects = new();
|
||||
private List<ObjectsTabSelection> _selections = new();
|
||||
private readonly List<ObjectsTabSelection> _selections = new();
|
||||
private bool _ascending = false; // Set to false for descending order by default
|
||||
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
|
||||
public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
// Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
|
||||
// OR
|
||||
// I can do this.
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
|
||||
|
||||
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
|
||||
private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
|
||||
private TimeSpan _nextUpdate;
|
||||
|
||||
public ObjectsTab()
|
||||
{
|
||||
@@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control
|
||||
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
|
||||
}
|
||||
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
SearchList.GenerateItem += GenerateButton;
|
||||
SearchList.DataFilterCondition += DataFilterCondition;
|
||||
|
||||
RefreshObjectList();
|
||||
// Set initial selection and refresh the list to apply the initial sort order
|
||||
var defaultSelection = ObjectsTabSelection.Grids;
|
||||
ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
|
||||
RefreshObjectList(defaultSelection); // Refresh the list with the default selection
|
||||
|
||||
// Initialize the next update time
|
||||
_nextUpdate = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_timing.CurTime < _nextUpdate)
|
||||
return;
|
||||
|
||||
_nextUpdate = _timing.CurTime + _updateFrequency;
|
||||
|
||||
RefreshObjectList();
|
||||
}
|
||||
|
||||
@@ -81,32 +107,72 @@ public sealed partial class ObjectsTab : Control
|
||||
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
|
||||
}
|
||||
|
||||
foreach (var control in _objects)
|
||||
entities.Sort((a, b) =>
|
||||
{
|
||||
ObjectList.RemoveChild(control);
|
||||
var valueA = GetComparableValue(a, _headerClicked);
|
||||
var valueB = GetComparableValue(b, _headerClicked);
|
||||
return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
|
||||
});
|
||||
|
||||
var listData = new List<ObjectsListData>();
|
||||
for (int index = 0; index < entities.Count; index++)
|
||||
{
|
||||
var info = entities[index];
|
||||
listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
|
||||
}
|
||||
|
||||
_objects.Clear();
|
||||
|
||||
foreach (var (name, nent) in entities)
|
||||
{
|
||||
var ctrl = new ObjectsTabEntry(name, nent);
|
||||
_objects.Add(ctrl);
|
||||
ObjectList.AddChild(ctrl);
|
||||
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
|
||||
}
|
||||
SearchList.PopulateList(listData);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_timing.CurTime < _nextUpdate)
|
||||
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
|
||||
return;
|
||||
|
||||
// I do not care for precision.
|
||||
_nextUpdate = _timing.CurTime + _updateFrequency;
|
||||
var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
button.ToolTip = $"{info.Name}, {info.Entity}";
|
||||
|
||||
// Add key binding event handler
|
||||
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
button.AddChild(entry);
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
{
|
||||
if (listData is not ObjectsListData { FilteringString: var filteringString })
|
||||
return false;
|
||||
|
||||
// If the filter is empty, do not filter out any entries
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
|
||||
{
|
||||
return header switch
|
||||
{
|
||||
ObjectsTabHeader.Header.ObjectName => entity.Name,
|
||||
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
|
||||
_ => entity.Name
|
||||
};
|
||||
}
|
||||
|
||||
private void HeaderClicked(ObjectsTabHeader.Header header)
|
||||
{
|
||||
if (_headerClicked == header)
|
||||
{
|
||||
_ascending = !_ascending;
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerClicked = header;
|
||||
_ascending = true;
|
||||
}
|
||||
|
||||
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
|
||||
RefreshObjectList();
|
||||
}
|
||||
|
||||
@@ -118,3 +184,4 @@ public sealed partial class ObjectsTab : Control
|
||||
}
|
||||
}
|
||||
|
||||
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : 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">
|
||||
@@ -14,4 +14,4 @@
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</ContainerButton>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTabEntry : ContainerButton
|
||||
public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity AssocEntity;
|
||||
|
||||
public ObjectsTabEntry(string name, NetEntity nent)
|
||||
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
AssocEntity = nent;
|
||||
EIDLabel.Text = nent.ToString();
|
||||
NameLabel.Text = name;
|
||||
BackgroundColorPanel.PanelOverride = styleBox;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<Label Name="ObjectNameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-object-name}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="EntityIDLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-entity-id}"
|
||||
MouseFilter="Pass"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,86 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTabHeader : Control
|
||||
{
|
||||
public event Action<Header>? OnHeaderClicked;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
private const string ArrowDown = "↓";
|
||||
|
||||
public ObjectsTabHeader()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
|
||||
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
|
||||
}
|
||||
|
||||
public Label GetHeader(Header header)
|
||||
{
|
||||
return header switch
|
||||
{
|
||||
Header.ObjectName => ObjectNameLabel,
|
||||
Header.EntityID => EntityIDLabel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||
};
|
||||
}
|
||||
|
||||
public void ResetHeaderText()
|
||||
{
|
||||
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
|
||||
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
|
||||
}
|
||||
|
||||
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
|
||||
{
|
||||
ResetHeaderText();
|
||||
var arrow = ascending ? ArrowUp : ArrowDown;
|
||||
GetHeader(headerClicked).Text += $" {arrow}";
|
||||
}
|
||||
|
||||
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnHeaderClicked?.Invoke(header);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.ObjectName);
|
||||
}
|
||||
|
||||
private void EntityIDClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.EntityID);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
|
||||
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Header
|
||||
{
|
||||
ObjectName,
|
||||
EntityID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
|
||||
}
|
||||
|
||||
#region Antag Overlay
|
||||
@@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
_players = players;
|
||||
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(players);
|
||||
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
|
||||
sortedPlayers.Sort(Compare);
|
||||
|
||||
UpdateHeaderSymbols();
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Content.Client.Construction
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
@@ -195,9 +196,8 @@ namespace Content.Client.Construction
|
||||
if (GhostPresent(loc))
|
||||
return false;
|
||||
|
||||
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
|
||||
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
|
||||
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
|
||||
return false;
|
||||
|
||||
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
|
||||
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
||||
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
||||
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10"/>
|
||||
|
||||
@@ -23,7 +23,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
private readonly ItemSlotsSystem _itemSlots;
|
||||
private readonly FlatpackSystem _flatpack;
|
||||
private readonly MaterialStorageSystem _materialStorage;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
|
||||
@@ -31,7 +30,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
|
||||
|
||||
private EntityUid? _currentBoard = EntityUid.Invalid;
|
||||
private EntityUid? _machinePreview;
|
||||
|
||||
public event Action? PackButtonPressed;
|
||||
|
||||
@@ -43,7 +41,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
_itemSlots = _entityManager.System<ItemSlotsSystem>();
|
||||
_flatpack = _entityManager.System<FlatpackSystem>();
|
||||
_materialStorage = _entityManager.System<MaterialStorageSystem>();
|
||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||
|
||||
_owner = uid;
|
||||
|
||||
@@ -57,17 +54,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
|
||||
{
|
||||
_machinePreview = null;
|
||||
MachineSprite.SetEntity(_machinePreview);
|
||||
}
|
||||
|
||||
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
|
||||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
|
||||
return;
|
||||
|
||||
MachineBoardComponent? machineBoardComp = null;
|
||||
if (flatpacker.Packing)
|
||||
{
|
||||
PackButton.Disabled = true;
|
||||
@@ -75,11 +65,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
else if (_currentBoard != null)
|
||||
{
|
||||
Dictionary<string, int> cost;
|
||||
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
|
||||
machineBoardComp.Prototype is not null)
|
||||
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp))
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
||||
else
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
||||
|
||||
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
|
||||
}
|
||||
@@ -87,9 +76,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
if (_currentBoard == itemSlot.Item)
|
||||
return;
|
||||
|
||||
if (_machinePreview != null)
|
||||
_entityManager.DeleteEntity(_machinePreview);
|
||||
|
||||
_currentBoard = itemSlot.Item;
|
||||
CostHeaderLabel.Visible = _currentBoard != null;
|
||||
InsertLabel.Visible = _currentBoard == null;
|
||||
@@ -99,35 +85,32 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
string? prototype = null;
|
||||
Dictionary<string, int>? cost = null;
|
||||
|
||||
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
|
||||
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var newMachineBoardComp))
|
||||
{
|
||||
prototype = machineBoardComp.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
||||
prototype = newMachineBoardComp.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
|
||||
}
|
||||
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
|
||||
{
|
||||
prototype = computerBoard.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
||||
}
|
||||
|
||||
if (prototype is not null && cost is not null)
|
||||
{
|
||||
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
|
||||
_machinePreview = _entityManager.Spawn(proto.ID);
|
||||
_spriteSystem.ForceUpdate(_machinePreview.Value);
|
||||
MachineSprite.SetPrototype(prototype);
|
||||
MachineNameLabel.SetMessage(proto.Name);
|
||||
CostLabel.SetMarkup(GetCostString(cost));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
|
||||
MachineSprite.SetPrototype(NoBoardEffectId);
|
||||
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
|
||||
MachineNameLabel.SetMessage(" ");
|
||||
PackButton.Disabled = true;
|
||||
}
|
||||
|
||||
MachineSprite.SetEntity(_machinePreview);
|
||||
}
|
||||
|
||||
private string GetCostString(Dictionary<string, int> costs)
|
||||
@@ -149,7 +132,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
("amount", amountText),
|
||||
("material", Loc.GetString(matProto.Name)));
|
||||
|
||||
msg.AddMarkup(text);
|
||||
msg.TryAddMarkup(text, out _);
|
||||
|
||||
if (i != orderedCosts.Length - 1)
|
||||
msg.PushNewline();
|
||||
@@ -157,12 +140,4 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
|
||||
return msg.ToMarkup();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
|
||||
_entityManager.DeleteEntity(_machinePreview);
|
||||
_machinePreview = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ using Content.Client.Administration.Managers;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Flash;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Input;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Launcher;
|
||||
@@ -53,7 +51,6 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly IScreenshotHook _screenshotHook = default!;
|
||||
[Dependency] private readonly FullscreenHook _fullscreenHook = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelogManager = default!;
|
||||
[Dependency] private readonly RulesManager _rulesManager = default!;
|
||||
[Dependency] private readonly ViewportManager _viewportManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -126,7 +123,6 @@ namespace Content.Client.Entry
|
||||
_screenshotHook.Initialize();
|
||||
_fullscreenHook.Initialize();
|
||||
_changelogManager.Initialize();
|
||||
_rulesManager.Initialize();
|
||||
_viewportManager.Initialize();
|
||||
_ghostKick.Initialize();
|
||||
_extendedDisconnectInformation.Initialize();
|
||||
|
||||
@@ -4,10 +4,12 @@ using Content.Client.Lobby;
|
||||
using Content.Client.RoundEnd;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameWindow;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.GameTicking.Managers
|
||||
{
|
||||
@@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
|
||||
private Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> _jobsAvailable = new();
|
||||
private Dictionary<NetEntity, string> _stationNames = new();
|
||||
|
||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||
@@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers
|
||||
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
||||
[ViewVariables] public new bool Paused { get; private set; }
|
||||
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> JobsAvailable => _jobsAvailable;
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
||||
|
||||
public event Action? InfoBlobUpdated;
|
||||
public event Action? LobbyStatusUpdated;
|
||||
public event Action? LobbyLateJoinStatusUpdated;
|
||||
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? LobbyJobsAvailableUpdated;
|
||||
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>>? LobbyJobsAvailableUpdated;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
// reading the console. E.g., logs like this one could leak the nuke station/grid:
|
||||
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
|
||||
#if !DEBUG
|
||||
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||
EntityManager.System<SharedMapSystem>().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Components;
|
||||
|
||||
@@ -13,9 +14,8 @@ public sealed partial class GuideHelpComponent : Component
|
||||
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
|
||||
/// selected guidebook.
|
||||
/// </summary>
|
||||
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)]
|
||||
[ViewVariables]
|
||||
public List<string> Guides = new();
|
||||
[DataField(required: true)]
|
||||
public List<ProtoId<GuideEntryPrototype>> Guides = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically include the children of the given guides.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="750 700"
|
||||
SetSize="850 700"
|
||||
MinSize="100 200"
|
||||
Resizable="True"
|
||||
Title="{Loc 'guidebook-window-title'}">
|
||||
@@ -18,12 +18,16 @@
|
||||
Name="SearchBar"
|
||||
PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 5 10 5">
|
||||
Margin="0 5 10 5">
|
||||
</LineEdit>
|
||||
</BoxContainer>
|
||||
<BoxContainer Access="Internal" Name="ReturnContainer" Orientation="Horizontal" HorizontalAlignment="Right" Visible="False">
|
||||
<Button Name="HomeButton" Text="{Loc 'ui-rules-button-home'}" Margin="0 0 10 0"/>
|
||||
</BoxContainer>
|
||||
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Control>
|
||||
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
|
||||
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False">
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.RichText;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Controls.FancyTree;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||
|
||||
private Dictionary<string, GuideEntry> _entries = new();
|
||||
private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
|
||||
|
||||
public GuidebookWindow()
|
||||
{
|
||||
@@ -37,7 +37,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
private void OnSelectionChanged(TreeItem? item)
|
||||
{
|
||||
if (item != null && item.Metadata is GuideEntry entry)
|
||||
{
|
||||
ShowGuide(entry);
|
||||
|
||||
var isRulesEntry = entry.RuleEntry;
|
||||
ReturnContainer.Visible = isRulesEntry;
|
||||
HomeButton.OnPressed += _ => ShowGuide(entry);
|
||||
}
|
||||
else
|
||||
ClearSelectedGuide();
|
||||
}
|
||||
@@ -69,10 +75,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
}
|
||||
|
||||
public void UpdateGuides(
|
||||
Dictionary<string, GuideEntry> entries,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
string? selected = null)
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> entries,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
_entries = entries;
|
||||
RepopulateTree(rootEntries, forceRoot);
|
||||
@@ -98,11 +104,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<GuideEntry> GetSortedEntries(List<string>? rootEntries)
|
||||
private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototype>>? rootEntries)
|
||||
{
|
||||
if (rootEntries == null)
|
||||
{
|
||||
HashSet<string> entries = new(_entries.Keys);
|
||||
HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
|
||||
foreach (var entry in _entries.Values)
|
||||
{
|
||||
if (entry.Children.Count > 0)
|
||||
@@ -111,7 +117,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
.Select(childId => _entries[childId])
|
||||
.OrderBy(childEntry => childEntry.Priority)
|
||||
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
|
||||
.Select(childEntry => childEntry.Id)
|
||||
.Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
|
||||
.ToList();
|
||||
|
||||
entry.Children = sortedChildren;
|
||||
@@ -127,13 +133,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
|
||||
}
|
||||
|
||||
private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
|
||||
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
|
||||
{
|
||||
Tree.Clear();
|
||||
|
||||
HashSet<string> addedEntries = new();
|
||||
HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();
|
||||
|
||||
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries);
|
||||
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
|
||||
foreach (var entry in GetSortedEntries(roots))
|
||||
{
|
||||
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter
|
||||
@@ -142,17 +148,23 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
Tree.SetAllExpanded(true);
|
||||
}
|
||||
|
||||
private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
|
||||
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
|
||||
{
|
||||
if (!_entries.TryGetValue(id, out var entry))
|
||||
return null;
|
||||
|
||||
if (!addedEntries.Add(id))
|
||||
{
|
||||
// TODO GUIDEBOOK Maybe allow duplicate entries?
|
||||
// E.g., for adding medicine under both chemicals & the chemist job
|
||||
Logger.Error($"Adding duplicate guide entry: {id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var rulesProto = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
|
||||
if (entry.RuleEntry && entry.Id != rulesProto.Id)
|
||||
return null;
|
||||
|
||||
var item = Tree.AddItem(parent);
|
||||
item.Metadata = entry;
|
||||
var name = Loc.GetString(entry.Name);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Shared.Guidebook;
|
||||
using Pidgin;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Sandboxing;
|
||||
using static Pidgin.Parser;
|
||||
@@ -13,7 +16,9 @@ namespace Content.Client.Guidebook;
|
||||
/// </summary>
|
||||
public sealed partial class DocumentParsingManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
|
||||
|
||||
private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
|
||||
@@ -37,6 +42,21 @@ public sealed partial class DocumentParsingManager
|
||||
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
|
||||
{
|
||||
if (!_prototype.TryIndex(entryId, out var entry))
|
||||
return false;
|
||||
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
|
||||
{
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, string text, bool log = true)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Client.Light;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Speech;
|
||||
@@ -34,7 +35,12 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!; //CrystallPunk guidebook filter
|
||||
|
||||
public event Action<List<string>, List<string>?, string?, bool, string?>? OnGuidebookOpen;
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>,
|
||||
List<ProtoId<GuideEntryPrototype>>?,
|
||||
ProtoId<GuideEntryPrototype>?,
|
||||
bool,
|
||||
ProtoId<GuideEntryPrototype>?>? OnGuidebookOpen;
|
||||
|
||||
public const string GuideEmbedTag = "GuideEmbeded";
|
||||
|
||||
private EntityUid _defaultUser;
|
||||
@@ -99,7 +105,7 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenHelp(List<string> guides)
|
||||
public void OpenHelp(List<ProtoId<GuideEntryPrototype>> guides)
|
||||
{
|
||||
OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Shared.Info;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
public sealed class InfoSystem : EntitySystem
|
||||
{
|
||||
public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules.");
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<RulesMessage>(OnRulesReceived);
|
||||
Log.Debug("Requested server info.");
|
||||
RaiseNetworkEvent(new RequestRulesMessage());
|
||||
}
|
||||
|
||||
private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
Log.Debug("Received server rules.");
|
||||
Rules = message;
|
||||
_rules.UpdateRules();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.Client.Info
|
||||
@@ -12,7 +10,6 @@ namespace Content.Client.Info
|
||||
public sealed class RulesAndInfoWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
|
||||
public RulesAndInfoWindow()
|
||||
{
|
||||
@@ -22,8 +19,14 @@ namespace Content.Client.Info
|
||||
|
||||
var rootContainer = new TabContainer();
|
||||
|
||||
var rulesList = new Info();
|
||||
var tutorialList = new Info();
|
||||
var rulesList = new RulesControl
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
var tutorialList = new Info
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
rootContainer.AddChild(rulesList);
|
||||
rootContainer.AddChild(tutorialList);
|
||||
@@ -31,7 +34,6 @@ namespace Content.Client.Info
|
||||
TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules"));
|
||||
TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial"));
|
||||
|
||||
AddSection(rulesList, _rules.RulesSection());
|
||||
PopulateTutorial(tutorialList);
|
||||
|
||||
Contents.AddChild(rootContainer);
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Name="InfoContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="2 2 0 0"
|
||||
SeparationOverride="10"
|
||||
Access="Public"/>
|
||||
<BoxContainer xmlns="https://spacestation14.io" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Control HorizontalExpand="True" VerticalExpand="True" HorizontalAlignment="Stretch">
|
||||
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="RulesContainer" VerticalExpand="True" Margin="0 0 5 0"/>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Margin="0 0 15 0" HorizontalExpand="True" HorizontalAlignment="Right">
|
||||
<Button Name="BackButton"
|
||||
Text="{Loc 'ui-rules-button-back'}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"/>
|
||||
<Control MinWidth="5"/>
|
||||
<Button Name="HomeButton"
|
||||
Text="{Loc 'ui-rules-button-home'}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,22 +1,54 @@
|
||||
using System.IO;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.RichText;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RulesControl : BoxContainer
|
||||
public sealed partial class RulesControl : BoxContainer, ILinkClickHandler
|
||||
{
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||
|
||||
private string? _currentEntry;
|
||||
private readonly Stack<string> _priorEntries = new();
|
||||
|
||||
public RulesControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
AddChild(_rules.RulesSection());
|
||||
|
||||
SetGuide();
|
||||
|
||||
HomeButton.OnPressed += _ => SetGuide();
|
||||
|
||||
BackButton.OnPressed += _ => SetGuide(_priorEntries.Pop(), false);
|
||||
}
|
||||
|
||||
public void HandleClick(string link)
|
||||
{
|
||||
SetGuide(link);
|
||||
}
|
||||
|
||||
private void SetGuide(ProtoId<GuideEntryPrototype>? entry = null, bool addToPrior = true)
|
||||
{
|
||||
var coreEntry = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
|
||||
entry ??= coreEntry;
|
||||
|
||||
Scroll.SetScrollValue(default);
|
||||
RulesContainer.Children.Clear();
|
||||
if (!_parsingMan.TryAddMarkup(RulesContainer, entry.Value))
|
||||
return;
|
||||
|
||||
if (addToPrior && _currentEntry != null)
|
||||
_priorEntries.Push(_currentEntry);
|
||||
_currentEntry = entry.Value;
|
||||
|
||||
HomeButton.Visible = entry.Value != coreEntry.Id;
|
||||
BackButton.Visible = _priorEntries.Count != 0 && _priorEntries.Peek() != entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
public sealed class RulesManager : SharedRulesManager
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
|
||||
private InfoSection rulesSection = new InfoSection("", "", false);
|
||||
private bool _shouldShowRules = false;
|
||||
|
||||
private RulesPopup? _activePopup;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>(OnShouldShowRules);
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
|
||||
_stateManager.OnStateChanged += OnStateChanged;
|
||||
|
||||
_consoleHost.RegisterCommand("fuckrules", "", "", (_, _, _) =>
|
||||
{
|
||||
OnAcceptPressed();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnShouldShowRules(ShouldShowRulesPopupMessage message)
|
||||
{
|
||||
_shouldShowRules = true;
|
||||
}
|
||||
|
||||
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
|
||||
{
|
||||
ShowRules(message.PopupTime);
|
||||
}
|
||||
|
||||
private void OnStateChanged(StateChangedEventArgs args)
|
||||
{
|
||||
if (args.NewState is not (GameplayState or LobbyState))
|
||||
return;
|
||||
|
||||
if (!_shouldShowRules)
|
||||
return;
|
||||
|
||||
_shouldShowRules = false;
|
||||
|
||||
ShowRules(_configManager.GetCVar(CCVars.RulesWaitTime));
|
||||
}
|
||||
|
||||
private void ShowRules(float time)
|
||||
{
|
||||
if (_activePopup != null)
|
||||
return;
|
||||
|
||||
_activePopup = new RulesPopup
|
||||
{
|
||||
Timer = time
|
||||
};
|
||||
|
||||
_activePopup.OnQuitPressed += OnQuitPressed;
|
||||
_activePopup.OnAcceptPressed += OnAcceptPressed;
|
||||
_userInterfaceManager.WindowRoot.AddChild(_activePopup);
|
||||
LayoutContainer.SetAnchorPreset(_activePopup, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
_consoleHost.ExecuteCommand("quit");
|
||||
}
|
||||
|
||||
private void OnAcceptPressed()
|
||||
{
|
||||
_netManager.ClientSendMessage(new RulesAcceptedMessage());
|
||||
|
||||
_activePopup?.Orphan();
|
||||
_activePopup = null;
|
||||
}
|
||||
|
||||
public void UpdateRules()
|
||||
{
|
||||
var rules = _sysMan.GetEntitySystem<InfoSystem>().Rules;
|
||||
rulesSection.SetText(rules.Title, rules.Text, true);
|
||||
}
|
||||
|
||||
public Control RulesSection()
|
||||
{
|
||||
rulesSection = new InfoSection("", "", false);
|
||||
UpdateRules();
|
||||
return rulesSection;
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,20 @@
|
||||
MouseFilter="Stop">
|
||||
<parallax:ParallaxControl />
|
||||
<Control VerticalExpand="True"
|
||||
MaxWidth="650">
|
||||
MaxWidth="800"
|
||||
MaxHeight="900">
|
||||
<PanelContainer StyleClasses="windowPanel" />
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10">
|
||||
<info:RulesControl />
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10" Margin="10 10 5 10">
|
||||
<info:RulesControl/>
|
||||
<Label Name="WaitLabel" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="AcceptButton"
|
||||
Text="{Loc 'ui-rules-accept'}"
|
||||
Disabled="True" />
|
||||
<Button Name="QuitButton"
|
||||
StyleClasses="Caution"
|
||||
Text="{Loc 'ui-escape-quit'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</Control>
|
||||
</Control>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Client.Chat.Managers;
|
||||
using Content.Client.Clickable;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
@@ -41,7 +40,6 @@ namespace Content.Client.IoC
|
||||
collection.Register<EuiManager, EuiManager>();
|
||||
collection.Register<IVoteManager, VoteManager>();
|
||||
collection.Register<ChangelogManager, ChangelogManager>();
|
||||
collection.Register<RulesManager, RulesManager>();
|
||||
collection.Register<ViewportManager, ViewportManager>();
|
||||
collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
|
||||
collection.Register<GhostKickManager>();
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace Content.Client.LateJoin
|
||||
}
|
||||
}
|
||||
|
||||
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
|
||||
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
|
||||
{
|
||||
foreach (var stationEntries in updatedJobs)
|
||||
{
|
||||
@@ -337,10 +337,10 @@ namespace Content.Client.LateJoin
|
||||
public Label JobLabel { get; }
|
||||
public string JobId { get; }
|
||||
public string JobLocalisedName { get; }
|
||||
public uint? Amount { get; private set; }
|
||||
public int? Amount { get; private set; }
|
||||
private bool _initialised = false;
|
||||
|
||||
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
|
||||
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
|
||||
{
|
||||
JobLabel = jobLabel;
|
||||
JobId = jobId;
|
||||
@@ -350,7 +350,7 @@ namespace Content.Client.LateJoin
|
||||
_initialised = true;
|
||||
}
|
||||
|
||||
public void RefreshLabel(uint? amount)
|
||||
public void RefreshLabel(int? amount)
|
||||
{
|
||||
if (Amount == amount && _initialised)
|
||||
{
|
||||
|
||||
@@ -207,8 +207,11 @@ namespace Content.Client.Light
|
||||
|
||||
public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity<RgbLightControllerComponent> rgb)
|
||||
{
|
||||
var delta = (float)(curTime - offset).TotalSeconds;
|
||||
var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f);
|
||||
var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1;
|
||||
return Color.FromHsv(new Vector4(
|
||||
(float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1),
|
||||
MathF.Abs(hue),
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Client.Lobby.UI;
|
||||
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
@@ -232,6 +234,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_requirements,
|
||||
_markings);
|
||||
|
||||
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
|
||||
|
||||
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
|
||||
|
||||
_characterSetup.CloseButton.OnPressed += _ =>
|
||||
@@ -302,7 +306,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
{
|
||||
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
|
||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
|
||||
}
|
||||
|
||||
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
|
||||
|
||||
@@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton
|
||||
.LoadProfileEntity(humanoid, null, true);
|
||||
|
||||
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
if (highPriorityJob != null)
|
||||
if (highPriorityJob != default)
|
||||
{
|
||||
var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
|
||||
var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
|
||||
description = $"{description}\n{jobName}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Lobby.UI.Loadouts;
|
||||
using Content.Client.Lobby.UI.Roles;
|
||||
@@ -13,13 +12,13 @@ using Content.Shared._CP14.Humanoid;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.Traits;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -97,6 +96,8 @@ namespace Content.Client.Lobby.UI
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultSpeciesGuidebook = "Species";
|
||||
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
public HumanoidProfileEditor(
|
||||
@@ -616,13 +617,15 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var title = Loc.GetString(antag.Name);
|
||||
var description = Loc.GetString(antag.Objective);
|
||||
selector.Setup(items, title, 250, description);
|
||||
selector.Setup(items, title, 250, description, guides: antag.Guides);
|
||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||
|
||||
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
|
||||
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
||||
if (!_requirements.CheckRoleTime(requirements, out var reason))
|
||||
{
|
||||
selector.LockRequirements(reason);
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
@@ -754,6 +757,10 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// TODO GUIDEBOOK
|
||||
// make the species guide book a field on the species prototype.
|
||||
// I.e., do what jobs/antags do.
|
||||
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var page = DefaultSpeciesGuidebook;
|
||||
@@ -762,10 +769,10 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
{
|
||||
var dict = new Dictionary<string, GuideEntry>();
|
||||
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
//TODO: Don't close the guidebook if its already open, just go to the correct page
|
||||
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
|
||||
guidebookController.OpenGuidebook(dict, includeChildren:true, selected: page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,6 +867,7 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var icon = new TextureRect
|
||||
{
|
||||
@@ -868,7 +876,7 @@ namespace Content.Client.Lobby.UI
|
||||
};
|
||||
var jobIcon = _prototypeManager.Index(job.Icon);
|
||||
icon.Texture = jobIcon.Icon.Frame0();
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
||||
|
||||
if (!_requirements.IsAllowed(job, out var reason))
|
||||
{
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Orientation="Horizontal">
|
||||
<Label Name="TitleLabel"
|
||||
Margin="5 0"
|
||||
MouseFilter="Stop"/>
|
||||
<BoxContainer Name="OptionsContainer"
|
||||
SetWidth="400"/>
|
||||
<Label Name="TitleLabel"
|
||||
Margin="5 0"
|
||||
MouseFilter="Stop"/>
|
||||
|
||||
<!--21 was the height of OptionsContainer at the time that this button was added. So I am limiting the texture to 21x21-->
|
||||
<Control SetSize="21 21">
|
||||
<TextureButton Name="Help" StyleClasses="HelpButton"/>
|
||||
</Control>
|
||||
<BoxContainer Name="OptionsContainer"
|
||||
SetWidth="400"/>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Roles;
|
||||
@@ -17,8 +19,10 @@ public sealed partial class RequirementsSelector : BoxContainer
|
||||
{
|
||||
private readonly RadioOptions<int> _options;
|
||||
private readonly StripeBack _lockStripe;
|
||||
private List<ProtoId<GuideEntryPrototype>>? _guides;
|
||||
|
||||
public event Action<int>? OnSelected;
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
public int Selected => _options.SelectedId;
|
||||
|
||||
@@ -60,18 +64,33 @@ public sealed partial class RequirementsSelector : BoxContainer
|
||||
requirementsLabel
|
||||
}
|
||||
};
|
||||
|
||||
Help.OnPressed += _ =>
|
||||
{
|
||||
if (_guides != null)
|
||||
OnOpenGuidebook?.Invoke(_guides);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually adds the controls.
|
||||
/// </summary>
|
||||
public void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
|
||||
public void Setup(
|
||||
(string, int)[] items,
|
||||
string title,
|
||||
int titleSize,
|
||||
string? description,
|
||||
TextureRect? icon = null,
|
||||
List<ProtoId<GuideEntryPrototype>>? guides = null)
|
||||
{
|
||||
foreach (var (text, value) in items)
|
||||
{
|
||||
_options.AddItem(Loc.GetString(text), value);
|
||||
}
|
||||
|
||||
Help.Visible = guides != null;
|
||||
_guides = guides;
|
||||
|
||||
TitleLabel.Text = title;
|
||||
TitleLabel.MinSize = new Vector2(titleSize, 0f);
|
||||
TitleLabel.ToolTip = description;
|
||||
|
||||
@@ -106,7 +106,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
||||
if (player == null)
|
||||
return true;
|
||||
|
||||
return CheckRoleTime(job.Requirements, out reason);
|
||||
return CheckRoleTime(job, out reason);
|
||||
}
|
||||
|
||||
public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
|
||||
return CheckRoleTime(reqs, out reason);
|
||||
}
|
||||
|
||||
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
@@ -27,6 +28,8 @@ public sealed partial class LawDisplay : Control
|
||||
var identifier = law.LawIdentifierOverride ?? $"{law.Order}";
|
||||
var lawIdentifier = Loc.GetString("laws-ui-law-header", ("id", identifier));
|
||||
var lawDescription = Loc.GetString(law.LawString);
|
||||
var lawIdentifierPlaintext = FormattedMessage.RemoveMarkupPermissive(lawIdentifier);
|
||||
var lawDescriptionPlaintext = FormattedMessage.RemoveMarkupPermissive(lawDescription);
|
||||
|
||||
LawNumberLabel.SetMarkup(lawIdentifier);
|
||||
LawLabel.SetMessage(lawDescription);
|
||||
@@ -46,7 +49,7 @@ public sealed partial class LawDisplay : Control
|
||||
|
||||
localButton.OnPressed += _ =>
|
||||
{
|
||||
_chatManager.SendMessage($"{lawIdentifier}: {lawDescription}", ChatSelectChannel.Local);
|
||||
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Local);
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(localButton);
|
||||
@@ -73,9 +76,9 @@ public sealed partial class LawDisplay : Control
|
||||
switch (radioChannel)
|
||||
{
|
||||
case SharedChatSystem.CommonChannel:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
default:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ namespace Content.Client.Stylesheets
|
||||
public const string StyleClassLabelSmall = "LabelSmall";
|
||||
public const string StyleClassButtonBig = "ButtonBig";
|
||||
|
||||
public const string StyleClassButtonHelp = "HelpButton";
|
||||
|
||||
public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
|
||||
public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution";
|
||||
public const string StyleClassPopupMessageMedium = "PopupMessageMedium";
|
||||
@@ -1346,6 +1348,10 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
|
||||
}),
|
||||
|
||||
Element<TextureButton>()
|
||||
.Class(StyleClassButtonHelp)
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
|
||||
|
||||
// Labels ---
|
||||
Element<Label>().Class(StyleClassLabelBig)
|
||||
.Prop(Label.StylePropertyFont, notoSans16),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -32,8 +33,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
set => WindowTitle.Text = value;
|
||||
}
|
||||
|
||||
private List<string>? _helpGuidebookIds;
|
||||
public List<string>? HelpGuidebookIds
|
||||
private List<ProtoId<GuideEntryPrototype>>? _helpGuidebookIds;
|
||||
public List<ProtoId<GuideEntryPrototype>>? HelpGuidebookIds
|
||||
{
|
||||
get => _helpGuidebookIds;
|
||||
set
|
||||
|
||||
@@ -289,6 +289,10 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
if (_action.IconOn != null)
|
||||
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
|
||||
else if (_action.Icon != null)
|
||||
SetActionIcon(_spriteSys.Frame0(_action.Icon));
|
||||
else
|
||||
SetActionIcon(null);
|
||||
|
||||
if (_action.BackgroundOn != null)
|
||||
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
|
||||
|
||||
@@ -198,9 +198,12 @@ public sealed class AdminUIController : UIController,
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args)
|
||||
private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data)
|
||||
{
|
||||
var uid = entry.AssocEntity;
|
||||
if (data is not ObjectsListData { Info: var info })
|
||||
return;
|
||||
|
||||
var uid = info.Entity;
|
||||
var function = args.Function;
|
||||
|
||||
if (function == EngineKeyFunctions.UIClick)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
@@ -74,12 +75,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
|
||||
public void OnSystemLoaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
|
||||
_guidebookSystem.OnGuidebookOpen += OpenGuidebook;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
|
||||
_guidebookSystem.OnGuidebookOpen -= OpenGuidebook;
|
||||
}
|
||||
|
||||
internal void UnloadButton()
|
||||
@@ -103,10 +104,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
ToggleGuidebook();
|
||||
}
|
||||
|
||||
public void ToggleGuidebook()
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenGuidebook();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
{
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.Pressed = false;
|
||||
|
||||
if (_guideWindow != null)
|
||||
_guideWindow.ReturnContainer.Visible = false;
|
||||
}
|
||||
|
||||
private void OnWindowOpen()
|
||||
@@ -127,30 +147,23 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
/// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
|
||||
/// show the specified entries</param>
|
||||
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
|
||||
public void ToggleGuidebook(
|
||||
Dictionary<string, GuideEntry>? guides = null,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
public void OpenGuidebook(
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>? guides = null,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
|
||||
|
||||
if (guides == null)
|
||||
{
|
||||
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
|
||||
.ToDictionary(x => x.ID, x => (GuideEntry) x);
|
||||
.ToDictionary(x => new ProtoId<GuideEntryPrototype>(x.ID), x => (GuideEntry) x);
|
||||
}
|
||||
else if (includeChildren)
|
||||
{
|
||||
@@ -171,17 +184,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
_guideWindow.OpenCenteredRight();
|
||||
}
|
||||
|
||||
public void ToggleGuidebook(
|
||||
List<string> guideList,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
public void OpenGuidebook(
|
||||
List<ProtoId<GuideEntryPrototype>> guideList,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
Dictionary<string, GuideEntry> guides = new();
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides = new();
|
||||
foreach (var guideId in guideList)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
|
||||
if (!_prototypeManager.TryIndex(guideId, out var guide))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {guideId}");
|
||||
continue;
|
||||
@@ -189,17 +202,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
guides.Add(guideId, guide);
|
||||
}
|
||||
|
||||
ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
|
||||
public void CloseGuidebook()
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides)
|
||||
{
|
||||
foreach (var childId in guide.Children)
|
||||
{
|
||||
if (guides.ContainsKey(childId))
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
|
||||
if (!_prototypeManager.TryIndex(childId, out var child))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
|
||||
continue;
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Info;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Info;
|
||||
|
||||
public sealed class InfoUIController : UIController, IOnStateExited<GameplayState>
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private RulesPopup? _rulesPopup;
|
||||
private RulesAndInfoWindow? _infoWindow;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
|
||||
|
||||
_consoleHost.RegisterCommand("fuckrules",
|
||||
"",
|
||||
"",
|
||||
(_, _, _) =>
|
||||
{
|
||||
OnAcceptPressed();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
|
||||
{
|
||||
ShowRules(message.PopupTime);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
if (_infoWindow == null)
|
||||
@@ -17,12 +53,46 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
_infoWindow = null;
|
||||
}
|
||||
|
||||
private void ShowRules(float time)
|
||||
{
|
||||
if (_rulesPopup != null)
|
||||
return;
|
||||
|
||||
_rulesPopup = new RulesPopup
|
||||
{
|
||||
Timer = time
|
||||
};
|
||||
|
||||
_rulesPopup.OnQuitPressed += OnQuitPressed;
|
||||
_rulesPopup.OnAcceptPressed += OnAcceptPressed;
|
||||
UIManager.WindowRoot.AddChild(_rulesPopup);
|
||||
LayoutContainer.SetAnchorPreset(_rulesPopup, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
_consoleHost.ExecuteCommand("quit");
|
||||
}
|
||||
|
||||
private void OnAcceptPressed()
|
||||
{
|
||||
_netManager.ClientSendMessage(new RulesAcceptedMessage());
|
||||
|
||||
_rulesPopup?.Orphan();
|
||||
_rulesPopup = null;
|
||||
}
|
||||
|
||||
public GuideEntryPrototype GetCoreRuleEntry()
|
||||
{
|
||||
var guide = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(guide);
|
||||
return guideEntryPrototype;
|
||||
}
|
||||
|
||||
public void OpenWindow()
|
||||
{
|
||||
if (_infoWindow == null || _infoWindow.Disposed)
|
||||
{
|
||||
_infoWindow = UIManager.CreateWindow<RulesAndInfoWindow>();
|
||||
}
|
||||
|
||||
_infoWindow?.OpenCentered();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
@@ -133,27 +134,73 @@ public sealed partial class TestPair
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for enabling or disabling a antag role
|
||||
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
|
||||
/// </summary>
|
||||
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
|
||||
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
|
||||
{
|
||||
user ??= Client.User!.Value;
|
||||
if (user is not {} userId)
|
||||
return;
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
var prefs = prefMan.GetPreferences(userId);
|
||||
|
||||
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;
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||
|
||||
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var newProfile = profile.WithAntagPreference(id, value);
|
||||
_modifiedProfiles.Add(userId);
|
||||
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
|
||||
}
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
/// <summary>
|
||||
/// Set a user's job preferences. Modified preferences are automatically reset at the end of the test.
|
||||
/// </summary>
|
||||
public async Task SetJobPriority(ProtoId<JobPrototype> id, JobPriority value, NetUserId? user = null)
|
||||
{
|
||||
user ??= Client.User!.Value;
|
||||
if (user is { } userId)
|
||||
await SetJobPriorities(userId, (id, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetJobPriority"/>
|
||||
public async Task SetJobPriorities(params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||
=> await SetJobPriorities(Client.User!.Value, priorities);
|
||||
|
||||
/// <inheritdoc cref="SetJobPriority"/>
|
||||
public async Task SetJobPriorities(NetUserId user, params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||
{
|
||||
var highCount = priorities.Count(x => x.Item2 == JobPriority.High);
|
||||
Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job");
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
var prefs = prefMan.GetPreferences(user);
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
|
||||
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||
|
||||
if (highCount != 0)
|
||||
{
|
||||
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
|
||||
});
|
||||
foreach (var (key, priority) in dictionary)
|
||||
{
|
||||
if (priority == JobPriority.High)
|
||||
dictionary[key] = JobPriority.Medium;
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
foreach (var (job, priority) in priorities)
|
||||
{
|
||||
if (priority == JobPriority.Never)
|
||||
dictionary.Remove(job);
|
||||
else
|
||||
dictionary[job] = priority;
|
||||
}
|
||||
|
||||
var newProfile = profile.WithJobPriorities(dictionary);
|
||||
_modifiedProfiles.Add(user);
|
||||
await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -34,6 +36,11 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
|
||||
private async Task OnCleanDispose()
|
||||
{
|
||||
await Server.WaitIdleAsync();
|
||||
await Client.WaitIdleAsync();
|
||||
await ResetModifiedPreferences();
|
||||
await Server.RemoveAllDummySessions();
|
||||
|
||||
if (TestMap != null)
|
||||
{
|
||||
await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
|
||||
@@ -79,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
|
||||
}
|
||||
|
||||
private async Task ResetModifiedPreferences()
|
||||
{
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
foreach (var user in _modifiedProfiles)
|
||||
{
|
||||
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
|
||||
}
|
||||
_modifiedProfiles.Clear();
|
||||
}
|
||||
|
||||
public async ValueTask CleanReturnAsync()
|
||||
{
|
||||
if (State != PairState.InUse)
|
||||
|
||||
@@ -26,6 +26,8 @@ public sealed partial class TestPair
|
||||
public readonly List<string> TestHistory = new();
|
||||
public PoolSettings Settings = default!;
|
||||
public TestMapData? TestMap;
|
||||
private List<NetUserId> _modifiedProfiles = new();
|
||||
|
||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||
|
||||
@@ -37,7 +39,8 @@ public sealed partial class TestPair
|
||||
client = Client;
|
||||
}
|
||||
|
||||
public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault();
|
||||
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
|
||||
|
||||
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
||||
|
||||
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;
|
||||
|
||||
@@ -28,6 +28,7 @@ public static partial class PoolManager
|
||||
(CCVars.EmergencyShuttleEnabled.Name, "false"),
|
||||
(CCVars.ProcgenPreload.Name, "false"),
|
||||
(CCVars.WorldgenEnabled.Name, "false"),
|
||||
(CCVars.GatewayGeneratorEnabled.Name, "false"),
|
||||
(CVars.ReplayClientRecordingEnabled.Name, "false"),
|
||||
(CVars.ReplayServerRecordingEnabled.Name, "false"),
|
||||
(CCVars.GameDummyTicker.Name, "true"),
|
||||
|
||||
@@ -340,6 +340,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"MapGrid",
|
||||
"Broadphase",
|
||||
"StationData", // errors when removed mid-round
|
||||
"StationJobs",
|
||||
"Actor", // We aren't testing actor components, those need their player session set.
|
||||
"BlobFloorPlanBuilder", // Implodes if unconfigured.
|
||||
"DebrisFeaturePlacerController", // Above.
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class AntagPreferenceTest
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
// Opt into the traitor role.
|
||||
await pair.SetAntagPref("Traitor", true);
|
||||
await pair.SetAntagPreference("Traitor", true);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
@@ -63,7 +63,7 @@ public sealed class AntagPreferenceTest
|
||||
Assert.That(sessions.Count, Is.EqualTo(1));
|
||||
|
||||
// opt back out
|
||||
await pair.SetAntagPref("Traitor", false);
|
||||
await pair.SetAntagPreference("Traitor", false);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
|
||||
@@ -57,8 +57,17 @@ public sealed class NukeOpsTest
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Add several dummy players
|
||||
var dummies = await pair.Server.AddDummySessions(3);
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Opt into the nukies role.
|
||||
await pair.SetAntagPref("NukeopsCommander", true);
|
||||
await pair.SetAntagPreference("NukeopsCommander", true);
|
||||
await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId);
|
||||
|
||||
// Initially, the players have no attached entities
|
||||
Assert.That(pair.Player?.AttachedEntity, Is.Null);
|
||||
Assert.That(dummies.All(x => x.AttachedEntity == null));
|
||||
|
||||
// There are no grids or maps
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
|
||||
@@ -75,17 +84,20 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.Count<NukeOperativeSpawnerComponent>(), Is.Zero);
|
||||
|
||||
// Ready up and start nukeops
|
||||
await pair.WaitClientCommand("toggleready True");
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
ticker.ToggleReadyAll(true);
|
||||
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
|
||||
await pair.WaitCommand("forcepreset Nukeops");
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
// Game should have started
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
|
||||
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame));
|
||||
Assert.That(client.EntMan.EntityExists(client.AttachedEntity));
|
||||
|
||||
var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray();
|
||||
var player = pair.Player!.AttachedEntity!.Value;
|
||||
Assert.That(entMan.EntityExists(player));
|
||||
Assert.That(dummyEnts.All(e => entMan.EntityExists(e)));
|
||||
|
||||
// Maps now exist
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.GreaterThan(0));
|
||||
@@ -96,8 +108,8 @@ public sealed class NukeOpsTest
|
||||
|
||||
// And we now have nukie related components
|
||||
Assert.That(entMan.Count<NukeopsRuleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.EqualTo(2));
|
||||
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.EqualTo(2));
|
||||
Assert.That(entMan.Count<NukeOpsShuttleComponent>(), Is.EqualTo(1));
|
||||
|
||||
// The player entity should be the nukie commander
|
||||
@@ -107,11 +119,36 @@ public sealed class NukeOpsTest
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
|
||||
var roles = roleSys.MindGetAllRoles(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The second dummy player should be a medic
|
||||
var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
||||
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
||||
roles = roleSys.MindGetAllRoles(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The other two players should have just spawned in as normal.
|
||||
CheckDummy(0);
|
||||
CheckDummy(2);
|
||||
void CheckDummy(int i)
|
||||
{
|
||||
var ent = dummyEnts[i];
|
||||
var mind = mindSys.GetMind(ent)!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
||||
Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
|
||||
}
|
||||
|
||||
// The game rule exists, and all the stations/shuttles/maps are properly initialized
|
||||
var rule = entMan.AllComponents<NukeopsRuleComponent>().Single().Component;
|
||||
var gridsRule = entMan.AllComponents<RuleGridsComponent>().Single().Component;
|
||||
@@ -178,7 +215,7 @@ public sealed class NukeOpsTest
|
||||
// While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be
|
||||
// likely to have in the future. But nukies should probably have at least 3 slots with something in them.
|
||||
var enumerator = invSys.GetSlotEnumerator(player);
|
||||
int total = 0;
|
||||
var total = 0;
|
||||
while (enumerator.NextItem(out _))
|
||||
{
|
||||
total++;
|
||||
@@ -199,7 +236,6 @@ public sealed class NukeOpsTest
|
||||
}
|
||||
|
||||
ticker.SetGamePreset((GamePresetPrototype?)null);
|
||||
await pair.SetAntagPref("NukeopsCommander", false);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Client.Guidebook.Richtext;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Guidebook;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
@@ -49,12 +51,11 @@ public sealed class MachineBoardTest
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mId, Is.Not.Null, $"Machine board {p.ID} does not have a corresponding machine.");
|
||||
Assert.That(protoMan.TryIndex<EntityPrototype>(mId, out var mProto),
|
||||
$"Machine board {p.ID}'s corresponding machine has an invalid prototype.");
|
||||
Assert.That(mProto.TryGetComponent<MachineComponent>(out var mComp),
|
||||
$"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent");
|
||||
Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID),
|
||||
Assert.That(mComp.Board, Is.EqualTo(p.ID),
|
||||
$"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}");
|
||||
});
|
||||
}
|
||||
@@ -101,4 +102,40 @@ public sealed class MachineBoardTest
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that every single computer board's corresponding entity
|
||||
/// is a computer that can be properly deconstructed to the correct board
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestValidateBoardComponentRequirements()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
foreach (var p in protoMan.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !_ignoredPrototypes.Contains(p.ID)))
|
||||
{
|
||||
if (!p.TryGetComponent<MachineBoardComponent>(out var board, entMan.ComponentFactory))
|
||||
continue;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var component in board.ComponentRequirements.Keys)
|
||||
{
|
||||
Assert.That(entMan.ComponentFactory.TryGetRegistration(component, out _), $"Invalid component requirement {component} specified on machine board entity {p}");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ public sealed class MaterialArbitrageTest
|
||||
}
|
||||
|
||||
// Lets assume the possible lathe for resource multipliers:
|
||||
var multiplier = MathF.Pow(LatheComponent.DefaultPartRatingMaterialUseMultiplier, MachinePartComponent.MaxRating - 1);
|
||||
// TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in.
|
||||
var multiplier = MathF.Pow(0.85f, 3);
|
||||
|
||||
// create construction dictionary
|
||||
Dictionary<string, ConstructionComponent> constructionRecipes = new();
|
||||
|
||||
@@ -249,22 +249,15 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
// Test all availableJobs have spawnPoints
|
||||
// This is done inside gamemap test because loading the map takes ages and we already have it.
|
||||
var jobList = entManager.GetComponent<StationJobsComponent>(station).RoundStartJobList
|
||||
.Where(x => x.Value != 0)
|
||||
.Select(x => x.Key);
|
||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||
.Where(spawnpoint => spawnpoint.SpawnType == SpawnPointType.Job)
|
||||
.Select(spawnpoint => spawnpoint.Job.ID)
|
||||
.Distinct();
|
||||
List<string> missingSpawnPoints = new();
|
||||
foreach (var spawnpoint in jobList.Except(spawnPoints))
|
||||
{
|
||||
if (protoManager.Index<JobPrototype>(spawnpoint).SetPreference)
|
||||
missingSpawnPoints.Add(spawnpoint);
|
||||
}
|
||||
var comp = entManager.GetComponent<StationJobsComponent>(station);
|
||||
var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
|
||||
|
||||
Assert.That(missingSpawnPoints, Has.Count.EqualTo(0),
|
||||
$"There is no spawnpoint for {string.Join(", ", missingSpawnPoints)} on {mapProto}.");
|
||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||
.Where(x => x.SpawnType == SpawnPointType.Job)
|
||||
.Select(x => x.Job!.Value);
|
||||
|
||||
jobs.ExceptWith(spawnPoints);
|
||||
Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Round;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class JobTest
|
||||
{
|
||||
private static ProtoId<JobPrototype> _passenger = "Passenger";
|
||||
private static ProtoId<JobPrototype> _engineer = "StationEngineer";
|
||||
private static ProtoId<JobPrototype> _captain = "Captain";
|
||||
|
||||
private static string _map = "JobTestMap";
|
||||
|
||||
[TestPrototypes]
|
||||
public static string JobTestMap = @$"
|
||||
- type: gameMap
|
||||
id: {_map}
|
||||
mapName: {_map}
|
||||
mapPath: /Maps/Test/empty.yml
|
||||
minPlayers: 0
|
||||
stations:
|
||||
Empty:
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationNameSetup
|
||||
mapNameTemplate: ""Empty""
|
||||
- type: StationJobs
|
||||
availableJobs:
|
||||
{_passenger}: [ -1, -1 ]
|
||||
{_engineer}: [ -1, -1 ]
|
||||
{_captain}: [ 1, 1 ]
|
||||
";
|
||||
|
||||
public void AssertJob(TestPair pair, ProtoId<JobPrototype> job, NetUserId? user = null, bool isAntag = false)
|
||||
{
|
||||
var jobSys = pair.Server.System<SharedJobSystem>();
|
||||
var mindSys = pair.Server.System<MindSystem>();
|
||||
var roleSys = pair.Server.System<RoleSystem>();
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
|
||||
user ??= pair.Client.User!.Value;
|
||||
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
Assert.That(ticker.PlayerGameStatuses[user.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
|
||||
|
||||
var uid = pair.Server.PlayerMan.SessionsDict.GetValueOrDefault(user.Value)?.AttachedEntity;
|
||||
Assert.That(pair.Server.EntMan.EntityExists(uid));
|
||||
var mind = mindSys.GetMind(uid!.Value);
|
||||
Assert.That(pair.Server.EntMan.EntityExists(mind));
|
||||
Assert.That(jobSys.MindTryGetJobId(mind, out var actualJob));
|
||||
Assert.That(actualJob, Is.EqualTo(job));
|
||||
Assert.That(roleSys.MindIsAntagonist(mind), Is.EqualTo(isAntag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple test that checks that starting the round spawns the player into the test map as a passenger.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task StartRoundTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
|
||||
// Initially in the lobby
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Ready up and start the round
|
||||
ticker.ToggleReadyAll(true);
|
||||
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _passenger);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that job preferences are respected.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobPreferenceTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _engineer);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _passenger);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check high priority jobs (e.g., captain) are selected before other roles, even if it means a player does not
|
||||
/// get their preferred job.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobWeightTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
var captain = pair.Server.ProtoMan.Index(_captain);
|
||||
var engineer = pair.Server.ProtoMan.Index(_engineer);
|
||||
var passenger = pair.Server.ProtoMan.Index(_passenger);
|
||||
Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight));
|
||||
Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight));
|
||||
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _captain);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that jobs are preferentially given to players that have marked those jobs as higher priority.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobPriorityTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
await pair.Server.AddDummySessions(5);
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
var engineers = pair.Server.PlayerMan.Sessions.Select(x => x.UserId).ToList();
|
||||
var captain = engineers[3];
|
||||
engineers.RemoveAt(3);
|
||||
|
||||
await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||
foreach (var engi in engineers)
|
||||
{
|
||||
await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||
}
|
||||
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _captain, captain);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var engi in engineers)
|
||||
{
|
||||
AssertJob(pair, _engineer, engi);
|
||||
}
|
||||
});
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -46,8 +45,6 @@ public sealed class StationJobsTest
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationJobs
|
||||
overflowJobs:
|
||||
- Passenger
|
||||
availableJobs:
|
||||
TMime: [0, -1]
|
||||
TAssistant: [-1, -1]
|
||||
@@ -164,7 +161,6 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
||||
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
||||
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
||||
@@ -215,6 +211,8 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
var name = compFact.GetComponentName<StationJobsComponent>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -233,11 +231,14 @@ public sealed class StationJobsTest
|
||||
{
|
||||
foreach (var (stationId, station) in gameMap.Stations)
|
||||
{
|
||||
if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp))
|
||||
if (!station.StationComponentOverrides.TryGetComponent(name, out var comp))
|
||||
continue;
|
||||
|
||||
foreach (var (job, _) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
||||
foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
||||
{
|
||||
Assert.That(array.Length, Is.EqualTo(2));
|
||||
Assert.That(array[0] is -1 or >= 0);
|
||||
Assert.That(array[1] is -1 or >= 0);
|
||||
Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -27,11 +28,22 @@ public sealed class StorageInteractionTest : InteractionTest
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
|
||||
|
||||
await Server.WaitPost(() => SEntMan.RemoveComponent<UseDelayComponent>(STarget!.Value));
|
||||
await RunTicks(5);
|
||||
|
||||
// Activating the backpack opens the UI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
|
||||
|
||||
// Activating it again closes the UI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False);
|
||||
|
||||
// Open it again
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
|
||||
// Pick up a PDA
|
||||
var pda = await PlaceInHands("PassengerPDA");
|
||||
var sPda = ToServer(pda);
|
||||
|
||||
@@ -11,6 +11,11 @@ if (!CommandLineArgs.TryParse(args, out var parsed))
|
||||
|
||||
if (parsed.WipeRelease)
|
||||
WipeRelease();
|
||||
else
|
||||
{
|
||||
// Ensure the release directory exists. Otherwise, the packaging will fail.
|
||||
Directory.CreateDirectory("release");
|
||||
}
|
||||
|
||||
if (!parsed.SkipBuild)
|
||||
WipeBin();
|
||||
|
||||
1909
Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.Designer.cs
generated
Normal file
1909
Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1913
Content.Server.Database/Migrations/Postgres/20240606175154_ReturnLastReadRules.Designer.cs
generated
Normal file
1913
Content.Server.Database/Migrations/Postgres/20240606175154_ReturnLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReturnLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
}
|
||||
}
|
||||
1834
Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.Designer.cs
generated
Normal file
1834
Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1838
Content.Server.Database/Migrations/Sqlite/20240606175141_ReturnLastReadRules.Designer.cs
generated
Normal file
1838
Content.Server.Database/Migrations/Sqlite/20240606175141_ReturnLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReturnLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,10 @@ public sealed partial class AdminLogManager
|
||||
{
|
||||
players.Add(actor.PlayerSession.UserId.UserId);
|
||||
}
|
||||
else if (value is SerializablePlayer player)
|
||||
{
|
||||
players.Add(player.Player.UserId.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Database;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
|
||||
@@ -131,59 +131,6 @@ namespace Content.Server.Administration.Systems
|
||||
prayerVerb.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(prayerVerb);
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
@@ -263,6 +210,60 @@ namespace Content.Server.Administration.Systems
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Admin Logs
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Logs))
|
||||
{
|
||||
|
||||
@@ -182,7 +182,7 @@ public sealed class AmeNodeGroup : BaseNodeGroup
|
||||
// Fuel is squared so more fuel vastly increases power and efficiency
|
||||
// We divide by the number of cores so a larger AME is less efficient at the same fuel settings
|
||||
// this results in all AMEs having the same efficiency at the same fuel-per-core setting
|
||||
return 2000000f * fuel * fuel / cores;
|
||||
return 20000f * fuel * fuel / cores;
|
||||
}
|
||||
|
||||
public int GetTotalStability()
|
||||
|
||||
@@ -374,6 +374,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// </summary>
|
||||
public bool IsSessionValid(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null)
|
||||
{
|
||||
// TODO ROLE TIMERS
|
||||
// Check if antag role requirements are met
|
||||
|
||||
if (session == null)
|
||||
return true;
|
||||
|
||||
|
||||
@@ -10,21 +10,21 @@ public sealed partial class AtmosphereSystem
|
||||
SubscribeLocalEvent<BreathToolComponent, ComponentShutdown>(OnBreathToolShutdown);
|
||||
}
|
||||
|
||||
private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args)
|
||||
private void OnBreathToolShutdown(Entity<BreathToolComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
DisconnectInternals(component);
|
||||
DisconnectInternals(entity);
|
||||
}
|
||||
|
||||
public void DisconnectInternals(BreathToolComponent component)
|
||||
public void DisconnectInternals(Entity<BreathToolComponent> entity)
|
||||
{
|
||||
var old = component.ConnectedInternalsEntity;
|
||||
component.ConnectedInternalsEntity = null;
|
||||
var old = entity.Comp.ConnectedInternalsEntity;
|
||||
entity.Comp.ConnectedInternalsEntity = null;
|
||||
|
||||
if (TryComp<InternalsComponent>(old, out var internalsComponent))
|
||||
{
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent));
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner);
|
||||
}
|
||||
|
||||
component.IsFunctional = false;
|
||||
entity.Comp.IsFunctional = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public bool CanConnectToInternals(GasTankComponent component)
|
||||
{
|
||||
var internals = GetInternalsComponent(component, component.User);
|
||||
return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen;
|
||||
return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen;
|
||||
}
|
||||
|
||||
public void ConnectToInternals(Entity<GasTankComponent> ent)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.Body.Components
|
||||
public EntityUid? GasTankEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? BreathToolEntity;
|
||||
public HashSet<EntityUid> BreathTools { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Toggle Internals delay when the target is not you.
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args)
|
||||
{
|
||||
if (component.BreathToolEntity == null)
|
||||
if (component.BreathTools.Count == 0)
|
||||
return;
|
||||
|
||||
if (component.GasTankEntity != null)
|
||||
@@ -111,7 +111,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
}
|
||||
|
||||
// If they're not on then check if we have a mask to use
|
||||
if (internals.BreathToolEntity is null)
|
||||
if (internals.BreathTools.Count == 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
||||
return;
|
||||
@@ -178,28 +178,24 @@ public sealed class InternalsSystem : EntitySystem
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
}
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
var old = ent.Comp.BreathToolEntity;
|
||||
ent.Comp.BreathToolEntity = null;
|
||||
ent.Comp.BreathTools.Remove(toolEntity);
|
||||
|
||||
if (TryComp(old, out BreathToolComponent? breathTool))
|
||||
{
|
||||
_atmos.DisconnectInternals(breathTool);
|
||||
if (TryComp(toolEntity, out BreathToolComponent? breathTool))
|
||||
_atmos.DisconnectInternals((toolEntity, breathTool));
|
||||
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
DisconnectTank(ent);
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool))
|
||||
{
|
||||
_atmos.DisconnectInternals(tool);
|
||||
}
|
||||
if (!ent.Comp.BreathTools.Add(toolEntity))
|
||||
return;
|
||||
|
||||
ent.Comp.BreathToolEntity = toolEntity;
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
@@ -217,7 +213,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||
{
|
||||
if (ent.Comp.BreathToolEntity is null)
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
return false;
|
||||
|
||||
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||
@@ -236,14 +232,14 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool AreInternalsWorking(InternalsComponent component)
|
||||
{
|
||||
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool)
|
||||
return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool)
|
||||
&& breathTool.IsFunctional
|
||||
&& HasComp<GasTankComponent>(component.GasTankEntity);
|
||||
}
|
||||
|
||||
private short GetSeverity(InternalsComponent component)
|
||||
{
|
||||
if (component.BreathToolEntity is null || !AreInternalsWorking(component))
|
||||
if (component.BreathTools.Count == 0 || !AreInternalsWorking(component))
|
||||
return 2;
|
||||
|
||||
// If pressure in the tank is below low pressure threshold, flash warning on internals UI
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class LungSystem : EntitySystem
|
||||
{
|
||||
if (args.IsToggled || args.IsEquip)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent.Comp);
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
@@ -14,6 +15,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -60,6 +62,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
|
||||
public const int VoiceRange = 10; // how far voice goes in world units
|
||||
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
|
||||
@@ -504,8 +507,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (data.Range <= WhisperClearRange)
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel);
|
||||
//If listener is too far, they only hear fragments of the message
|
||||
//Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind
|
||||
else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) //Shared.Physics.CollisionGroup.Opaque
|
||||
else if (_examineSystem.InRangeUnOccluded(source, listener, WhisperMuffledRange))
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel);
|
||||
//If listener is too far and has no line of sight, they can't identify the whisperer's identity
|
||||
else
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
[RequiresExplicitImplementation]
|
||||
public interface IRefreshParts
|
||||
{
|
||||
void RefreshParts(IEnumerable<MachinePartComponent> parts);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,17 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[RegisterComponent, ComponentProtoName("Machine")]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? BoardPrototype { get; private set; }
|
||||
[DataField]
|
||||
public EntProtoId<MachineBoardComponent>? Board { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different types of scaling that are available for machine upgrades
|
||||
/// </summary>
|
||||
public enum MachineUpgradeScalingType : byte
|
||||
{
|
||||
Linear,
|
||||
Exponential
|
||||
}
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
@@ -14,29 +15,23 @@ namespace Content.Server.Construction.Components
|
||||
[ViewVariables]
|
||||
public bool HasBoard => BoardContainer?.ContainedEntities.Count != 0;
|
||||
|
||||
[DataField("progress", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Progress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> MaterialProgress = new();
|
||||
public readonly Dictionary<ProtoId<StackPrototype>, int> MaterialProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> ComponentProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> TagProgress = new();
|
||||
|
||||
[DataField("requirements", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Requirements = new();
|
||||
public readonly Dictionary<ProtoId<TagPrototype>, int> TagProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, int> MaterialRequirements = new();
|
||||
public Dictionary<ProtoId<StackPrototype>, int> MaterialRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> ComponentRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> TagRequirements = new();
|
||||
public Dictionary<ProtoId<TagPrototype>, GenericPartInfo> TagRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PartExchangerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to exchange the parts
|
||||
/// </summary>
|
||||
[DataField("exchangeDuration")]
|
||||
public float ExchangeDuration = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the distance check is needed.
|
||||
/// Good for BRPED.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// I fucking hate BRPED and if you ever add it
|
||||
/// i will personally kill your dog.
|
||||
/// </remarks>
|
||||
[DataField("doDistanceCheck")]
|
||||
public bool DoDistanceCheck = true;
|
||||
|
||||
[DataField("exchangeSound")]
|
||||
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
|
||||
|
||||
public EntityUid? AudioStream;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Construction.Components;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction.Conditions
|
||||
@@ -17,7 +18,7 @@ namespace Content.Server.Construction.Conditions
|
||||
public SpriteSpecifier? GuideIconBoard { get; private set; }
|
||||
|
||||
[DataField("guideIconParts")]
|
||||
public SpriteSpecifier? GuideIconPart { get; private set; }
|
||||
public SpriteSpecifier? GuideIconParts { get; private set; }
|
||||
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
@@ -33,6 +34,8 @@ namespace Content.Server.Construction.Conditions
|
||||
var entity = args.Examined;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var constructionSys = entityManager.System<ConstructionSystem>();
|
||||
|
||||
if (!entityManager.TryGetComponent(entity, out MachineFrameComponent? machineFrame))
|
||||
return false;
|
||||
@@ -47,17 +50,6 @@ namespace Content.Server.Construction.Conditions
|
||||
return false;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label"));
|
||||
foreach (var (part, required) in machineFrame.Requirements)
|
||||
{
|
||||
var amount = required - machineFrame.Progress[part];
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(part))));
|
||||
}
|
||||
|
||||
foreach (var (material, required) in machineFrame.MaterialRequirements)
|
||||
{
|
||||
@@ -65,10 +57,12 @@ namespace Content.Server.Construction.Conditions
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
var stack = protoManager.Index(material);
|
||||
var stackEnt = protoManager.Index(stack.Spawn);
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(material))));
|
||||
("elementName", stackEnt.Name)));
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineFrame.ComponentRequirements)
|
||||
@@ -78,9 +72,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName))));
|
||||
("elementName", examineName)));
|
||||
}
|
||||
|
||||
foreach (var (tagName, info) in machineFrame.TagRequirements)
|
||||
@@ -90,9 +85,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName)))
|
||||
("elementName", examineName))
|
||||
+ "\n");
|
||||
}
|
||||
|
||||
@@ -111,7 +107,7 @@ namespace Content.Server.Construction.Conditions
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
Localization = "construction-step-condition-machine-frame-parts",
|
||||
Icon = GuideIconPart,
|
||||
Icon = GuideIconParts,
|
||||
EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// If the set graph prototype does not exist, also return null. This could be due to admemes changing values
|
||||
// in ViewVariables, so even though the construction state is invalid, just return null.
|
||||
return _prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
return PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +300,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
// Exit if the new entity's prototype is the same as the original, or the prototype is invalid
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !PrototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
return null;
|
||||
|
||||
// [Optional] Exit if the new entity's prototype is a parent of the original
|
||||
@@ -310,7 +310,7 @@ namespace Content.Server.Construction
|
||||
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
|
||||
metaData.EntityPrototype?.ID != null)
|
||||
{
|
||||
var parents = _prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
var parents = PrototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
|
||||
if (parents != null && parents.Any(x => x.ID == newEntity))
|
||||
return null;
|
||||
@@ -427,7 +427,7 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return false;
|
||||
|
||||
if (!_prototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
if (!PrototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
return false;
|
||||
|
||||
if(GetNodeFromGraph(graph, nodeId) is not {})
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.Construction
|
||||
|
||||
private void OnGuideRequested(RequestConstructionGuide msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
if (!PrototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
return;
|
||||
|
||||
if(GetGuide(prototype) is {} guide)
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.Construction
|
||||
component.Node == component.DeconstructionNode)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
return;
|
||||
|
||||
if (component.DeconstructionNode == null)
|
||||
@@ -145,7 +145,7 @@ namespace Content.Server.Construction
|
||||
return guide;
|
||||
|
||||
// If the graph doesn't actually exist, do nothing.
|
||||
if (!_prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
return null;
|
||||
|
||||
// If either the start node or the target node are missing, do nothing.
|
||||
|
||||
@@ -325,13 +325,13 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
public async Task<bool> TryStartItemConstruction(string prototype, EntityUid user)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{prototype}'!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error(
|
||||
@@ -404,14 +404,14 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Examine;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed partial class ConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly ExamineSystem _examineSystem = default!;
|
||||
|
||||
private void InitializeMachines()
|
||||
{
|
||||
SubscribeLocalEvent<MachineComponent, ComponentInit>(OnMachineInit);
|
||||
SubscribeLocalEvent<MachineComponent, MapInitEvent>(OnMachineMapInit);
|
||||
SubscribeLocalEvent<MachineComponent, GetVerbsEvent<ExamineVerb>>(OnMachineExaminableVerb);
|
||||
}
|
||||
|
||||
private void OnMachineInit(EntityUid uid, MachineComponent component, ComponentInit args)
|
||||
@@ -29,84 +21,6 @@ public sealed partial class ConstructionSystem
|
||||
private void OnMachineMapInit(EntityUid uid, MachineComponent component, MapInitEvent args)
|
||||
{
|
||||
CreateBoardAndStockParts(uid, component);
|
||||
RefreshParts(uid, component);
|
||||
}
|
||||
|
||||
private void OnMachineExaminableVerb(EntityUid uid, MachineComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var markup = new FormattedMessage();
|
||||
RaiseLocalEvent(uid, new UpgradeExamineEvent(ref markup));
|
||||
if (markup.IsEmpty)
|
||||
return; // Not upgradable.
|
||||
|
||||
markup = FormattedMessage.FromMarkup(markup.ToMarkup().TrimEnd('\n')); // Cursed workaround to https://github.com/space-wizards/RobustToolbox/issues/3371
|
||||
|
||||
var verb = new ExamineVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
_examineSystem.SendExamineTooltip(args.User, uid, markup, getVerbs: false, centerAtCursor: false);
|
||||
},
|
||||
Text = Loc.GetString("machine-upgrade-examinable-verb-text"),
|
||||
Message = Loc.GetString("machine-upgrade-examinable-verb-message"),
|
||||
Category = VerbCategory.Examine,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(EntityUid uid, MachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return new List<MachinePartComponent>();
|
||||
|
||||
return GetAllParts(component);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(MachineComponent component)
|
||||
{
|
||||
var parts = new List<MachinePartComponent>();
|
||||
|
||||
foreach (var entity in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(entity, out var machinePart))
|
||||
parts.Add(machinePart);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public Dictionary<string, float> GetPartsRatings(List<MachinePartComponent> parts)
|
||||
{
|
||||
var output = new Dictionary<string, float>();
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<MachinePartPrototype>())
|
||||
{
|
||||
var amount = 0f;
|
||||
var sumRating = 0f;
|
||||
foreach (var part in parts.Where(part => part.PartType == type.ID))
|
||||
{
|
||||
amount++;
|
||||
sumRating += part.Rating;
|
||||
}
|
||||
var rating = amount != 0 ? sumRating / amount : 0;
|
||||
output.Add(type.ID, rating);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public void RefreshParts(EntityUid uid, MachineComponent component)
|
||||
{
|
||||
var parts = GetAllParts(component);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new RefreshPartsEvent
|
||||
{
|
||||
Parts = parts,
|
||||
PartRatings = GetPartsRatings(parts),
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component)
|
||||
@@ -115,54 +29,37 @@ public sealed partial class ConstructionSystem
|
||||
var boardContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.BoardContainerName);
|
||||
var partContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.PartContainerName);
|
||||
|
||||
if (string.IsNullOrEmpty(component.BoardPrototype))
|
||||
if (string.IsNullOrEmpty(component.Board))
|
||||
return;
|
||||
|
||||
// We're done here, let's suppose all containers are correct just so we don't screw SaveLoadSave.
|
||||
if (boardContainer.ContainedEntities.Count > 0)
|
||||
return;
|
||||
|
||||
var board = EntityManager.SpawnEntity(component.BoardPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if (!_container.Insert(board, component.BoardContainer))
|
||||
var xform = Transform(uid);
|
||||
if (!TrySpawnInContainer(component.Board, uid, MachineFrameComponent.BoardContainerName, out var board))
|
||||
{
|
||||
throw new Exception($"Couldn't insert board with prototype {component.BoardPrototype} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}!");
|
||||
throw new Exception($"Couldn't insert board with prototype {component.Board} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}!");
|
||||
}
|
||||
|
||||
if (!TryComp<MachineBoardComponent>(board, out var machineBoard))
|
||||
{
|
||||
throw new Exception($"Entity with prototype {component.BoardPrototype} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
throw new Exception($"Entity with prototype {component.Board} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
foreach (var (part, amount) in machineBoard.Requirements)
|
||||
foreach (var (stackType, amount) in machineBoard.StackRequirements)
|
||||
{
|
||||
var partProto = _prototypeManager.Index<MachinePartPrototype>(part);
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates);
|
||||
|
||||
if (!_container.Insert(p, partContainer))
|
||||
throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (stackType, amount) in machineBoard.MaterialRequirements)
|
||||
{
|
||||
var stack = _stackSystem.Spawn(amount, stackType, Transform(uid).Coordinates);
|
||||
|
||||
var stack = _stackSystem.Spawn(amount, stackType, xform.Coordinates);
|
||||
if (!_container.Insert(stack, partContainer))
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineBoard.ComponentRequirements)
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,58 +67,9 @@ public sealed partial class ConstructionSystem
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RefreshPartsEvent : EntityEventArgs
|
||||
{
|
||||
public IReadOnlyList<MachinePartComponent> Parts = new List<MachinePartComponent>();
|
||||
|
||||
public Dictionary<string, float> PartRatings = new();
|
||||
}
|
||||
|
||||
public sealed class UpgradeExamineEvent : EntityEventArgs
|
||||
{
|
||||
private FormattedMessage Message;
|
||||
|
||||
public UpgradeExamineEvent(ref FormattedMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a percentage-based increase or decrease.
|
||||
/// </summary>
|
||||
public void AddPercentageUpgrade(string upgradedLocId, float multiplier)
|
||||
{
|
||||
var percent = Math.Round(100 * MathF.Abs(multiplier - 1), 2);
|
||||
var locId = multiplier switch {
|
||||
< 1 => "machine-upgrade-decreased-by-percentage",
|
||||
1 or float.NaN => "machine-upgrade-not-upgraded",
|
||||
> 1 => "machine-upgrade-increased-by-percentage",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("percent", percent)) + '\n');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a numeric increase or decrease.
|
||||
/// </summary>
|
||||
public void AddNumberUpgrade(string upgradedLocId, int number)
|
||||
{
|
||||
var difference = Math.Abs(number);
|
||||
var locId = number switch {
|
||||
< 0 => "machine-upgrade-decreased-by-amount",
|
||||
0 => "machine-upgrade-not-upgraded",
|
||||
> 0 => "machine-upgrade-increased-by-amount",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("difference", difference)) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Content.Server.Construction
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
@@ -30,21 +31,24 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (!this.IsPowered(ent, EntityManager) || comp.Packing)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.CanChangeMaterialAmount(uid, cost))
|
||||
return;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, true);
|
||||
comp.Packing = true;
|
||||
comp.PackEndTime = _timing.CurTime + comp.PackDuration;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, true);
|
||||
@@ -63,6 +67,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, false);
|
||||
comp.Packing = false;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, false);
|
||||
_ambientSound.SetAmbience(uid, false);
|
||||
@@ -71,24 +76,33 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (interrupted)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
EntProtoId proto;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
proto = machine.Prototype;
|
||||
}
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
proto = computer.Prototype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.TryChangeMaterialAmount((ent, null), cost))
|
||||
return;
|
||||
|
||||
var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
|
||||
SetupFlatpack(flatpack, machineBoard);
|
||||
Del(machineBoard);
|
||||
SetupFlatpack(flatpack, proto, board);
|
||||
Del(board);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -7,7 +7,7 @@ using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
@@ -62,24 +62,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
// If this changes in the future, then RegenerateProgress() also needs to be updated.
|
||||
// Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is
|
||||
// necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress().
|
||||
var stack = CompOrNull<StackComponent>(args.Used);
|
||||
var machinePart = CompOrNull<MachinePartComponent>(args.Used);
|
||||
if (stack != null && machinePart != null)
|
||||
{
|
||||
if (TryInsertPartStack(uid, args.Used, component, machinePart, stack))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle parts
|
||||
if (machinePart != null)
|
||||
{
|
||||
if (TryInsertPart(uid, args.Used, component, machinePart))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stack != null)
|
||||
if (TryComp<StackComponent>(args.Used, out var stack))
|
||||
{
|
||||
if (TryInsertStack(uid, args.Used, component, stack))
|
||||
args.Handled = true;
|
||||
@@ -172,67 +155,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart)
|
||||
{
|
||||
DebugTools.Assert(!HasComp<StackComponent>(uid));
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType])
|
||||
return false;
|
||||
|
||||
if (!_container.TryRemoveFromContainer(used))
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType]++;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack)
|
||||
{
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
var progress = component.Progress[machinePart.PartType];
|
||||
var requirement = component.Requirements[machinePart.PartType];
|
||||
|
||||
var needed = requirement - progress;
|
||||
if (needed <= 0)
|
||||
return false;
|
||||
|
||||
var count = stack.Count;
|
||||
if (count < needed)
|
||||
{
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack);
|
||||
|
||||
if (splitStack == null)
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(splitStack.Value, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += needed;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack)
|
||||
{
|
||||
@@ -281,12 +203,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!component.HasBoard)
|
||||
return false;
|
||||
|
||||
foreach (var (part, amount) in component.Requirements)
|
||||
{
|
||||
if (component.Progress[part] < amount)
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (type, amount) in component.MaterialRequirements)
|
||||
{
|
||||
if (component.MaterialProgress[type] < amount)
|
||||
@@ -310,21 +226,14 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
public void ResetProgressAndRequirements(MachineFrameComponent component, MachineBoardComponent machineBoard)
|
||||
{
|
||||
component.Requirements = new Dictionary<string, int>(machineBoard.Requirements);
|
||||
component.MaterialRequirements = new Dictionary<string, int>(machineBoard.MaterialIdRequirements);
|
||||
component.MaterialRequirements = new Dictionary<ProtoId<StackPrototype>, int>(machineBoard.StackRequirements);
|
||||
component.ComponentRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.ComponentRequirements);
|
||||
component.TagRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
component.TagRequirements = new Dictionary<ProtoId<TagPrototype>, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
|
||||
foreach (var (machinePart, _) in component.Requirements)
|
||||
{
|
||||
component.Progress[machinePart] = 0;
|
||||
}
|
||||
|
||||
foreach (var (stackType, _) in component.MaterialRequirements)
|
||||
{
|
||||
component.MaterialProgress[stackType] = 0;
|
||||
@@ -349,7 +258,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
component.MaterialRequirements.Clear();
|
||||
component.ComponentRequirements.Clear();
|
||||
component.TagRequirements.Clear();
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
@@ -368,19 +276,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
foreach (var part in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(part, out var machinePart))
|
||||
{
|
||||
// Check this is part of the requirements...
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
continue;
|
||||
|
||||
if (!component.Progress.ContainsKey(machinePart.PartType))
|
||||
component.Progress[machinePart.PartType] = 1;
|
||||
else
|
||||
component.Progress[machinePart.PartType]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryComp<StackComponent>(part, out var stack))
|
||||
{
|
||||
var type = stack.StackTypeId;
|
||||
@@ -404,9 +299,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!HasComp(part, registration.Type))
|
||||
continue;
|
||||
|
||||
if (!component.ComponentProgress.ContainsKey(compName))
|
||||
component.ComponentProgress[compName] = 1;
|
||||
else
|
||||
if (!component.ComponentProgress.TryAdd(compName, 1))
|
||||
component.ComponentProgress[compName]++;
|
||||
}
|
||||
|
||||
@@ -419,18 +312,17 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!_tag.HasTag(tagComp, tagName))
|
||||
continue;
|
||||
|
||||
if (!component.TagProgress.ContainsKey(tagName))
|
||||
component.TagProgress[tagName] = 1;
|
||||
else
|
||||
if (!component.TagProgress.TryAdd(tagName, 1))
|
||||
component.TagProgress[tagName]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
if (!args.IsInDetailsRange || !component.HasBoard)
|
||||
return;
|
||||
if (component.HasBoard)
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", EntityManager.GetComponent<MetaDataComponent>(component.BoardContainer.ContainedEntities[0]).EntityName)));
|
||||
|
||||
var board = component.BoardContainer.ContainedEntities[0];
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", Name(board))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Exchanger;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed class PartExchangerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ConstructionSystem _construction = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StorageSystem _storage = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
{
|
||||
component.AudioStream = _audio.Stop(component.AudioStream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<StorageComponent>(uid, out var storage))
|
||||
return; //the parts are stored in here
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var machineParts = new List<(EntityUid, MachinePartComponent)>();
|
||||
|
||||
foreach (var item in storage.Container.ContainedEntities) //get parts in RPED
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
machineParts.Add((item, part));
|
||||
}
|
||||
|
||||
TryExchangeMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
TryConstructMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void TryExchangeMachineParts(EntityUid uid, EntityUid storageUid, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (board == null || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var part in updatedParts)
|
||||
{
|
||||
_container.Insert(part.part, machine.PartContainer);
|
||||
machineParts.Remove(part);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageUid, unused, out _, playSound: false);
|
||||
}
|
||||
_construction.RefreshParts(uid, machine);
|
||||
}
|
||||
|
||||
private void TryConstructMachineParts(EntityUid uid, EntityUid storageEnt, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineFrameComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (!machine.HasBoard || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
machine.Progress[part.PartType]--;
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var pair in updatedParts)
|
||||
{
|
||||
var part = pair.partComp;
|
||||
var partEnt = pair.part;
|
||||
|
||||
if (!machine.Requirements.ContainsKey(part.PartType))
|
||||
continue;
|
||||
|
||||
_container.Insert(partEnt, machine.PartContainer);
|
||||
machine.Progress[part.PartType]++;
|
||||
machineParts.Remove(pair);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageEnt, unused, out _, playSound: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.DoDistanceCheck && !args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Target == null)
|
||||
return;
|
||||
|
||||
if (!HasComp<MachineComponent>(args.Target) && !HasComp<MachineFrameComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (TryComp<WiresPanelComponent>(args.Target, out var panel) && !panel.Open)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"),
|
||||
args.Target.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid).Value.Entity;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,29 +33,32 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
//CrystallPunk Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallPunk Melee pgrade end
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
if (!TerminatingOrDeleted(args.Target))
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
//CP14 - Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CP14 - Melee upgrade End
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: If more stuff touches this then handle it after.
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -183,9 +184,9 @@ namespace Content.Server.Database
|
||||
|
||||
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
|
||||
{
|
||||
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => a.AntagName);
|
||||
var traits = profile.Traits.Select(t => t.TraitName);
|
||||
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
|
||||
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
|
||||
|
||||
var sex = Sex.Male;
|
||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Devour;
|
||||
@@ -15,6 +16,7 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args)
|
||||
@@ -45,5 +47,15 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid);
|
||||
}
|
||||
|
||||
private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args)
|
||||
{
|
||||
if (!component.ShouldStoreDevoured)
|
||||
return;
|
||||
|
||||
// For some reason we have two different systems that should handle gibbing,
|
||||
// and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
|
||||
ContainerSystem.EmptyContainer(component.Stomach);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Content.Server.Entry
|
||||
factory.RegisterIgnore(IgnoredComponents.List);
|
||||
|
||||
prototypes.RegisterIgnore("parallax");
|
||||
prototypes.RegisterIgnore("guideEntry");
|
||||
|
||||
ServerContentIoC.Register();
|
||||
|
||||
|
||||
@@ -171,7 +171,8 @@ public sealed partial class TriggerSystem
|
||||
if (args.Handled || HasComp<AutomatedTimerComponent>(uid) || component.UseVerbInstead)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
if (component.DoPopup)
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Content.Server.GameTicking
|
||||
HumanoidCharacterProfile profile;
|
||||
if (_prefsManager.TryGetCachedPreferences(userId, out var preferences))
|
||||
{
|
||||
profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex);
|
||||
profile = (HumanoidCharacterProfile) preferences.SelectedCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -154,8 +154,8 @@ namespace Content.Server.Ghost
|
||||
|
||||
if (_ticker.RunLevel != GameRunLevel.PostRound)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -174,8 +174,8 @@ namespace Content.Server.Ghost
|
||||
// Entity can't be seen by ghosts anymore.
|
||||
if (TryComp(uid, out VisibilityComponent? visibility))
|
||||
{
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -382,13 +382,13 @@ namespace Content.Server.Ghost
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
}
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components
|
||||
|
||||
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
|
||||
|
||||
// TODO ROLE TIMERS
|
||||
// Actually make use of / enforce this requirement?
|
||||
// Why is this even here.
|
||||
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
|
||||
[DataField("requirements")]
|
||||
public HashSet<JobRequirement>? Requirements;
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class InfoSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<RequestRulesMessage>(OnRequestRules);
|
||||
}
|
||||
|
||||
private void OnRequestRules(RequestRulesMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
Log.Debug("Client requested rules.");
|
||||
var title = Loc.GetString(_cfg.GetCVar(CCVars.RulesHeader));
|
||||
var path = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var rules = "Server could not read its rules.";
|
||||
try
|
||||
{
|
||||
rules = _res.ContentFileReadAllText($"/ServerInfo/{path}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Debug("Could not read server rules file.");
|
||||
}
|
||||
var response = new RulesMessage(title, rules);
|
||||
RaiseNetworkEvent(response, eventArgs.SenderSession.Channel);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class RulesManager : SharedRulesManager
|
||||
public sealed class RulesManager
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
@@ -17,26 +17,22 @@ public sealed class RulesManager : SharedRulesManager
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>();
|
||||
_netManager.Connected += OnConnected;
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>(OnRulesAccepted);
|
||||
_netManager.Connected += OnConnected;
|
||||
}
|
||||
|
||||
private async void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
if (IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) && _cfg.GetCVar(CCVars.RulesExemptLocal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lastRead = await _dbManager.GetLastReadRules(e.Channel.UserId);
|
||||
if (lastRead > LastValidReadTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new ShouldShowRulesPopupMessage();
|
||||
var message = new ShowRulesPopupMessage();
|
||||
message.PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime);
|
||||
_netManager.ServerSendMessage(message, e.Channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,20 +47,16 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
}
|
||||
}
|
||||
|
||||
var locator = IoCManager.Resolve<IPlayerLocator>();
|
||||
var located = await locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
|
||||
var message = new ShowRulesPopupMessage { PopupTime = seconds };
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(target, out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name.");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
var netManager = IoCManager.Resolve<INetManager>();
|
||||
|
||||
var message = new SharedRulesManager.ShowRulesPopupMessage();
|
||||
message.PopupTime = seconds;
|
||||
|
||||
var player = IoCManager.Resolve<IPlayerManager>().GetSessionById(located.UserId);
|
||||
netManager.ServerSendMessage(message, player.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user