forked from SpaceStation14-Shenanigans/Monolith
[DNM] v271.2.0 RT Update - .NET 10 (#3469)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com> Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -348,7 +348,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
|
||||
resharper_csharp_prefer_qualified_reference = false
|
||||
resharper_csharp_allow_alias = false
|
||||
|
||||
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,json}]
|
||||
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,json,slnx}]
|
||||
indent_size = 2
|
||||
|
||||
[nuget.config]
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Get Engine Tag
|
||||
run: |
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# MSbuild binlog files
|
||||
*.binlog
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
|
||||
# If this doesn't match the saved version we overwrite them all.
|
||||
CURRENT_HOOKS_VERSION = "2"
|
||||
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" />
|
||||
|
||||
<!-- pin transitive deps -->
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
||||
@@ -19,10 +22,12 @@
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
||||
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Testing.props" />
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Global usings for Content.Benchmarks
|
||||
|
||||
global using Robust.UnitTesting.Pool;
|
||||
@@ -36,8 +36,6 @@ public sealed partial class ContentAudioSystem
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly CombatModeSystem _combatModeSystem = default!; //CLIENT ONE. WHY ARE THERE 3???
|
||||
[Dependency] private readonly IPrototypeManager _protMan = default!;
|
||||
[Dependency] private readonly SpaceBiomeSystem _spaceBiome = default!;
|
||||
|
||||
//options menu ---
|
||||
private static float _volumeSliderAmbient;
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<!-- Main -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" Name="GeneralCategoryTexture"/>
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/>
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" Name="HandsCategoryTexture"/>
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
</ui:RadialContainer>
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ using Content.Shared.Speech;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Chat.UI;
|
||||
|
||||
@@ -86,6 +88,9 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
continue;
|
||||
AddEmoteClickAction(container);
|
||||
}
|
||||
|
||||
GeneralCategoryTexture.Texture = spriteSystem.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Head/Soft/mimesoft.rsi"), "icon"));
|
||||
HandsCategoryTexture.Texture = spriteSystem.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/latex.rsi"), "icon"));
|
||||
}
|
||||
|
||||
private void AddEmoteClickAction(RadialContainer container)
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nett" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="Pidgin" />
|
||||
<PackageReference Include="Robust.Shared.AuthLib" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -34,7 +35,7 @@ namespace Content.Client.Cooldown
|
||||
if (Progress >= 0f)
|
||||
{
|
||||
var hue = (5f / 18f) * lerp;
|
||||
color = Color.FromHsv((hue, 0.75f, 0.75f, 0.50f));
|
||||
color = Color.FromHsv(new Vector4(hue, 0.75f, 0.75f, 0.50f));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Disposal;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -36,39 +36,37 @@ public sealed partial class DocumentParsingManager
|
||||
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
|
||||
.Bind(tag => _tagControlParsers[tag]);
|
||||
|
||||
// Frontier: comment parser
|
||||
Parser<char, Unit> whitespaceAndCommentParser = SkipWhitespaces.Then(Try(String("<!--").Then(Parser<char>.Any.SkipUntil(Try(String("-->"))))).SkipMany());
|
||||
var whitespaceAndCommentParser = SkipWhitespaces.Then(Try(String("<!--").Then(Parser<char>.Any.SkipUntil(Try(String("-->"))))).SkipMany());
|
||||
|
||||
_controlParser = OneOf(_tagParser, TryHeaderControl, TryListControl, TextControlParser)
|
||||
.Before(whitespaceAndCommentParser);
|
||||
// End Frontier
|
||||
|
||||
foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
|
||||
{
|
||||
_tagControlParsers.Add(typ.Name, CreateTagControlParser(typ.Name, typ, _sandboxHelper));
|
||||
}
|
||||
|
||||
ControlParser = whitespaceAndCommentParser.Then(_controlParser.Many()); // Frontier: SkipWhitespaces<whitespaceAndCommentParser
|
||||
ControlParser = whitespaceAndCommentParser.Then(_controlParser.Many());
|
||||
|
||||
_sawmill = Logger.GetSawmill("Guidebook");
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
|
||||
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId)
|
||||
{
|
||||
if (!_prototype.TryIndex(entryId, out var entry))
|
||||
if (!_prototype.Resolve(entryId, out var entry))
|
||||
return false;
|
||||
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
return TryAddMarkup(control, file.ReadToEnd());
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
|
||||
public bool TryAddMarkup(Control control, GuideEntry entry)
|
||||
{
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
return TryAddMarkup(control, file.ReadToEnd());
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, string text, bool log = true)
|
||||
public bool TryAddMarkup(Control control, string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed partial class DocumentParsingManager
|
||||
}
|
||||
|
||||
msg.Pop();
|
||||
rt.SetMessage(msg);
|
||||
rt.SetMessage(msg, tagsAllowed: null);
|
||||
return rt;
|
||||
},
|
||||
TextParser)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Holopad;
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
|
||||
{
|
||||
TitleLabel.Text = title;
|
||||
if (markup)
|
||||
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
|
||||
else
|
||||
Content.SetMessage(text);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Client.Info
|
||||
}
|
||||
public void SetInfoBlob(string markup)
|
||||
{
|
||||
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
|
||||
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Prometheus;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Nyanotrasen.Kitchen.UI;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -98,10 +98,13 @@ public sealed class ParallaxManager : IParallaxManager
|
||||
}
|
||||
else
|
||||
{
|
||||
layers = await Task.WhenAll(
|
||||
// Explicitly allocate params array to avoid sandbox violation since C# 14.
|
||||
var tasks = new[]
|
||||
{
|
||||
LoadParallaxLayers(parallaxPrototype.Layers, loadedLayers, cancel),
|
||||
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel)
|
||||
);
|
||||
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel),
|
||||
};
|
||||
layers = await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Power.APC.UI
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Sprite;
|
||||
|
||||
public sealed class ScaleVisualsSystem : SharedScaleVisualsSystem
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ScaleVisualsComponent, AppearanceChangeEvent>(OnChangeData);
|
||||
}
|
||||
|
||||
private void OnChangeData(Entity<ScaleVisualsComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!args.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
|
||||
args.Sprite == null) return;
|
||||
|
||||
// save the original scale
|
||||
ent.Comp.OriginalScale ??= args.Sprite.Scale;
|
||||
|
||||
var vecScale = (Vector2)scale;
|
||||
_sprite.SetScale((ent.Owner, args.Sprite), vecScale);
|
||||
}
|
||||
|
||||
// revert to the original scale
|
||||
protected override void ResetScale(Entity<ScaleVisualsComponent> ent)
|
||||
{
|
||||
base.ResetScale(ent);
|
||||
|
||||
if (ent.Comp.OriginalScale != null)
|
||||
_sprite.SetScale(ent.Owner, ent.Comp.OriginalScale.Value);
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public partial class ChatBox : UIWidget
|
||||
("size", 8+sizeIncrease)
|
||||
));
|
||||
} // WD EDIT END
|
||||
Contents.AddMessage(formatted);
|
||||
Contents.AddMessage(formatted, tagsAllowed: null);
|
||||
}
|
||||
|
||||
public void Focus(ChatSelectChannel? channel = null)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed class ProgressColorSystem : EntitySystem
|
||||
|
||||
// lerp
|
||||
var hue = 5f / 18f * progress;
|
||||
return Color.FromHsv((hue, 1f, 0.75f, 1f));
|
||||
return Color.FromHsv(new Vector4(hue, 1f, 0.75f, 1f));
|
||||
}
|
||||
|
||||
return InterpolateColorGaussian(Plasma, progress);
|
||||
|
||||
@@ -614,7 +614,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
{
|
||||
marked.Add(cell);
|
||||
cell.ModulateSelfOverride = spotFree
|
||||
? Color.FromHsv((0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
|
||||
? Color.FromHsv(new Vector4(0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
|
||||
: Color.FromHex("#2222CC");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,9 @@ public sealed partial class StationPickerControl : PickerControl
|
||||
|
||||
foreach (var (stationEntity, stationJobInformation) in stationList)
|
||||
{
|
||||
var icon = stationJobInformation.StationDisplayInfo?.StationIcon;
|
||||
var iconTexture = icon != null ? _spriteSystem.Frame0(icon) : null;
|
||||
|
||||
var viewState = new StationListItem.ViewState(
|
||||
stationEntity,
|
||||
stationJobInformation.GetStationNameWithJobCount(),
|
||||
@@ -136,7 +139,7 @@ public sealed partial class StationPickerControl : PickerControl
|
||||
? _loc.GetString(stationJobInformation.StationDisplayInfo.StationDescription)
|
||||
: "",
|
||||
_lastSelectedStation?.StationEntity == stationEntity,
|
||||
stationJobInformation.StationDisplayInfo?.StationIcon?.CanonPath
|
||||
iconTexture
|
||||
);
|
||||
|
||||
// Always select the first station in the list if none is selected yet.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -13,7 +14,7 @@ public sealed partial class StationListItem : PanelContainer
|
||||
string stationSubtext,
|
||||
string stationDescription,
|
||||
bool selected,
|
||||
string? iconPath)
|
||||
Texture? icon)
|
||||
{
|
||||
public string StationName { get; } = stationName;
|
||||
public string StationSubtext { get; } = stationSubtext;
|
||||
@@ -22,7 +23,7 @@ public sealed partial class StationListItem : PanelContainer
|
||||
|
||||
public bool Selected { get; set; } = selected;
|
||||
|
||||
public string IconPath { get; } = iconPath ?? "";
|
||||
public Texture? Icon { get; } = icon;
|
||||
}
|
||||
|
||||
public StationListItem(ViewState state)
|
||||
@@ -35,9 +36,9 @@ public sealed partial class StationListItem : PanelContainer
|
||||
// Disallow repeated selection of same station that does UI reloads.
|
||||
StationButton.Disabled = state.Selected;
|
||||
|
||||
if (state.IconPath.Length > 0)
|
||||
if (state.Icon != null)
|
||||
{
|
||||
StationIcon.TexturePath = state.IconPath;
|
||||
StationIcon.Texture = state.Icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class RMCCombatModeUISystem : EntitySystem
|
||||
_hands.GetActiveHandEntity() is { } held &&
|
||||
_rmcCombatMode.GetCrosshair(held) != null)
|
||||
{
|
||||
_crosshairCursor ??= _clyde.CreateCursor(new Image<Rgba32>(1, 1), Vector2i.One);
|
||||
_crosshairCursor ??= _clyde.CreateCursor(new Image<Rgba32>(1, 1), Vector2i.Zero);
|
||||
_clyde.SetCursor(_crosshairCursor);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<OutputPath>..\bin\Content.IntegrationTests\</OutputPath>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" />
|
||||
<PackageReference Include="NUnit3TestAdapter" />
|
||||
@@ -17,12 +15,12 @@
|
||||
<ProjectReference Include="..\Content.Server\Content.Server.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets" />
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Testing.props" />
|
||||
</Project>
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
global using NUnit.Framework;
|
||||
global using System;
|
||||
global using System.Threading.Tasks;
|
||||
global using Robust.UnitTesting.Pool;
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
/// <summary>
|
||||
/// Simple data class that stored information about a map being used by a test.
|
||||
/// </summary>
|
||||
public sealed class TestMapData
|
||||
{
|
||||
public EntityUid MapUid { get; set; }
|
||||
public Entity<MapGridComponent> Grid;
|
||||
public MapId MapId;
|
||||
public EntityCoordinates GridCoords { get; set; }
|
||||
public MapCoordinates MapCoords { get; set; }
|
||||
public TileRef Tile { get; set; }
|
||||
|
||||
// Client-side uids
|
||||
public EntityUid CMapUid { get; set; }
|
||||
public EntityUid CGridUid { get; set; }
|
||||
public EntityCoordinates CGridCoords { get; set; }
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
private readonly Dictionary<string, object> _modifiedClientCvars = new();
|
||||
private readonly Dictionary<string, object> _modifiedServerCvars = new();
|
||||
|
||||
private void OnServerCvarChanged(CVarChangeInfo args)
|
||||
{
|
||||
_modifiedServerCvars.TryAdd(args.Name, args.OldValue);
|
||||
}
|
||||
|
||||
private void OnClientCvarChanged(CVarChangeInfo args)
|
||||
{
|
||||
_modifiedClientCvars.TryAdd(args.Name, args.OldValue);
|
||||
}
|
||||
|
||||
internal void ClearModifiedCvars()
|
||||
{
|
||||
_modifiedClientCvars.Clear();
|
||||
_modifiedServerCvars.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts any cvars that were modified during a test back to their original values.
|
||||
/// </summary>
|
||||
public async Task RevertModifiedCvars()
|
||||
{
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
foreach (var (name, value) in _modifiedServerCvars)
|
||||
{
|
||||
if (Server.CfgMan.GetCVar(name).Equals(value))
|
||||
continue;
|
||||
Server.Log.Info($"Resetting cvar {name} to {value}");
|
||||
Server.CfgMan.SetCVar(name, value);
|
||||
}
|
||||
|
||||
// I just love order dependent cvars
|
||||
if (_modifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik))
|
||||
Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik);
|
||||
|
||||
});
|
||||
|
||||
await Client.WaitPost(() =>
|
||||
{
|
||||
foreach (var (name, value) in _modifiedClientCvars)
|
||||
{
|
||||
if (Client.CfgMan.GetCVar(name).Equals(value))
|
||||
continue;
|
||||
|
||||
var flags = Client.CfgMan.GetCVarFlags(name);
|
||||
if (flags.HasFlag(CVar.REPLICATED) && flags.HasFlag(CVar.SERVER))
|
||||
continue;
|
||||
|
||||
Client.Log.Info($"Resetting cvar {name} to {value}");
|
||||
Client.CfgMan.SetCVar(name, value);
|
||||
}
|
||||
});
|
||||
|
||||
ClearModifiedCvars();
|
||||
}
|
||||
}
|
||||
@@ -1,180 +1,68 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.UnitTesting;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
// Contains misc helper functions to make writing tests easier.
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
public Task<TestMapData> CreateTestMap(bool initialized = true)
|
||||
=> CreateTestMap(initialized, "Plating");
|
||||
|
||||
/// <summary>
|
||||
/// Creates a map, a grid, and a tile, and gives back references to them.
|
||||
/// Loads a test map and returns a <see cref="TestMapData"/> representing it.
|
||||
/// </summary>
|
||||
[MemberNotNull(nameof(TestMap))]
|
||||
public async Task<TestMapData> CreateTestMap(bool initialized = true, string tile = "Plating")
|
||||
/// <param name="testMapPath">The <see cref="ResPath"/> to the test map to load.</param>
|
||||
/// <param name="initialized">Whether to initialize the map on load.</param>
|
||||
/// <returns>A <see cref="TestMapData"/> representing the loaded map.</returns>
|
||||
public async Task<TestMapData> LoadTestMap(ResPath testMapPath, bool initialized = true)
|
||||
{
|
||||
var mapData = new TestMapData();
|
||||
TestMap = mapData;
|
||||
await Server.WaitIdleAsync();
|
||||
var tileDefinitionManager = Server.ResolveDependency<ITileDefinitionManager>();
|
||||
TestMapData mapData = new();
|
||||
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = initialized };
|
||||
var mapLoaderSys = Server.EntMan.System<MapLoaderSystem>();
|
||||
var mapSys = Server.System<SharedMapSystem>();
|
||||
|
||||
TestMap = mapData;
|
||||
await Server.WaitPost(() =>
|
||||
// Load our test map in and assert that it exists.
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
mapData.MapUid = Server.System<SharedMapSystem>().CreateMap(out mapData.MapId, runMapInit: initialized);
|
||||
mapData.Grid = Server.MapMan.CreateGridEntity(mapData.MapId);
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
|
||||
var plating = tileDefinitionManager[tile];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
Server.System<SharedMapSystem>().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
mapData.Tile = Server.System<SharedMapSystem>().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
|
||||
});
|
||||
Assert.That(mapLoaderSys.TryLoadMap(testMapPath, out var map, out var gridSet, deserializationOptions),
|
||||
$"Failed to load map {testMapPath}.");
|
||||
Assert.That(gridSet, Is.Not.Empty, "There were no grids loaded from the map!");
|
||||
|
||||
TestMap = mapData;
|
||||
if (!Settings.Connected)
|
||||
return mapData;
|
||||
mapData.MapUid = map!.Value.Owner;
|
||||
mapData.MapId = map!.Value.Comp.MapId;
|
||||
mapData.Grid = gridSet!.First();
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
mapData.Tile = mapSys.GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
|
||||
});
|
||||
|
||||
await RunTicksSync(10);
|
||||
mapData.CMapUid = ToClientUid(mapData.MapUid);
|
||||
mapData.CGridUid = ToClientUid(mapData.Grid);
|
||||
mapData.CGridCoords = new EntityCoordinates(mapData.CGridUid, 0, 0);
|
||||
|
||||
TestMap = mapData;
|
||||
return mapData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a client-side uid into a server-side uid
|
||||
/// </summary>
|
||||
public EntityUid ToServerUid(EntityUid uid) => ConvertUid(uid, Client, Server);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a server-side uid into a client-side uid
|
||||
/// </summary>
|
||||
public EntityUid ToClientUid(EntityUid uid) => ConvertUid(uid, Server, Client);
|
||||
|
||||
private static EntityUid ConvertUid(
|
||||
EntityUid uid,
|
||||
RobustIntegrationTest.IntegrationInstance source,
|
||||
RobustIntegrationTest.IntegrationInstance destination)
|
||||
{
|
||||
if (!uid.IsValid())
|
||||
return EntityUid.Invalid;
|
||||
|
||||
if (!source.EntMan.TryGetComponent<MetaDataComponent>(uid, out var meta))
|
||||
{
|
||||
Assert.Fail($"Failed to resolve MetaData while converting the EntityUid for entity {uid}");
|
||||
return EntityUid.Invalid;
|
||||
}
|
||||
|
||||
if (!destination.EntMan.TryGetEntity(meta.NetEntity, out var otherUid))
|
||||
{
|
||||
Assert.Fail($"Failed to resolve net ID while converting the EntityUid entity {source.EntMan.ToPrettyString(uid)}");
|
||||
return EntityUid.Invalid;
|
||||
}
|
||||
|
||||
return otherUid.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command on the server and wait some number of ticks.
|
||||
/// </summary>
|
||||
public async Task WaitCommand(string cmd, int numTicks = 10)
|
||||
{
|
||||
await Server.ExecuteCommand(cmd);
|
||||
await RunTicksSync(numTicks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command on the client and wait some number of ticks.
|
||||
/// </summary>
|
||||
public async Task WaitClientCommand(string cmd, int numTicks = 10)
|
||||
{
|
||||
await Client.ExecuteCommand(cmd);
|
||||
await RunTicksSync(numTicks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all entity prototypes that have some component.
|
||||
/// </summary>
|
||||
public List<(EntityPrototype, T)> GetPrototypesWithComponent<T>(
|
||||
HashSet<string>? ignored = null,
|
||||
bool ignoreAbstract = true,
|
||||
bool ignoreTestPrototypes = true)
|
||||
where T : IComponent, new()
|
||||
{
|
||||
if (!Server.ResolveDependency<IComponentFactory>().TryGetRegistration<T>(out var reg)
|
||||
&& !Client.ResolveDependency<IComponentFactory>().TryGetRegistration<T>(out reg))
|
||||
{
|
||||
Assert.Fail($"Unknown component: {typeof(T).Name}");
|
||||
return new();
|
||||
}
|
||||
|
||||
var id = reg.Name;
|
||||
var list = new List<(EntityPrototype, T)>();
|
||||
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (ignored != null && ignored.Contains(proto.ID))
|
||||
continue;
|
||||
|
||||
if (ignoreAbstract && proto.Abstract)
|
||||
continue;
|
||||
|
||||
if (ignoreTestPrototypes && IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (proto.Components.TryGetComponent(id, out var cmp))
|
||||
list.Add((proto, (T)cmp));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all entity prototypes that have some component.
|
||||
/// </summary>
|
||||
public List<EntityPrototype> GetPrototypesWithComponent(Type type,
|
||||
HashSet<string>? ignored = null,
|
||||
bool ignoreAbstract = true,
|
||||
bool ignoreTestPrototypes = true)
|
||||
{
|
||||
var id = Server.ResolveDependency<IComponentFactory>().GetComponentName(type);
|
||||
var list = new List<EntityPrototype>();
|
||||
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (ignored != null && ignored.Contains(proto.ID))
|
||||
continue;
|
||||
|
||||
if (ignoreAbstract && proto.Abstract)
|
||||
continue;
|
||||
|
||||
if (ignoreTestPrototypes && IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (proto.Components.ContainsKey(id))
|
||||
list.Add((proto));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
|
||||
/// </summary>
|
||||
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
|
||||
{
|
||||
user ??= Client.User!.Value;
|
||||
if (user is not {} userId)
|
||||
if (user is not { } userId)
|
||||
return;
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
@@ -183,7 +71,7 @@ public sealed partial class TestPair
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var profile = (HumanoidCharacterProfile)prefs.Characters[0];
|
||||
var newProfile = profile.WithAntagPreference(id, value);
|
||||
_modifiedProfiles.Add(userId);
|
||||
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
|
||||
@@ -211,7 +99,7 @@ public sealed partial class TestPair
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
var prefs = prefMan.GetPreferences(user);
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var profile = (HumanoidCharacterProfile)prefs.Characters[0];
|
||||
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
|
||||
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
// This partial class contains helper methods to deal with yaml prototypes.
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
private Dictionary<Type, HashSet<string>> _loadedPrototypes = new();
|
||||
private HashSet<string> _loadedEntityPrototypes = new();
|
||||
|
||||
public async Task LoadPrototypes(List<string> prototypes)
|
||||
{
|
||||
await LoadPrototypes(Server, prototypes);
|
||||
await LoadPrototypes(Client, prototypes);
|
||||
}
|
||||
|
||||
private async Task LoadPrototypes(RobustIntegrationTest.IntegrationInstance instance, List<string> prototypes)
|
||||
{
|
||||
var changed = new Dictionary<Type, HashSet<string>>();
|
||||
foreach (var file in prototypes)
|
||||
{
|
||||
instance.ProtoMan.LoadString(file, changed: changed);
|
||||
}
|
||||
|
||||
await instance.WaitPost(() => instance.ProtoMan.ReloadPrototypes(changed));
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
{
|
||||
_loadedPrototypes.GetOrNew(kind).UnionWith(ids);
|
||||
}
|
||||
|
||||
if (_loadedPrototypes.TryGetValue(typeof(EntityPrototype), out var entIds))
|
||||
_loadedEntityPrototypes.UnionWith(entIds);
|
||||
}
|
||||
|
||||
public bool IsTestPrototype(EntityPrototype proto)
|
||||
{
|
||||
return _loadedEntityPrototypes.Contains(proto.ID);
|
||||
}
|
||||
|
||||
public bool IsTestEntityPrototype(string id)
|
||||
{
|
||||
return _loadedEntityPrototypes.Contains(id);
|
||||
}
|
||||
|
||||
public bool IsTestPrototype<TPrototype>(string id) where TPrototype : IPrototype
|
||||
{
|
||||
return IsTestPrototype(typeof(TPrototype), id);
|
||||
}
|
||||
|
||||
public bool IsTestPrototype<TPrototype>(TPrototype proto) where TPrototype : IPrototype
|
||||
{
|
||||
return IsTestPrototype(typeof(TPrototype), proto.ID);
|
||||
}
|
||||
|
||||
public bool IsTestPrototype(Type kind, string id)
|
||||
{
|
||||
return _loadedPrototypes.TryGetValue(kind, out var ids) && ids.Contains(id);
|
||||
}
|
||||
}
|
||||
@@ -8,82 +8,17 @@ 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;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
// This partial class contains logic related to recycling & disposing test pairs.
|
||||
public sealed partial class TestPair : IAsyncDisposable
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
public PairState State { get; private set; } = PairState.Ready;
|
||||
|
||||
private async Task OnDirtyDispose()
|
||||
protected override async Task Cleanup()
|
||||
{
|
||||
var usageTime = Watch.Elapsed;
|
||||
Watch.Restart();
|
||||
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Test gave back pair {Id} in {usageTime.TotalMilliseconds} ms");
|
||||
Kill();
|
||||
var disposeTime = Watch.Elapsed;
|
||||
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Disposed pair {Id} in {disposeTime.TotalMilliseconds} ms");
|
||||
// Test pairs should only dirty dispose if they are failing. If they are not failing, this probably happened
|
||||
// because someone forgot to clean-return the pair.
|
||||
Assert.Warn("Test was dirty-disposed.");
|
||||
}
|
||||
|
||||
private async Task OnCleanDispose()
|
||||
{
|
||||
await Server.WaitIdleAsync();
|
||||
await Client.WaitIdleAsync();
|
||||
await base.Cleanup();
|
||||
await ResetModifiedPreferences();
|
||||
await Server.RemoveAllDummySessions();
|
||||
|
||||
if (TestMap != null)
|
||||
{
|
||||
await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
|
||||
TestMap = null;
|
||||
}
|
||||
|
||||
await RevertModifiedCvars();
|
||||
|
||||
var usageTime = Watch.Elapsed;
|
||||
Watch.Restart();
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms");
|
||||
// Let any last minute failures the test cause happen.
|
||||
await ReallyBeIdle();
|
||||
if (!Settings.Destructive)
|
||||
{
|
||||
if (Client.IsAlive == false)
|
||||
{
|
||||
throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the client in pair {Id}:", Client.UnhandledException);
|
||||
}
|
||||
|
||||
if (Server.IsAlive == false)
|
||||
{
|
||||
throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the server in pair {Id}:", Server.UnhandledException);
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.MustNotBeReused)
|
||||
{
|
||||
Kill();
|
||||
await ReallyBeIdle();
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Clean disposed in {Watch.Elapsed.TotalMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
var sRuntimeLog = Server.ResolveDependency<IRuntimeLog>();
|
||||
if (sRuntimeLog.ExceptionCount > 0)
|
||||
throw new Exception($"{nameof(CleanReturnAsync)}: Server logged exceptions");
|
||||
var cRuntimeLog = Client.ResolveDependency<IRuntimeLog>();
|
||||
if (cRuntimeLog.ExceptionCount > 0)
|
||||
throw new Exception($"{nameof(CleanReturnAsync)}: Client logged exceptions");
|
||||
|
||||
var returnTime = Watch.Elapsed;
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
|
||||
}
|
||||
|
||||
private async Task ResetModifiedPreferences()
|
||||
@@ -93,61 +28,14 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
{
|
||||
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
|
||||
}
|
||||
|
||||
_modifiedProfiles.Clear();
|
||||
}
|
||||
|
||||
public async ValueTask CleanReturnAsync()
|
||||
protected override async Task Recycle(PairSettings next, TextWriter testOut)
|
||||
{
|
||||
if (State != PairState.InUse)
|
||||
throw new Exception($"{nameof(CleanReturnAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
|
||||
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Return of pair {Id} started");
|
||||
State = PairState.CleanDisposed;
|
||||
await OnCleanDispose();
|
||||
State = PairState.Ready;
|
||||
PoolManager.NoCheckReturn(this);
|
||||
ClearContext();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case PairState.Dead:
|
||||
case PairState.Ready:
|
||||
break;
|
||||
case PairState.InUse:
|
||||
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Dirty return of pair {Id} started");
|
||||
await OnDirtyDispose();
|
||||
PoolManager.NoCheckReturn(this);
|
||||
ClearContext();
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"{nameof(DisposeAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CleanPooledPair(PoolSettings settings, TextWriter testOut)
|
||||
{
|
||||
Settings = default!;
|
||||
Watch.Restart();
|
||||
await testOut.WriteLineAsync($"Recycling...");
|
||||
|
||||
var gameTicker = Server.System<GameTicker>();
|
||||
var cNetMgr = Client.ResolveDependency<IClientNetManager>();
|
||||
|
||||
await RunTicksSync(1);
|
||||
|
||||
// Disconnect the client if they are connected.
|
||||
if (cNetMgr.IsConnected)
|
||||
{
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Disconnecting client.");
|
||||
await Client.WaitPost(() => cNetMgr.ClientDisconnect("Test pooling cleanup disconnect"));
|
||||
await RunTicksSync(1);
|
||||
}
|
||||
Assert.That(cNetMgr.IsConnected, Is.False);
|
||||
|
||||
// Move to pre-round lobby. Required to toggle dummy ticker on and off
|
||||
var gameTicker = Server.System<GameTicker>();
|
||||
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round.");
|
||||
@@ -160,8 +48,7 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
|
||||
//Apply Cvars
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Setting CVar ");
|
||||
await PoolManager.SetupCVars(Client, settings);
|
||||
await PoolManager.SetupCVars(Server, settings);
|
||||
await ApplySettings(next);
|
||||
await RunTicksSync(1);
|
||||
|
||||
// Restart server.
|
||||
@@ -169,52 +56,30 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
await Server.WaitPost(() => Server.EntMan.FlushEntities());
|
||||
await Server.WaitPost(() => gameTicker.RestartRound());
|
||||
await RunTicksSync(1);
|
||||
|
||||
// Connect client
|
||||
if (settings.ShouldBeConnected)
|
||||
{
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Connecting client");
|
||||
Client.SetConnectTarget(Server);
|
||||
await Client.WaitPost(() => cNetMgr.ClientConnect(null!, 0, null!));
|
||||
}
|
||||
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Idling");
|
||||
await ReallyBeIdle();
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Done recycling");
|
||||
}
|
||||
|
||||
public void ValidateSettings(PoolSettings settings)
|
||||
public override void ValidateSettings(PairSettings s)
|
||||
{
|
||||
base.ValidateSettings(s);
|
||||
var settings = (PoolSettings) s;
|
||||
|
||||
var cfg = Server.CfgMan;
|
||||
Assert.That(cfg.GetCVar(CCVars.AdminLogsEnabled), Is.EqualTo(settings.AdminLogsEnabled));
|
||||
Assert.That(cfg.GetCVar(CCVars.GameLobbyEnabled), Is.EqualTo(settings.InLobby));
|
||||
Assert.That(cfg.GetCVar(CCVars.GameDummyTicker), Is.EqualTo(settings.UseDummyTicker));
|
||||
Assert.That(cfg.GetCVar(CCVars.GameDummyTicker), Is.EqualTo(settings.DummyTicker));
|
||||
|
||||
var entMan = Server.ResolveDependency<EntityManager>();
|
||||
var ticker = entMan.System<GameTicker>();
|
||||
Assert.That(ticker.DummyTicker, Is.EqualTo(settings.UseDummyTicker));
|
||||
var ticker = Server.System<GameTicker>();
|
||||
Assert.That(ticker.DummyTicker, Is.EqualTo(settings.DummyTicker));
|
||||
|
||||
var expectPreRound = settings.InLobby | settings.DummyTicker;
|
||||
var expectedLevel = expectPreRound ? GameRunLevel.PreRoundLobby : GameRunLevel.InRound;
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(expectedLevel));
|
||||
|
||||
var baseClient = Client.ResolveDependency<IBaseClient>();
|
||||
var netMan = Client.ResolveDependency<INetManager>();
|
||||
Assert.That(netMan.IsConnected, Is.Not.EqualTo(!settings.ShouldBeConnected));
|
||||
|
||||
if (!settings.ShouldBeConnected)
|
||||
if (ticker.DummyTicker || !settings.Connected)
|
||||
return;
|
||||
|
||||
Assert.That(baseClient.RunLevel, Is.EqualTo(ClientRunLevel.InGame));
|
||||
var cPlayer = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
||||
var sPlayer = Server.ResolveDependency<IPlayerManager>();
|
||||
Assert.That(sPlayer.Sessions.Count(), Is.EqualTo(1));
|
||||
var sPlayer = Server.ResolveDependency<ISharedPlayerManager>();
|
||||
var session = sPlayer.Sessions.Single();
|
||||
Assert.That(cPlayer.LocalSession?.UserId, Is.EqualTo(session.UserId));
|
||||
|
||||
if (ticker.DummyTicker)
|
||||
return;
|
||||
|
||||
var status = ticker.PlayerGameStatuses[session.UserId];
|
||||
var expected = settings.InLobby
|
||||
? PlayerGameStatus.NotReadyToPlay
|
||||
@@ -229,11 +94,11 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
}
|
||||
|
||||
Assert.That(session.AttachedEntity, Is.Not.Null);
|
||||
Assert.That(entMan.EntityExists(session.AttachedEntity));
|
||||
Assert.That(entMan.HasComponent<MindContainerComponent>(session.AttachedEntity));
|
||||
var mindCont = entMan.GetComponent<MindContainerComponent>(session.AttachedEntity!.Value);
|
||||
Assert.That(Server.EntMan.EntityExists(session.AttachedEntity));
|
||||
Assert.That(Server.EntMan.HasComponent<MindContainerComponent>(session.AttachedEntity));
|
||||
var mindCont = Server.EntMan.GetComponent<MindContainerComponent>(session.AttachedEntity!.Value);
|
||||
Assert.That(mindCont.Mind, Is.Not.Null);
|
||||
Assert.That(entMan.TryGetComponent(mindCont.Mind, out MindComponent? mind));
|
||||
Assert.That(Server.EntMan.TryGetComponent(mindCont.Mind, out MindComponent? mind));
|
||||
Assert.That(mind!.VisitingEntity, Is.Null);
|
||||
Assert.That(mind.OwnedEntity, Is.EqualTo(session.AttachedEntity!.Value));
|
||||
Assert.That(mind.UserId, Is.EqualTo(session.UserId));
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
|
||||
// This partial class contains methods for running the server/client pairs for some number of ticks
|
||||
public sealed partial class TestPair
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the server-client pair in sync
|
||||
/// </summary>
|
||||
/// <param name="ticks">How many ticks to run them for</param>
|
||||
public async Task RunTicksSync(int ticks)
|
||||
{
|
||||
for (var i = 0; i < ticks; i++)
|
||||
{
|
||||
await Server.WaitRunTicks(1);
|
||||
await Client.WaitRunTicks(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a time interval to some number of ticks.
|
||||
/// </summary>
|
||||
public int SecondsToTicks(float seconds)
|
||||
{
|
||||
return (int) Math.Ceiling(seconds / Server.Timing.TickPeriod.TotalSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the server & client in sync for some amount of time
|
||||
/// </summary>
|
||||
public async Task RunSeconds(float seconds)
|
||||
{
|
||||
await RunTicksSync(SecondsToTicks(seconds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the server-client pair in sync, but also ensures they are both idle each tick.
|
||||
/// </summary>
|
||||
/// <param name="runTicks">How many ticks to run</param>
|
||||
public async Task ReallyBeIdle(int runTicks = 25)
|
||||
{
|
||||
for (var i = 0; i < runTicks; i++)
|
||||
{
|
||||
await Client.WaitRunTicks(1);
|
||||
await Server.WaitRunTicks(1);
|
||||
for (var idleCycles = 0; idleCycles < 4; idleCycles++)
|
||||
{
|
||||
await Client.WaitIdleAsync();
|
||||
await Server.WaitIdleAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the server/clients until the ticks are synchronized.
|
||||
/// By default the client will be one tick ahead of the server.
|
||||
/// </summary>
|
||||
public async Task SyncTicks(int targetDelta = 1)
|
||||
{
|
||||
var sTick = (int)Server.Timing.CurTick.Value;
|
||||
var cTick = (int)Client.Timing.CurTick.Value;
|
||||
var delta = cTick - sTick;
|
||||
|
||||
if (delta == targetDelta)
|
||||
return;
|
||||
if (delta > targetDelta)
|
||||
await Server.WaitRunTicks(delta - targetDelta);
|
||||
else
|
||||
await Client.WaitRunTicks(targetDelta - delta);
|
||||
|
||||
sTick = (int)Server.Timing.CurTick.Value;
|
||||
cTick = (int)Client.Timing.CurTick.Value;
|
||||
delta = cTick - sTick;
|
||||
Assert.That(delta, Is.EqualTo(targetDelta));
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.IntegrationTests.Tests.Destructible;
|
||||
using Content.IntegrationTests.Tests.DeviceNetwork;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Content.IntegrationTests.Pair;
|
||||
@@ -18,156 +19,109 @@ namespace Content.IntegrationTests.Pair;
|
||||
/// <summary>
|
||||
/// This object wraps a pooled server+client pair.
|
||||
/// </summary>
|
||||
public sealed partial class TestPair
|
||||
public sealed partial class TestPair : RobustIntegrationTest.TestPair
|
||||
{
|
||||
public readonly int Id;
|
||||
private bool _initialized;
|
||||
private TextWriter _testOut = default!;
|
||||
public readonly Stopwatch Watch = new();
|
||||
public readonly List<string> TestHistory = new();
|
||||
public PoolSettings Settings = default!;
|
||||
public TestMapData? TestMap;
|
||||
private List<NetUserId> _modifiedProfiles = new();
|
||||
|
||||
private int _nextServerSeed;
|
||||
private int _nextClientSeed;
|
||||
|
||||
public int ServerSeed;
|
||||
public int ClientSeed;
|
||||
|
||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||
|
||||
public void Deconstruct(
|
||||
out RobustIntegrationTest.ServerIntegrationInstance server,
|
||||
out RobustIntegrationTest.ClientIntegrationInstance client)
|
||||
{
|
||||
server = Server;
|
||||
client = Client;
|
||||
}
|
||||
|
||||
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
|
||||
|
||||
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
||||
|
||||
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;
|
||||
public PoolTestLogHandler ClientLogHandler { get; private set; } = default!;
|
||||
|
||||
public TestPair(int id)
|
||||
protected override async Task Initialize()
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
await base.Initialize();
|
||||
|
||||
public async Task Initialize(PoolSettings settings, TextWriter testOut, List<string> testPrototypes)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Already initialized");
|
||||
|
||||
_initialized = true;
|
||||
Settings = settings;
|
||||
(Client, ClientLogHandler) = await PoolManager.GenerateClient(settings, testOut);
|
||||
(Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut);
|
||||
ActivateContext(testOut);
|
||||
|
||||
Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged;
|
||||
Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged;
|
||||
|
||||
if (!settings.NoLoadTestPrototypes)
|
||||
await LoadPrototypes(testPrototypes!);
|
||||
|
||||
if (!settings.UseDummyTicker)
|
||||
// Prevent info log spam in some tests (particularly SpawnAndDeleteAllEntitiesOnDifferentMaps)
|
||||
Server.System<SharedMapSystem>().Log.Level = LogLevel.Warning;
|
||||
Client.EntMan.EntitySysManager.SystemLoaded += (_, e) =>
|
||||
{
|
||||
var gameTicker = Server.ResolveDependency<IEntityManager>().System<GameTicker>();
|
||||
if (e.System is SharedMapSystem map)
|
||||
map.Log.Level = LogLevel.Warning;
|
||||
};
|
||||
|
||||
var settings = (PoolSettings)Settings;
|
||||
if (!settings.DummyTicker)
|
||||
{
|
||||
var gameTicker = Server.System<GameTicker>();
|
||||
await Server.WaitPost(() => gameTicker.RestartRound());
|
||||
}
|
||||
}
|
||||
|
||||
// Always initially connect clients to generate an initial random set of preferences/profiles.
|
||||
// This is to try and prevent issues where if the first test that connects the client is consistently some test
|
||||
// that uses a fixed seed, it would effectively prevent it from beingrandomized.
|
||||
public override async Task RevertModifiedCvars()
|
||||
{
|
||||
// I just love order dependent cvars
|
||||
// I.e., cvars that when changed automatically cause others to also change.
|
||||
var modified = ModifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik);
|
||||
|
||||
Client.SetConnectTarget(Server);
|
||||
await Client.WaitIdleAsync();
|
||||
var netMgr = Client.ResolveDependency<IClientNetManager>();
|
||||
await Client.WaitPost(() => netMgr.ClientConnect(null!, 0, null!));
|
||||
await ReallyBeIdle(10);
|
||||
await Client.WaitRunTicks(1);
|
||||
await base.RevertModifiedCvars();
|
||||
|
||||
if (!settings.ShouldBeConnected)
|
||||
if (!modified)
|
||||
return;
|
||||
|
||||
await Server.WaitPost(() => Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik!));
|
||||
ClearModifiedCvars();
|
||||
}
|
||||
|
||||
protected override async Task ApplySettings(IIntegrationInstance instance, PairSettings n)
|
||||
{
|
||||
var next = (PoolSettings)n;
|
||||
await base.ApplySettings(instance, next);
|
||||
var cfg = instance.CfgMan;
|
||||
await instance.WaitPost(() =>
|
||||
{
|
||||
await Client.WaitPost(() => netMgr.ClientDisconnect("Initial disconnect"));
|
||||
await ReallyBeIdle(10);
|
||||
}
|
||||
if (cfg.IsCVarRegistered(CCVars.GameDummyTicker.Name))
|
||||
cfg.SetCVar(CCVars.GameDummyTicker, next.DummyTicker);
|
||||
|
||||
var cRand = Client.ResolveDependency<IRobustRandom>();
|
||||
var sRand = Server.ResolveDependency<IRobustRandom>();
|
||||
_nextClientSeed = cRand.Next();
|
||||
_nextServerSeed = sRand.Next();
|
||||
if (cfg.IsCVarRegistered(CCVars.GameLobbyEnabled.Name))
|
||||
cfg.SetCVar(CCVars.GameLobbyEnabled, next.InLobby);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.GameMap.Name))
|
||||
cfg.SetCVar(CCVars.GameMap, next.Map);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.AdminLogsEnabled.Name))
|
||||
cfg.SetCVar(CCVars.AdminLogsEnabled, next.AdminLogsEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
protected override RobustIntegrationTest.ClientIntegrationOptions ClientOptions()
|
||||
{
|
||||
State = PairState.Dead;
|
||||
ServerLogHandler.ShuttingDown = true;
|
||||
ClientLogHandler.ShuttingDown = true;
|
||||
Server.Dispose();
|
||||
Client.Dispose();
|
||||
}
|
||||
var opts = base.ClientOptions();
|
||||
|
||||
private void ClearContext()
|
||||
{
|
||||
_testOut = default!;
|
||||
ServerLogHandler.ClearContext();
|
||||
ClientLogHandler.ClearContext();
|
||||
}
|
||||
|
||||
public void ActivateContext(TextWriter testOut)
|
||||
{
|
||||
_testOut = testOut;
|
||||
ServerLogHandler.ActivateContext(testOut);
|
||||
ClientLogHandler.ActivateContext(testOut);
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
if (State != PairState.Ready)
|
||||
throw new InvalidOperationException($"Pair is not ready to use. State: {State}");
|
||||
State = PairState.InUse;
|
||||
}
|
||||
|
||||
public enum PairState : byte
|
||||
{
|
||||
Ready = 0,
|
||||
InUse = 1,
|
||||
CleanDisposed = 2,
|
||||
Dead = 3,
|
||||
}
|
||||
|
||||
public void SetupSeed()
|
||||
{
|
||||
var sRand = Server.ResolveDependency<IRobustRandom>();
|
||||
if (Settings.ServerSeed is { } severSeed)
|
||||
opts.LoadTestAssembly = false;
|
||||
opts.ContentStart = true;
|
||||
opts.FailureLogLevel = LogLevel.Warning;
|
||||
opts.Options = new()
|
||||
{
|
||||
ServerSeed = severSeed;
|
||||
sRand.SetSeed(ServerSeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerSeed = _nextServerSeed;
|
||||
sRand.SetSeed(ServerSeed);
|
||||
_nextServerSeed = sRand.Next();
|
||||
}
|
||||
LoadConfigAndUserData = false,
|
||||
};
|
||||
|
||||
var cRand = Client.ResolveDependency<IRobustRandom>();
|
||||
if (Settings.ClientSeed is { } clientSeed)
|
||||
opts.BeforeStart += () =>
|
||||
{
|
||||
ClientSeed = clientSeed;
|
||||
cRand.SetSeed(ClientSeed);
|
||||
}
|
||||
else
|
||||
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ClientModuleTestingCallbacks
|
||||
{
|
||||
ClientBeforeIoC = () => IoCManager.Register<IParallaxManager, DummyParallaxManager>(true)
|
||||
});
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
|
||||
protected override RobustIntegrationTest.ServerIntegrationOptions ServerOptions()
|
||||
{
|
||||
var opts = base.ServerOptions();
|
||||
|
||||
opts.LoadTestAssembly = false;
|
||||
opts.ContentStart = true;
|
||||
opts.Options = new()
|
||||
{
|
||||
ClientSeed = _nextClientSeed;
|
||||
cRand.SetSeed(ClientSeed);
|
||||
_nextClientSeed = cRand.Next();
|
||||
}
|
||||
LoadConfigAndUserData = false,
|
||||
};
|
||||
|
||||
opts.BeforeStart += () =>
|
||||
{
|
||||
// Server-only systems (i.e., systems that subscribe to events with server-only components)
|
||||
// There's probably a better way to do this.
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
|
||||
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#nullable enable
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
// Partial class containing cvar logic
|
||||
// Partial class containing test cvars
|
||||
// This could probably be merged into the main file, but I'm keeping it separate to reduce
|
||||
// conflicts for forks.
|
||||
public static partial class PoolManager
|
||||
{
|
||||
private static readonly (string cvar, string value)[] TestCvars =
|
||||
public static readonly (string cvar, string value)[] TestCvars =
|
||||
{
|
||||
// @formatter:off
|
||||
(CCVars.DatabaseSynchronous.Name, "true"),
|
||||
@@ -17,9 +16,7 @@ public static partial class PoolManager
|
||||
(CCVars.HolidaysEnabled.Name, "false"),
|
||||
(CCVars.GameMap.Name, TestMap),
|
||||
(CCVars.AdminLogsQueueSendDelay.Name, "0"),
|
||||
(CVars.NetPVS.Name, "false"),
|
||||
(CCVars.NPCMaxUpdates.Name, "999999"),
|
||||
(CVars.ThreadParallelCount.Name, "1"),
|
||||
(CCVars.GameRoleTimers.Name, "false"),
|
||||
(CCVars.GameRoleWhitelist.Name, "false"),
|
||||
(CCVars.GridFill.Name, "false"),
|
||||
@@ -29,55 +26,16 @@ public static partial class PoolManager
|
||||
(CCVars.ProcgenPreload.Name, "false"),
|
||||
(CCVars.WorldgenEnabled.Name, "false"),
|
||||
(CCVars.GatewayGeneratorEnabled.Name, "false"),
|
||||
(CVars.ReplayClientRecordingEnabled.Name, "false"),
|
||||
(CVars.ReplayServerRecordingEnabled.Name, "false"),
|
||||
(CCVars.GameDummyTicker.Name, "true"),
|
||||
(CCVars.GameLobbyEnabled.Name, "false"),
|
||||
(CCVars.ConfigPresetDevelopment.Name, "false"),
|
||||
(CCVars.AdminLogsEnabled.Name, "false"),
|
||||
(CCVars.AutosaveEnabled.Name, "false"),
|
||||
(CVars.NetBufferSize.Name, "0"),
|
||||
(CCVars.InteractionRateLimitCount.Name, "9999999"),
|
||||
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
|
||||
(CCVars.MovementMobPushing.Name, "false"),
|
||||
(CCVars.GameLobbyDefaultPreset.Name, "secret"), // Frontier: Adventure takes ages, default to secret
|
||||
(CCVars.StaticStorageUI.Name, "true"),// Frontier: causes storage test failures
|
||||
(CCVars.StorageLimit.Name, "1")// Frontier: test failures with multiple storage enabled
|
||||
(CCVars.StorageLimit.Name, "1")// Frontier: test failures with multiple storage
|
||||
};
|
||||
|
||||
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)
|
||||
{
|
||||
var cfg = instance.ResolveDependency<IConfigurationManager>();
|
||||
await instance.WaitPost(() =>
|
||||
{
|
||||
if (cfg.IsCVarRegistered(CCVars.GameDummyTicker.Name))
|
||||
cfg.SetCVar(CCVars.GameDummyTicker, settings.UseDummyTicker);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.GameLobbyEnabled.Name))
|
||||
cfg.SetCVar(CCVars.GameLobbyEnabled, settings.InLobby);
|
||||
|
||||
if (cfg.IsCVarRegistered(CVars.NetInterp.Name))
|
||||
cfg.SetCVar(CVars.NetInterp, settings.DisableInterpolate);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.GameMap.Name))
|
||||
cfg.SetCVar(CCVars.GameMap, settings.Map);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.AdminLogsEnabled.Name))
|
||||
cfg.SetCVar(CCVars.AdminLogsEnabled, settings.AdminLogsEnabled);
|
||||
|
||||
if (cfg.IsCVarRegistered(CVars.NetInterp.Name))
|
||||
cfg.SetCVar(CVars.NetInterp, !settings.DisableInterpolate);
|
||||
|
||||
if (cfg.IsCVarRegistered(CCVars.GameLobbyDefaultPreset.Name) && !string.IsNullOrEmpty(settings.GameLobbyDefaultPreset)) // Frontier
|
||||
cfg.SetCVar(CCVars.GameLobbyDefaultPreset, settings.GameLobbyDefaultPreset); // Frontier
|
||||
});
|
||||
}
|
||||
|
||||
private static void SetDefaultCVars(RobustIntegrationTest.IntegrationOptions options)
|
||||
{
|
||||
foreach (var (cvar, value) in TestCvars)
|
||||
{
|
||||
options.CVarOverrides[cvar] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,371 +1,17 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.IntegrationTests.Tests;
|
||||
using Content.IntegrationTests.Tests.Destructible;
|
||||
using Content.IntegrationTests.Tests.DeviceNetwork;
|
||||
using Content.IntegrationTests.Tests.Interaction.Click;
|
||||
using Robust.Client;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Making clients, and servers is slow, this manages a pool of them so tests can reuse them.
|
||||
/// </summary>
|
||||
// The static class exist to avoid breaking changes
|
||||
public static partial class PoolManager
|
||||
{
|
||||
public static readonly ContentPoolManager Instance = new();
|
||||
public const string TestMap = "Empty";
|
||||
private static int _pairId;
|
||||
private static readonly object PairLock = new();
|
||||
private static bool _initialized;
|
||||
|
||||
// Pair, IsBorrowed
|
||||
private static readonly Dictionary<TestPair, bool> Pairs = new();
|
||||
private static bool _dead;
|
||||
private static Exception? _poolFailureReason;
|
||||
|
||||
private static HashSet<Assembly> _contentAssemblies = default!;
|
||||
|
||||
public static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer(
|
||||
PoolSettings poolSettings,
|
||||
TextWriter testOut)
|
||||
{
|
||||
var options = new RobustIntegrationTest.ServerIntegrationOptions
|
||||
{
|
||||
ContentStart = true,
|
||||
Options = new ServerOptions()
|
||||
{
|
||||
LoadConfigAndUserData = false,
|
||||
LoadContentResources = !poolSettings.NoLoadContent,
|
||||
},
|
||||
ContentAssemblies = _contentAssemblies.ToArray()
|
||||
};
|
||||
|
||||
var logHandler = new PoolTestLogHandler("SERVER");
|
||||
logHandler.ActivateContext(testOut);
|
||||
options.OverrideLogHandler = () => logHandler;
|
||||
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
// Server-only systems (i.e., systems that subscribe to events with server-only components)
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
|
||||
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
|
||||
|
||||
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
|
||||
if (!poolSettings.NoValidate) // Mono
|
||||
IoCManager.Resolve<IConfigurationManager>()
|
||||
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
|
||||
};
|
||||
|
||||
SetDefaultCVars(options);
|
||||
var server = new RobustIntegrationTest.ServerIntegrationInstance(options);
|
||||
await server.WaitIdleAsync();
|
||||
await SetupCVars(server, poolSettings);
|
||||
return (server, logHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This shuts down the pool, and disposes all the server/client pairs.
|
||||
/// This is a one time operation to be used when the testing program is exiting.
|
||||
/// </summary>
|
||||
public static void Shutdown()
|
||||
{
|
||||
List<TestPair> localPairs;
|
||||
lock (PairLock)
|
||||
{
|
||||
if (_dead)
|
||||
return;
|
||||
_dead = true;
|
||||
localPairs = Pairs.Keys.ToList();
|
||||
}
|
||||
|
||||
foreach (var pair in localPairs)
|
||||
{
|
||||
pair.Kill();
|
||||
}
|
||||
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public static string DeathReport()
|
||||
{
|
||||
lock (PairLock)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var pairs = Pairs.Keys.OrderBy(pair => pair.Id);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var borrowed = Pairs[pair];
|
||||
builder.AppendLine($"Pair {pair.Id}, Tests Run: {pair.TestHistory.Count}, Borrowed: {borrowed}");
|
||||
for (var i = 0; i < pair.TestHistory.Count; i++)
|
||||
{
|
||||
builder.AppendLine($"#{i}: {pair.TestHistory[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(RobustIntegrationTest.ClientIntegrationInstance, PoolTestLogHandler)> GenerateClient(
|
||||
PoolSettings poolSettings,
|
||||
TextWriter testOut)
|
||||
{
|
||||
var options = new RobustIntegrationTest.ClientIntegrationOptions
|
||||
{
|
||||
FailureLogLevel = LogLevel.Warning,
|
||||
ContentStart = true,
|
||||
ContentAssemblies = new[]
|
||||
{
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Client.Entry.EntryPoint).Assembly,
|
||||
typeof(PoolManager).Assembly,
|
||||
}
|
||||
};
|
||||
|
||||
if (poolSettings.NoLoadContent)
|
||||
{
|
||||
Assert.Warn("NoLoadContent does not work on the client, ignoring");
|
||||
}
|
||||
|
||||
options.Options = new GameControllerOptions()
|
||||
{
|
||||
LoadConfigAndUserData = false,
|
||||
// LoadContentResources = !poolSettings.NoLoadContent
|
||||
};
|
||||
|
||||
var logHandler = new PoolTestLogHandler("CLIENT");
|
||||
logHandler.ActivateContext(testOut);
|
||||
options.OverrideLogHandler = () => logHandler;
|
||||
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ClientModuleTestingCallbacks
|
||||
{
|
||||
ClientBeforeIoC = () =>
|
||||
{
|
||||
// do not register extra systems or components here -- they will get cleared when the client is
|
||||
// disconnected. just use reflection.
|
||||
IoCManager.Register<IParallaxManager, DummyParallaxManager>(true);
|
||||
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
|
||||
if (!poolSettings.NoValidate) // Mono
|
||||
IoCManager.Resolve<IConfigurationManager>()
|
||||
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
SetDefaultCVars(options);
|
||||
var client = new RobustIntegrationTest.ClientIntegrationInstance(options);
|
||||
await client.WaitIdleAsync();
|
||||
await SetupCVars(client, poolSettings);
|
||||
return (client, logHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Pair.TestPair"/>, which can be used to get access to a server, and client <see cref="Pair.TestPair"/>
|
||||
/// </summary>
|
||||
/// <param name="poolSettings">See <see cref="PoolSettings"/></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TestPair> GetServerClient(PoolSettings? poolSettings = null)
|
||||
{
|
||||
return await GetServerClientPair(poolSettings ?? new PoolSettings());
|
||||
}
|
||||
|
||||
private static string GetDefaultTestName(TestContext testContext)
|
||||
{
|
||||
return testContext.Test.FullName.Replace("Content.IntegrationTests.Tests.", "");
|
||||
}
|
||||
|
||||
private static async Task<TestPair> GetServerClientPair(PoolSettings poolSettings)
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException($"Pool manager has not been initialized");
|
||||
|
||||
// Trust issues with the AsyncLocal that backs this.
|
||||
var testContext = TestContext.CurrentContext;
|
||||
var testOut = TestContext.Out;
|
||||
|
||||
DieIfPoolFailure();
|
||||
var currentTestName = poolSettings.TestName ?? GetDefaultTestName(testContext);
|
||||
var poolRetrieveTimeWatch = new Stopwatch();
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Called by test {currentTestName}");
|
||||
TestPair? pair = null;
|
||||
try
|
||||
{
|
||||
poolRetrieveTimeWatch.Start();
|
||||
if (poolSettings.MustBeNew)
|
||||
{
|
||||
await testOut.WriteLineAsync(
|
||||
$"{nameof(GetServerClientPair)}: Creating pair, because settings of pool settings");
|
||||
pair = await CreateServerClientPair(poolSettings, testOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Looking in pool for a suitable pair");
|
||||
pair = GrabOptimalPair(poolSettings);
|
||||
if (pair != null)
|
||||
{
|
||||
pair.ActivateContext(testOut);
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Suitable pair found");
|
||||
var canSkip = pair.Settings.CanFastRecycle(poolSettings);
|
||||
|
||||
if (canSkip)
|
||||
{
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Cleanup not needed, Skipping cleanup of pair");
|
||||
await SetupCVars(pair.Client, poolSettings);
|
||||
await SetupCVars(pair.Server, poolSettings);
|
||||
await pair.RunTicksSync(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Cleaning existing pair");
|
||||
await pair.CleanPooledPair(poolSettings, testOut);
|
||||
}
|
||||
|
||||
await pair.RunTicksSync(5);
|
||||
await pair.SyncTicks(targetDelta: 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Creating a new pair, no suitable pair found in pool");
|
||||
pair = await CreateServerClientPair(poolSettings, testOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pair != null && pair.TestHistory.Count > 0)
|
||||
{
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Pair {pair.Id} Test History Start");
|
||||
for (var i = 0; i < pair.TestHistory.Count; i++)
|
||||
{
|
||||
await testOut.WriteLineAsync($"- Pair {pair.Id} Test #{i}: {pair.TestHistory[i]}");
|
||||
}
|
||||
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Pair {pair.Id} Test History End");
|
||||
}
|
||||
}
|
||||
|
||||
if (!poolSettings.NoValidate) // Mono
|
||||
pair.ValidateSettings(poolSettings);
|
||||
|
||||
var poolRetrieveTime = poolRetrieveTimeWatch.Elapsed;
|
||||
await testOut.WriteLineAsync(
|
||||
$"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms");
|
||||
|
||||
pair.ClearModifiedCvars();
|
||||
pair.Settings = poolSettings;
|
||||
pair.TestHistory.Add(currentTestName);
|
||||
pair.SetupSeed();
|
||||
await testOut.WriteLineAsync(
|
||||
$"{nameof(GetServerClientPair)}: Returning pair {pair.Id} with client/server seeds: {pair.ClientSeed}/{pair.ServerSeed}");
|
||||
|
||||
pair.Watch.Restart();
|
||||
return pair;
|
||||
}
|
||||
|
||||
private static TestPair? GrabOptimalPair(PoolSettings poolSettings)
|
||||
{
|
||||
lock (PairLock)
|
||||
{
|
||||
TestPair? fallback = null;
|
||||
foreach (var pair in Pairs.Keys)
|
||||
{
|
||||
if (Pairs[pair])
|
||||
continue;
|
||||
|
||||
if (!pair.Settings.CanFastRecycle(poolSettings))
|
||||
{
|
||||
fallback = pair;
|
||||
continue;
|
||||
}
|
||||
|
||||
pair.Use();
|
||||
Pairs[pair] = true;
|
||||
return pair;
|
||||
}
|
||||
|
||||
if (fallback != null)
|
||||
{
|
||||
fallback.Use();
|
||||
Pairs[fallback!] = true;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by TestPair after checking the server/client pair, Don't use this.
|
||||
/// </summary>
|
||||
public static void NoCheckReturn(TestPair pair)
|
||||
{
|
||||
lock (PairLock)
|
||||
{
|
||||
if (pair.State == TestPair.PairState.Dead)
|
||||
Pairs.Remove(pair);
|
||||
else if (pair.State == TestPair.PairState.Ready)
|
||||
Pairs[pair] = false;
|
||||
else
|
||||
throw new InvalidOperationException($"Attempted to return a pair in an invalid state. Pair: {pair.Id}. State: {pair.State}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DieIfPoolFailure()
|
||||
{
|
||||
if (_poolFailureReason != null)
|
||||
{
|
||||
// If the _poolFailureReason is not null, we can assume at least one test failed.
|
||||
// So we say inconclusive so we don't add more failed tests to search through.
|
||||
Assert.Inconclusive(@$"
|
||||
In a different test, the pool manager had an exception when trying to create a server/client pair.
|
||||
Instead of risking that the pool manager will fail at creating a server/client pairs for every single test,
|
||||
we are just going to end this here to save a lot of time. This is the exception that started this:\n {_poolFailureReason}");
|
||||
}
|
||||
|
||||
if (_dead)
|
||||
{
|
||||
// If Pairs is null, we ran out of time, we can't assume a test failed.
|
||||
// So we are going to tell it all future tests are a failure.
|
||||
Assert.Fail("The pool was shut down");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<TestPair> CreateServerClientPair(PoolSettings poolSettings, TextWriter testOut)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = Interlocked.Increment(ref _pairId);
|
||||
var pair = new TestPair(id);
|
||||
await pair.Initialize(poolSettings, testOut, _testPrototypes);
|
||||
pair.Use();
|
||||
await pair.RunTicksSync(5);
|
||||
await pair.SyncTicks(targetDelta: 1);
|
||||
return pair;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_poolFailureReason = ex;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a server, or a client until a condition is true
|
||||
@@ -421,29 +67,42 @@ we are just going to end this here to save a lot of time. This is the exception
|
||||
Assert.That(passed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the pool manager.
|
||||
/// </summary>
|
||||
/// <param name="extraAssemblies">Assemblies to search for to discover extra prototypes and systems.</param>
|
||||
public static void Startup(params Assembly[] extraAssemblies)
|
||||
public static async Task<TestPair> GetServerClient(
|
||||
PoolSettings? settings = null,
|
||||
ITestContextLike? testContext = null)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Already initialized");
|
||||
return await Instance.GetPair(settings, testContext);
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
_contentAssemblies =
|
||||
[
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Server.Entry.EntryPoint).Assembly,
|
||||
typeof(PoolManager).Assembly
|
||||
];
|
||||
_contentAssemblies.UnionWith(extraAssemblies);
|
||||
public static void Startup(params Assembly[] extra)
|
||||
=> Instance.Startup(extra);
|
||||
|
||||
_testPrototypes.Clear();
|
||||
DiscoverTestPrototypes(typeof(PoolManager).Assembly);
|
||||
foreach (var assembly in extraAssemblies)
|
||||
{
|
||||
DiscoverTestPrototypes(assembly);
|
||||
}
|
||||
public static void Shutdown() => Instance.Shutdown();
|
||||
public static string DeathReport() => Instance.DeathReport();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Making clients, and servers is slow, this manages a pool of them so tests can reuse them.
|
||||
/// </summary>
|
||||
public sealed class ContentPoolManager : PoolManager<TestPair>
|
||||
{
|
||||
public override PairSettings DefaultSettings => new PoolSettings();
|
||||
protected override string GetDefaultTestName(ITestContextLike testContext)
|
||||
{
|
||||
return testContext.FullName.Replace("Content.IntegrationTests.Tests.", "");
|
||||
}
|
||||
|
||||
public override void Startup(params Assembly[] extraAssemblies)
|
||||
{
|
||||
DefaultCvars.AddRange(PoolManager.TestCvars);
|
||||
|
||||
var shared = extraAssemblies
|
||||
.Append(typeof(Shared.Entry.EntryPoint).Assembly)
|
||||
.Append(typeof(PoolManager).Assembly)
|
||||
.ToArray();
|
||||
|
||||
Startup([typeof(Client.Entry.EntryPoint).Assembly],
|
||||
[typeof(Server.Entry.EntryPoint).Assembly],
|
||||
shared);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,31 @@
|
||||
#nullable enable
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for the pooled server, and client pair.
|
||||
/// Some options are for changing the pair, and others are
|
||||
/// so the pool can properly clean up what you borrowed.
|
||||
/// </summary>
|
||||
public sealed class PoolSettings
|
||||
/// <inheritdoc/>
|
||||
public sealed class PoolSettings : PairSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Set to true if the test will ruin the server/client pair.
|
||||
/// </summary>
|
||||
public bool Destructive { get; init; }
|
||||
public override bool Connected
|
||||
{
|
||||
get => _connected || InLobby;
|
||||
init => _connected = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the given server/client pair should be created fresh.
|
||||
/// </summary>
|
||||
public bool Fresh { get; init; }
|
||||
private readonly bool _dummyTicker = true;
|
||||
private readonly bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the given server should be using a dummy ticker. Ignored if <see cref="InLobby"/> is true.
|
||||
/// </summary>
|
||||
public bool DummyTicker { get; init; } = true;
|
||||
public bool DummyTicker
|
||||
{
|
||||
get => _dummyTicker && !InLobby;
|
||||
init => _dummyTicker = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, this enables the creation of admin logs during the test.
|
||||
/// </summary>
|
||||
public bool AdminLogsEnabled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the given server/client pair should be connected from each other.
|
||||
/// Defaults to disconnected as it makes dirty recycling slightly faster.
|
||||
/// If <see cref="InLobby"/> is true, this option is ignored.
|
||||
/// </summary>
|
||||
public bool Connected { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the given server/client pair should be in the lobby.
|
||||
/// If the pair is not in the lobby at the end of the test, this test must be marked as dirty.
|
||||
@@ -53,94 +41,22 @@ public sealed class PoolSettings
|
||||
/// </summary>
|
||||
public bool NoLoadContent { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// This will return a server-client pair that has not loaded test prototypes.
|
||||
/// Try avoiding this whenever possible, as this will always create & destroy a new pair.
|
||||
/// Use <see cref="Pair.TestPair.IsTestPrototype(Robust.Shared.Prototypes.EntityPrototype)"/> if you need to exclude test prototypees.
|
||||
/// </summary>
|
||||
public bool NoLoadTestPrototypes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this to true to disable the NetInterp CVar on the given server/client pair
|
||||
/// </summary>
|
||||
public bool DisableInterpolate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this to true to always clean up the server/client pair before giving it to another borrower
|
||||
/// </summary>
|
||||
public bool Dirty { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this to the path of a map to have the given server/client pair load the map.
|
||||
/// </summary>
|
||||
public string Map { get; init; } = PoolManager.TestMap;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the test name detection, and uses this in the test history instead
|
||||
/// </summary>
|
||||
public string? TestName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, this will be used to call <see cref="IRobustRandom.SetSeed"/>
|
||||
/// </summary>
|
||||
public int? ServerSeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, this will be used to call <see cref="IRobustRandom.SetSeed"/>
|
||||
/// </summary>
|
||||
public int? ClientSeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mono - skips pair validation if set. Also stops termination on error.
|
||||
/// </summary>
|
||||
public bool NoValidate = false;
|
||||
|
||||
/// <summary>
|
||||
/// Frontier: the preset to run the game in.
|
||||
/// Set to secret for upstream tests to mimic upstream behaviour.
|
||||
/// If you need to check adventure game rule things, set this to nfadventure or nfpirate.
|
||||
/// </summary>
|
||||
public string GameLobbyDefaultPreset { get; set; } = "secret";
|
||||
|
||||
#region Inferred Properties
|
||||
|
||||
/// <summary>
|
||||
/// If the returned pair must not be reused
|
||||
/// </summary>
|
||||
public bool MustNotBeReused => Destructive || NoLoadContent || NoLoadTestPrototypes;
|
||||
|
||||
/// <summary>
|
||||
/// If the given pair must be brand new
|
||||
/// </summary>
|
||||
public bool MustBeNew => Fresh || NoLoadContent || NoLoadTestPrototypes;
|
||||
|
||||
public bool UseDummyTicker => !InLobby && DummyTicker;
|
||||
|
||||
public bool ShouldBeConnected => InLobby || Connected;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Tries to guess if we can skip recycling the server/client pair.
|
||||
/// </summary>
|
||||
/// <param name="nextSettings">The next set of settings the old pair will be set to</param>
|
||||
/// <returns>If we can skip cleaning it up</returns>
|
||||
public bool CanFastRecycle(PoolSettings nextSettings)
|
||||
public override bool CanFastRecycle(PairSettings nextSettings)
|
||||
{
|
||||
if (MustNotBeReused)
|
||||
throw new InvalidOperationException("Attempting to recycle a non-reusable test.");
|
||||
if (!base.CanFastRecycle(nextSettings))
|
||||
return false;
|
||||
|
||||
if (nextSettings.MustBeNew)
|
||||
throw new InvalidOperationException("Attempting to recycle a test while requesting a fresh test.");
|
||||
|
||||
if (Dirty)
|
||||
if (nextSettings is not PoolSettings next)
|
||||
return false;
|
||||
|
||||
// Check that certain settings match.
|
||||
return !ShouldBeConnected == !nextSettings.ShouldBeConnected
|
||||
&& UseDummyTicker == nextSettings.UseDummyTicker
|
||||
&& Map == nextSettings.Map
|
||||
&& InLobby == nextSettings.InLobby
|
||||
&& GameLobbyDefaultPreset == nextSettings.GameLobbyDefaultPreset; // Frontier: swappable presets
|
||||
return DummyTicker == next.DummyTicker
|
||||
&& Map == next.Map
|
||||
&& InLobby == next.InLobby;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
using System.IO;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Log handler intended for pooled integration tests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class logs to two places: an NUnit <see cref="TestContext"/>
|
||||
/// (so it nicely gets attributed to a test in your IDE),
|
||||
/// and an in-memory ring buffer for diagnostic purposes.
|
||||
/// If test pooling breaks, the ring buffer can be used to see what the broken instance has gone through.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The active test context can be swapped out so pooled instances can correctly have their logs attributed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class PoolTestLogHandler : ILogHandler
|
||||
{
|
||||
private readonly string? _prefix;
|
||||
|
||||
private RStopwatch _stopwatch;
|
||||
|
||||
public TextWriter? ActiveContext { get; private set; }
|
||||
|
||||
public LogLevel? FailureLevel { get; set; }
|
||||
|
||||
public PoolTestLogHandler(string? prefix)
|
||||
{
|
||||
_prefix = prefix != null ? $"{prefix}: " : "";
|
||||
}
|
||||
|
||||
public bool ShuttingDown;
|
||||
|
||||
public void Log(string sawmillName, LogEvent message)
|
||||
{
|
||||
var level = message.Level.ToRobust();
|
||||
|
||||
if (ShuttingDown && (FailureLevel == null || level < FailureLevel))
|
||||
return;
|
||||
|
||||
if (ActiveContext is not { } testContext)
|
||||
{
|
||||
// If this gets hit it means something is logging to this instance while it's "between" tests.
|
||||
// This is a bug in either the game or the testing system, and must always be investigated.
|
||||
throw new InvalidOperationException("Log to pool test log handler without active test context");
|
||||
}
|
||||
|
||||
var name = LogMessage.LogLevelToName(level);
|
||||
var seconds = _stopwatch.Elapsed.TotalSeconds;
|
||||
var rendered = message.RenderMessage();
|
||||
var line = $"{_prefix}{seconds:F3}s [{name}] {sawmillName}: {rendered}";
|
||||
|
||||
testContext.WriteLine(line);
|
||||
|
||||
if (FailureLevel == null || level < FailureLevel)
|
||||
return;
|
||||
|
||||
testContext.Flush();
|
||||
Assert.Fail($"{line} Exception: {message.Exception}");
|
||||
}
|
||||
|
||||
public void ClearContext()
|
||||
{
|
||||
ActiveContext = null;
|
||||
}
|
||||
|
||||
public void ActivateContext(TextWriter context)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
ActiveContext = context;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute that indicates that a string contains yaml prototype data that should be loaded by integration tests.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class TestPrototypesAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
@@ -56,7 +57,7 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -114,7 +115,7 @@ namespace Content.IntegrationTests.Tests
|
||||
SpawnEntity(entityMan, protoId, map.GridCoords); // Monolith
|
||||
}
|
||||
});
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
|
||||
@@ -278,74 +279,104 @@ namespace Content.IntegrationTests.Tests
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(e => !entMan.HasComponent<AudioComponent>(e));
|
||||
|
||||
foreach (var protoId in protoIds)
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
// TODO fix ninja
|
||||
// Currently ninja fails to equip their own loadout.
|
||||
if (protoId == "MobHumanSpaceNinja")
|
||||
continue;
|
||||
|
||||
var count = Count(server.EntMan);
|
||||
var clientCount = Count(client.EntMan);
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = SpawnEntity(server.EntMan, protoId, coords)); // Monolith
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// If the entity deleted itself, check that it didn't spawn other entities
|
||||
if (!server.EntMan.EntityExists(uid))
|
||||
foreach (var protoId in protoIds)
|
||||
{
|
||||
if (Count(server.EntMan) != count)
|
||||
var count = Count(server.EntMan);
|
||||
var clientCount = Count(client.EntMan);
|
||||
var serverEntities = new HashSet<EntityUid>(Entities(server.EntMan));
|
||||
var clientEntities = new HashSet<EntityUid>(Entities(client.EntMan));
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = SpawnEntity(server.EntMan, protoId, coords)); // Monolith
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// If the entity deleted itself, check that it didn't spawn other entities
|
||||
if (!server.EntMan.EntityExists(uid))
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
|
||||
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deleting itself\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deleting itself\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Count(client.EntMan) != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Check that the number of entities has increased.
|
||||
Assert.That(Count(server.EntMan), Is.GreaterThan(count), $"Server prototype {protoId} failed on spawning as entity count didn't increase\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(Count(client.EntMan), Is.GreaterThan(clientCount), $"Client prototype {protoId} failed on spawning as entity count didn't increase\n" +
|
||||
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
|
||||
// Check that the number of entities has increased.
|
||||
if (Count(server.EntMan) <= count)
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
|
||||
}
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
if (Count(client.EntMan) <= clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
|
||||
$"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
|
||||
$"Server was {count}");
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deletion: count didn't reset properly\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
}
|
||||
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
if (Count(server.EntMan) != count)
|
||||
{
|
||||
// Frontier: add expected vs. actual
|
||||
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly:\n" +
|
||||
$"Expected {count} and found {server.EntMan.EntityCount}.\n");
|
||||
// End Frontier
|
||||
}
|
||||
|
||||
if (Count(client.EntMan) != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private static string BuildDiffString(IEnumerable<EntityUid> oldEnts, IEnumerable<EntityUid> newEnts, IEntityManager entMan)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var addedEnts = newEnts.Except(oldEnts);
|
||||
var removedEnts = oldEnts.Except(newEnts);
|
||||
if (addedEnts.Any())
|
||||
sb.AppendLine("Listing new entities:");
|
||||
foreach (var addedEnt in addedEnts)
|
||||
{
|
||||
sb.AppendLine(entMan.ToPrettyString(addedEnt));
|
||||
}
|
||||
if (removedEnts.Any())
|
||||
sb.AppendLine("Listing removed entities:");
|
||||
foreach (var removedEnt in removedEnts)
|
||||
{
|
||||
sb.AppendLine("\t" + entMan.ToPrettyString(removedEnt));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool HasRequiredDataField(Component component)
|
||||
{
|
||||
foreach (var field in component.GetType().GetFields())
|
||||
{
|
||||
foreach (var attribute in field.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is not DataFieldAttribute dataField)
|
||||
continue;
|
||||
|
||||
if (dataField.Required)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
foreach (var property in component.GetType().GetProperties())
|
||||
{
|
||||
foreach (var attribute in property.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is not DataFieldAttribute dataField)
|
||||
continue;
|
||||
|
||||
if (dataField.Required)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AllComponentsOneToOneDeleteTest()
|
||||
{
|
||||
@@ -373,9 +404,6 @@ namespace Content.IntegrationTests.Tests
|
||||
"GridSpawner", // mono - i wouldn't
|
||||
};
|
||||
|
||||
// TODO TESTS
|
||||
// auto ignore any components that have a "required" data field.
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
@@ -393,9 +421,12 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
foreach (var type in componentFactory.AllRegisteredTypes)
|
||||
{
|
||||
var component = (Component) componentFactory.GetComponent(type);
|
||||
var component = (Component)componentFactory.GetComponent(type);
|
||||
var name = componentFactory.GetComponentName(type);
|
||||
|
||||
if (HasRequiredDataField(component))
|
||||
continue;
|
||||
|
||||
// If this component is ignored
|
||||
if (skipComponents.Contains(name))
|
||||
{
|
||||
@@ -432,9 +463,9 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
private EntityUid SpawnEntity(
|
||||
IEntityManager entManager,
|
||||
string? protoName,
|
||||
string protoName,
|
||||
MapCoordinates coordinates,
|
||||
ComponentRegistry? registry = null)
|
||||
ComponentRegistry registry = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -451,9 +482,9 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
private EntityUid SpawnEntity(
|
||||
IEntityManager entManager,
|
||||
string? protoName,
|
||||
string protoName,
|
||||
EntityCoordinates coordinates,
|
||||
ComponentRegistry? registry = null)
|
||||
ComponentRegistry registry = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ public sealed class StorageInteractionTest : InteractionTest
|
||||
await ClickControl(ctrl, ContentKeyFunctions.ActivateItemInWorld);
|
||||
await RunTicks(10);
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
|
||||
// Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); // Mono heisenfail - TODO: fix
|
||||
|
||||
// Click on the pda to pick it up and remove it from the backpack.
|
||||
await ClickControl(ctrl, ContentKeyFunctions.MoveStoredItem);
|
||||
@@ -71,7 +71,7 @@ public sealed class StorageInteractionTest : InteractionTest
|
||||
|
||||
// UIs should still be open
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
|
||||
// Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ public sealed class AdminTest : ToolshedTest
|
||||
var toolMan = Server.ResolveDependency<ToolshedManager>();
|
||||
var admin = Server.ResolveDependency<IAdminManager>();
|
||||
var ignored = new HashSet<Assembly>()
|
||||
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
|
||||
{typeof(LocTest).Assembly};
|
||||
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -25,6 +25,11 @@ public sealed class AdminTest : ToolshedTest
|
||||
if (ignored.Contains(cmd.Cmd.GetType().Assembly))
|
||||
continue;
|
||||
|
||||
// Only care about content commands.
|
||||
var assemblyName = cmd.Cmd.GetType().Assembly.FullName;
|
||||
if (assemblyName == null || !assemblyName.StartsWith("Content."))
|
||||
continue;
|
||||
|
||||
Assert.That(admin.TryGetCommandFlags(cmd, out _), $"Command does not have admin permissions set up: {cmd.FullName()}");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class LocTest : ToolshedTest
|
||||
var locStrings = new HashSet<string>();
|
||||
|
||||
var ignored = new HashSet<Assembly>()
|
||||
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
|
||||
{typeof(LocTest).Assembly};
|
||||
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
|
||||
@@ -3,19 +3,27 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>..\bin\Content.MapRenderer\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Nullable>enable</Nullable>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Testing.props" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Content.MapRenderer.Painters
|
||||
Fresh = true,
|
||||
// Seriously whoever made MapPainter use GameMapPrototype I wish you step on a lego one time.
|
||||
Map = map,
|
||||
NoValidate = true, // Mono
|
||||
});
|
||||
|
||||
await foreach (var image in RenderPair(stopwatch, pair))
|
||||
@@ -56,7 +55,6 @@ namespace Content.MapRenderer.Painters
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
Fresh = false,
|
||||
NoValidate = true, // Mono
|
||||
});
|
||||
|
||||
var server = pair.Server;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ServerGarbageCollection>True</ServerGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Packaging\Robust.Packaging.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Packaging.props" />
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for working with <c>.deps.json</c> files.
|
||||
/// </summary>
|
||||
public sealed class DepsHandler
|
||||
{
|
||||
public readonly Dictionary<string, LibraryInfo> Libraries = new();
|
||||
|
||||
public DepsHandler(DepsData data)
|
||||
{
|
||||
if (data.Targets.Count != 1)
|
||||
throw new Exception("Expected exactly one target");
|
||||
|
||||
var target = data.Targets.Single().Value;
|
||||
|
||||
foreach (var (libNameAndVersion, libInfo) in target)
|
||||
{
|
||||
var split = libNameAndVersion.Split('/', 2);
|
||||
|
||||
Libraries.Add(split[0], libInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static DepsHandler Load(string depsFile)
|
||||
{
|
||||
using var f = File.OpenRead(depsFile);
|
||||
var depsData = JsonSerializer.Deserialize<DepsData>(f) ?? throw new InvalidOperationException("Deps are null!");
|
||||
|
||||
return new DepsHandler(depsData);
|
||||
}
|
||||
|
||||
public HashSet<string> RecursiveGetLibrariesFrom(string start)
|
||||
{
|
||||
var found = new HashSet<string>();
|
||||
|
||||
RecursiveAddLibraries(start, found);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private void RecursiveAddLibraries(string start, HashSet<string> set)
|
||||
{
|
||||
if (!set.Add(start))
|
||||
return;
|
||||
|
||||
var lib = Libraries[start];
|
||||
if (lib.Dependencies == null)
|
||||
return;
|
||||
|
||||
foreach (var dep in lib.Dependencies.Keys)
|
||||
{
|
||||
RecursiveAddLibraries(dep, set);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DepsData
|
||||
{
|
||||
[JsonInclude, JsonPropertyName("targets")]
|
||||
public required Dictionary<string, Dictionary<string, LibraryInfo>> Targets;
|
||||
}
|
||||
|
||||
public sealed class LibraryInfo
|
||||
{
|
||||
[JsonInclude, JsonPropertyName("dependencies")]
|
||||
public Dictionary<string, string>? Dependencies;
|
||||
|
||||
[JsonInclude, JsonPropertyName("runtime")]
|
||||
public Dictionary<string, object>? Runtime;
|
||||
|
||||
// Paths are like lib/netstandard2.0/JetBrains.Annotations.dll
|
||||
public IEnumerable<string> GetDllNames()
|
||||
{
|
||||
return Runtime == null ? [] : Runtime.Keys.Select(p => p.Split('/')[^1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,15 @@ public static class ServerPackaging
|
||||
new PlatformReg("osx-x64", "MacOS", true),
|
||||
new PlatformReg("osx-arm64", "MacOS", true),
|
||||
// Non-default platforms (i.e. for Watchdog Git)
|
||||
new PlatformReg("win-x86", "Windows", false),
|
||||
new PlatformReg("linux-x86", "Linux", false),
|
||||
new PlatformReg("linux-arm", "Linux", false),
|
||||
new PlatformReg("freebsd-x64", "FreeBSD", false),
|
||||
};
|
||||
|
||||
private static IReadOnlySet<string> ServerContentIgnoresResources { get; } = new HashSet<string>
|
||||
{
|
||||
"ServerInfo",
|
||||
"Changelog",
|
||||
};
|
||||
|
||||
private static List<string> PlatformRids => Platforms
|
||||
.Select(o => o.Rid)
|
||||
.ToList();
|
||||
@@ -34,25 +37,9 @@ public static class ServerPackaging
|
||||
.Select(o => o.Rid)
|
||||
.ToList();
|
||||
|
||||
private static readonly List<string> ServerContentAssemblies = new()
|
||||
{
|
||||
"Content.Server.Database",
|
||||
"Content.Server",
|
||||
"Content.Shared",
|
||||
"Content.Shared.Database",
|
||||
};
|
||||
|
||||
private static readonly List<string> ServerExtraAssemblies = new()
|
||||
{
|
||||
// Python script had Npgsql. though we want Npgsql.dll as well soooo
|
||||
"Npgsql",
|
||||
"Microsoft",
|
||||
"Discord",
|
||||
};
|
||||
|
||||
private static readonly List<string> ServerNotExtraAssemblies = new()
|
||||
{
|
||||
"Microsoft.CodeAnalysis",
|
||||
"JetBrains.Annotations",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> BinSkipFolders = new()
|
||||
@@ -178,23 +165,13 @@ public static class ServerPackaging
|
||||
|
||||
var inputPassCore = graph.InputCore;
|
||||
var inputPassResources = graph.InputResources;
|
||||
var contentAssemblies = new List<string>(ServerContentAssemblies);
|
||||
|
||||
// Additional assemblies that need to be copied such as EFCore.
|
||||
var sourcePath = Path.Combine(contentDir, "bin", "Content.Server");
|
||||
|
||||
// Should this be an asset pass?
|
||||
// For future archaeologists I just want audio rework to work and need the audio pass so
|
||||
// just porting this as is from python.
|
||||
foreach (var fullPath in Directory.EnumerateFiles(sourcePath, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(fullPath);
|
||||
var deps = DepsHandler.Load(Path.Combine(sourcePath, "Content.Server.deps.json"));
|
||||
|
||||
if (!ServerNotExtraAssemblies.Any(o => fileName.StartsWith(o)) && ServerExtraAssemblies.Any(o => fileName.StartsWith(o)))
|
||||
{
|
||||
contentAssemblies.Add(fileName);
|
||||
}
|
||||
}
|
||||
var contentAssemblies = GetContentAssemblyNamesToCopy(deps);
|
||||
|
||||
await RobustSharedPackaging.DoResourceCopy(
|
||||
Path.Combine("RobustToolbox", "bin", "Server",
|
||||
@@ -211,7 +188,11 @@ public static class ServerPackaging
|
||||
contentAssemblies,
|
||||
cancel: cancel);
|
||||
|
||||
await RobustServerPackaging.WriteServerResources(contentDir, inputPassResources, cancel);
|
||||
await RobustServerPackaging.WriteServerResources(
|
||||
contentDir,
|
||||
inputPassResources,
|
||||
ServerContentIgnoresResources.Concat(RobustSharedPackaging.SharedIgnoredResources).ToHashSet(),
|
||||
cancel);
|
||||
|
||||
if (hybridAcz)
|
||||
{
|
||||
@@ -222,5 +203,21 @@ public static class ServerPackaging
|
||||
inputPassResources.InjectFinished();
|
||||
}
|
||||
|
||||
// This returns both content assemblies (e.g. Content.Server.dll) and dependencies (e.g. Npgsql)
|
||||
private static IEnumerable<string> GetContentAssemblyNamesToCopy(DepsHandler deps)
|
||||
{
|
||||
var depsContent = deps.RecursiveGetLibrariesFrom("Content.Server").SelectMany(GetLibraryNames);
|
||||
var depsRobust = deps.RecursiveGetLibrariesFrom("Robust.Server").SelectMany(GetLibraryNames);
|
||||
|
||||
var depsContentExclusive = depsContent.Except(depsRobust).ToHashSet();
|
||||
|
||||
// Remove .dll suffix and apply filtering.
|
||||
var names = depsContentExclusive.Select(p => p[..^4]).Where(p => !ServerNotExtraAssemblies.Any(p.StartsWith));
|
||||
|
||||
return names;
|
||||
|
||||
IEnumerable<string> GetLibraryNames(string library) => deps.Libraries[library].GetDllNames();
|
||||
}
|
||||
|
||||
private readonly record struct PlatformReg(string Rid, string TargetOs, bool BuildByDefault);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Replay\</OutputPath>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nett" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
</Project>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Server.Database\</OutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>RA0003</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class PanicBunkerCommand : LocalizedCommands
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
||||
shell.WriteError(Robust.Shared.Localization.Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class PanicBunkerCommand : LocalizedCommands
|
||||
|
||||
if (args.Length == 1 && !bool.TryParse(args[0], out enabled))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
|
||||
shell.WriteError(Robust.Shared.Localization.Loc.GetString("shell-argument-must-be-boolean"));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -310,10 +308,10 @@ public sealed partial class CargoSystem
|
||||
/// <returns>true if <paramref name="entity"/> is a valid item for the bounty entry, otherwise false</returns>
|
||||
public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry)
|
||||
{
|
||||
if (!_whitelistSys.IsValid(entry.Whitelist, entity))
|
||||
if (!_whitelist.IsValid(entry.Whitelist, entity))
|
||||
return false;
|
||||
|
||||
if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))
|
||||
if (entry.Blacklist != null && _whitelist.IsValid(entry.Blacklist, entity))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -472,7 +470,7 @@ public sealed partial class CargoSystem
|
||||
skipped
|
||||
? CargoBountyHistoryData.BountyResult.Skipped
|
||||
: CargoBountyHistoryData.BountyResult.Completed,
|
||||
_gameTiming.CurTime,
|
||||
_timing.CurTime,
|
||||
actorName));
|
||||
ent.Comp.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
|
||||
@@ -37,8 +37,6 @@ namespace Content.Server.Cargo.Systems
|
||||
/// </summary>
|
||||
private float _timer;
|
||||
|
||||
[Dependency] private readonly BankSystem _bankSystem = default!;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
|
||||
@@ -272,9 +270,9 @@ namespace Content.Server.Cargo.Systems
|
||||
if (!float.IsFinite(taxCoeff) || taxCoeff <= 0.0f)
|
||||
continue;
|
||||
var tax = (int)Math.Floor(cost * taxCoeff);
|
||||
_bankSystem.TrySectorDeposit(account, tax, LedgerEntryType.CargoTax);
|
||||
_bank.TrySectorDeposit(account, tax, LedgerEntryType.CargoTax);
|
||||
}
|
||||
_bankSystem.TryBankWithdraw(player, cost);
|
||||
_bank.TryBankWithdraw(player, cost);
|
||||
// End Frontier
|
||||
|
||||
UpdateOrders(station.Value);
|
||||
|
||||
@@ -25,14 +25,10 @@ namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] BankSystem _bank = default!; // Mono
|
||||
|
||||
/*
|
||||
* Handles cargo shuttle / trade mechanics.
|
||||
*/
|
||||
|
||||
[Dependency] EntityWhitelistSystem _whitelist = default!; // Frontier
|
||||
|
||||
// Frontier addition:
|
||||
// The maximum distance from the console to look for pallets.
|
||||
private const int DefaultPalletDistance = 8;
|
||||
|
||||
@@ -19,8 +19,8 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server._NF.SectorServices; // Frontier
|
||||
using Content.Shared._NF.Trade; // Frontier
|
||||
using Content.Shared.Whitelist; // Frontier
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Server._NF.Bank; // Frontier
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
@@ -44,9 +44,9 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
[Dependency] private readonly SectorServiceSystem _sectorService = default!; // Frontier
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; // Frontier
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; // Frontier
|
||||
[Dependency] private readonly BankSystem _bank = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<CargoSellBlacklistComponent> _blacklistQuery;
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Server\</OutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
<NoWarn>1998</NoWarn>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="prometheus-net" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />
|
||||
<ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared.Database\Content.Shared.Database.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Packaging.props" />
|
||||
</Project>
|
||||
|
||||
@@ -641,7 +641,7 @@ namespace Content.Server.Database
|
||||
// This allows us to semi-efficiently load all entities we need in a single DB query.
|
||||
// Then we can update & insert without further round-trips to the DB.
|
||||
|
||||
var players = updates.Select(u => u.User.UserId).Distinct().ToArray();
|
||||
var players = updates.Select(u => u.User.UserId).Distinct().ToList();
|
||||
var dbTimes = (await db.DbContext.PlayTime
|
||||
.Where(p => players.Contains(p.PlayerId))
|
||||
.ToArrayAsync())
|
||||
@@ -875,8 +875,10 @@ namespace Content.Server.Database
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
var playerIdsList = playerIds.ToList();
|
||||
|
||||
var players = await db.DbContext.Player
|
||||
.Where(player => playerIds.Contains(player.UserId))
|
||||
.Where(player => playerIdsList.Contains(player.UserId))
|
||||
.ToListAsync();
|
||||
|
||||
var round = new Round
|
||||
@@ -907,10 +909,11 @@ namespace Content.Server.Database
|
||||
public async Task AddRoundPlayers(int id, Guid[] playerIds)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var playerIdsList = playerIds.ToList();
|
||||
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Dictionary<Guid, int> players = await db.DbContext.Player
|
||||
.Where(player => playerIds.Contains(player.UserId))
|
||||
.Where(player => playerIdsList.Contains(player.UserId))
|
||||
.ToDictionaryAsync(player => player.UserId, player => player.Id);
|
||||
|
||||
foreach (var player in playerIds)
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
@@ -12,9 +11,11 @@ namespace Content.Server.EntityEffects.Effects;
|
||||
/// </summary>
|
||||
public sealed partial class PlantSpeciesChange : EntityEffect
|
||||
{
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var log = IoCManager.Resolve<ILogManager>().GetSawmill(nameof(PlantSpeciesChange));
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
@@ -29,7 +30,7 @@ public sealed partial class PlantSpeciesChange : EntityEffect
|
||||
|
||||
if (protoSeed == null)
|
||||
{
|
||||
Log.Error($"Seed prototype could not be found: {targetProto}!");
|
||||
log.Error($"Seed prototype could not be found: {targetProto}!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ namespace Content.Server.Forensics
|
||||
[Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!; // Frontier
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!; // Frontier
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; // Frontier
|
||||
[Dependency] private readonly RadioSystem _radio = default!; // Frontier
|
||||
[Dependency] private readonly DeadDropSystem _deadDrop = default!; // Frontier
|
||||
@@ -100,7 +99,7 @@ namespace Content.Server.Forensics
|
||||
private void GiveReward(EntityUid uidOrigin, EntityUid target, int spesoAmount, FixedPoint2 fmcAmount, string msg)
|
||||
{
|
||||
SoundSpecifier confirmSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
|
||||
_audio.PlayPvs(_audio.GetSound(confirmSound), uidOrigin);
|
||||
_audioSystem.PlayPvs(_audioSystem.GetSound(confirmSound), uidOrigin);
|
||||
|
||||
if (spesoAmount > 0)
|
||||
_bank.TrySectorDeposit(SectorBankAccount.Nfsd, spesoAmount, LedgerEntryType.AntiSmugglingBonus);
|
||||
|
||||
@@ -33,7 +33,6 @@ public sealed class CrematoriumSystem : EntitySystem
|
||||
[Dependency] private readonly SharedMindSystem _minds = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!; // Frontier
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!; // frontier
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -128,7 +127,7 @@ public sealed class CrematoriumSystem : EntitySystem
|
||||
return false;
|
||||
if (TryComp<MobStateComponent>(entity, out var comp) && !_mobState.IsDead(entity, comp))
|
||||
return false;
|
||||
if (_mind.TryGetMind(entity, out var _, out var mind) && mind.Session?.State?.Status == SessionStatus.InGame)
|
||||
if (_minds.TryGetMind(entity, out var _, out var mind) && mind.Session?.State?.Status == SessionStatus.InGame)
|
||||
return false;
|
||||
|
||||
return Cremate(uid, component, storage);
|
||||
|
||||
@@ -13,7 +13,6 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Server.Players.JobWhitelist;
|
||||
|
||||
@@ -25,6 +24,9 @@ public sealed class JobWhitelistManager : IPostInjectInit
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private readonly ISawmill _sawmill = default!;
|
||||
|
||||
private readonly Dictionary<NetUserId, HashSet<string>> _whitelists = new();
|
||||
private readonly Dictionary<NetUserId, bool> _globalWhitelists = new(); // Frontier
|
||||
@@ -33,6 +35,8 @@ public sealed class JobWhitelistManager : IPostInjectInit
|
||||
{
|
||||
_net.RegisterNetMessage<MsgJobWhitelist>();
|
||||
_net.RegisterNetMessage<MsgWhitelist>();
|
||||
|
||||
_log.GetSawmill(nameof(JobWhitelistManager));
|
||||
}
|
||||
|
||||
private async Task LoadData(ICommonSession session, CancellationToken cancel)
|
||||
@@ -93,7 +97,7 @@ public sealed class JobWhitelistManager : IPostInjectInit
|
||||
if (!_whitelists.TryGetValue(player, out var whitelists) || // Frontier: added globalWhitelist check
|
||||
!_globalWhitelists.TryGetValue(player, out var globalWhitelist)) // Frontier
|
||||
{
|
||||
Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}",
|
||||
_sawmill.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}",
|
||||
player,
|
||||
job,
|
||||
Environment.StackTrace);
|
||||
@@ -153,7 +157,7 @@ public sealed class JobWhitelistManager : IPostInjectInit
|
||||
if (!_whitelists.TryGetValue(player, out var whitelists) ||
|
||||
!_globalWhitelists.TryGetValue(player, out var globalWhitelist))
|
||||
{
|
||||
Log.Error("Unable to check if player {Player} is whitelisted for {GhostRole}. Stack trace:\\n{StackTrace}",
|
||||
_sawmill.Error("Unable to check if player {Player} is whitelisted for {GhostRole}. Stack trace:\\n{StackTrace}",
|
||||
player,
|
||||
ghostRole,
|
||||
Environment.StackTrace);
|
||||
@@ -190,7 +194,7 @@ public sealed class JobWhitelistManager : IPostInjectInit
|
||||
|
||||
if (!_globalWhitelists.TryGetValue(player, out var whitelist))
|
||||
{
|
||||
Log.Error("Unable to check if player {Player} is globally whitelisted. Stack trace:\\n{StackTrace}",
|
||||
_sawmill.Error("Unable to check if player {Player} is globally whitelisted. Stack trace:\\n{StackTrace}",
|
||||
player,
|
||||
Environment.StackTrace);
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Sprite;
|
||||
|
||||
namespace Content.Server.Sprite;
|
||||
|
||||
public sealed class ScaleVisualsSystem : SharedScaleVisualsSystem;
|
||||
@@ -19,7 +19,6 @@ public sealed partial class StationJobsSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IBanManager _banManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!; // Frontier
|
||||
[Dependency] private readonly PlayTimeTrackingSystem _playTime = default!; // Frontier
|
||||
|
||||
private Dictionary<int, HashSet<string>> _jobsByWeight = default!;
|
||||
@@ -303,7 +302,7 @@ public sealed partial class StationJobsSystem
|
||||
_random.Shuffle(givenStations);
|
||||
|
||||
// Frontier: get player session
|
||||
_playerManager.TryGetSessionById(player, out var nfSession);
|
||||
_player.TryGetSessionById(player, out var nfSession);
|
||||
// End Frontier
|
||||
|
||||
foreach (var station in givenStations)
|
||||
|
||||
@@ -257,7 +257,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
|
||||
// Enforce the MaxJobSlots limit
|
||||
var clampedAmount = Math.Min(amount, MaxJobSlots);
|
||||
|
||||
|
||||
var jobList = stationJobs.JobList;
|
||||
|
||||
switch (jobList.ContainsKey(jobPrototypeId))
|
||||
@@ -537,7 +537,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
stationDisplay = new StationDisplayInformation(
|
||||
stationSubtext: extraStationInformation.StationSubtext,
|
||||
stationDescription: extraStationInformation.StationDescription,
|
||||
stationIcon: extraStationInformation.IconPath,
|
||||
stationIcon: extraStationInformation.Icon,
|
||||
lobbySortOrder: extraStationInformation.LobbySortOrder
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Content.Server.Toolshed.Commands.Misc;
|
||||
|
||||
/// <summary>
|
||||
/// Used to change an entity's sprite scale.
|
||||
/// </summary>
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.VarEdit)]
|
||||
public sealed class ScaleCommand : ToolshedCommand
|
||||
{
|
||||
private SharedScaleVisualsSystem? _scaleVisuals;
|
||||
private SharedPhysicsSystem? _physics;
|
||||
|
||||
[CommandImplementation("set")]
|
||||
public IEnumerable<EntityUid> Set([PipedArgument] IEnumerable<EntityUid> input, Vector2 scale)
|
||||
{
|
||||
_scaleVisuals ??= GetSys<SharedScaleVisualsSystem>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
_scaleVisuals.SetSpriteScale(ent, scale);
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("multiply")]
|
||||
public IEnumerable<EntityUid> Multiply([PipedArgument] IEnumerable<EntityUid> input, float factor)
|
||||
{
|
||||
_scaleVisuals ??= GetSys<SharedScaleVisualsSystem>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
var scale = _scaleVisuals.GetSpriteScale(ent) * factor;
|
||||
_scaleVisuals.SetSpriteScale(ent, scale);
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("multiplywithfixture")]
|
||||
public IEnumerable<EntityUid> MultiplyWithFixture([PipedArgument] IEnumerable<EntityUid> input, float factor)
|
||||
{
|
||||
_scaleVisuals ??= GetSys<SharedScaleVisualsSystem>();
|
||||
_physics ??= GetSys<SharedPhysicsSystem>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
var scale = _scaleVisuals.GetSpriteScale(ent) * factor;
|
||||
_scaleVisuals.SetSpriteScale(ent, scale);
|
||||
_physics.ScaleFixtures(ent, factor);
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("get")]
|
||||
public IEnumerable<Vector2> Get([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
_scaleVisuals ??= GetSys<SharedScaleVisualsSystem>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
yield return _scaleVisuals.GetSpriteScale(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,36 +6,36 @@ namespace Content.Server.Worldgen.Prototypes;
|
||||
/// This is a prototype for a GC queue.
|
||||
/// </summary>
|
||||
[Prototype("gcQueue")]
|
||||
public sealed class GCQueuePrototype : IPrototype
|
||||
public sealed partial class GCQueuePrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How deep the GC queue is at most. If this value is ever exceeded entities get processed automatically regardless of
|
||||
/// tick-time cap.
|
||||
/// </summary>
|
||||
[DataField("depth", required: true)]
|
||||
public int Depth { get; }
|
||||
public int Depth { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many miliseconds to spend deleting objects per object in the queue above the MinDepth? Mono Dynamic Queueing
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public double TimeDeletePerObject { get; } = 0.1; // Mono - at 100 objects past the MinDepth will spend up to 10 milliseconds trying to do deletions
|
||||
public double TimeDeletePerObject { get; private set; } = 0.1; // Mono - at 100 objects past the MinDepth will spend up to 10 milliseconds trying to do deletions
|
||||
|
||||
/// <summary>
|
||||
/// The minimum depth before entities in the queue actually get processed for deletion.
|
||||
/// </summary>
|
||||
[DataField("minDepthToProcess", required: true)]
|
||||
public int MinDepthToProcess { get; }
|
||||
public int MinDepthToProcess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the GC should fire an event on the entity to see if it's eligible to skip the queue.
|
||||
/// Useful for making it so only objects a player has actually interacted with get put in the collection queue.
|
||||
/// </summary>
|
||||
[DataField("trySkipQueue")]
|
||||
public bool TrySkipQueue { get; }
|
||||
public bool TrySkipQueue { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Content.Server._NF.M_Emp
|
||||
}
|
||||
|
||||
[CopyByRef, DataRecord]
|
||||
public record struct GeneratorState(GeneratorStateType StateType, TimeSpan Until)
|
||||
public partial record struct GeneratorState(GeneratorStateType StateType, TimeSpan Until)
|
||||
{
|
||||
public static readonly GeneratorState Inactive = new (GeneratorStateType.Inactive, TimeSpan.Zero);
|
||||
};
|
||||
|
||||
@@ -71,7 +71,6 @@ public sealed partial class ShipyardSystem : SharedShipyardSystem
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ShuttleRecordsSystem _shuttleRecordsSystem = default!;
|
||||
[Dependency] private readonly ShuttleConsoleLockSystem _shuttleConsoleLock = default!;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Content.Shared._NF.SizeAttribute;
|
||||
using Content.Shared.Nyanotrasen.Item.PseudoItem;
|
||||
using Content.Shared.Sprite;
|
||||
|
||||
namespace Content.Server.SizeAttribute
|
||||
{
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Content.Server._NF.Speech.Components;
|
||||
public sealed partial class CavemanAccentComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("MaxWordLength")]
|
||||
public static int MaxWordLength = 5; // so man not talk smart, any word up dis be gone
|
||||
|
||||
[ViewVariables]
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Content.Server._NF.Station.Components;
|
||||
public sealed partial class ExtraStationInformationComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ResPath? IconPath;
|
||||
public SpriteSpecifier? Icon;
|
||||
|
||||
[DataField]
|
||||
public LocId? StationSubtext;
|
||||
|
||||
@@ -126,7 +126,7 @@ public enum BluespaceDatasetNameType
|
||||
}
|
||||
|
||||
[DataRecord]
|
||||
public sealed class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup
|
||||
public sealed partial class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototypes we can choose from to spawn.
|
||||
@@ -169,7 +169,7 @@ public sealed class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup
|
||||
}
|
||||
|
||||
[DataRecord]
|
||||
public sealed class BluespaceGridSpawnGroup : IBluespaceSpawnGroup
|
||||
public sealed partial class BluespaceGridSpawnGroup : IBluespaceSpawnGroup
|
||||
{
|
||||
public List<ResPath> Paths = new();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,7 @@ using Content.Shared.Atmos.Piping.Binary.Components; // Frontier
|
||||
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
|
||||
public sealed partial class GasPressurePumpComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Shared.Audio;
|
||||
[Prototype]
|
||||
public sealed partial class AmbientMusicPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = string.Empty;
|
||||
[IdDataField] public string ID { get; private set; } = string.Empty;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("sound", required: true)]
|
||||
public SoundSpecifier Sound = default!;
|
||||
|
||||
@@ -48,7 +48,6 @@ public partial class SharedBodySystem
|
||||
[Dependency] private readonly GibbingSystem _gibbingSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!; // Shitmed Change
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!; // Shitmed Change
|
||||
|
||||
private const float GibletLaunchImpulse = 8;
|
||||
private const float GibletLaunchImpulseVariance = 3;
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Content.Shared.Body.Systems;
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!; // Shitmed Change
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!; // Shitmed Change
|
||||
|
||||
private void InitializeParts()
|
||||
{
|
||||
@@ -135,7 +134,7 @@ public partial class SharedBodySystem
|
||||
&& TryGetPartSlotContainerName(partEnt.Comp.PartType, out var containerNames))
|
||||
{
|
||||
foreach (var containerName in containerNames)
|
||||
_inventorySystem.DropSlotContents(partEnt.Comp.Body.Value, containerName, inventory);
|
||||
_inventory.DropSlotContents(partEnt.Comp.Body.Value, containerName, inventory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -205,7 +204,7 @@ public partial class SharedBodySystem
|
||||
// I don't know if this can cause issues, since any part that's being detached HAS to have a Body.
|
||||
// though I really just want the compiler to shut the fuck up.
|
||||
var body = partEnt.Comp.Body.GetValueOrDefault();
|
||||
if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted)
|
||||
if (TryComp(partEnt, out TransformComponent? transform) && _timing.IsFirstTimePredicted)
|
||||
{
|
||||
var enableEvent = new BodyPartEnableChangedEvent(false);
|
||||
RaiseLocalEvent(partEnt, ref enableEvent);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Shared.Configurable
|
||||
/// <remarks>
|
||||
/// If you want a more detailed description ask the original coder.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
|
||||
public sealed partial class ConfigurationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="YamlDotNet" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Shared.Database\Content.Shared.Database.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Content.Shared.Follower.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(FollowerSystem))]
|
||||
[NetworkedComponent, AutoGenerateComponentState(RaiseAfterAutoHandleState = true)]
|
||||
[NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
|
||||
public sealed partial class FollowerComponent : Component
|
||||
{
|
||||
[AutoNetworkedField, DataField("following")]
|
||||
|
||||
@@ -179,13 +179,13 @@ namespace Content.Shared.GameTicking
|
||||
public sealed class StationDisplayInformation(
|
||||
LocId? stationSubtext,
|
||||
LocId? stationDescription,
|
||||
ResPath? stationIcon,
|
||||
SpriteSpecifier? stationIcon,
|
||||
int lobbySortOrder
|
||||
)
|
||||
{
|
||||
public LocId? StationSubtext { get; } = stationSubtext;
|
||||
public LocId? StationDescription { get; } = stationDescription;
|
||||
public ResPath? StationIcon { get; } = stationIcon;
|
||||
public SpriteSpecifier? StationIcon { get; } = stationIcon;
|
||||
public int LobbySortOrder { get; } = lobbySortOrder;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Content.Shared.Sprite;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Shared.Nyanotrasen.Kitchen.Prototypes;
|
||||
[Prototype("crispinessLevelSet")]
|
||||
public sealed partial class CrispinessLevelSetPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Crispiness level strings. The index is the crispiness value used, starting with 0.
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Shared.Overlays;
|
||||
/// <summary>
|
||||
/// This component allows you to see health bars above damageable mobs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] // Shitmed Change
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)] // Shitmed Change
|
||||
public sealed partial class ShowHealthBarsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Content.Shared.Overlays;
|
||||
/// <summary>
|
||||
/// This component allows you to see health status icons above damageable mobs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] // Shitmed Change
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)] // Shitmed Change
|
||||
public sealed partial class ShowHealthIconsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -40,7 +40,6 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
{
|
||||
public const string ProjectileFixture = "projectile";
|
||||
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
@@ -366,7 +365,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
private void OnEmbedRemove(Entity<EmbeddableProjectileComponent> embeddable, ref RemoveEmbeddedProjectileEvent args)
|
||||
{
|
||||
// Whacky prediction issues.
|
||||
if (args.Cancelled || _netManager.IsClient)
|
||||
if (args.Cancelled || _net.IsClient)
|
||||
return;
|
||||
|
||||
EmbedDetach(embeddable, embeddable.Comp, args.User);
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Content.Shared.Roles
|
||||
/// Nyano/DV: For e.g. prisoners, they'll never use their latejoin spawner.
|
||||
/// </summary>
|
||||
[DataField("alwaysUseSpawner")]
|
||||
public bool AlwaysUseSpawner { get; } = false;
|
||||
public bool AlwaysUseSpawner { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The "weight" or importance of this job. If this number is large, the job system will assign this job
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user